# Changelog (2.16)

For changes that have been updated across all versions, see the [Unversioned changes](/docs/build-an-integration/learn-more/rest-apis/unversioned-changes) page.

There are [breaking changes](/docs/build-an-integration/learn-more/rest-apis/api-changelog#about-breaking-changes-in-the-intercom-api) in this version, which are detailed below.

## Changes

### Ticket snooze webhook topics

You can now subscribe to webhook notifications when a ticket is snoozed or unsnoozed. Two new topics are available:

- `ticket.admin.snoozed` — fires when a teammate snoozes a ticket
- `ticket.admin.unsnoozed` — fires when a snoozed ticket is unsnoozed


Both topics require the **Read tickets** permission, and their payloads contain the Ticket, the same as the other `ticket.admin.*` topics. They are the ticket equivalents of `conversation.admin.snoozed` and `conversation.admin.unsnoozed`. See the [webhook topics reference](/docs/references/webhooks/webhook-models#ticket-topics) for details.

### 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:**

- [Get a contact](/docs/references/rest-api/api.intercom.io/contacts/showcontact)
- [Get a contact by External ID](/docs/references/rest-api/api.intercom.io/contacts/showcontactbyexternalid)
- [List contacts](/docs/references/rest-api/api.intercom.io/contacts/listcontacts)
- [Search contacts](/docs/references/rest-api/api.intercom.io/contacts/searchcontacts)
- [Create a contact](/docs/references/rest-api/api.intercom.io/contacts/createcontact)
- [Update a contact](/docs/references/rest-api/api.intercom.io/contacts/updatecontact)


### Set the ticket state when converting a conversation to a ticket

The [`POST /conversations/{id}/convert`](/docs/references/rest-api/api.intercom.io/conversations/convertconversationtoticket) 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`](/docs/references/rest-api/api.intercom.io/tickets/changetickettype) 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:**

- [`GET /conversations/{id}`](/docs/references/rest-api/api.intercom.io/conversations/retrieveconversation) — retrieve a single conversation
- [`GET /conversations`](/docs/references/rest-api/api.intercom.io/conversations/listconversations) — list conversations
- [`POST /conversations/search`](/docs/references/rest-api/api.intercom.io/conversations/searchconversations) — search conversations


**Example:**

```json
{
  "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:**

- [`POST /conversations/search`](/docs/references/rest-api/api.intercom.io/conversations/searchconversations) — search conversations


### Set `brand_id` when creating a conversation

You can now set `brand_id` when [creating a conversation](/docs/references/rest-api/api.intercom.io/conversations/createconversation). 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](/docs/references/rest-api/api.intercom.io/messages/createmessage) 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:**

- [`GET /help_center/help_centers`](/docs/references/rest-api/api.intercom.io/help-center/listhelpcenters) — list every Help Center in the workspace
- [`GET /help_center/help_centers/{id}`](/docs/references/rest-api/api.intercom.io/help-center/retrievehelpcenter) — retrieve a single Help Center


### 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}/reply` — `source.recipients[].drop_reason` and, on email parts, `conversation_parts[].email_message_metadata.email_address_headers[].drop_reason`
- `GET /conversations`, `POST /conversations/search` — `source.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](/docs/references/rest-api/api.intercom.io/contacts/createcontact) or [update](/docs/references/rest-api/api.intercom.io/contacts/updatecontact) 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`.

Verifying a lead's email also makes that lead reusable whenever Intercom matches an email address to a contact. When inbound email arrives from an address no user has, or an outbound conversation or ticket is created for one, Intercom reuses a lead with that address whose email is verified, instead of creating a new lead. Leads with an unverified email are not matched this way, so creating a lead with `email_verified: true` is what lets a later message reuse it rather than spawn a duplicate.

### Conversations: link URLs preserved in plaintext (`display_as=plaintext`)

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`](/docs/references/rest-api/api.intercom.io/tickets/replyticket) 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.

### 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:**

```json
{
  "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:**

```json
{
  "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 Pages** — `ai_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`](/docs/references/rest-api/api.intercom.io/articles/listarticles), [`GET /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/retrievearticle), [`GET /articles/search`](/docs/references/rest-api/api.intercom.io/articles/searcharticles), [`POST /articles`](/docs/references/rest-api/api.intercom.io/articles/createarticle), [`PUT /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/updatearticle), 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`](/docs/references/rest-api/api.intercom.io/articles/listarticles), [`GET /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/retrievearticle), [`GET /articles/search`](/docs/references/rest-api/api.intercom.io/articles/searcharticles), [`POST /articles`](/docs/references/rest-api/api.intercom.io/articles/createarticle), [`PUT /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/updatearticle), 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}`](/docs/references/rest-api/api.intercom.io/articles/retrievearticle), [`POST /articles`](/docs/references/rest-api/api.intercom.io/articles/createarticle), and [`PUT /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/updatearticle). 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:

