# Preview a contact merge

Preview the result of merging one contact into another without applying the merge. This is a read-only dry run: it reports whether the merge is allowed and, if so, exactly what would change, **without modifying any data**.
Send the same `from` and `into` contact IDs you would pass to [Merge two contacts](/docs/references/preview/rest-api/api.intercom.io/contacts/mergecontact). As with the merge endpoint, `from` may have a `role` of `lead` or `user`, and `into` must have a `role` of `user`. You can optionally pass `custom_attribute_names` to include specific custom attributes in the previewed attribute changes.
- When `allowed` is `true`, the response contains a `summary`: every attribute change (with the resulting value on the surviving contact) and, for each object type, two counts — `from`, how many are on the source contact and would move to the destination, and `into`, how many the destination already has. Counts are reported for conversations, tickets, phone calls, notes, and tags. `conversations` counts non-ticket conversations only; tickets are reported separately under `tickets`, and `phone_calls` is present only for phone-channel merges.
- When `allowed` is `false`, the response contains `reasons`. Each reason has a stable `code` you can branch on and a human-readable `message` you can display.

By default a merge requires the two contacts to share an identifier (email or phone). Set `skip_duplicate_validation` to `true` to preview a merge of contacts that don't, the same rule the merge endpoint enforces.
When `allowed` is `false`, `reasons[].code` is one of:
| code | meaning |
|  --- | --- |
| `source_same_as_target` | The source contact (`from`) and the destination contact (`into`) are the same contact. A contact can't be merged into itself. |
| `invalid_merge_role_pair` | The `from`/`into` roles aren't mergeable. `into` must be a user. |
| `no_shared_identifier` | The contacts share no email or phone. Set `skip_duplicate_validation` to preview anyway. |
| `facebook_user` | Facebook contacts can't be merged. |
| `external_lead_channel_not_allowed` | The source is an external-channel lead that isn't eligible for merging. |
| `mailing_list` | Contacts created from a mailing list can't be merged. |
| `user_to_lead` | `into` is a lead or visitor. A user can only be merged into another user. |
| `whatsapp_data_cannot_be_merged` | The WhatsApp number on the source contact can't be reassigned to the destination. |
| `telegram_data_cannot_be_merged` | The Telegram sender on the source contact can't be reassigned to the destination. |
| `ms_teams_data_cannot_be_merged` | The destination contact already has a Microsoft Teams sender for the same team as the source contact, so the source's sender can't be reassigned to it. |
| `source_has_telegram_data` | The source contact has Telegram data, which user-into-user merges don't yet support moving. |
| `phone_data_cannot_be_merged` | The phone data on the source contact can't be reassigned to the destination. |

Endpoint: POST /contacts/merge/preview
Version: Preview
Security: bearerAuth

## Header parameters:

  - `Intercom-Version` (string)

## Request fields (application/json):

  - `from` (string, required)
    The unique identifier for the contact to merge away from (the source contact). Can have a `role` of `lead` or `user`.
    Example: 6762f0d51bb69f9f2193bb7f

  - `into` (string, required)
    The unique identifier for the contact to merge into (the destination contact). Must have a `role` of `user`.
    Example: 6762f0d51bb69f9f2193bb80

  - `skip_duplicate_validation` (boolean)
    When `true`, previews the merge even if the contacts don't share a common identifier. Must be a JSON boolean; the string `"true"` does not bypass the check. Defaults to false.

  - `custom_attribute_names` (array)
    Custom attribute names to include in the previewed attribute changes.

