Skip to content

Changelog (2.16)

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.

Changes

Contacts: email_domain field now returned in Contact responses

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's email address, for example example.com for jane@example.com. null when the contact has no email.

Affected endpoints:

Set the ticket state when converting a conversation to a ticket

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.

Change a ticket's type

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: external_references to source helpdesk and CRM records

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:

Example:

{
  "external_references": [
    { "type": "zendesk_ticket", "id": "3633338" },
    { "type": "zendesk_sunshine_conversation", "id": "abc-def-uuid" }
  ]
}

Conversations: search by custom conversation attributes

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:

Set brand_id when creating a conversation

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.

Send WhatsApp messages via the Messages API

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: default flag and supported locales on Help Center responses

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:

Conversations: dropped email recipients now returned with a drop_reason

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 of source.recipients and of email_message_metadata.email_address_headers. null for a delivered recipient; otherwise a machine-readable reason such as user_bounced (address previously hard-bounced), user_suppressed (on the suppression list), user_not_contactable (opted out of email), email_address_ignored, or banned_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}/replysource.recipients[].drop_reason and, on email parts, conversation_parts[].email_message_metadata.email_address_headers[].drop_reason
  • GET /conversations, POST /conversations/searchsource.recipients[].drop_reason
  • GET /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.

Conversations: Reply-To addresses on email sources

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.

Contacts: mark an email as verified

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} (with display_as=plaintext)
  • GET /conversations (with display_as=plaintext)
  • Webhook topic: conversation.operator.replied

attachment_files on ticket reply for admin senders

The POST /tickets/{id}/reply endpoint now accepts attachment_files for admin replies (type: admin). Each entry requires:

FieldTypeDescription
datastringBase64-encoded file content
namestringFilename including extension
content_typestringMIME type (e.g. application/pdf)

Up to 10 files per request. When both attachment_files and attachment_urls are provided, attachment_files takes precedence.

Conversations API: channel field for channel mix reporting

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. Returns null if 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. null if channel data is unavailable.
    • current (string, nullable) — the current channel of the conversation. May differ from initial if the conversation was migrated between channels. null if channel data is unavailable.

Example:

{
  "channel": {
    "initial": "zendesk_sunshine",
    "current": "zendesk_sunshine"
  }
}

Conversations API: source field accuracy improved

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.

Company Activity Tracking via API

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.

Reporting Data Export API: qualified_id for Attribute Disambiguation

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 attributes
  • company - Company custom data attributes
  • conversation - Conversation custom data attributes
  • ticket - Ticket attributes
  • system - 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.

Email History in Conversation Source

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's Message-ID header
  • subject — the subject of the email
  • email_address_headers — a list of an email address headers
  • history — the HTML content of any quoted or forwarded email history from the initial inbound message

Updated Endpoint:

  • GET /conversations/{id}?include_email_history=true — returns email_message_metadata in the source object

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.

Activity Log: hide_csat_from_agents_setting_change event type added

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.

Articles, Internal Articles & Content Snippets: body_markdown field

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/search
  • POST /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: audience_ids field

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 inside translated_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: [] on PUT clears all audience memberships from every locale
  • Unknown audience IDs return a 404 error with no partial commit

Affected endpoints:

  • GET /articles, GET /articles/{id}
  • POST /articles, PUT /articles/{id}

Content Snippets, Internal Articles & AI External Pages: audience_ids field

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_snippets and POST /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 404 error 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}

Agent availability fields added to Content Snippets, Internal Articles, Articles, and External Pages

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, and PUT responses 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, and PUT responses and request bodies.
  • Articles — Added to POST and PUT request bodies (already present in responses).
  • External Pagesai_sales_agent_availability added to GET responses and POST/PUT request bodies. ai_agent_availability and ai_copilot_availability added to PUT request bodies (already present in GET responses and POST requests).

Audit and suggestion fields added to Articles

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 in translated_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 in translated_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.

Help Center audience added to Articles

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 of everyone, all_users, all_visitors, all_leads, all_visitors_and_leads, or restricted (a custom audience ruleset). For multilingual articles this is the article-level audience, and it is null when 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.

Fin statistics added to article statistics

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.

Contacts: merge_history field and GET /contacts/{id}/merge_history endpoint added

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:

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 in
  • source_contact_role — the role (lead or user) of the merged contact
  • merged_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.

Schedule Article publishing and unpublishing

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 with state: "published" to schedule the article instead of publishing it immediately. Setting it to null cancels a pending publish schedule. Combining with state: "draft" returns a 400 error.
  • scheduled_unpublish_at — schedule a future unpublish for the article. Setting it to null cancels 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}.

Admin role now included in API responses

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.

Sales Agent metadata on Conversations

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 participated
  • sales_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 identifier
    • collected_data — flat key-value map of memory fields collected during the conversation

Applies to:

  • GET /conversations/{id}
  • POST /conversations/search

Ticket state change tracking

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.

Conversations API: include QA monitor evaluations and scorecards

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 a monitor_evaluations array listing any QA monitors that flagged the conversation.
  • include_scorecards=true — adds a scorecards array 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.