- [Get a contact](/docs/references/rest-api/api.intercom.io/contacts/showcontact)
- [Get a contact by External ID](/docs/references/rest-api/api.intercom.io/contacts/showcontactbyexternalid)
- [List contacts](/docs/references/rest-api/api.intercom.io/contacts/listcontacts)
- [Search contacts](/docs/references/rest-api/api.intercom.io/contacts/searchcontacts)
- [Update a contact](/docs/references/rest-api/api.intercom.io/contacts/updatecontact)
- [Merge a lead and a user](/docs/references/rest-api/api.intercom.io/contacts/mergecontact) — 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 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`](/docs/references/rest-api/api.intercom.io/contacts/listcontactmergehistory) 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`](/docs/references/rest-api/api.intercom.io/articles/createarticle) and [`PUT /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/updatearticle), and returned by [`GET /articles`](/docs/references/rest-api/api.intercom.io/articles/listarticles), [`GET /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/retrievearticle), [`GET /articles/search`](/docs/references/rest-api/api.intercom.io/articles/searcharticles), [`POST /articles`](/docs/references/rest-api/api.intercom.io/articles/createarticle), and [`PUT /articles/{id}`](/docs/references/rest-api/api.intercom.io/articles/updatearticle).

### 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:**

```json
{
  "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:

- [`GET /conversations`](/docs/references/rest-api/api.intercom.io/conversations/listconversations)
- [`GET /conversations/{id}`](/docs/references/rest-api/api.intercom.io/conversations/retrieveconversation)
- [`POST /conversations/search`](/docs/references/rest-api/api.intercom.io/conversations/searchconversations)


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](/docs/references/rest-api/api.intercom.io/conversations/deleteconversation) 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](/docs/build-an-integration/learn-more/authentication/oauth-scopes) for details.

**Example Request:**

```bash
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](https://www.intercom.com/help/en/articles/13885146-deleting-a-conversation).

## Breaking Changes

### Fin Agent API: New `replying` status for intermediate reply events

The `fin_replied` webhook event now uses a `replying` status for intermediate reply parts, and a separate `fin_status_updated` event with `awaiting_user_reply` fires once Fin's reply is done. This replaces the previous behavior where every `fin_replied` event had `status: awaiting_user_reply`, making it impossible to distinguish intermediate replies from the final one.

**Before:**

```
fin_replied (status: awaiting_user_reply) — part 1
fin_replied (status: awaiting_user_reply) — part 2
fin_replied (status: awaiting_user_reply) — part 3
```

**After:**

```
fin_replied (status: replying) — part 1
fin_replied (status: replying) — part 2
fin_replied (status: replying) — part 3
fin_status_updated (status: awaiting_user_reply) — Fin's reply is done
```

**What changed:**

- `fin_replied` event: `status` enum now includes `replying` (new default for intermediate replies) and `awaiting_user_reply` (legacy)
- `fin_status_updated` event: `status` enum now includes `awaiting_user_reply` in addition to `escalated`, `resolved`, and `complete`


Clients keying off `fin_replied.status == "awaiting_user_reply"` to detect that Fin has finished lose that signal on upgrade. Update your integration to listen for the `fin_status_updated` event with `status: awaiting_user_reply`, which fires once Fin's reply is done and the user can respond.

### 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:

```json
{
  "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](/docs/references/rest-api/api.intercom.io/contacts/showcontact) and lookups by `external_id`. Merged contacts continue to be excluded from [List contacts](/docs/references/rest-api/api.intercom.io/contacts/listcontacts) and [Search contacts](/docs/references/rest-api/api.intercom.io/contacts/searchcontacts) results.

### POST /contacts/merge rejects contacts that are not duplicates

[Merging a lead into a user](/docs/references/rest-api/api.intercom.io/contacts/mergecontact) 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**:

```json
{
  "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:

```json
{
  "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`](/docs/references/rest-api/api.intercom.io/conversations-attributes/listconversationattributes) 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:


```json
{
  "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:*

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

*After:*

```json
{
  "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):**

```json
{
  "admin_assignee_id": null,
  "team_assignee_id": null
}
```

**After (integer):**

```json
{
  "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):**

```json
{
  "admin_assignee_id": "991268013",
  "team_assignee_id": "0"
}
```

**After (integer):**

```json
{
  "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):**

```json
{
  "type": "tag",
  "id": "789",
  "name": "VIP",
  "users": [],
  "companies": []
}
```

**Example response (one company tagged, one missing):**

```json
{
  "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 /articles` — `parent_id` and `parent_type` removed from response
- `POST /articles` — `parent_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/search` — `parent_id` and `parent_type` removed from response


### POST /contacts/search now validates sort.order

When sorting [Search contacts](/docs/references/rest-api/api.intercom.io/contacts/searchcontacts) results, `sort.order` now accepts only `ascending` or `descending`. Any other value returns **HTTP 400 Bad Request** instead of silently falling back to `descending`:

```json
{
  "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:

```json
{
  "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](/docs/references/rest-api/api.intercom.io/data-connectors).

### 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:**

- [`POST /admins/activity_logs/search`](/docs/references/rest-api/api.intercom.io/admins/searchactivitylogs)


**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:**

```json
{
  "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:**

```json
{
  "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](/docs/references/rest-api/api.intercom.io/admins/searchactivitylogs) and the [Activity Log Event Types API Reference](/docs/references/rest-api/api.intercom.io/admins/listactivitylogeventtypes).

### 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:**

```json
{
  "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](/docs/references/rest-api/api.intercom.io/teams/getteammetrics).

### 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:**

```bash
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:**

```json
{
  "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](/docs/references/rest-api/api.intercom.io/macros).

### 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:**

```json
{
  "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.

- [`POST /articles/{article_id}/tags`](/docs/references/rest-api/api.intercom.io/articles/attachtagtoarticle) — Apply a tag to an article. Requires the `read_write_articles_scope` OAuth scope.
- [`DELETE /articles/{article_id}/tags/{id}`](/docs/references/rest-api/api.intercom.io/articles/detachtagfromarticle) — Remove a tag from an article. Requires the `read_write_articles_scope` OAuth scope.
- [`POST /content_snippets/{content_snippet_id}/tags`](/docs/references/rest-api/api.intercom.io/content-snippets/attachtagtocontentsnippet) — Apply a tag to a content snippet. Requires the `read_write_content_snippets` OAuth scope.
- [`DELETE /content_snippets/{content_snippet_id}/tags/{id}`](/docs/references/rest-api/api.intercom.io/content-snippets/detachtagfromcontentsnippet) — Remove a tag from a content snippet. Requires the `read_write_content_snippets` OAuth scope.
- [`POST /internal_articles/{internal_article_id}/tags`](/docs/references/rest-api/api.intercom.io/internal-articles/attachtagtointernalarticle) — Apply a tag to an internal article. Requires the `read_write_articles_scope` OAuth scope.
- [`DELETE /internal_articles/{internal_article_id}/tags/{id}`](/docs/references/rest-api/api.intercom.io/internal-articles/detachtagfrominternalarticle) — Remove a tag from an internal article. Requires the `read_write_articles_scope` OAuth 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`](/docs/references/rest-api/api.intercom.io/internal-articles/listinternalarticles) and [`GET /internal_articles/{id}`](/docs/references/rest-api/api.intercom.io/internal-articles/retrieveinternalarticle) return the tags currently applied to each internal article.

### Bulk tag Knowledge Hub content

The [`POST /content/bulk_actions`](/docs/references/rest-api/api.intercom.io/content/bulkcontentactions) 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.

- [`GET /help_center/help_centers/{help_center_id}/redirects`](/docs/references/rest-api/api.intercom.io/help-center/listhelpcenterredirects) — List all redirects for a help center
- [`POST /help_center/help_centers/{help_center_id}/redirects`](/docs/references/rest-api/api.intercom.io/help-center/createhelpcenterredirect) — Create a redirect
- [`GET /help_center/help_centers/{help_center_id}/redirects/{id}`](/docs/references/rest-api/api.intercom.io/help-center/retrievehelpcenterredirect) — Retrieve a redirect
- [`DELETE /help_center/help_centers/{help_center_id}/redirects/{id}`](/docs/references/rest-api/api.intercom.io/help-center/deletehelpcenterredirect) — 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.

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

- [`GET /articles/{id}/draft`](/docs/references/rest-api/api.intercom.io/articles/retrievearticledraft) — Retrieve a staged draft
- [`PUT /articles/{id}/draft`](/docs/references/rest-api/api.intercom.io/articles/stagearticledraft) — Stage a draft
- [`POST /articles/{id}/draft/publish`](/docs/references/rest-api/api.intercom.io/articles/publisharticledraft) — 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.

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

- [`GET /articles/{article_id}/versions`](/docs/references/rest-api/api.intercom.io/articles/listarticleversions) — List version metadata for an article (newest-first, paginated).
- [`GET /articles/{article_id}/versions/{id}`](/docs/references/rest-api/api.intercom.io/articles/retrievearticleversion) — Retrieve a single version with its full content.


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`](/docs/references/rest-api/api.intercom.io/content/searchcontent). 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**

- [`GET /office_hours_schedules`](/docs/references/rest-api/api.intercom.io/office-hours/listofficehoursschedules) — List all schedules
- [`POST /office_hours_schedules`](/docs/references/rest-api/api.intercom.io/office-hours/createofficehoursschedule) — Create a schedule
- [`GET /office_hours_schedules/{id}`](/docs/references/rest-api/api.intercom.io/office-hours/getofficehoursschedule) — Retrieve a schedule
- [`PUT /office_hours_schedules/{id}`](/docs/references/rest-api/api.intercom.io/office-hours/updateofficehoursschedule) — Update a schedule
- [`DELETE /office_hours_schedules/{id}`](/docs/references/rest-api/api.intercom.io/office-hours/deleteofficehoursschedule) — Delete a schedule


**Exceptions**

- [`GET /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions`](/docs/references/rest-api/api.intercom.io/office-hours/listofficehoursexceptions) — List exceptions for a schedule
- [`POST /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions`](/docs/references/rest-api/api.intercom.io/office-hours/createofficehoursexception) — Create an exception
- [`GET /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions/{id}`](/docs/references/rest-api/api.intercom.io/office-hours/getofficehoursexception) — Retrieve an exception
- [`PUT /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions/{id}`](/docs/references/rest-api/api.intercom.io/office-hours/updateofficehoursexception) — Update an exception
- [`DELETE /office_hours_schedules/{office_hours_schedule_id}/office_hours_exceptions/{id}`](/docs/references/rest-api/api.intercom.io/office-hours/deleteofficehoursexception) — Delete an exception


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

- [`GET /contacts/{id}/banners`](/docs/references/rest-api/api.intercom.io/banners/listcontactbanners) — List the banners a contact currently matches
- [`POST /contacts/{id}/banners/{view_id}/dismiss`](/docs/references/rest-api/api.intercom.io/banners/dismisscontactbanner) — 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.

### 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:**

- [`GET /conversations/{id}/side_conversations`](/docs/references/rest-api/api.intercom.io/conversations/listsideconversations)


**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:**

```json
{
  "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.

### Link conversations to tracker tickets

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](/docs/references/rest-api/api.intercom.io/whatsapp/getwhatsappmessagestatus) 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.