---
openapi: 3.0.3
info:
  title: Braze REST API – Merge users
  description: |
    Merge one user profile into another. Up to 50 merges may be specified per
    request. This endpoint is asynchronous; merges are queued for processing.

    Each entry in `merge_updates` must contain exactly two keys —
    `identifier_to_merge` and `identifier_to_keep` — each referencing a user by
    `external_id`, `user_alias`, `email`, or `phone`. When `email` or `phone` is
    used, a `prioritization` array is required to disambiguate matching users.
  version: 1.0.0
  contact:
    name: Braze Support
    url: https://www.braze.com/docs/braze_support/
  license:
    name: Braze Documentation
    url: https://www.braze.com/docs/api/home/
servers:
  - url: https://rest.iad-01.braze.com
    description: US-01
  - url: https://rest.iad-02.braze.com
    description: US-02
  - url: https://rest.iad-03.braze.com
    description: US-03
  - url: https://rest.iad-04.braze.com
    description: US-04
  - url: https://rest.iad-05.braze.com
    description: US-05
  - url: https://rest.iad-06.braze.com
    description: US-06
  - url: https://rest.iad-07.braze.com
    description: US-07
  - url: https://rest.iad-08.braze.com
    description: US-08
  - url: https://rest.us-10.braze.com
    description: US-10
  - url: https://rest.fra-01.braze.eu
    description: EU-01
  - url: https://rest.fra-02.braze.eu
    description: EU-02
  - url: https://rest.au-01.braze.com
    description: AU-01
  - url: https://rest.id-01.braze.com
    description: ID-01
  - url: https://rest.jp-01.braze.com
    description: JP-01
  - url: https://rest.kr-01.braze.com
    description: KR-01
tags:
  - name: User data
    description: Identify, track, and manage your users.
security:
  - BearerAuth: []