## Response 200 fields (application/json):

  - `allowed` (boolean)
    Always true. The merge is allowed.
    Enum: true

  - `summary` (object)
    A summary of the changes the merge would make to the surviving contact.

  - `summary.attribute_changes` (object)
    The attribute changes the merge would make to the surviving contact.

  - `summary.attribute_changes.attributes` (array)
    The individual attribute changes. Capped at 50 items; total_count reflects the full number of changes.

  - `summary.attribute_changes.attributes.field` (string)
    The contact field that would change.
    Example: name

  - `summary.attribute_changes.attributes.outcome` (string)
    How the value is resolved. adopted = the source's value is taken; discarded = the source's value is dropped and the target's kept; merged = the two values are combined.
    Enum: "adopted", "discarded", "merged"

  - `summary.attribute_changes.attributes.from_value` (any)
    The source contact's value. Omitted when null. May be a string, array, or object depending on the field.

  - `summary.attribute_changes.attributes.into_value` (any)
    The target contact's value. Omitted when null. May be a string, array, or object depending on the field.

  - `summary.attribute_changes.attributes.new_into_value` (any)
    The resulting value on the surviving contact. Always present; may be null. May be a string, array, or object depending on the field.

  - `summary.attribute_changes.total_count` (integer)
    The total number of attribute changes detected, which may exceed the number of items in attributes.
    Example: 3

  - `summary.reassignments` (object)
    Two counts per object type — `from`, how many are on the source contact and would move to the surviving contact, and `into`, how many the surviving contact already has. Only types with reassignments are included, and both counts are always present on a type that is included.

  - `summary.reassignments.conversations` (object)
    Distinct non-ticket conversations, counting both conversations the contact owns and conversations it participates in. Tickets are excluded here and reported separately under `tickets`.

  - `summary.reassignments.conversations.from` (integer, required)
    The number of non-ticket conversations on the source contact that would be reassigned to the surviving contact.
    Example: 12

  - `summary.reassignments.conversations.into` (integer, required)
    The number of non-ticket conversations the surviving contact already has.
    Example: 5

  - `summary.reassignments.tickets` (object)
    Tickets, meaning conversations of any ticket type. Counted separately from `conversations` so a single conversation is never counted twice.

  - `summary.reassignments.tickets.from` (integer, required)
    The number of tickets on the source contact that would be reassigned to the surviving contact.
    Example: 2

  - `summary.reassignments.tickets.into` (integer, required)
    The number of tickets the surviving contact already has.
    Example: 1

  - `summary.reassignments.phone_calls` (object)
    Phone calls. Present only for phone-channel merges (phone lead or phone user).

  - `summary.reassignments.phone_calls.from` (integer, required)
    The number of phone calls on the source contact that would be reassigned to the surviving contact.
    Example: 1

  - `summary.reassignments.phone_calls.into` (integer, required)
    The number of phone calls the surviving contact already has.
    Example: 0

  - `summary.reassignments.tags` (object)
    Tags. `from` counts only tags the surviving contact does not already have.

  - `summary.reassignments.tags.from` (integer, required)
    The number of tags on the source contact that would be added to the surviving contact.
    Example: 3

  - `summary.reassignments.tags.into` (integer, required)
    The number of tags the surviving contact already has.
    Example: 4

  - `summary.reassignments.notes` (object)
    Notes attached to the contact.

  - `summary.reassignments.notes.from` (integer, required)
    The number of notes on the source contact that would be reassigned to the surviving contact.
    Example: 2

  - `summary.reassignments.notes.into` (integer, required)
    The number of notes the surviving contact already has.
    Example: 6

  - `allowed` (boolean)
    Always false. The merge is not allowed.
    Enum: false

  - `reasons` (array)
    One or more reasons the merge is not allowed.

  - `reasons.code` (string)
    A stable code identifying the reason. Branch on this value.
    Enum: "source_same_as_target", "invalid_merge_role_pair", "no_shared_identifier", "facebook_user", "external_lead_channel_not_allowed", "mailing_list", "user_to_lead", "whatsapp_data_cannot_be_merged", "telegram_data_cannot_be_merged", "ms_teams_data_cannot_be_merged", "source_has_telegram_data", "phone_data_cannot_be_merged"

  - `reasons.message` (string)
    A human-readable explanation. Display this to people; do not match on it, as the wording may change.

## Response 401 fields (application/json):

  - `type` (string, required)
    The type is error.list
    Example: error.list

  - `request_id` (string)
    Example: f93ecfa8-d08a-4325-8694-89aeb89c8f85

  - `errors` (array, required)
    An array of one or more error objects

  - `errors.code` (string, required)
    A string indicating the kind of error, used to further qualify the HTTP response code
    Example: unauthorized

  - `errors.message` (string)
    Optional. Human readable description of the error.
    Example: Access Token Invalid

  - `errors.field` (string)
    Optional. Used to identify a particular field or query parameter that was in error.
    Example: email