Conversations API: display_as parameter behavior

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.

Conversations API: retain_metrics parameter for conversation deletion

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.
Required scope for retain_metrics=false

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.

Breaking Changes

Merged contacts now return 410 Gone instead of 404

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.

POST /contacts/merge rejects contacts that are not duplicates

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
}

Reporting Data Export API now includes no-reply conversations

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.

Dedicated Conversation Attributes API

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 attributes
  • GET /conversations/attributes/{id} — Get a single conversation attribute
  • POST /conversations/attributes — Create a new conversation attribute
  • PUT /conversations/attributes/{id} — Update a conversation attribute
  • DELETE /conversations/attributes/{id} — Archive a conversation attribute (soft delete)
  • POST /conversations/attributes/{id}/options — Add an option to a list attribute
  • PUT /conversations/attributes/{id}/options/{option_id} — Rename a list option
  • DELETE /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 with id UUID, label, and archived)
  • Relationship — includes reference object with type (one/many) and object_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_attributes without a model parameter no longer includes conversation attributes in the response.
  • Calling GET /data_attributes?model=conversation returns a 422 error:
{
  "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.

Contact owner_id now returns as string

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 /contacts
  • PUT /contacts/{id}

Example responses:

Before:

{
  "id": "5ba682d23d7cf92bef87bfd4",
  "owner_id": 321
}

After:

{
  "id": "5ba682d23d7cf92bef87bfd4",
  "owner_id": "321"
}

Conversation assignee IDs return 0 instead of null when unassigned

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
}

Ticket assignee IDs are now integers

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 response status

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: true if the entity was tagged, false if 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.

Conversation priority returns granular levels

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.

Articles API: parent_ids replaces parent_id and parent_type

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 /articlesparent_id and parent_type removed from response
  • POST /articlesparent_ids now accepted in request; parent_id and parent_type removed from response
  • GET /articles/{id}parent_id and parent_type removed from response
  • PUT /articles/{id}parent_ids now accepted in request; parent_id and parent_type removed from response
  • GET /articles/searchparent_id and parent_type removed from response

POST /contacts/search now validates sort.order

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.

New Features

Data Connectors API

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 connector
  • PATCH /data_connectors/{id} - Update a data connector (fields, state transitions)
  • DELETE /data_connectors/{id} - Delete a data connector
  • GET /data_connectors - List all data connectors (paginated)
  • GET /data_connectors/{id} - Retrieve a single data connector with full detail
  • GET /data_connectors/{id}/execution_results - List execution results
  • GET /data_connectors/{id}/execution_results/{id} - Retrieve a single execution result

Key Features:

  • Create in draft: New connectors start in draft state — 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 409 if the connector is in use or not in draft state
  • Cursor-based pagination: Navigate through data connectors and execution results using starting_after and per_page
  • Filtering: Filter execution results by success, error_type, and time range
  • Opt-in bodies: Use include_bodies=true to 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.

Search Activity Logs API

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 timestamp
  • event_types (optional) - Array of event type strings to filter by
  • page (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.

Real-Time Team Metrics

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. Use GET /teams to 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/metrics

Example 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.

Custom Object Instances: list-all endpoint

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).

Macros API

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 pagination
  • GET /macros/{id} - Retrieve a specific macro by ID

Key Features:

  • Cursor-based pagination: Navigate through large sets of macros efficiently using the starting_after parameter 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_since parameter 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.

Content Snippets API

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 snippet
  • POST /content_snippets - Create a new content snippet
  • PUT /content_snippets/{id} - Update an existing content snippet
  • DELETE /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 created
  • content_snippet.updated — fires when a content snippet's content or metadata is updated
  • content_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_snippets

Example 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
}

Tag Knowledge Hub content

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.

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.

Bulk tag Knowledge Hub content

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.

Audiences API

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 containing id, name, created_at, updated_at, predicates, and role_predicates fields.
  • GET /audiences/{id} — Retrieve a single audience
  • POST /audiences — Create a new audience
  • PUT /audiences/{id} — Update an existing audience
  • DELETE /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 created
  • audience.updated — fires when an audience's name, predicates, or role predicates change
  • audience.deleted — fires when an audience is deleted
  • audience.member_added — fires when a piece of content is added as a member of an audience
  • audience.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.

Help Center Redirects API

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.

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.

Article drafts

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.

On a multilingual workspace, publishing requires a locales array naming which locales to publish; on a single-language workspace the body can be omitted.

Article version history

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.

Both endpoints accept an optional locale query parameter to scope the response to a specific locale.

Search Knowledge Hub content

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.

Office Hours API

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

Exceptions

Banners API

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.

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.

Side Conversations API

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

Company Notes API

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.

Merge conversations via API

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 ticket
  • DELETE /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.

New WhatsApp Message status API

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.

List Deleted Conversations API

You can now retrieve a list of deleted conversation IDs and their deletion timestamps using the new GET /conversations/deleted endpoint.