paths:
  /users/merge:
    post:
      summary: Merge users
      description: |
        Merge one user into another by specifying source and destination
        identifiers. Requires a REST API key with the `users.merge` permission.

        **Rate limit:** 20,000 requests per minute (shared with `/users/delete`,
        `/users/alias/new`, `/users/identify`, and `/users/alias/update`).

        The endpoint does not guarantee the order in which `merge_updates`
        entries are processed.

        ## Prerequisites

        To use this endpoint, you'll need an [API key]({{site.baseurl}}/api/basics) with the `users.merge` permission.

        ## Rate limit

        We apply a shared rate limit of 20,000 requests per minute to this endpoint. This rate limit is shared with the `/users/delete`, `/users/alias/new`, `/users/identify`, and `/users/alias/update` endpoints, as documented in [API rate limits]({{site.baseurl}}/api/api_limits/).

        ## Request body

        ```
        Content-Type: application/json
        Authorization: Bearer YOUR_REST_API_KEY
        ```

        ```json
        {
          "merge_updates" : (required, array of objects)
        }
        ```

        ## Request parameters

        | Parameter | Required | Data Type | Description |
        |---|---|---|---|
        | `merge_updates` | Required | Array | An object array. Each object should contain an `identifier_to_merge` object and an `identifier_to_keep` object, which should each reference a user either by `external_id`,  `user_alias`, `phone`, or `email`. |

        ### Merge behavior

        The following behavior is true for all Braze features that **are not** powered by Snowflake. User merges won't be reflected for the **Messaging History** tab, Segment Extensions, Query Builder, and Currents.

        **Important:**
        The endpoint does not guarantee the sequence of `merge_updates` objects being updated.

        This endpoint merges the following fields if they're not found on the target user.

        - First name
        - Last name
        - Email addresses (unless they are [encrypted]({{site.baseurl}}/user_guide/data/infrastructure/field_level_encryption))
        - Gender
        - Date of birth
        - Phone number
        - Time zone
        - Home city
        - Country
        - Language
        - Device information
        - Session count (the sum of sessions from both profiles)
        - Date of first session (Braze picks the earlier date of the two dates)
        - Date of last session (Braze picks the later date of the two dates)
        - Custom attributes (Braze retains existing custom attributes on the target profile and includes custom attributes that didn't exist on the target profile)
        - Custom event and purchase event data
        - Custom event and purchase event properties for "X times in Y days" segmentation (where X<=50 and Y<=30)
        - Segmentable custom events summary
          - Event count (the sum from both profiles)
          - Event first occurred (Braze picks the earlier date of the two dates)
          - Event last occurred (Braze picks the later date of the two dates)
        - In-app purchase total in cents (the sum from both profiles)
        - Total number of purchases (the sum from both profiles)
        - Date of first purchase (Braze picks the earlier date of the two dates)
        - Date of last purchase (Braze picks the later date of the two dates)
        - App summaries
        - Last_X_at fields (Braze updates the fields if the orphaned profile fields are more recent)
        - Campaign interaction data (Braze picks the most recent date fields)
        - Workflow summaries (Braze picks the most recent date fields)
        - Message and message engagement history
        - Braze merges session data only if the app exists on both user profiles.

        **Note:**
        When merging users, using the `/users/merge` endpoint works the same way as using the [`changeUser()` method](https://js.appboycdn.com/web-sdk/latest/doc/modules/braze.html#changeuser).

        Braze handles three user types differently when merging: users marked for deletion, test users, and Global Control Group users. For details, see [User merge behavior]({{site.baseurl}}/user_guide/audience/manage_audience/merge_duplicate_users/merge_behavior).

        #### Custom event date and purchase event date behavior

        These merged fields update "for X events in Y days" filters. For purchase events, these filters include "number of purchases in Y days" and "money spent in last Y days".

        ### Merging users by email or phone number

        If an `email` or `phone` is specified as an identifier, you must include an additional `prioritization` value in the identifier. The `prioritization` should be an ordered array specifying which user to merge if multiple users are found. This means if more than one user matches from a prioritization, then merging does not occur.

        The allowed values for the array are:

        - `identified`
        - `unidentified`
        - `most_recently_updated` (refers to prioritizing the most recently updated user)
        - `least_recently_updated` (refers to prioritizing the least recently updated user)

        Only one of the following options may exist in the prioritization array at a time:

        - `identified` refers to prioritizing a user with an `external_id`
        - `unidentified` refers to prioritizing a user without an `external_id`

        **Important:**
        If both profiles have invalid phone numbers, Braze does not merge them. Invalid numbers are not stored in E.164 format, and the merge job does not combine those profiles. The endpoint still returns `202 Accepted` with a success message, so the HTTP response does not indicate that the merge was skipped. Correct the phone numbers on one or both profiles before merging.

        ## Example request

        ### Basic request

        This is a basic request body to show the pattern of the request.

        ```
        curl --location --request POST 'https://rest.iad-01.braze.com/users/merge' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YOUR_REST_API_KEY' \
        --data-raw '{
          "merge_updates": [
            {
              "identifier_to_merge": {
                "external_id": "old-user1"
              },
              "identifier_to_keep": {
                "external_id": "current-user1"
              }
            },
            {
              "identifier_to_merge": {
                "email": "user1@example.com",
                "prioritization": ["unidentified", "most_recently_updated"]
              },
              "identifier_to_keep":  {
                "email": "user2@example.com",
                "prioritization": ["identified", "most_recently_updated"]
              }
            },
            {
              "identifier_to_merge": {
                "user_alias": {
                  "alias_name": "old-user2@example.com",
                  "alias_label": "email"
                }
              },
              "identifier_to_keep": {
                "user_alias": {
                  "alias_name": "current-user2@example.com",
                  "alias_label": "email"
                }
              }
            }
          ]
        }'
        ```

        ### Merging unidentified user

        The following request would merge the most recently updated unidentified user with email address `john.smith@example.com` into the user with an external ID `john`. In this example, using `most_recently_updated` filters the query to one unidentified user. So, if there were two unidentified users with this email address, only one would get merged into the user who has an external ID `john`.

        ```
        curl --location --request POST 'https://rest.iad-01.braze.com/users/merge' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YOUR_REST_API_KEY' \
        --data-raw '{
          "merge_updates": [
            {
              "identifier_to_merge": {
                "email": "john.smith@example.com",
                "prioritization": ["unidentified", "most_recently_updated"]
              },
              "identifier_to_keep": {
                "external_id": "john"
              }
            }
          ]
        }'
        ```

        ### Merging unidentified user into identified user

        This next example merges the most recently updated unidentified user with email address `john.smith@example.com` into the most recently updated identified user with email address `john.smith@example.com`.

        Using `most_recently_updated` filters the queries to one user (one unidentified user for `identifier_to_merge`, and one identified user for the `identifier_to_keep`).

        ```
        curl --location --request POST 'https://rest.iad-01.braze.com/users/merge' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YOUR_REST_API_KEY' \
        --data-raw '{
          "merge_updates": [
            {
              "identifier_to_merge": {
                "email": "john.smith@example.com",
                "prioritization": ["unidentified", "most_recently_updated"]
              },
              "identifier_to_keep": {
                "email": "john.smith@example.com",
                "prioritization": ["identified", "most_recently_updated"]
              }
            }
          ]
        }'
        ```

        ### Merging an unidentified user without including the most_recently_updated prioritization

        If there are two unidentified users with the mail address `john.smith@example.com`, this example request doesn't merge any users because there are two unidentified users with that email address. This request only works if there is only one unidentified user with the email address `john.smith@example.com`.

        ```
        curl --location --request POST 'https://rest.iad-01.braze.com/users/merge' \
        --header 'Content-Type: application/json' \
        --header 'Authorization: Bearer YOUR_REST_API_KEY' \
        --data-raw '{
          "merge_updates": [
            {
              "identifier_to_merge": {
                "email": "john.smith@example.com",
                "prioritization": ["unidentified"]
              },
              "identifier_to_keep": {
                "external_id": "john"
              }
            }
          ]
        }'
        ```

        ## Response

        There are two status code responses for this endpoint: `202` and `400`.

        ### Example success response

        The status code `202` could return the following response body.

        ```json
        {
          "message": "success"
        }
        ```

        ### Example error response

        The status code `400` could return the following response body. Refer to [Troubleshooting](#troubleshooting) for more information about errors you may encounter.

        ```json
        {
          "message": "'merge_updates' must be an array of objects"
        }
        ```

        ## Troubleshooting

        ### A success response was returned but the merged user is still searchable

        A successful response confirms the request was accepted, but the merge operation involves two steps: merging the profiles and then removing the source profile. Because of this, the `identifier_to_merge` profile may remain searchable in the dashboard for a short period after a successful response. This is expected behavior—wait a few minutes and then verify the merge is complete.

        If the merged user still exists after several minutes, verify that the identifiers in your request are correct and belong to users in the same workspace as the API key used for the request.

        ### Error reference

        The following table lists possible error messages that may occur.

        | Error | Troubleshooting |
        | --- |
        | `'merge_updates' must be an array of objects` | Check that `merge_updates` is an array of objects. |
        | `a single request may not contain more than 50 merge updates` | You can only specify up to 50 merge updates in a single request. |
        | `identifiers must be objects with an 'external_id' property that is a string, 'user_alias' property that is an object, 'email' property that is a string, or 'phone' property that is a string` | Check the identifiers in your request. |
        | `'merge_updates' must only have 'identifier_to_merge' and 'identifier_to_keep'` | Check that `merge_updates` only contains the two objects `identifier_to_merge` and `identifier_to_keep`. |

        ## Response status codes

        The following table lists the responses for this endpoint, the error message you may receive, and how to resolve it.

        | Status code | Meaning | Error message | How to resolve |
        | --- | --- | --- | --- |
        | `201 Created` | The request succeeded and the users were merged. | `success` | No action needed. |
        | `400 Bad Request` | The `merge_updates` value isn't an array of objects. | `'merge_updates' must be an array of objects` | Pass `merge_updates` as an array of objects. |
        | `400 Bad Request` | More than 50 merge updates were provided. | `a single request may not contain more than 50 merge updates` | Send 50 or fewer merge updates per request. |
        | `400 Bad Request` | The identifiers to keep and merge point to the same user. | `'identifier_to_keep' and 'identifier_to_merge' are pointing to the same user` | Use two different users for the merge. |
        | `401 Unauthorized` | The REST API key is missing, malformed, or sent to the wrong REST endpoint. | `Invalid API key` | Send the key as `Authorization: Bearer YOUR_REST_API_KEY` to the correct [REST endpoint]({{site.baseurl}}/api/basics/#endpoints). For more causes, see [Errors and responses]({{site.baseurl}}/api/errors/#fatal-errors). |
        | `403 Access Denied` | The REST API key doesn't have the required permission. | `Access Denied` | Use a REST API key that has the `users.merge` permission. |
        | `429 Rate Limited` | You exceeded the rate limit for this endpoint. | `Over rate limit` | Slow your request rate and retry with exponential backoff. See [API rate limits]({{site.baseurl}}/api/api_limits/). |
        | `5XX Internal Server Error` | An unexpected error occurred on the Braze server. | `Internal Server Error` | Retry with exponential backoff. If the error persists, contact [Support]({{site.baseurl}}/braze_support/). |
      operationId: mergeUsers
      tags:
        - User data
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MergeUsersRequest'
            examples:
              by_external_id:
                summary: Merge by external_id
                description: |
                  The simplest case — collapse one identified profile into
                  another by external ID.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        external_id: old-user-1
                      identifier_to_keep:
                        external_id: current-user-1
              unidentified_email_into_identified:
                summary: Merge unidentified email-only profile into an identified profile
                description: |
                  Move an unidentified email-only profile into an identified
                  profile keyed by `external_id`. `prioritization` resolves the
                  case where multiple unidentified profiles share the email.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        email: jane.doe@example.com
                        prioritization:
                          - unidentified
                          - most_recently_updated
                      identifier_to_keep:
                        external_id: jane-identified
              unidentified_email_into_identified_email:
                summary: Merge unidentified email profile into identified email profile
                description: |
                  Both sides reference an email. Use `prioritization` on each
                  side to pick a single matching profile.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        email: jane.doe@example.com
                        prioritization:
                          - unidentified
                          - most_recently_updated
                      identifier_to_keep:
                        email: jane.doe@example.com
                        prioritization:
                          - identified
                          - most_recently_updated
              by_phone_with_prioritization:
                summary: Merge by phone with prioritization
                description: |
                  Both profiles must have phone numbers stored in E.164 format.
                  If either side has an invalid phone, Braze returns
                  `202 Accepted` but skips the merge — fix the phone numbers
                  before retrying.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        phone: "+15555550100"
                        prioritization:
                          - unidentified
                          - most_recently_updated
                      identifier_to_keep:
                        phone: "+15555550100"
                        prioritization:
                          - identified
                          - least_recently_updated
              by_user_alias:
                summary: Merge by user_alias
                description: |
                  Both `identifier_to_merge` and `identifier_to_keep` reference
                  user aliases. Each alias object requires both `alias_name`
                  and `alias_label`.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        user_alias:
                          alias_name: jane.old@example.com
                          alias_label: email
                      identifier_to_keep:
                        user_alias:
                          alias_name: jane.new@example.com
                          alias_label: email
              batch_mixed_identifiers:
                summary: Multiple merges in one request (mixed identifier types)
                description: |
                  Up to 50 merge entries may be included per request, and each
                  entry can use a different identifier type.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        external_id: old-user-1
                      identifier_to_keep:
                        external_id: current-user-1
                    - identifier_to_merge:
                        email: archived@example.com
                        prioritization:
                          - unidentified
                          - most_recently_updated
                      identifier_to_keep:
                        external_id: current-user-2
                    - identifier_to_merge:
                        user_alias:
                          alias_name: legacy_id_123
                          alias_label: amplitude_id
                      identifier_to_keep:
                        user_alias:
                          alias_name: braze_id_456
                          alias_label: amplitude_id
      responses:
        "202":
          description: Merge updates accepted and queued for asynchronous processing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeUsersResponse'
              examples:
                success:
                  summary: Successful queue
                  value:
                    message: success
        "400":
          description: |
            Invalid request — for example, a malformed `merge_updates` entry,
            more than 50 merge updates in one request, or an identifier missing
            a required `prioritization` array.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
              examples:
                bad_merge_updates:
                  summary: merge_updates is not an array of objects
                  value:
                    message: "'merge_updates' must be an array of objects"
                too_many_updates:
                  summary: More than 50 merge updates
                  value:
                    message: "a single request may not contain more than 50 merge updates"
                bad_identifier:
                  summary: Identifier object is malformed
                  value:
                    message: "identifiers must be objects with an 'external_id' property that is a string, 'user_alias' property that is an object, 'email' property that is a string, or 'phone' property that is a string"
                missing_prioritization:
                  summary: Email identifier missing prioritization
                  value:
                    message: "Email identifiers require both email and prioritization keys"
                same_user:
                  summary: Both sides reference the same user
                  value:
                    message: "'identifier_to_keep' and 'identifier_to_merge' are pointing to the same user"
                extra_keys:
                  summary: merge_updates entry contains unexpected keys
                  value:
                    message: "'merge_updates' must only have 'identifier_to_merge' and 'identifier_to_keep'"
        "401":
          description: Unauthorized – the API key is missing, invalid, or lacks the `users.merge` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
              examples:
                unauthorized:
                  value:
                    message: invalid_api_key
        "429":
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
              examples:
                rate_limited:
                  value:
                    message: rate_limited
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: REST API key
      description: |
        REST API keys are created in the Braze dashboard at **Settings → APIs and
        Identifiers → API Keys**. The key must include the permission required by
        the endpoint. Pass the key as `Authorization: Bearer YOUR_REST_API_KEY`.
  schemas:
    MergeUserAlias:
      type: object
      required: [alias_name, alias_label]
      properties:
        alias_name:
          type: string
          description: The alias name.
          example: jane.doe@example.com
        alias_label:
          type: string
          description: The alias label.
          example: email
    Prioritization:
      type: array
      minItems: 1
      maxItems: 2
      items:
        type: string
        enum:
          - identified
          - unidentified
          - most_recently_updated
          - least_recently_updated
      description: |
        Ordered priority for resolving multiple matching users when an `email`
        or `phone` identifier is supplied.

        Constraints enforced by the API:

        - `identified` and `unidentified` are mutually exclusive.
        - `most_recently_updated` and `least_recently_updated` are mutually
          exclusive.
        - Values must be unique within the array.
      example:
        - unidentified
        - most_recently_updated
    ExternalIdIdentifier:
      type: object
      additionalProperties: false
      required: [external_id]
      properties:
        external_id:
          type: string
          description: External user ID.
          example: current-user-1
    UserAliasIdentifier:
      type: object
      additionalProperties: false
      required: [user_alias]
      properties:
        user_alias:
          $ref: '#/components/schemas/MergeUserAlias'
    EmailIdentifier:
      type: object
      additionalProperties: false
      required: [email, prioritization]
      properties:
        email:
          type: string
          format: email
          example: jane.doe@example.com
        prioritization:
          $ref: '#/components/schemas/Prioritization'
    PhoneIdentifier:
      type: object
      additionalProperties: false
      required: [phone, prioritization]
      properties:
        phone:
          type: string
          description: Phone number in E.164 format.
          example: "+15555550100"
        prioritization:
          $ref: '#/components/schemas/Prioritization'
    MergeIdentifier:
      description: |
        A single identifier referencing a user by exactly one of `external_id`,
        `user_alias`, `email`, or `phone`. When `email` or `phone` is used,
        `prioritization` is also required.
      oneOf:
        - $ref: '#/components/schemas/ExternalIdIdentifier'
        - $ref: '#/components/schemas/UserAliasIdentifier'
        - $ref: '#/components/schemas/EmailIdentifier'
        - $ref: '#/components/schemas/PhoneIdentifier'
    MergeUpdate:
      type: object
      additionalProperties: false
      required: [identifier_to_merge, identifier_to_keep]
      properties:
        identifier_to_merge:
          allOf:
            - $ref: '#/components/schemas/MergeIdentifier'
          description: |
            The source user — its data is merged into `identifier_to_keep`
            and the source profile is removed.
        identifier_to_keep:
          allOf:
            - $ref: '#/components/schemas/MergeIdentifier'
          description: The destination user — retained after the merge.
    MergeUsersRequest:
      type: object
      additionalProperties: false
      required: [merge_updates]
      properties:
        merge_updates:
          type: array
          minItems: 1
          maxItems: 50
          items:
            $ref: '#/components/schemas/MergeUpdate'
          description: |
            One entry per merge operation. A maximum of 50 entries may be
            included per request.
    MergeUsersResponse:
      type: object
      additionalProperties: false
      required: [message]
      properties:
        message:
          type: string
          example: success
    MergeErrorResponse:
      type: object
      additionalProperties: false
      properties:
        message:
          type: string
          description: Human-readable error message.
        errors:
          type: array
          description: Optional list of detailed validation errors.
          items:
            type: string
