How to Export Data from Intercom: Methods, API Limits & Transcripts
A complete guide to exporting all Intercom data — conversations, transcripts, attachments, and Help Center articles — using the Intercom UI, REST API, and cloud storage exports. Covers authentication, pagination, rate limits, attachment handling, incremental exports, security, and troubleshooting.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Intercom gives you several ways to get data out: a CSV dataset export for reporting metadata, a cloud storage export to S3 or Google Cloud Storage for machine-readable JSON, and a REST API where you control everything but eat rate limits. There is no single "export all" button that gives you every object, full transcripts, attachments, and relationships in one clean package.
The defining constraint is transcripts. Intercom's built-in export tools silently drop conversation message content. According to Intercom's own documentation: "Conversation and ticket data exported to S3 will not contain a transcript. To retrieve a transcript, you can either export the conversation directly from the Intercom UI, or use the REST API." There is also no option to download a CSV of all conversation content directly from your workspace. If you need full message histories — which you almost certainly do for a migration or data warehouse load — you need to build or buy something.
This guide covers every export method step by step, what each one actually returns, what it silently omits, and the exact rate limits and pagination constraints you will hit when extracting contacts, conversations, articles, and companies programmatically.
If you are exporting as part of a platform migration, these destination-specific guides cover field mapping: Intercom to Zendesk, Intercom to Help Scout, or Intercom to Freshdesk.
What Data Can You Actually Export from Intercom?
Before choosing a method, you need to know what Intercom considers exportable and where each extraction path falls short.
| Data Type | UI CSV Export | Cloud Storage (S3/GCS) | REST API |
|---|---|---|---|
| Contacts (Users & Leads) | ✅ Selected columns | ❌ | ✅ Full objects |
| Companies | ✅ Selected columns | ❌ | ✅ Full objects |
| Conversation metadata | ✅ Filtered fields | ✅ JSON/JSONL | ✅ Full objects |
| Conversation transcripts | ❌ | ❌ | ✅ (via conversation parts) |
| Tickets | ✅ Filtered fields | ✅ JSON/JSONL | ✅ Full objects |
| Ticket transcripts | ❌ | ❌ | ✅ (via conversation parts) |
| Attachments | ❌ | ✅ (toggle) | ✅ (URLs in parts) |
| Help Center articles | ❌ | ❌ | ✅ (Articles API) |
| Internal articles | ❌ | ❌ | ❌ (not supported) |
| Tags | ❌ | ❌ | ✅ List endpoint |
| Custom attributes | ✅ (on contacts) | ✅ (on conversations) | ✅ |
| Call recordings/transcripts | Manual download | ❌ | ✅ (Calls API) |
The split that matters is flat exports vs. relational content exports. CSV gets you rows. Support history lives in nested conversation parts, attachments, notes, ticket state, and company relationships. A CSV that looks complete on first pass often fails the moment you try to rebuild history in another system.
Authentication & Setup
Before you can call any Intercom API endpoint, you need a valid access token.
Creating a Private App and Generating an Access Token
- Go to Settings > Integrations > Developer Hub in your Intercom workspace.
- Click New app and give it a descriptive name (e.g., "Data Export Script").
- Under Authentication, copy the Access Token. This is a Bearer token you will pass in the
Authorizationheader of every API request. - Under Permissions, enable the scopes your export requires. At minimum for a full export you need:
Read users and companies— for contacts and company dataRead conversations— for conversation metadata and partsRead tickets— for ticket dataRead articles— for Help Center contentRead tags— for tag dataExport data— for the Data Export API (/export/content/data)
- Click Save.
Store your access token securely. Do not commit it to version control. Use environment variables or a secrets manager. If a token is compromised, regenerate it immediately from the Developer Hub — the old token is invalidated instantly.
If your organization uses OAuth for multi-workspace access, you will need to register an OAuth application in the Developer Hub and implement the authorization code flow. For single-workspace exports, the private app token above is sufficient.
Verifying Your Token
curl -X GET \
'https://api.intercom.io/me' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.11'A successful response returns your app's identity and confirms the token is valid. If you get a 401, the token is invalid or expired.
Method 1: Intercom's Native CSV and Cloud Storage Exports
CSV Dataset Export (Reports > Dataset Export)
This is the quickest way to pull structured metadata out of Intercom without writing code. Go to Reports > Dataset export, select the dataset you want (Conversations, Tickets, etc.), define your date range, optionally filter by attribute, select columns, and preview before exporting.
What you get: A CSV with conversation metadata — IDs, timestamps, assignees, tags, SLA data, custom attributes — or contact/company records with selected fields.
What you don't get: Conversation content. No message bodies, no transcripts, no inline images, no attachments.
Conversation data exports are limited to 10,000 rows when downloading in the browser. Exports exceeding this limit are automatically emailed to you instead, which can take up to an hour.
Expected CSV format for conversation dataset export:
conversation_id,created_at,updated_at,state,assignee_name,team_name,first_response_time,tags,custom_attribute_plan
12345,2024-03-01T10:15:00Z,2024-03-02T14:30:00Z,closed,Jane Smith,Support,120,billing;urgent,enterprise
12346,2024-03-01T11:00:00Z,2024-03-01T11:45:00Z,closed,Bob Lee,Support,45,onboarding,starterColumns vary depending on what you select in the export builder. The above is representative — your export may include additional custom attribute columns, SLA fields, or rating data depending on your workspace configuration.
For tickets, there are two extra gotchas to know. A ticket that was converted from a conversation can appear with the earlier conversation start date in the CSV. The export also shows all companies associated with the user, even if the UI only shows one company on the ticket. (intercom.com)
To use Dataset export, teammates need report access plus export permissions. Intercom's permission system requires Can access Reports and Can export CSV roles. (intercom.com)
Downloading a Single Conversation Transcript
For one-off audits, legal review, or complaint handling, the fastest path is the Inbox. Open the conversation or ticket, click the three-dot menu, and export as text or PDF.
What you do not get is a complete forensic record: transcript files exclude internal notes, assignment history, CC/BCC, images, attachments, GIFs, and embedded apps. Some of these appear only as placeholders in the exported file. (intercom.com)
Teammates need the Can export conversation transcripts permission to download transcript files from the Inbox. (intercom.com)
Contact and Company CSV Export
In the Contacts section, filter the list, open the More menu, and export users, leads, or companies as CSV. Intercom emails the file, and the download link expires after one hour. (intercom.com)
Expected CSV format for contact export:
user_id,email,name,phone,signed_up_at,last_seen_at,company_name,company_id,plan,custom_attribute_role
usr_abc123,jane@example.com,Jane Doe,+15551234567,2023-06-15T09:00:00Z,2024-03-10T16:20:00Z,Acme Corp,comp_xyz789,enterprise,admin
usr_def456,bob@example.com,Bob Lee,,2024-01-02T12:00:00Z,2024-03-09T10:05:00Z,Beta Inc,comp_uvw456,starter,memberNote that only one company appears per row — this is the limitation described below.
The company data gotcha: The user export currently only includes the name and ID of the most recent company a user was tracked with. No other company data is included. If your users belong to multiple companies over time, you lose all but the latest association. To extract all company relationships, you must bypass the CSV and use the /contacts/{id}/companies API endpoint. (developers.intercom.com)
Archived users also cannot be exported after they have been archived. (intercom.com)
Cloud Storage Export (S3 / Google Cloud Storage)
Intercom can export conversation data as JSON or JSONL to Amazon S3 or Google Cloud Storage. This is the best native option for warehousing or backup at scale.
Setup steps:
- Navigate to Settings > Data > Imports & exports > Export data and select New data export.
- Select the Data API version, output format (JSON or JSONL), and toggle whether to include attachments.
- Configure folder organization: enter a folder name (optional), choose whether to organize into date-named folders, and set whether to skip empty exports.
- Choose your export type:
- Historical: A two-year backfill of all conversation data.
- Periodic: Hourly or daily exports of new and updated conversations.
- Connect your S3 or GCS bucket.
The critical limitation: Intercom's documentation states that "conversation and ticket data exported to S3 will not contain a transcript." If you need guaranteed access to full message content for a migration or compliance archive, use the REST API.
Practical constraints to know:
- Each batch file contains at most 200 conversations. (intercom.com)
- Periodic files contain the full updated conversation (not a delta), so downstream loaders should upsert by conversation ID and updated timestamp.
- Attachments can be toggled and referenced from the JSON/JSONL data.
- Intercom can write to the bucket but cannot read or delete files.
- You cannot export multiple workspaces to the same bucket. (intercom.com)
- Exported conversation data cannot be imported into a new Intercom workspace. (intercom.com)
The cloud storage export is useful for ongoing backup or feeding a data warehouse. For transcript-grade extraction at scale, you must use the API.
Method 2: Exporting via the REST API
To get complete conversation content — every message, every note, every attachment URL — you need the API.
API versioning: Always pass the Intercom-Version header explicitly in your requests. All examples in this guide use Intercom-Version: 2.11. Intercom frequently updates its API, and relying on the workspace default version can cause extraction scripts to break unexpectedly if the default is bumped. Check the Intercom API changelog and pin to the version you have tested against.
Conversations API: The N+1 Pattern
The standard approach is: list or search conversations to get IDs, then retrieve each conversation individually for full content.
Step 1: List all conversations
curl -X GET \
'https://api.intercom.io/conversations?per_page=150' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.11'The default is 20 conversations per page. You can increase to a maximum of 150 per page using the per_page parameter. Intercom uses cursor-based pagination with a starting_after parameter — you cannot jump to a specific page number. (developers.intercom.com)
For incremental exports, the Search endpoint is better than List because you can filter on updated_at:
import requests
import time
BASE_URL = "https://api.intercom.io"
HEADERS = {
"Authorization": "Bearer <YOUR_TOKEN>",
"Content-Type": "application/json",
"Accept": "application/json",
"Intercom-Version": "2.11"
}
def search_conversations_updated_since(since_timestamp):
"""Search conversations updated since a Unix timestamp. Returns all matching conversation IDs."""
url = f"{BASE_URL}/conversations/search"
all_conversations = []
starting_after = None
while True:
payload = {
"query": {
"field": "updated_at",
"operator": ">",
"value": since_timestamp
},
"pagination": {
"per_page": 150
}
}
if starting_after:
payload["pagination"]["starting_after"] = starting_after
response = requests.post(url, json=payload, headers=HEADERS)
if response.status_code == 429:
reset_at = int(response.headers.get("X-RateLimit-Reset", time.time() + 10))
sleep_for = max(reset_at - int(time.time()), 1)
time.sleep(sleep_for)
continue
response.raise_for_status()
data = response.json()
all_conversations.extend(data.get("conversations", []))
pages = data.get("pages", {})
if pages.get("next"):
starting_after = pages["next"].get("starting_after")
else:
break
return all_conversationsIntercom's search language is more limited than a SQL-style filter builder. The docs specify a maximum of 2 nested filter levels and 15 filters inside each AND or OR group. The source.body field is tokenized: searching for "I need support" should use the value support, not need support. (developers.intercom.com)
Step 2: Retrieve full conversation content
The list endpoint returns metadata, not message content. Intercom conversations are broken into conversation parts — each individual message, note, assignment event, or state change. To get these, retrieve each conversation by ID:
curl -X GET \
'https://api.intercom.io/conversations/{conversation_id}' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.11'The response includes a conversation_parts array with the full text of every message, note, assignment event, and state change — along with author info, timestamps, and attachment URLs. Append ?display_as=plaintext to get plain-text output instead of HTML.
Sample JSON response structure (simplified — actual responses include more fields):
{
"type": "conversation",
"id": "12345",
"created_at": 1709290500,
"updated_at": 1709376900,
"state": "closed",
"source": {
"type": "conversation",
"id": "12345",
"delivered_as": "customer_initiated",
"subject": "",
"body": "<p>Hi, I can't access my dashboard since this morning.</p>",
"author": {
"type": "user",
"id": "usr_abc123",
"name": "Jane Doe",
"email": "jane@example.com"
},
"attachments": [
{
"type": "upload",
"name": "screenshot.png",
"url": "https://downloads.intercomcdn.com/i/o/abc123/screenshot.png",
"content_type": "image/png",
"filesize": 284510
}
]
},
"contacts": {
"type": "contact.list",
"contacts": [
{ "type": "contact", "id": "usr_abc123" }
]
},
"teammates": {
"type": "admin.list",
"admins": [
{ "type": "admin", "id": "admin_001", "name": "Bob Lee" }
]
},
"tags": {
"type": "tag.list",
"tags": [
{ "type": "tag", "id": "tag_99", "name": "billing" }
]
},
"conversation_parts": {
"type": "conversation_part.list",
"conversation_parts": [
{
"type": "conversation_part",
"id": "part_001",
"part_type": "comment",
"body": "<p>Hi Jane, sorry about that. Can you try clearing your browser cache?</p>",
"created_at": 1709291400,
"author": {
"type": "admin",
"id": "admin_001",
"name": "Bob Lee"
},
"attachments": []
},
{
"type": "conversation_part",
"id": "part_002",
"part_type": "comment",
"body": "<p>That worked, thank you!</p>",
"created_at": 1709293200,
"author": {
"type": "user",
"id": "usr_abc123",
"name": "Jane Doe"
},
"attachments": []
},
{
"type": "conversation_part",
"id": "part_003",
"part_type": "close",
"body": null,
"created_at": 1709293500,
"author": {
"type": "admin",
"id": "admin_001",
"name": "Bob Lee"
},
"attachments": []
}
],
"total_count": 3
}
}GET /conversations/{conversation_id} returns at most 500 conversation parts and keeps the 500 most recent. If you have long email-style threads, the oldest history is silently dropped. For those records, use the cloud storage export for bulk data and the API for recent content or transcript rendering. (developers.intercom.com)
This is an N+1 pattern: one call to list, then one call per conversation. For 50,000 conversations, that is 50,000+ API calls just for the content retrieval pass. Plan your rate limiting accordingly.
The Data Export API (/export/content/data)
Intercom also provides an asynchronous bulk export endpoint. To create an export job:
curl -X POST \
'https://api.intercom.io/export/content/data' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Intercom-Version: 2.11' \
-d '{
"created_at_after": 1704067200,
"created_at_before": 1711929600
}'The workflow is asynchronous:
- Submit the job: POST to
/export/content/datawith Unix timestamp date ranges. - Poll for status: GET
/export/content/data/{job_identifier}until the status returnscompleted. - Download the data: Use the provided download URL to retrieve the gzipped CSV.
Key constraints on this endpoint:
- 90-day maximum per request. If you need more than 90 days of history, chain sequential jobs.
- One active job per workspace. Attempting a second concurrent job returns a
429with "Exceeded rate limit of 1 pending message data export jobs." - Filters by
created_at, notupdated_at. If a message was updated yesterday but sent two days ago, you need to setcreated_at_afterbefore the message was sent. This catches teams who assume "updated in the last 90 days" equals "created in the last 90 days." It does not. - Jobs expire after two days. Download results promptly after completion.
- Available from API v2.5 onwards.
Because of the created_at filtering behavior, this endpoint is risky as your only incremental mechanism for historical threads that were recently edited. Pair it with the Search endpoint for recently updated conversations.
Exporting Help Center Articles
Intercom does not offer a UI export for articles. To back up or migrate Help Center content, use the Articles API. (intercom.com)
# List all articles (paginated)
curl -X GET \
'https://api.intercom.io/articles?per_page=50' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.11'
# Retrieve a single article
curl -X GET \
'https://api.intercom.io/articles/{article_id}' \
-H 'Authorization: Bearer <YOUR_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.11'Using the Intercom Node SDK (intercom-client):
const { IntercomClient } = require('intercom-client');
const client = new IntercomClient({ tokenAuth: { token: '<YOUR_TOKEN>' } });
async function exportAllArticles() {
const allArticles = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await client.articles.list({ page, per_page: 50 });
const articles = response.data || [];
allArticles.push(...articles);
if (articles.length < 50) {
hasMore = false;
} else {
page++;
}
}
return allArticles;
}
exportAllArticles().then(articles => {
console.log(`Exported ${articles.length} articles`);
// Each article object contains: id, title, body (HTML),
// author_id, state, created_at, updated_at, parent_id, etc.
});Intercom returns articles in descending updated_at order, which is convenient for incremental exports. Article bodies are HTML, not Markdown, so cleanup is usually required if your target knowledge base uses a different format.
Use the Help Center Collections API at /help_center/collections to preserve your KB folder structure alongside the articles.
The Intercom API does not support retrieval of internal articles from the Knowledge Hub. There is no endpoint for exporting or accessing internal articles. If you use Intercom's internal-facing knowledge base for agent documentation, those articles must be copied manually.
Some HTML elements — div, span, form, input, textarea, and script tags — are not supported in article bodies and may be removed or replaced with paragraph tags during export. If your articles contain embedded forms or custom widgets, expect content loss.
Exporting Tickets via API
Intercom exposes ticket data through the Tickets API. The key gotcha: the API uses the internal ticket id, not the Inbox-facing ticket_id (like #12345). This is a common cause of bad joins and 404s during migrations. (developers.intercom.com)
To map ticket schema, list ticket types first, then search or retrieve ticket records. For one-off human exports, the Inbox ticket export can optionally include hidden attributes and internal notes.
Call Transcripts (Intercom Phone)
If you use Intercom Phone, call recordings and line-by-line call transcripts are handled separately from conversation export. Teammates can download recordings and transcripts from the Call Details panel, and the Calls API exposes GET /calls/{id}/transcript. You can retrieve calls by conversation IDs with a maximum of 20 per request. (intercom.com)
Intercom API Rate Limits and Export Constraints
This is where most DIY export scripts break. Intercom's rate limits look generous on paper but have a windowing mechanic that punishes burst traffic.
The 10-Second Window Trap
Private apps have a default rate limit of 10,000 API calls per minute per app and 25,000 API calls per minute per workspace. If a workspace has multiple private apps installed, every one contributes toward the workspace total. (developers.intercom.com)
The real constraint is the windowing. Intercom distributes the per-minute limit evenly into 10-second windows. A 10,000/min limit means roughly 1,666 requests per 10-second period. If your extraction script fires 500 requests in a 2-second burst — common during initial data syncs — you blow past the window limit instantly and start eating 429 responses.
Rate Limit Reference
| Constraint | Value | Notes |
|---|---|---|
| Per app (private) | 10,000/min | ~1,666 per 10-second window |
| Per workspace | 25,000/min | Shared across all apps |
| Data Export jobs | 1 concurrent | 429 if you attempt a second |
| Data Export timeframe | 90 days max | Per request |
| Data Export download | Expires in 2 days | After job completion |
| Conversations per page | 150 max | Cursor-based pagination only |
| Conversation parts returned | 500 max | Most recent 500 per GET |
| Company listing | 10,000 max | Scroll API required beyond this |
| Ticket IDs | Internal id only |
Not the Inbox ticket_id |
Handling Rate Limits in Practice
The response headers tell you everything you need:
| Header | Purpose |
|---|---|
X-RateLimit-Limit |
Max requests allowed in current window |
X-RateLimit-Remaining |
Requests left in current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
Read the X-RateLimit-Reset header and calculate the delta. Implement exponential backoff with jitter — not a fixed sleep. A fixed sleep(10) after every batch is wasteful. Read the headers, pace accordingly, and add random jitter to prevent thundering herd problems if you are running multiple workers. (developers.intercom.com)
If your exporter ignores these headers and just sleeps every 60 seconds, you will get bursty 429s for no good reason.
Handling Attachments
Attachments appear in two places: the cloud storage export (if toggled on) and in the attachments array on conversation parts returned by the API.
Attachment URLs from the API
Each conversation part can include an attachments array. Each attachment object contains url, name, content_type, and filesize. The URLs point to Intercom's CDN (downloads.intercomcdn.com or similar).
Intercom CDN attachment URLs are time-limited. They are valid at the time the API response is generated, but they expire. If you are exporting conversations and plan to download attachments later, do not store URLs alone — download the files during the extraction pass and store them locally or in your own cloud storage bucket.
Downloading and Packaging Attachments
For large exports, download attachments in the same loop where you retrieve conversation parts:
import os
import requests
def download_attachment(attachment, output_dir, conversation_id, part_id):
"""Download a single attachment and save it with a collision-safe filename."""
# Avoid filename collisions across conversations
safe_name = f"{conversation_id}_{part_id}_{attachment['name']}"
filepath = os.path.join(output_dir, safe_name)
response = requests.get(attachment["url"], stream=True)
response.raise_for_status()
with open(filepath, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return filepathKey considerations for attachment handling:
- Filename collisions: Multiple conversations may have attachments with the same filename (e.g.,
screenshot.png). Prefix filenames with conversation ID and part ID. - Storage size: Estimate storage requirements before starting. If your workspace has 50,000 conversations and even 10% include attachments averaging 500 KB, that is ~2.5 GB of attachment data.
- Authentication: Attachment CDN URLs generally do not require your API Bearer token to download — they use signed URLs. But the URLs expire, so download promptly.
- Cloud storage toggle: If you use S3/GCS export with the attachment toggle enabled, Intercom writes attachment files alongside the conversation JSON. This avoids the CDN expiration problem but does not include transcripts.
Exporting Large Volumes: Delta Exports, Pagination, and Incremental Runs
For workspaces with tens or hundreds of thousands of conversations, a single full export is impractical to repeat. You need an incremental strategy.
Incremental Export Pattern
The core idea: store a checkpoint (the last updated_at timestamp you successfully processed), then on each run, query only for conversations updated since that checkpoint.
import json
import time
import requests
CHECKPOINT_FILE = "export_checkpoint.json"
def load_checkpoint():
try:
with open(CHECKPOINT_FILE, "r") as f:
return json.load(f)
except FileNotFoundError:
return {"last_updated_at": 0}
def save_checkpoint(timestamp):
with open(CHECKPOINT_FILE, "w") as f:
json.dump({"last_updated_at": timestamp}, f)
def incremental_export():
checkpoint = load_checkpoint()
since = checkpoint["last_updated_at"]
# Search for conversations updated since checkpoint
conversations = search_conversations_updated_since(since)
seen_ids = set()
max_updated = since
for conv in conversations:
conv_id = conv["id"]
# Deduplicate — the same conversation may appear in multiple pages
# if it is updated during the export run
if conv_id in seen_ids:
continue
seen_ids.add(conv_id)
# Retrieve full conversation with parts
full_conv = retrieve_conversation(conv_id) # your function with rate limit handling
process_conversation(full_conv) # your storage/transform logic
updated_at = full_conv.get("updated_at", since)
if updated_at > max_updated:
max_updated = updated_at
# Save checkpoint only after successful processing
save_checkpoint(max_updated)
print(f"Exported {len(seen_ids)} conversations. Checkpoint: {max_updated}")Key points for incremental runs:
- Use
updated_at, notcreated_at: The Search endpoint supports filtering byupdated_at. This catches reopened conversations, new replies on old threads, and tag changes. - Deduplication by conversation ID: A conversation can be updated multiple times during a long export run. Deduplicate by ID before processing.
- Checkpoint persistence: Store your checkpoint in a file, database, or environment variable — not in memory. If your script crashes, you need to resume from the last safe point.
- Idempotent processing: Your downstream loader (database, data warehouse, target platform) should upsert by conversation ID, not insert. Duplicate deliveries should be harmless.
- Attachment re-download: If you are downloading attachments, check whether the file already exists locally before re-downloading. CDN URLs change between API calls, but the
nameandfilesizefields can serve as a deduplication key.
Pagination at Scale
Intercom's cursor-based pagination means you must iterate sequentially — there is no way to parallelize by jumping to page N. For the Search endpoint, each page returns up to 150 results with a starting_after cursor for the next page.
If you are exporting 100,000+ conversations, the pagination pass alone is ~667 API calls just for the listing step (before retrieving individual conversations for their parts). At a sustainable pace within rate limits, expect the listing pass to take several minutes and the full content retrieval pass to take hours.
Troubleshooting Common Export Issues
| Problem | Cause | Resolution |
|---|---|---|
| 429 errors during extraction | Exceeded 10-second window rate limit | Read X-RateLimit-Reset header and sleep until reset. Implement exponential backoff with jitter. Reduce concurrency. |
| Empty export files from S3/GCS | No new or updated conversations in the export period, or bucket permissions are misconfigured | Check the "skip empty exports" toggle. Verify IAM permissions on the bucket. Ensure the export type (Historical vs. Periodic) matches your expectation. |
| Missing conversations in S3 export | Conversations created outside the historical 2-year window, or export job did not complete | Verify the export job status in Settings. For older conversations, use the REST API. |
| "Archived users can't be exported" | Intercom does not include archived users in CSV or API contact exports | Unarchive users before exporting if you need their data. There is no way to export archived user records. |
| Permission errors (403) | Access token missing required scopes, or teammate lacks export permissions | Check your app's scopes in the Developer Hub. Ensure the teammate has "Can export CSV" and "Can export conversation transcripts" permissions. |
| Ticket ID mismatch (404s) | Using the Inbox-facing ticket_id (e.g., #12345) instead of the internal id |
Use the internal id from the Tickets API list/search response, not the human-readable ticket number. |
| Conversation parts truncated | Thread exceeds 500 parts | The API returns only the 500 most recent parts. For complete history of very long threads, supplement with cloud storage export data. |
| Data Export API returns stale data | Using created_at filter when you need updated_at behavior |
The /export/content/data endpoint filters by created_at only. For recently updated old conversations, use the Search endpoint with updated_at filter instead. |
Security and Retention Considerations
Exported Intercom data often contains PII: customer names, email addresses, phone numbers, and the full text of support conversations. Handle it accordingly.
Protecting Exported Data
- Encryption at rest: Store exported files in encrypted storage. If using S3, enable SSE-S3 or SSE-KMS encryption on your export bucket. For GCS, server-side encryption is on by default, but consider using customer-managed encryption keys (CMEK) for additional control.
- Bucket access policies: Restrict bucket access to the minimum set of IAM principals that need it. Intercom only needs write access to deposit export files — it does not need read or delete permissions. Your own access should also be scoped narrowly.
- Token security: API access tokens are long-lived by default. Rotate them periodically. Store them in a secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault), not in code, configuration files, or environment variables on shared machines. If a token is compromised, regenerate it immediately from the Intercom Developer Hub.
PII and Compliance
- Conversation transcripts contain customer data. Every message body, attachment filename, and inline image in your export may contain PII. Treat exported conversation data with the same access controls you apply to your production database.
- GDPR and deletion requests: If a customer exercises their right to erasure (GDPR Article 17), you must delete their data from your exports too — not just from Intercom. Build a process to identify and purge specific users' data from exported files.
- Retention periods: Define how long you need to retain exported data. For migration purposes, delete export files after the migration is verified and the transition period ends. For compliance archives, align retention with your organization's data retention policy and applicable regulations.
- Audit logging: Track who accessed exported data and when. If you store exports in S3 or GCS, enable access logging on the bucket.
Intercom's own cloud storage exports cannot be read back or deleted by Intercom after delivery. Once files land in your bucket, you are solely responsible for their security, access control, and lifecycle management.
Full Export Playbook: Extracting Everything
If you need a complete dump of all exportable Intercom data, follow this sequence. The order matters because some objects reference others.
- Export companies —
GET /companieswith scroll pagination. Companies are referenced by contacts and conversations, so export them first to build your company lookup table. - Export contacts (users and leads) —
POST /contacts/searchorGET /contactswith pagination. For each contact, optionally callGET /contacts/{id}/companiesto get all company associations (the CSV export only includes the most recent company). - Export tags —
GET /tags. Tags are referenced on conversations, contacts, and companies. Having the full tag list lets you resolve tag IDs to names. - Export conversations with full transcripts — Use the N+1 pattern: search or list conversations for IDs, then
GET /conversations/{id}for each to retrieve conversation parts. Download attachments in the same pass. - Export tickets —
GET /ticketsor search tickets, then retrieve each by internalid. Ticket conversations use the same conversation parts structure. - Export Help Center articles and collections —
GET /articlesfor all articles,GET /help_center/collectionsfor the folder structure. - Export call transcripts (if using Intercom Phone) —
GET /calls/{id}/transcriptfor each call, referenced by conversation ID.
Dependency note: Export companies and tags before contacts and conversations. This lets you resolve company IDs and tag IDs to human-readable names during the conversation export pass, rather than requiring a second pass to join data.
For a workspace with 50,000 conversations, expect the full export to take several hours due to the N+1 API pattern and rate limits. Run it during off-peak hours and use the incremental pattern described above to capture any new activity that arrives during the export window.
What Cannot Be Exported at All
Some Intercom data simply cannot be extracted through any native method:
- Workflow automations, bots, and Series: Assignment rules, Series automated workflows, bot configurations, and Inbox macros are workspace-specific and non-portable. These require manual re-creation.
- Ongoing conversations: Cannot be migrated between workspaces and must be manually recreated.
- Internal knowledge base articles: No API endpoint exists.
- One-off and ongoing outbound messages: Not fully covered by standard export tools.
This matters whether you are migrating to a new Intercom workspace (e.g., moving from US to EU hosting) or switching platforms entirely.
Third-Party Tools vs. Custom Scripts
You have three paths: native exports (limited), DIY scripts (full control, high effort), and third-party tools (trade-offs in cost and fidelity).
DIY API scripts give you total control over field selection, output format, and destination. You must handle pagination, rate limiting, error recovery, the N+1 conversation pattern, and data stitching yourself. For large workspaces (50K+ conversations), extraction can take hours. You also own the maintenance burden — Intercom API versions change, and breaking changes ship in new versions.
Help Desk Data Migration is an Intercom-partnered SaaS tool that automates transfer of conversations, contacts, and KB articles to other platforms without code. Good for platform-to-platform migrations with standard field mapping. Less flexible if you need custom transformations or are loading into a data warehouse.
Skyvia is a no-code cloud tool that automates CSV import/export between Intercom and external storage on a schedule. Useful for flat-object periodic syncing. Not purpose-built for helpdesk history reconstruction or complex one-time migrations with relational data.
| Approach | Transcripts | Setup Time | Cost | Flexibility |
|---|---|---|---|---|
| Native CSV export | ❌ | Minutes | Free | Low |
| Cloud Storage (S3/GCS) | ❌ | 30 min | Free (+ storage costs) | Medium |
| DIY API script | ✅ | Days–weeks | Engineering time | High |
| Help Desk Data Migration | ✅ | Hours | Per-record pricing | Medium |
| Skyvia | Partial | Hours | Subscription | Medium |
Choosing the Right Export Path
Use this decision logic:
- Need a flat list of users, leads, or companies? Use Contacts CSV.
- Need reporting, SLA, or volume analysis? Use Dataset export CSV.
- Need machine-readable support history in bulk? Use cloud storage JSON/JSONL to S3 or GCS.
- Need transcripts, articles, long-thread handling, or cross-object mapping? Use the REST API — or a specialist that works above it.
- Need to move between Intercom regions or workspaces? Export alone is not enough. Intercom states that exported conversation data cannot be imported into a new workspace, so plan the migration path separately.
Intercom can export a lot of data, but it exports it in layers. CSV is not transcripts. Cloud storage JSON is not a human transcript file. The API is powerful but only if you design around rate limits, one-job constraints, the 500-part ceiling, and relationship recovery. Once you know that, the right export method is much easier to choose.
Frequently Asked Questions
- Can I export Intercom conversation transcripts from the UI?
- Only one conversation at a time. Open the conversation in the Inbox, click the three-dot menu, and export as text or PDF. There is no bulk transcript export from the UI — for that you need the REST API.
- Does the Intercom S3/GCS cloud storage export include transcripts?
- No. Intercom's documentation states that conversation and ticket data exported to S3 will not contain a transcript. You must use the REST API to retrieve full message content.
- How do I handle Intercom API rate limits during a large export?
- Intercom allows 10,000 API calls per minute per app, distributed across 10-second windows (~1,666 per window). Read the X-RateLimit-Remaining and X-RateLimit-Reset response headers and implement exponential backoff with jitter instead of fixed sleep intervals.
- Do Intercom attachment URLs expire?
- Yes. Attachment URLs from the API point to Intercom's CDN and are time-limited. Download attachment files during your extraction pass rather than storing URLs for later retrieval.
- Can I export Intercom internal knowledge base articles?
- No. The Intercom API does not support retrieval of internal articles from the Knowledge Hub. There is no endpoint for accessing them — they must be copied manually.
- What is the maximum number of conversation parts returned by the API?
- The GET /conversations/{id} endpoint returns at most 500 conversation parts, keeping the 500 most recent. Older parts in long threads are silently dropped.
- How do I set up authentication for the Intercom API?
- Create a private app in Settings > Integrations > Developer Hub, generate an access token, and enable the required scopes (Read users and companies, Read conversations, Read tickets, Read articles, Export data). Pass the token as a Bearer token in the Authorization header.


