---
openapi: 3.0.3
info:
  title: Braze REST API – Merge users
  description: |
    ## 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/).
  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
security:
  - BearerAuth: []
paths:
  /users/merge:
    post:
      summary: Merge users
      description: |
        ## 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".
      operationId: mergeUsers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MergeUsersRequest'
            examples:
              basic_request:
                summary: Basic request
                description: This is a basic request body to show the pattern of the request.
                value:
                  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:
                summary: Merging unidentified user
                description: 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`.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        email: john.smith@example.com
                        prioritization:
                          - unidentified
                          - most_recently_updated
                      identifier_to_keep:
                        external_id: john
              merging_unidentified_into_identified:
                summary: Merging unidentified user into identified user
                description: |
                  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`).
                value:
                  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
              without_most_recently_updated:
                summary: Merging an unidentified user without including the most_recently_updated prioritization
                description: 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`.
                value:
                  merge_updates:
                    - identifier_to_merge:
                        email: john.smith@example.com
                        prioritization:
                          - unidentified
                      identifier_to_keep:
                        external_id: john
              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: |
            The request succeeded and the merge updates were queued for asynchronous processing. No action needed.

            The status code `202` could return the following response body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeUsersResponse'
              examples:
                success:
                  summary: Merge updates queued
                  value:
                    message: success
        "400":
          description: |
            Bad request. To resolve it:

            - **The `merge_updates` value isn't an array of objects:** Pass `merge_updates` as an array of objects.
            - **More than 50 merge updates were provided:** Send 50 or fewer merge updates per request.
            - **The identifiers to keep and merge point to the same user:** Use two different users for the merge.
            - **An identifier is malformed:** Check the identifiers in your request.
            - **An `email` or `phone` identifier has no `prioritization`:** Include a `prioritization` array with each `email` or `phone` identifier.
            - **A `merge_updates` entry has keys other than `identifier_to_merge` and `identifier_to_keep`:** Check that `merge_updates` only contains the two objects `identifier_to_merge` and `identifier_to_keep`.

            The status code `400` could return the following response body. Refer to [Troubleshooting](#troubleshooting) for more information about errors you may encounter.
          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"
                same_user:
                  summary: Both sides reference the same user
                  value:
                    message: "'identifier_to_keep' and 'identifier_to_merge' are pointing to the same user"
                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"
                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 REST API key is missing, malformed, or sent to the wrong REST endpoint. 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).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
              examples:
                invalid_api_key:
                  summary: Invalid API key
                  value:
                    message: 'Invalid API key: YOUR_REST_API_KEY'
        "403":
          description: Access denied. The REST API key doesn't have the required permission. Use a REST API key that has the `users.merge` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
              examples:
                access_denied:
                  summary: Missing permission
                  value:
                    message: Access Denied
        "429":
          description: |
            Rate limited. You exceeded the rate limit for this endpoint. Slow your request rate and retry with exponential backoff. See [API rate limits]({{site.baseurl}}/api/api_limits/).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
              examples:
                over_rate_limit:
                  summary: Over rate limit
                  value:
                    message: You have exceeded your limit of 20000 requests per minute. Please contact your account executive to increase your limit. If you do not have their info, you may contact Braze directly at https://www.braze.com/contact/.
        5XX:
          description: |
            Internal server error. An unexpected error occurred on the Braze server. Retry with exponential backoff. If the error persists, contact [Support]({{site.baseurl}}/braze_support/).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MergeErrorResponse'
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 `users.merge` permission.
        Pass the key as `Authorization: Bearer YOUR_REST_API_KEY`.
  schemas:
    MergeUserAlias:
      title: User alias
      type: object
      additionalProperties: false
      required: [alias_name, alias_label]
      properties:
        alias_name:
          type: string
          description: The alias name.
          example: old-user2@example.com
        alias_label:
          type: string
          description: The alias label.
          example: email
    Prioritization:
      title: 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.

        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`

      example:
        - unidentified
        - most_recently_updated
    ExternalIdIdentifier:
      title: By external ID
      type: object
      additionalProperties: false
      required: [external_id]
      properties:
        external_id:
          type: string
          description: External user ID.
          example: current-user1
    UserAliasIdentifier:
      title: By user alias
      type: object
      additionalProperties: false
      required: [user_alias]
      properties:
        user_alias:
          $ref: '#/components/schemas/MergeUserAlias'
    EmailIdentifier:
      title: By email address
      type: object
      additionalProperties: false
      required: [email, prioritization]
      properties:
        email:
          type: string
          format: email
          example: john.smith@example.com
        prioritization:
          $ref: '#/components/schemas/Prioritization'
    PhoneIdentifier:
      title: By phone number
      type: object
      additionalProperties: false
      required: [phone, prioritization]
      properties:
        phone:
          type: string
          description: |
            Phone number in E.164 format.

            **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: "+15555550100"
        prioritization:
          $ref: '#/components/schemas/Prioritization'
    MergeIdentifier:
      title: Merge identifier
      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:
      title: Merge update
      type: object
      description: |
        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.
      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:
      title: Merge users request
      type: object
      additionalProperties: false
      required: [merge_updates]
      properties:
        merge_updates:
          type: array
          minItems: 1
          maxItems: 50
          items:
            $ref: '#/components/schemas/MergeUpdate'
          description: |
            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`.

            One entry per merge operation. A maximum of 50 entries may be
            included per request. The endpoint does not guarantee the order in which `merge_updates`
            entries are processed.
    MergeUsersResponse:
      title: Merge users response
      type: object
      additionalProperties: false
      required: [message]
      properties:
        message:
          type: string
          example: success
    MergeErrorResponse:
      title: Error
      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
