For changes that have been updated across all versions, see the Unversioned changes page.
There are breaking changes in this version, which are detailed below.
Contact responses now include the email_domain field. It had been documented in the OpenAPI spec but wasn't actually returned in responses; it's now populated as a read-only, computed field.
New field:
email_domain(string, nullable) — the domain portion of the contact'semailaddress, for exampleexample.comforjane@example.com.nullwhen the contact has no email.
Affected endpoints:
- Get a contact
- Get a contact by External ID
- List contacts
- Search contacts
- Create a contact
- Update a contact
The POST /conversations/{id}/convert endpoint now accepts an optional ticket_state_id parameter, letting you set the ticket's initial state during conversion instead of defaulting to the first state. The ticket_state_id must belong to the ticket type passed in ticket_type_id.
You can now change the type of an existing ticket with the new POST /tickets/{ticket_id}/change_type endpoint. The new type must be in the same category as the current type. Attributes that match by name and type are transferred automatically from the old type.
Request body parameters:
ticket_type_id(required) — The ID of the new ticket type. Must be in the same category as the current type.ticket_state_id(required) — The ID of the ticket state for the new ticket type.ticket_attributes(optional) — Attributes to set on the ticket for the new type. Values provided here override attributes transferred from the old type.
Conversations synced into Intercom from an external platform now expose the records they came from — for example the originating Zendesk ticket or Salesforce case — so you can link an Intercom conversation back to its source system.
New field on conversation and conversation_list_item:
external_references(array) — the external records this conversation is linked to. Each entry has:type(string) — the external system the reference points to. Possible values:zendesk_ticket,zendesk_sunshine_conversation,salesforce_case,salesforce_in_app_message_conversation,freshdesk_ticket,freshchat_conversation,hubspot_conversation,custom_helpdesk_conversation,api_conversation.id(string) — the record's identifier in that system, always returned as a string.
The array is empty for conversations with no external references, sorted alphabetically by
type, and capped at 20 entries.
Affected endpoints:
GET /conversations/{id}— retrieve a single conversationGET /conversations— list conversationsPOST /conversations/search— search conversations
Example:
{
"external_references": [
{ "type": "zendesk_ticket", "id": "3633338" },
{ "type": "zendesk_sunshine_conversation", "id": "abc-def-uuid" }
]
}You can now filter POST /conversations/search by your workspace's custom conversation attributes, using the field name custom_attributes.{attribute} — the same attributes you can search on in the Inbox. Previously only standard conversation fields were searchable through the API.
Affected endpoint:
POST /conversations/search— search conversations
You can now set brand_id when creating a conversation. Multi-brand workspaces can specify the brand at creation time, so email notifications are sent with the correct brand identity. The field is optional; when omitted, the workspace's default brand is used.
You can now send WhatsApp messages through the Messages API by setting message_type to whatsapp. WhatsApp messages are sent from an approved template: provide the template name and its components, along with the from and to recipients.
Help Center responses now report whether the Help Center is the workspace's default and which locales it supports.
New fields:
default(boolean) — whether this Help Center is the workspace's default Help Center.locales(array of strings) — the locale codes of the languages the Help Center supports, for example["en", "fr"].
Affected endpoints:
GET /help_center/help_centers— list every Help Center in the workspaceGET /help_center/help_centers/{id}— retrieve a single Help Center
Email recipients that Intercom did not deliver to — because the address had bounced, was suppressed, or was otherwise skipped — are now included in conversation responses instead of being silently omitted. Each recipient and email address header carries a new nullable drop_reason string explaining why it was dropped; recipients that were delivered to have drop_reason: null.
New field:
drop_reason(string, nullable) — on each entry ofsource.recipientsand ofemail_message_metadata.email_address_headers.nullfor a delivered recipient; otherwise a machine-readable reason such asuser_bounced(address previously hard-bounced),user_suppressed(on the suppression list),user_not_contactable(opted out of email),email_address_ignored, orbanned_domain. Treat the value as an open string — additional reasons may be added over time.
Affected endpoints:
GET /conversations/{id},PUT /conversations/{id},POST /conversations,POST /conversations/{id}/reply—source.recipients[].drop_reasonand, on email parts,conversation_parts[].email_message_metadata.email_address_headers[].drop_reasonGET /conversations,POST /conversations/search—source.recipients[].drop_reasonGET /tickets/{id}and other surfaces that embed a conversation source
drop_reason is only recorded for emails sent after this feature was enabled; older messages report null for every recipient.
The source object of an email-initiated conversation message now includes a reply_to array — the Reply-To header addresses where a reply to the email is actually routed. This can differ from the apparent sender (the From address), which is useful for detecting spoofed email where replies would be silently redirected.
You can now tell Intercom whether a contact's email address has been verified when you create or update a contact, using the new email_verified field.
Pass email_verified: true alongside an email to indicate you have verified that the contact owns that address, or email_verified: false to mark the email as unverified. email_verified must be sent together with an email in the same request — sending it without an email returns a 400.
When retrieving a conversation with display_as=plaintext, inline links in the conversation source and conversation parts now render their URL alongside the visible label — label (url) — instead of dropping the URL. This applies to the body and subject fields and matches the URLs already present in the HTML representation. The same rendering applies to the plaintext body in the conversation.operator.replied webhook payload.
Affected endpoints:
GET /conversations/{id}(withdisplay_as=plaintext)GET /conversations(withdisplay_as=plaintext)- Webhook topic:
conversation.operator.replied
The POST /tickets/{id}/reply endpoint now accepts attachment_files for admin replies (type: admin). Each entry requires:
| Field | Type | Description |
|---|---|---|
data | string | Base64-encoded file content |
name | string | Filename including extension |
content_type | string | MIME type (e.g. application/pdf) |
Up to 10 files per request. When both attachment_files and attachment_urls are provided, attachment_files takes precedence.
The GET /conversations/{id}, GET /conversations, and POST /conversations/search endpoints now include a channel object on each conversation, exposing which channel the conversation originated from and its current channel.
This makes it possible to accurately report on channel mix.
New field on conversation and conversation_list_item:
channel(object, nullable) — channel metadata for the conversation. Returnsnullif channel data is unavailable.initial(string, nullable) — the channel through which the conversation was originally initiated. Possible values:messenger,zendesk_sunshine,zendesk_ticket,twitter,email.nullif channel data is unavailable.current(string, nullable) — the current channel of the conversation. May differ frominitialif the conversation was migrated between channels.nullif channel data is unavailable.
Example:
{
"channel": {
"initial": "zendesk_sunshine",
"current": "zendesk_sunshine"
}
}The source field on conversation objects was returning null for some conversations — including those started by an admin sending an email, outbound phone calls, and conversations created via third-party integrations like Zendesk Tickets or Salesforce Cases.
The POST /companies and PUT /companies/{id} endpoints now accept an update_last_request_at boolean parameter. When set to true, the company's last_request_at field is updated to the current time.
This is useful for workspaces that manage companies via the REST API and need to keep the "Last seen" value up to date. Requires the write_users_companies OAuth scope.
The Reporting Data Export API now includes a qualified_id field in the get_datasets response to uniquely identify attributes across different types.
Problem Solved: Custom attributes can have the same name across different types (e.g., a "Brand" attribute might exist for both user custom data and conversation custom data). Previously, using just the id field could result in ambiguous attribute selection.
New Field: The qualified_id field uses a namespaced format: "type.name" (e.g., "people.Brand", "conversation.Brand").
Available Prefixes:
people- User custom data attributescompany- Company custom data attributesconversation- Conversation custom data attributesticket- Ticket attributessystem- System-defined attributes
Example Response:
{
"type": "list",
"data": [
{
"id": "conversation",
"name": "Conversation",
"attributes": [
{
"id": "Brand",
"qualified_id": "conversation.Brand",
"name": "Brand"
}
]
}
]
}Usage: When calling the enqueue endpoint, you must use qualified_id values in the attribute_ids array. The simple id is no longer accepted — use the qualified_id from the get_datasets response to ensure unambiguous attribute selection.
The GET /conversations/{id} endpoint now supports an include_email_history query parameter. When set to true, the source object of the conversation (email) will include an email_message_metadata field containing:
message_id— the unique identifier from the email'sMessage-IDheadersubject— the subject of the emailemail_address_headers— a list of an email address headershistory— the HTML content of any quoted or forwarded email history from the initial inbound message
Updated Endpoint:
GET /conversations/{id}?include_email_history=true— returnsemail_message_metadatain thesourceobject
Note: The history is only available for the conversation source (initial part), not for individual conversation parts. This parameter is not available on list/search endpoints.
The hide_csat_from_agents_setting_change event type has been added to the Activity Log event types enum. This event is recorded when a workspace admin enables or disables the "Hide CSAT scores from agents" setting.
All knowledge API endpoints now support a body_markdown field for reading and writing content in Markdown format, alongside the existing HTML body and json_blocks fields.
GET responses include body_markdown — the article content serialized as Markdown. Standard blocks (headings, paragraphs, lists, code, images, tables) render as native GFM Markdown. Rich blocks (callouts, video embeds, buttons, collapsible sections, attachments, etc.) render as CommonMark ::: directives (e.g. :::button label="Sign up" href="..." :::) for lossless round-tripping.
POST/PUT requests accept body_markdown as an alternative to body or json_blocks. The fields are mutually exclusive — providing more than one returns 422.
Affected endpoints:
GET /articles,GET /articles/{id},GET /articles/searchPOST /articles,PUT /articles/{id}GET /internal_articles,GET /internal_articles/{id}POST /internal_articles,PUT /internal_articles/{id}GET /content_snippets,GET /content_snippets/{id}POST /content_snippets,PUT /content_snippets/{id}
Articles can now be targeted to specific Fin AI Agent audiences via the API.
New field on responses (article_content):
audience_ids(array of integers) — the audience IDs this article content is targeted to. On multilingual help centers this appears per-locale insidetranslated_content. On single-language help centers it appears at the article root level. Empty array means no targeting is set.
New field on write requests (POST /articles, PUT /articles/{id}):
- Top-level
audience_ids: [id1, id2]— broadcasts the same set to every locale translated_content.<locale>.audience_ids— targets a specific locale without affecting others- When both are sent in the same request, top-level wins
audience_ids: []onPUTclears all audience memberships from every locale- Unknown audience IDs return a
404error with no partial commit
Affected endpoints:
GET /articles,GET /articles/{id}POST /articles,PUT /articles/{id}
Content snippets, internal articles, and AI external pages can now be targeted to specific Fin AI Agent audiences via the API. (Articles already supported this — see the entry above.)
New field on responses (content_snippet, internal_article, external_page):
audience_ids(array of integers, nullable) — the audience IDs the content item is targeted to. Empty array means no targeting is set.
New field on write requests (POST and PUT on each resource):
audience_ids: [id1, id2]— sets audience targeting for the content item- On
PUT, omitting the field leaves memberships intact (PATCH semantics);audience_ids: []clears all memberships - On
POST /content_snippetsandPOST /internal_articles, omitting the field creates the item with no audience targeting - On
POST /ai/external_pages, omitting the field preserves defaults inherited from the parent content import source; an explicit array overrides those defaults;[]clears all targeting (even when the source has defaults) - Unknown audience IDs return a
404error with no partial commit
Unlike multilingual help-center articles (which carry audience targeting per-locale inside translated_content), these resources are single-entity and use a flat top-level audience_ids field.
Affected endpoints:
GET /content_snippets,GET /content_snippets/{id},POST /content_snippets,PUT /content_snippets/{id}GET /internal_articles,GET /internal_articles/{id},POST /internal_articles,PUT /internal_articles/{id}GET /ai/external_pages,GET /ai/external_pages/{id},POST /ai/external_pages,PUT /ai/external_pages/{id}
Boolean fields ai_chatbot_availability, ai_copilot_availability, and ai_sales_agent_availability are now available on the following resources:
- Content Snippets — Added to
GET,POST, andPUTresponses and request bodies. These fields let you control whether a content snippet is available to AI Chatbot (Fin), AI Copilot, or AI Sales Agent. - Internal Articles — Added to
GET,POST, andPUTresponses and request bodies. - Articles — Added to
POSTandPUTrequest bodies (already present in responses). - External Pages —
ai_sales_agent_availabilityadded toGETresponses andPOST/PUTrequest bodies.ai_agent_availabilityandai_copilot_availabilityadded toPUTrequest bodies (already present inGETresponses andPOSTrequests).
Three new optional, read-only fields have been added to the Articles API:
created_by_id(integer) — the ID of the teammate who created the content. At the article root, this is the creator of the default language's content; inside each per-locale entry intranslated_content, it is the creator of that content version.updated_by_id(integer) — the ID of the teammate who last updated the content. At the article root, this is the last editor of the default language's content; inside each per-locale entry intranslated_content, it is the last editor of that content version.exclude_from_article_suggestions(boolean) — whether the article is excluded from Fin AI Agent article suggestions. Returned at the article root only.
These fields are returned by GET /articles, GET /articles/{id}, GET /articles/search, POST /articles, PUT /articles/{id}, and the article.* webhook topics. They are read-only — set the underlying values through the Articles UI in Intercom.
A new optional, read-only field has been added to the Articles API:
help_center_audience(string) — the audience that can view the article in the Help Center: one ofeveryone,all_users,all_visitors,all_leads,all_visitors_and_leads, orrestricted(a custom audience ruleset). For multilingual articles this is the article-level audience, and it isnullwhen no audience is configured.
This field is returned by GET /articles, GET /articles/{id}, GET /articles/search, POST /articles, PUT /articles/{id}, and the article.* webhook topics. It is read-only — set the audience through the Articles UI in Intercom.
Three new optional, read-only fields have been added to the statistics object on Article responses:
fin_involvements(integer) — the number of conversations in which Fin AI Agent used the article, summed across all of the article's locales.fin_resolutions(integer) — the number of those conversations that Fin AI Agent resolved using the article, summed across all of the article's locales.fin_resolution_rate(number) — the percentage of Fin involvements that resulted in a resolution (fin_resolutions/fin_involvements* 100).
These fields are returned in the article statistics object by GET /articles/{id}, POST /articles, and PUT /articles/{id}. They are read-only and reflect Fin AI Agent reporting.
You can now retrieve the merge history of a contact — the list of contacts that were merged into it.
Pass include_merge_history=true as a query parameter on any of the following endpoints to include a merge_history array in the response:
- Get a contact
- Get a contact by External ID
- List contacts
- Search contacts
- Update a contact
- Merge a lead and a user — returns the merge history of the resulting target contact
Only returned for contacts with a user role. Each entry contains:
type—"merge_history"source_contact_id— the Intercom ID of the contact that was merged insource_contact_role— the role (leadoruser) of the merged contactmerged_at— UNIX timestamp of when the merge occurred (nullable)
GET /contacts/{id}/merge_history also returns a paginated list of merge history entries. Supports cursor, per_page, and order query parameters. Only available for contacts with a user role.
You can now schedule when an article publishes and when it unpublishes. Two new optional fields have been added to the Articles API:
scheduled_publish_at— schedule a future publish for the article. Combine withstate: "published"to schedule the article instead of publishing it immediately. Setting it tonullcancels a pending publish schedule. Combining withstate: "draft"returns a 400 error.scheduled_unpublish_at— schedule a future unpublish for the article. Setting it tonullcancels a pending unpublish schedule. The article must have been published at least once.
Only one pending schedule can exist per article at a time — scheduled_publish_at and scheduled_unpublish_at are mutually exclusive in the same request. Both fields are null when no schedule is pending.
On requests, the fields accept an ISO 8601 timestamp string (for example, "2026-12-31T09:00:00Z"). On responses, they are returned as Unix timestamps in seconds (or null).
Timestamps must be strictly in the future — values in the past or equal to the current time are rejected with 400 parameter_invalid.
The fields are accepted on the request body of POST /articles and PUT /articles/{id}, and returned by GET /articles, GET /articles/{id}, GET /articles/search, POST /articles, and PUT /articles/{id}.
The admin object returned by GET /admins, GET /admins/{id}, GET /me, and PUT /admins/{id}/away now includes a role attribute when the admin has a role assigned. The role object contains the role id and name.
Example:
{
"type": "admin",
"id": "1295",
"name": "Joe Example",
"role": {
"type": "role",
"id": "456",
"name": "Support Agent"
}
}Note: Admins with custom per-user permissions (no role assigned) will not have the role field.
Conversations now include sales_agent_participated and sales_agent fields, exposing metadata about Sales Agent (SDR) involvement. This mirrors the existing ai_agent object.
New Fields on Conversation:
sales_agent_participated(boolean) — whether the Sales Agent participatedsales_agent(object, nullable) — Sales Agent metadata, including:outcome— fixed outcome enum (qualified,disqualified,product_discovery,escalated_to_support,spam)routing_outcome— user-defined routing outcome identifiercollected_data— flat key-value map of memory fields collected during the conversation
Applies to:
GET /conversations/{id}POST /conversations/search
The Ticket object now includes a previous_ticket_state_id field that returns the ID of the ticket's previous state. This enables tracking ticket state transitions for reporting and compliance. The field returns null when no state change history exists.
The Conversations API can now return QA monitor evaluations and scorecard results on conversation responses when you opt in. Two new query parameters control which fields are included:
include_monitors=true— adds amonitor_evaluationsarray listing any QA monitors that flagged the conversation.include_scorecards=true— adds ascorecardsarray with scorecard results, including per-evaluator outcomes and the reviewed teammate (Fin or a specific admin).
Both parameters are available on:
When the corresponding parameter is omitted or false, the response is unchanged. Each array is empty if the conversation has no QA evaluations.
The display_as query parameter in the Retrieve Conversation and List Conversation Parts endpoints now affects both the body and subject fields of conversation messages when set to plaintext. By default, both fields are returned in HTML format.
The Delete a conversation endpoint now supports a retain_metrics query parameter that controls how conversation data is deleted:
retain_metrics=true(default): Deletes the conversation while retaining reporting data.retain_metrics=false: Deletes the conversation and all associated reporting data.
Using retain_metrics=false requires the delete_conversations_and_metrics OAuth scope. See OAuth Scopes for details.
Example Request:
curl -X DELETE https://api.intercom.io/conversations/123?retain_metrics=false \
-H 'Authorization: Bearer <YOUR_ACCESS_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.16'For more info, see this help center article.
After a merge, requesting the source contact by its original ID now returns HTTP 410 Gone instead of 404 Not Found, with a Link response header pointing to the canonical (merged-into) contact:
Link: </contacts/{canonical_id}>; rel="canonical"The response body contains:
{
"type": "error.list",
"errors": [{ "code": "contact_merged", "message": "This contact has been merged. See the 'Link' header for the canonical contact." }]
}The Link header resolves multi-hop merge chains (up to 3 hops) to return the final target contact. This applies to Get a contact and lookups by external_id. Merged contacts continue to be excluded from List contacts and Search contacts results.
Merging a lead into a user now requires the two contacts to be duplicates: they must share a matching email address or phone number. A request to merge two contacts that share no matching identifier returns HTTP 400 Bad Request:
{
"type": "error.list",
"errors": [
{
"code": "invalid_merge",
"message": "Contacts can only be merged when they are duplicates (matching email or phone). Pass skip_duplicate_validation=true to override this check."
}
]
}To merge two contacts that are not duplicates, set skip_duplicate_validation to true in the request body:
{
"from": "5d70dd30de4efd54f42fd526",
"into": "5ba682d23d7cf92bef87bfd4",
"skip_duplicate_validation": true
}The Reporting Data Export API now returns all conversations for the conversation dataset, including those that never received a user reply. Earlier versions return only conversations with at least one user reply. This applies to POST /export/reporting_data/enqueue.
This is a breaking change to the conversation population. Existing integrations on earlier dated versions are unaffected until they adopt 2.16.
You can now create, update, and archive conversation attributes through dedicated endpoints at /conversations/attributes. Responses include type-specific fields based on data_type.
New endpoints:
GET /conversations/attributes— List all conversation attributesGET /conversations/attributes/{id}— Get a single conversation attributePOST /conversations/attributes— Create a new conversation attributePUT /conversations/attributes/{id}— Update a conversation attributeDELETE /conversations/attributes/{id}— Archive a conversation attribute (soft delete)POST /conversations/attributes/{id}/options— Add an option to a list attributePUT /conversations/attributes/{id}/options/{option_id}— Rename a list optionDELETE /conversations/attributes/{id}/options/{option_id}— Archive a list option
Type-specific fields:
- String — includes
multiline(boolean) - List — includes
options(array of option objects, each withidUUID,label, andarchived) - Relationship — includes
referenceobject withtype(one/many) andobject_type_id - Other types — return base shape only
List option management:
List options are managed individually through the nested options endpoints rather than by replacing the full array on PUT /conversations/attributes/{id}. Each option object includes a UUID (id) that is used as the option_id path parameter when updating or archiving. A list attribute must always have at least 2 active (non-archived) options.
Breaking change: GET /data_attributes no longer returns conversation attributes
GET /data_attributes (with or without model=conversation) no longer returns conversation attributes. Use GET /conversations/attributes instead.
- Calling
GET /data_attributeswithout amodelparameter no longer includes conversation attributes in the response. - Calling
GET /data_attributes?model=conversationreturns a422error:
{
"type": "error.list",
"errors": [
{
"code": "parameter_invalid",
"message": "model=conversation is no longer supported. Use GET /conversations/attributes instead"
}
]
}Earlier stable API versions continue to return conversation attributes from this endpoint unchanged.
The Contact.owner_id field now returns as a string type, aligning with Admin.id. This change only affects this version — earlier stable versions (2.15, 2.14, etc.) continue to return owner_id as an integer for backward compatibility.
Affected endpoints:
GET /contacts/{id}POST /contactsPUT /contacts/{id}
Example responses:
Before:
{
"id": "5ba682d23d7cf92bef87bfd4",
"owner_id": 321
}After:
{
"id": "5ba682d23d7cf92bef87bfd4",
"owner_id": "321"
}The admin_assignee_id and team_assignee_id fields in Conversation API responses now return 0 instead of null when a conversation is unassigned.
Before (null):
{
"admin_assignee_id": null,
"team_assignee_id": null
}After (integer):
{
"admin_assignee_id": 0,
"team_assignee_id": 0
}The admin_assignee_id and team_assignee_id fields in the Ticket API response are now returned as integers instead of strings. This aligns the Ticket API with the Conversation API, which already returns these fields as integers.
Before (string):
{
"admin_assignee_id": "991268013",
"team_assignee_id": "0"
}After (integer):
{
"admin_assignee_id": 991268013,
"team_assignee_id": 0
}POST /tags no longer returns 404 when a referenced user or company does not exist. Instead, the tag is created (or updated), valid entities are linked and missing entities are skipped.
The response now includes two new fields: users and companies, list of objects containing:
id: the Intercom ID of the entity (string)tagged:trueif the entity was tagged,falseif untagged
Example response (company not found):
{
"type": "tag",
"id": "789",
"name": "VIP",
"users": [],
"companies": []
}Example response (one company tagged, one missing):
{
"type": "tag",
"id": "789",
"name": "VIP",
"users": [],
"companies": [{ "id": "valid-123", "tagged": true }]
}This fixes a long-standing issue where POST /tags would create an orphaned tag before validating that entities exist — the tag was committed but no entities were linked.
The conversation priority field now returns one of none, low, medium, high, or urgent instead of the binary priority / not_priority values. The current_priority and previous_priority fields on the priority_changed conversation part use the same values.
Migrating from an earlier version: not_priority maps to none and priority maps to high, with urgent added as a new tier above high. All stable API versions (2.15 and earlier) are unaffected and continue to return priority / not_priority.
The parent_id and parent_type fields have been removed from article API response payloads.
The parent_ids field is now also writable on create and update. You can send an array of collection IDs to place an article in one or more collections, or send an empty array to remove it from all collections.
Affected endpoints:
GET /articles—parent_idandparent_typeremoved from responsePOST /articles—parent_idsnow accepted in request;parent_idandparent_typeremoved from responseGET /articles/{id}—parent_idandparent_typeremoved from responsePUT /articles/{id}—parent_idsnow accepted in request;parent_idandparent_typeremoved from responseGET /articles/search—parent_idandparent_typeremoved from response
When sorting Search contacts results, sort.order now accepts only ascending or descending. Any other value returns HTTP 400 Bad Request instead of silently falling back to descending:
{
"type": "error.list",
"errors": [{ "code": "invalid_sort_order", "message": "Invalid sort order 'desc'. Must be one of: ascending, descending" }]
}Pass a sort object with a valid order to sort your results:
{
"query": { "field": "created_at", "operator": ">", "value": "1306054154" },
"sort": { "field": "created_at", "order": "ascending" }
}sort.order defaults to descending when omitted. All stable API versions (2.15 and earlier) are unaffected and continue to accept any sort.order value.
We've added a new Data Connectors API that lets you create, update, delete, and list your data connectors, plus view their execution history. Data connectors allow you to make HTTP requests to external APIs from Intercom workflows and AI agents.
Endpoints:
POST /data_connectors- Create a new data connectorPATCH /data_connectors/{id}- Update a data connector (fields, state transitions)DELETE /data_connectors/{id}- Delete a data connectorGET /data_connectors- List all data connectors (paginated)GET /data_connectors/{id}- Retrieve a single data connector with full detailGET /data_connectors/{id}/execution_results- List execution resultsGET /data_connectors/{id}/execution_results/{id}- Retrieve a single execution result
Key Features:
- Create in draft: New connectors start in
draftstate — configure URL, headers, data inputs, and audiences before publishing - Audience targeting: Control which user types (leads, users, visitors) can trigger the connector
- Safe deletion: Returns
409if the connector is in use or not in draft state - Cursor-based pagination: Navigate through data connectors and execution results using
starting_afterandper_page - Filtering: Filter execution results by
success,error_type, and time range - Opt-in bodies: Use
include_bodies=trueto include request/response bodies in execution results
You can also receive real-time notifications when Data Connector actions complete execution using the new data_connector.execution.completed webhook topic. This webhook provides operational visibility into connector execution outcomes, including success/failure status, HTTP response codes, error categorization, and execution timing.
OAuth Scope Required: Read data connectors (read-only) or Read and write data connectors (create/update/delete)
For complete documentation and examples, see the Data Connectors API Reference.
A new POST /admins/activity_logs/search endpoint is now available for searching and filtering admin activity logs. This provides a dedicated search endpoint with event type filtering capabilities.
New Endpoint:
Request Body Parameters:
created_at_after(optional) - Start date as a UNIX timestamp (defaults to 30 days ago)created_at_before(optional) - End date as a UNIX timestampevent_types(optional) - Array of event type strings to filter bypage(optional) - Page number of results to return (default: 1)per_page(optional) - Number of results per page, between 1 and 250 (default: 20)
Example Request:
{
"created_at_after": "1677253093",
"created_at_before": "1677861493",
"event_types": ["app_name_change", "message_state_change"]
}This endpoint requires the read_admins OAuth scope.
A companion GET /admins/activity_log_event_types endpoint lets you retrieve all possible event type values for admin activity logs, so you can understand what actions are trackable and filter results accordingly.
Example Response:
{
"type": "activity_log_event_type.list",
"event_types": [
"admin_login_success",
"admin_logout",
"app_name_change",
"message_state_change"
]
}OAuth Scope Required: Read admins and teammates
For complete documentation, see the Search Activity Logs API Reference and the Activity Log Event Types API Reference.
A new GET /teams/{team_id}/metrics endpoint is now available, providing real-time per-admin conversation metrics (open, idle, snoozed counts) for a given team. This is designed for workforce management integrations that need to monitor agent workload in real time.
Endpoint: GET /teams/{team_id}/metrics
Path Parameters:
team_id- The ID of the team to retrieve metrics for. UseGET /teamsto list available team IDs.
Optional Parameters:
idle_threshold- Seconds after which an open conversation is considered idle (default: 1800, clamped to 1–86400).
Example Request:
GET /teams/42/metricsExample Response:
{
"type": "team_metric.list",
"data": [
{
"type": "team_metric",
"admin_id": "123",
"open": 5,
"idle": 2,
"snoozed": 1
}
]
}This endpoint requires the real-time monitoring feature to be enabled for your workspace and the read_admins OAuth scope.
For complete documentation, see the Team Metrics API Reference.
GET /custom_object_instances/{type} now supports three modes:
- No filter — returns a paginated list of all instances for the type.
references_contact_id— returns instances associated with the given contact.references_conversation_id— returns instances associated with the given conversation.
Supports page and per_page pagination parameters (max 150 per page).
We've added a new Macros API that provides programmatic access to saved replies (macros) in Intercom. This API enables external applications to retrieve and display your team's pre-written responses, making it easier to integrate Intercom macros with third-party tools and workflows.
New Endpoints:
GET /macros- List all available macros with paginationGET /macros/{id}- Retrieve a specific macro by ID
Key Features:
- Cursor-based pagination: Navigate through large sets of macros efficiently using the
starting_afterparameter with Base64-encoded cursors - Smart placeholder transformation: Macros containing Intercom placeholders (e.g.,
{{user.name}}) are automatically transformed to XML-like attributes (<attribute key="user.name"/>) for easier parsing - Flexible filtering: Use the
updated_sinceparameter to retrieve only recently modified macros - Team visibility controls: Respects macro visibility settings, showing only macros available to the authenticated user's teams
- Channel availability: Each macro indicates where it can be used (
inbox,messenger, or both)
OAuth Scope Required: read_conversations
Example Request:
curl -X GET https://api.intercom.io/macros?per_page=10 \
-H 'Authorization: Bearer <YOUR_ACCESS_TOKEN>' \
-H 'Accept: application/json' \
-H 'Intercom-Version: 2.16'Example Response:
{
"type": "list",
"data": [
{
"type": "macro",
"id": "123",
"name": "Order Status Update",
"body": "<p>Hi <attribute key=\"user.name\" default=\"there\"/>, your order is ready!</p>",
"body_text": "Hi there, your order is ready!",
"created_at": "2025-07-17T11:18:08.000Z",
"updated_at": "2025-07-17T15:30:24.000Z",
"visible_to": "everyone",
"visible_to_team_ids": [],
"available_on": ["inbox", "messenger"]
}
],
"pages": {
"type": "pages",
"per_page": 10,
"next": {
"starting_after": "WzE3MTk0OTM3NTcuMCwgIjEyMyJd"
}
}
}Notes:
- Placeholders in macro bodies are transformed from Intercom format to XML-like attributes
- The pagination cursor encodes
[updated_at, id]for stable ordering - Maximum 150 macros can be retrieved per request
For complete documentation and more examples, see the Macros API Reference.
New endpoints to manage content snippets via the public API. Content snippets are reusable pieces of content that power Fin (AI agent) and Copilot responses.
Endpoints:
GET /content_snippets- List all content snippets (paginated)GET /content_snippets/{id}- Retrieve a single content snippetPOST /content_snippets- Create a new content snippetPUT /content_snippets/{id}- Update an existing content snippetDELETE /content_snippets/{id}- Delete a content snippet
DELETE /content_snippets/{id} returns 204 No Content on success. If the snippet is referenced by Fin Procedures, the request returns 422 with error code content_has_procedure_dependencies.
You can also subscribe to webhook notifications for content snippet lifecycle events:
content_snippet.created— fires when a content snippet is createdcontent_snippet.updated— fires when a content snippet's content or metadata is updatedcontent_snippet.deleted— fires when a content snippet is deleted
These topics require the Read content snippets permission.
OAuth Scope: read_write_content_snippets
Example Request:
GET /content_snippetsExample Response:
{
"type": "list",
"data": [
{
"type": "content_snippet",
"id": "123",
"title": "How to reset your password",
"locale": "en",
"json_blocks": [
{ "type": "paragraph", "text": "Navigate to Settings > Security > Reset password." }
],
"chatbot_availability": 1,
"copilot_availability": 1,
"created_at": 1663597223,
"updated_at": 1663597223
}
],
"total_count": 1,
"page": 1,
"per_page": 50,
"total_pages": 1
}You can now apply and remove an existing tag on Knowledge Hub content — articles, content snippets, and internal articles — through the API. The tag must already exist in the workspace (create tags with the Tags API), and the authenticating teammate must have the manage knowledge base content permission.
POST /articles/{article_id}/tags— Apply a tag to an article. Requires theread_write_articles_scopeOAuth scope.DELETE /articles/{article_id}/tags/{id}— Remove a tag from an article. Requires theread_write_articles_scopeOAuth scope.POST /content_snippets/{content_snippet_id}/tags— Apply a tag to a content snippet. Requires theread_write_content_snippetsOAuth scope.DELETE /content_snippets/{content_snippet_id}/tags/{id}— Remove a tag from a content snippet. Requires theread_write_content_snippetsOAuth scope.POST /internal_articles/{internal_article_id}/tags— Apply a tag to an internal article. Requires theread_write_articles_scopeOAuth scope.DELETE /internal_articles/{internal_article_id}/tags/{id}— Remove a tag from an internal article. Requires theread_write_articles_scopeOAuth scope.
The tag is returned with applied_at and applied_by populated when applied, and with both set to null when removed.
Internal article read responses now also include a tags field — GET /internal_articles and GET /internal_articles/{id} return the tags currently applied to each internal article.
The POST /content/bulk_actions endpoint now supports an update_tags action to apply and/or remove existing tags across Knowledge Hub content in a single call.
Provide a tags object with add_tag_ids and/or remove_tag_ids (supply at least one). Tags must already exist and not be archived (create tags with the Tags API); unknown or archived tag IDs are rejected with parameter_invalid (HTTP 422). Applying and removing are independent — this is not a replace.
update_tags accepts article, content_snippet, external_content, file_source_content, and internal_article content types. Unlike the other bulk actions, it addresses articles by the parent article id, not article_content — using article_content is silently dropped.
Requires the write_content OAuth scope.
You can now create, retrieve, update, and delete audiences through the Audiences API.
New endpoints:
GET /audiences— List all audiences configured for your workspace. The response is a paginated list of audience objects, each containingid,name,created_at,updated_at,predicates, androle_predicatesfields.GET /audiences/{id}— Retrieve a single audiencePOST /audiences— Create a new audiencePUT /audiences/{id}— Update an existing audienceDELETE /audiences/{id}— Delete an audience
The audience object includes predicates and role_predicates arrays, which define the matching rules for the audience. Each predicate contains attribute, type, comparison, and value fields.
OAuth Scope: read_audiences (list/retrieve)
You can also subscribe to webhook notifications for audience lifecycle and membership events:
audience.created— fires when an audience is createdaudience.updated— fires when an audience's name, predicates, or role predicates changeaudience.deleted— fires when an audience is deletedaudience.member_added— fires when a piece of content is added as a member of an audienceaudience.member_removed— fires when a piece of content is removed from an audience
All five topics require the Read audiences permission. The audience.created and audience.updated payloads contain the full audience object; audience.deleted sends a minimal identification payload; and the audience.member_* payloads contain an audience_membership object describing the content entity and the audience it belongs to.
You can now manage a help center's URL redirects through the API. A redirect maps a from_url to an article or collection, so links to old or external URLs resolve to live content — useful when migrating from another knowledge base or restructuring article URLs.
Listing and reading redirects works with the read_help_center_redirects_scope OAuth scope; creating and deleting require the read_write_help_center_redirects_scope OAuth scope.
GET /help_center/help_centers/{help_center_id}/redirects— List all redirects for a help centerPOST /help_center/help_centers/{help_center_id}/redirects— Create a redirectGET /help_center/help_centers/{help_center_id}/redirects/{id}— Retrieve a redirectDELETE /help_center/help_centers/{help_center_id}/redirects/{id}— Delete a redirect
The from_url must be an absolute URL within the help center's URL space, and a redirect's target (article or collection) must belong to the help center.
You can now manage the draft lifecycle of a published article through the API. Staging changes as a draft leaves the live article untouched until you choose to publish.
A published article that has staged edits reports has_unpublished_changes: true along with a draft_updated_at timestamp, so you can detect pending changes before reading or publishing them.
Reading a draft works with the read_articles_scope OAuth scope; staging and publishing require the read_write_articles_scope OAuth scope.
GET /articles/{id}/draft— Retrieve a staged draftPUT /articles/{id}/draft— Stage a draftPOST /articles/{id}/draft/publish— Publish a staged draft
On a multilingual workspace, publishing requires a locales array naming which locales to publish; on a single-language workspace the body can be omitted.
You can now read the version history of an article through the API. Versions are returned newest-first, and you can retrieve any prior version's full content (title, body, body_markdown) without affecting the live article.
These endpoints require the read_articles_scope OAuth scope.
GET /articles/{article_id}/versions— List version metadata for an article (newest-first, paginated).GET /articles/{article_id}/versions/{id}— Retrieve a single version with its full content.
Both endpoints accept an optional locale query parameter to scope the response to a specific locale.
You can now search across all Knowledge Hub content types — articles, content snippets, external content, file source content, and internal articles — in a single call with GET /content/search. The endpoint requires the read_content OAuth scope.
You can narrow results with filters for publication state, locale, tags, folders, content type, AI usage (Copilot, Fin AI Agent, and Fin Sales Agent), admin attribution (created by and last updated by), and creation or update date ranges.
You can now manage office hours schedules and their exceptions through the new Office Hours API. Schedules define the recurring weekly hours your workspace is open; exceptions override those hours on specific dates (for example, public holidays).
These endpoints require an OAuth token with the read_write_office_hours scope.
Schedules
GET /office_hours_schedules— List all schedulesPOST /office_hours_schedules— Create a scheduleGET /office_hours_schedules/{id}— Retrieve a schedulePUT /office_hours_schedules/{id}— Update a scheduleDELETE /office_hours_schedules/{id}— Delete a schedule
Exceptions
GET /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions— List exceptions for a schedulePOST /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions— Create an exceptionGET /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions/{id}— Retrieve an exceptionPUT /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions/{id}— Update an exceptionDELETE /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions/{id}— Delete an exception
You can now retrieve the banners a contact matches and record dismissals through the new Banners API. This lets you display banners on surfaces outside the Messenger, such as native mobile apps, kiosks, and embedded tools, while keeping dismissal state in sync with the web Messenger.
These endpoints require an OAuth token with the read_write_users scope.
GET /contacts/{id}/banners— List the banners a contact currently matchesPOST /contacts/{id}/banners/{view_id}/dismiss— Dismiss a banner for a contact
Requesting a contact's banners records an impression for each banner returned, so call the endpoint at the point you're about to display them. Banners that rely on client-side targeting rules (such as page URL or time on page) aren't returned by this endpoint.
A new GET /conversations/{id}/side_conversations endpoint is now available for retrieving side conversations (internal teammate threads) associated with a conversation. Results are paginated and include the full conversation parts for each side conversation.
New Endpoint:
Query Parameters:
page(optional) - Page number of results to return (default: 1)per_page(optional) - Number of side conversations per page, between 1 and 50 (default: 25)
Example Response:
{
"type": "side_conversation.list",
"side_conversations": [
{
"side_conversation_id": "456",
"conversation_parts": [
{
"type": "conversation_part",
"id": "789",
"part_type": "comment",
"body": "<p>Internal note about this issue</p>",
"author": {
"type": "admin",
"id": "123",
"name": "Jane Example",
"email": "jane@example.com"
}
}
],
"total_count": 1
}
],
"total_count": 1,
"pages": {
"type": "pages",
"page": 1,
"per_page": 25,
"total_pages": 1
}
}OAuth Scope: read_conversations
You can now create notes on company records via the API using POST /companies/{company_id}/notes, mirroring the existing contact notes functionality. The request and response format is identical to contact notes, with an optional admin_id parameter that defaults to the token owner. Notes are polymorphic — a single note belongs to either a contact or a company, never both.
Company API responses also include a notes field, so notes attached to a company are visible alongside the rest of the company record.
You can now merge a secondary conversation into a primary conversation via the API using POST /conversations/{id}/merge. The secondary conversation is closed and linked to the primary, which becomes the surviving thread. Returns the primary conversation on success.
Requires write_conversations OAuth scope. When the secondary is a ticket, write_tickets scope is also required.
You can now link an existing conversation or customer ticket to a tracker ticket after the tracker has been created, and unlink it later.
New endpoints:
POST /tickets/{ticket_id}/linked_conversations— Link a conversation to a tracker ticketDELETE /tickets/{ticket_id}/linked_conversations/{id}— Unlink a conversation from a tracker ticket
The ticket in the path must be a tracker ticket. Pass the conversation (or customer ticket) to link as conversation_id in the request body. A conversation can be linked to one tracker ticket at a time.
The WhatsApp Message status API is now available. This endpoint returns paginated status events for WhatsApp messages sent via the Outbound module, providing information about delivery state and related message details.
You can now retrieve a list of deleted conversation IDs and their deletion timestamps using the new GET /conversations/deleted endpoint.