---
openapi: 3.0.3
info:
  title: Braze REST API – Create and update users
  description: |
    ## Prerequisites

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

    Customers using the API for server-to-server calls may need to allowlist `rest.iad-01.braze.com` if they're behind a firewall.

    ## Rate limit

    Rate limits for this endpoint vary depending on your contract. For customers with data points in their pricing, Braze applies a burst limit of 3,000 requests per three seconds. For all other customers, limits are configured according to your contract terms. Current limits for your account can be found in the dashboard under **Settings** > **APIs and Identifiers** > **API Usage Dashboard**.

    Each `/users/track` request can contain up to 75 total objects combined across `attributes`, `events`, and `purchases`. Each object can update one user. A single user profile can be updated by multiple objects.

    For customers who have purchased Monthly Active Users CY 24-25, Universal MAU, Web MAU, or Mobile MAU, additional rate limits apply. For more information, reference [Monthly Active Users CY 24-25 limits]({{site.baseurl}}/api/endpoints/user_data/post_user_track#monthly-active-users-cy-24-25-universal-mau-web-mau-and-mobile-mau).

    For more information, see [API rate limits]({{site.baseurl}}/api/api_limits). Contact your customer success manager to request an increase.

    ### Legacy rate limits

    For customers on legacy rate limits, each `/users/track` request can contain up to 75 attribute objects, 75 event objects, and 75 purchase objects. Each object can update one user, for a combined maximum of up to 225 objects per request. A single user profile can be updated by multiple objects.
  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/track:
    post:
      summary: Create and update users
      operationId: trackUsers
      description: |
        ## Identifier resolution

        Each request object must include at least one identifier. The following table describes how Braze determines which identifier to use for user profile lookup.

        | Identifier type | Identifiers | Behavior |
        | --------------- | ----------- | -------- |
        | Primary | `external_id`, `user_alias`, `braze_id` | Used for user profile lookup. Only one primary identifier is allowed per request object—including more than one causes that object to be rejected. |
        | Secondary | `email`, `phone` | Used for user profile lookup **only** when no primary identifier is present. If both `email` and `phone` are included without a primary identifier, `email` takes precedence. |

        When a primary identifier is present, any `email` or `phone` values in the same request object are treated as profile attributes—not as identifiers for user lookup. For example, if a request includes both an `external_id` and an `email`:

        - Braze looks up the user profile by `external_id`.
        - The `email` value is set (or updated) as an attribute on the resolved profile.

        **Important:**
        Including a primary identifier that doesn't match any existing profile can create a duplicate profile even when `email` or `phone` in the same request match an existing profile. For more information, see [How do I avoid creating duplicate user profiles?](#how-do-i-avoid-creating-duplicate-user-profiles).

        ## Update a user profile by email address

        You can update a user profile by email address using the `/users/track` endpoint. For a sample request, see the **Update a user profile by email address** request example.

        ## Update a user profile by phone number

        You can update a user profile by phone number using the `/users/track` endpoint. This endpoint only works if you include a valid phone number. For a sample request, see the **Update a user profile by phone number** request example.

        **Important:**
        If you include a request with both `email` and `phone`, Braze uses the email as the identifier.

        ## Set subscription groups

        This example shows how to create a user and set their subscription group within the user attributes object. For a sample request, see the **Set subscription groups** request example.

        Updating the subscription status with this endpoint updates the user specified by their `external_id` (such as User1) and updates the subscription status of any users with the same email as that user (User1).

        **Note:**
        For SMS subscription groups, when you set a group's `subscription_state` to `subscribed`, you can include the optional `use_double_opt_in_logic` parameter set to `true` within that subscription group object to enter the user into the [SMS double opt-in]({{site.baseurl}}/user_guide/channels/sms_mms_and_rcs/message_features_and_optimization/keyword_processing/double_opt_in) workflow. If this parameter is omitted or set to `false` when `subscription_state` is `subscribed`, the user is subscribed without entering the double opt-in workflow. This parameter is not applied when `subscription_state` is set to other values, such as `unsubscribed`.

        ## Example request to create an alias-only user

        You can use the `/users/track` endpoint to create an alias-only user by setting the `_update_existing_only` key with a value of `false` in the body of the request. If you omit this value, Braze does not create the alias-only user profile. Using an alias-only user ensures that one profile with that alias exists. This is especially helpful when building an integration as it prevents Braze from creating duplicate user profiles. For a sample request, see the **Create an alias-only user** request example.

        ## Example request with a group ID for status tracking

        This example updates a user's loyalty tier and includes a `group_id` so you can check when Braze finishes processing the request. To check the status, call the [`/users/track/status` endpoint]({{site.baseurl}}/api/endpoints/user_data/get_users_track_status) with the same `group_id`. For more information, see [Track request processing status](#track-request-processing-status). For a sample request, see the **Update a profile and track processing status (beta)** request example.

        ## Endpoint-specific errors

        The following errors are specific to the `/users/track` endpoint and are returned in the `errors` array of the response. Use these to troubleshoot issues with individual objects in a request.

        | Error | Description |
        |---|---|
        | `BAD_DEVICE_ID` | The `device_id` for a token import must be between 8 and 255 bytes. |
        | `BAD_EMAIL_SUBSCRIPTION_STATE` | `email_subscribe` must be `subscribed`, `unsubscribed`, or `opted_in`. |
        | `BAD_LOCATION_UPDATE` | `current_location` must be an object containing `longitude` and `latitude`. |
        | `BAD_PUSH_SUBSCRIPTION_STATE` | `push_subscribe` must be `subscribed`, `unsubscribed`, or `opted_in`. |
        | `BAD_PUSH_TOKEN_APP_ID` | The `app_id` in a token import must be a valid app identifier from the current workspace. |
        | `BAD_PUSH_TOKEN_IMPORT` | Token imports must include tokens and exclude `external_id` and `braze_id`. |
        | `BAD_PUSH_TOKEN_STRING` | The `token` value in a token import must be a string. |
        | `BAD_PUSH_TOKEN_VALUE` | `push_tokens` must be an array of objects. |
        | `BAD_SUBSCRIPTION_GROUP_ARRAY` | `subscription_groups` must be an array. |
        | `BAD_SUBSCRIPTION_GROUP_HASH` | Each item in the `subscription_groups` array must be a JSON object with `subscription_group_id` and `subscription_state` keys. |
        | `BAD_SUBSCRIPTION_GROUP_ID` | `subscription_group_id` must be a valid subscription group UUID. |
        | `BAD_SUBSCRIPTION_GROUP_STATE` | `subscription_state` for a subscription group must be `subscribed` or `unsubscribed`. |
        | `BLACKLISTED_EXTERNAL_USER_ID` | The provided `external_id` is blocklisted and disallowed. |
        | `EMAIL_BAD_FORMAT` | The value provided for `email` is not a valid email address. |
        | `EXTERNAL_USER_ID_TOO_LARGE` | The `external_id` exceeds the maximum allowed length of 987 bytes. |
        | `INVALID_ATTRIBUTE_EMAIL_SUBSCRIPTION_INFO` | `email_subscription_info` is not a valid attribute. |

        If you receive the error "provided external_id is blacklisted and disallowed", your request may have included a "dummy user." For more information, refer to [Spam blocking]({{site.baseurl}}/user_archival#spam-blocking).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrackRequest'
            examples:
              by_email:
                summary: Update a user profile by email address
                value:
                  attributes:
                    - email: test@example.com
                      string_attribute: fruit
                      boolean_attribute_1: true
                      integer_attribute: 26
                      array_attribute:
                        - banana
                        - apple
                  events:
                    - email: test@example.com
                      app_id: your_app_identifier
                      name: rented_movie
                      time: "2022-12-06T19:20:45+01:00"
                      properties:
                        release:
                          studio: FilmStudio
                          year: "2022"
                        cast:
                          - name: Actor1
                          - name: Actor2
                    - user_alias:
                        alias_name: device123
                        alias_label: my_device_identifier
                      app_id: your_app_identifier
                      name: rented_movie
                      time: "2013-07-16T19:20:50+01:00"
                  purchases:
                    - email: test@example.com
                      app_id: your_app_identifier
                      product_id: product_name
                      currency: USD
                      price: 12.12
                      quantity: 6
                      time: "2017-05-12T18:47:12Z"
                      properties:
                        color: red
                        monogram: ABC
                        checkout_duration: 180
                        size: Large
                        brand: Backpack Locker
              by_phone:
                summary: Update a user profile by phone number
                description: |
                  Phone numbers should be in E.164 format. If both `email` and
                  `phone` are provided on the same object, Braze uses `email`
                  as the identifier.
                value:
                  attributes:
                    - phone: "+15555550100"
                      string_attribute: fruit
                      boolean_attribute_1: true
                      integer_attribute: 25
                      array_attribute:
                        - banana
                        - apple
              subscription_groups:
                summary: Set subscription groups
                value:
                  attributes:
                    - external_id: user_identifier
                      email: example@example.com
                      email_subscribe: subscribed
                      subscription_groups:
                        - subscription_group_id: subscription_group_identifier_1
                          subscription_state: unsubscribed
                        - subscription_group_id: subscription_group_identifier_2
                          subscription_state: subscribed
                        - subscription_group_id: subscription_group_identifier_3
                          subscription_state: subscribed
                          use_double_opt_in_logic: true
              alias_only_user:
                summary: Create an alias-only user
                value:
                  attributes:
                    - _update_existing_only: false
                      user_alias:
                        alias_name: example_name
                        alias_label: example_label
                      email: email@example.com
              with_group_id:
                summary: Update a profile and track processing status (beta)
                value:
                  group_id: loyalty_backfill_2026-09-23
                  attributes:
                    - external_id: user_identifier
                      loyalty_tier: gold
              by_external_id_attributes_only:
                summary: Update profile attributes by external_id
                description: |
                  Standard attribute update referencing the user by
                  `external_id`. Custom attributes are sent alongside reserved
                  Braze profile fields such as `first_name` and `dob`.
                value:
                  attributes:
                    - external_id: user_identifier_1
                      first_name: Jon
                      dob: "1988-02-14"
                      has_profile_picture: true
                      music_videos_favorited:
                        add:
                          - summer-video
                        remove:
                          - winter-video
              push_token_import_identified:
                summary: Push token import for an identified user
                description: |
                  Migrate a push token onto an existing identified profile.
                  Specify the `app_id` for the destination platform.
                value:
                  attributes:
                    - external_id: example_external_id
                      country: US
                      language: en
                      push_tokens:
                        - app_id: APP_ID_OF_OS
                          token: PUSH_TOKEN_STRING
              push_token_import_anonymous:
                summary: Push token import for an anonymous user
                description: |
                  When migrating tokens from another system without an
                  `external_id`, set `push_token_import: true`. Braze creates
                  a temporary anonymous profile per token.
                value:
                  attributes:
                    - push_token_import: true
                      email: jane.doe@example.com
                      country: US
                      language: en
                      push_tokens:
                        - app_id: APP_ID_OF_OS
                          token: PUSH_TOKEN_STRING
                          device_id: DEVICE_ID
              custom_event_with_properties:
                summary: Log a custom event with nested properties
                description: |
                  Custom event with structured properties. Property values
                  may be strings, numbers, booleans, datetimes, arrays, or
                  objects. Reserved keys (`time`, `event_name`) cannot be
                  used as property names.
                value:
                  events:
                    - external_id: user_identifier_1
                      app_id: your_app_identifier
                      name: watched_trailer
                      time: "2026-05-12T19:20:30+01:00"
                      properties:
                        movie: The Sad Egg
                        director: Dan Alexander
              purchase_event:
                summary: Log a purchase
                description: |
                  Purchase keyed by `external_id`. `quantity` defaults to 1
                  and may not exceed 100.
                value:
                  purchases:
                    - external_id: user_identifier_1
                      app_id: your_app_identifier
                      product_id: backpack
                      currency: USD
                      price: 40.00
                      time: "2026-05-12T19:20:30+01:00"
                      properties:
                        color: red
                        size: Large
                        brand: Backpack Locker
      responses:
        "201":
          description: |
            Created. The request was accepted and queued. Non-fatal, per-object issues are returned in the `errors` array; unaffected data is still processed. No action needed. Review any messages in the `errors` array.

            When using any of the aforementioned API requests, you should receive one of the following three general responses: a successful message, a successful message with non-fatal errors, or a message with fatal errors.

            Successful messages are met with the following response: see the **Successful message** example.

            If your message is successful but has non-fatal errors, such as one invalid event object out of a long list of events, you receive the following response: see the **Successful message with non-fatal errors** example. For success messages, Braze still processes any data not affected by an error in the `errors` array. For the errors specific to this endpoint, see [Endpoint-specific errors](#endpoint-specific-errors).

            Non-fatal errors include:

            - **The data point credit key is invalid:** Remove the invalid credit key, or use a valid one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackResponse'
              examples:
                success:
                  summary: Successful message
                  value:
                    message: success
                    request_id: 3f2b8c1e-7a4d-4e9b-9c6f-1d2e3a4b5c6d
                    attributes_processed: 1
                    events_processed: 1
                    purchases_processed: 1
                success_with_minor_errors:
                  summary: Successful message with non-fatal errors
                  value:
                    message: success
                    request_id: 3f2b8c1e-7a4d-4e9b-9c6f-1d2e3a4b5c6d
                    attributes_processed: 1
                    events_processed: 1
                    errors:
                      - type: "'time' must be an ISO 8601 timestamp"
                        input_array: events
                        index: 1
                invalid_credit_key:
                  summary: Invalid data point credit key
                  value:
                    message: success
                    request_id: 3f2b8c1e-7a4d-4e9b-9c6f-1d2e3a4b5c6d
                    attributes_processed: 1
                    errors:
                      - type: Invalid data point credit key
        "400":
          description: |
            Bad request. To resolve it:

            - **No valid `attributes`, `events`, or `purchases` were provided:** Include at least one valid attribute, event, or purchase object.
            - **The request body couldn't be parsed into any data:** Send a valid JSON body with `attributes`, `events`, or `purchases`.
            - **The request body, or an entry within an array, isn't a JSON object:** Send a JSON object, and make each array entry an object.
            - **The `attributes` value isn't an array:** Pass `attributes` as an array of objects.
            - **The `events` value isn't an array:** Pass `events` as an array of objects.
            - **The `purchases` value isn't an array:** Pass `purchases` as an array of objects.
            - **An `attributes`, `events`, or `purchases` array exceeds 75 objects (or the combined limit for rate-limit pricing models):** Send at most 75 objects in each of `attributes`, `events`, and `purchases` per request.
            - **Legacy purchase ingestion is disabled for this workspace:** Remove the `purchases` array and use eCommerce recommended events instead.

            If your message has a fatal error, you receive the following response: a `message` with the fatal error message, and an `errors` array when individual objects also had errors. For status codes and associated error messages that Braze returns if your request encounters a fatal error, reference [Fatal errors & responses]({{site.baseurl}}/api/errors#fatal-errors).

            If the request body isn't valid JSON, see [Why do I get `400 Bad Request` with a bad syntax or parse error?](#why-do-i-get-400-bad-request-with-a-bad-syntax-or-parse-error).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackErrorResponse'
              examples:
                no_valid_input:
                  summary: No valid attributes, events, or purchases
                  value:
                    message: "Valid data must be provided in the 'attributes', 'events', or 'purchases' fields."
                no_data_parsed:
                  summary: Request body couldn't be parsed into any data
                  value:
                    message: No data parsed
                not_hash:
                  summary: Request body or array entry isn't a JSON object
                  value:
                    message: JSON hash not provided
                attributes_not_array:
                  summary: attributes is not an array
                  value:
                    message: "'attributes' must be an array"
                events_not_array:
                  summary: events is not an array
                  value:
                    message: "'events' must be an array"
                purchases_not_array:
                  summary: purchases is not an array
                  value:
                    message: "'purchases' must be an array"
                too_many_objects:
                  summary: Per-request object limit exceeded
                  value:
                    message: Max input length exceeded.
                legacy_purchases_disabled:
                  summary: Legacy purchase ingestion is disabled
                  value:
                    message: "Legacy purchase ingestion is disabled. The 'purchases' array must not be provided. Use eCommerce recommended events instead."
        "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/TrackErrorResponse'
              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.track` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackErrorResponse'
              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/).

            For Monthly Active Users CY 24-25, Universal MAU, Web MAU, and Mobile MAU pricing models, an `X-RateLimit-Retry-After` header indicates the number of seconds to wait before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrackErrorResponse'
              examples:
                over_rate_limit:
                  summary: Over rate limit (burst limit for data points pricing)
                  value:
                    message: You have exceeded your limit of 3000 requests per three seconds. 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/TrackErrorResponse'
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.track` permission.
        Pass the key as `Authorization: Bearer YOUR_REST_API_KEY`.
  schemas:
    TrackUserAlias:
      title: User alias
      type: object
      required: [alias_name, alias_label]
      properties:
        alias_name:
          type: string
          example: device123
        alias_label:
          type: string
          example: my_device_identifier
    CurrentLocation:
      title: Current location
      type: object
      required: [longitude, latitude]
      properties:
        longitude:
          type: number
          format: float
          example: -73.991443
        latitude:
          type: number
          format: float
          example: 40.753824
    PushToken:
      title: Push token
      type: object
      required: [app_id, token]
      description: |
        Braze assumes imported push tokens are valid foreground push tokens for your app and environment.

        When you import iOS tokens, Braze doesn't store an Apple Push Notification service (APNs) gateway (`development` or `production`) for them. Braze sends to these tokens through your app's configured gateway. If a token was generated for the other environment, APNs rejects it with `BadDeviceToken`, and the send bounces.

        Braze stores a gateway for the token only if the SDK later registers the same token and reports a gateway.
      properties:
        app_id:
          type: string
          description: The Braze app identifier the token belongs to.
        token:
          type: string
          description: The push token value.
        device_id:
          type: string
          description: |
            Device identifier the token is associated with. If omitted, Braze
            generates one.
    SubscriptionGroup:
      title: Subscription group
      type: object
      required: [subscription_group_id, subscription_state]
      properties:
        subscription_group_id:
          type: string
          description: The subscription group UUID.
        subscription_state:
          type: string
          enum: [subscribed, unsubscribed]
        use_double_opt_in_logic:
          type: boolean
          description: |
            For SMS subscription groups, set to `true` (only when
            `subscription_state` is `subscribed`) to enter the user into the
            SMS double opt-in workflow. Ignored for other states.
    FacebookProfile:
      title: Facebook profile
      type: object
      properties:
        id:
          type: string
        likes:
          type: array
          items:
            type: string
        num_friends:
          type: integer
    TwitterProfile:
      title: Twitter profile
      type: object
      properties:
        id:
          type: integer
        screen_name:
          type: string
        followers_count:
          type: integer
        friends_count:
          type: integer
        statuses_count:
          type: integer
    UserAttributes:
      title: User attributes object
      type: object
      additionalProperties: true
      description: |
        A user-attribute object. One identifier (`external_id`, `user_alias`,
        `braze_id`, `email`, or `phone`) is required unless this is an
        anonymous push token import.

        `additionalProperties` is `true` because customers may set arbitrary
        custom attributes alongside the reserved Braze profile fields listed
        below.

        For the full specification, see [user attributes object]({{site.baseurl}}/api/objects_filters/user_attributes_object).
      properties:
        external_id:
          type: string
          description: External user ID.
        user_alias:
          $ref: '#/components/schemas/TrackUserAlias'
        braze_id:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
          description: Phone number, recommended in E.164 format.
        _update_existing_only:
          type: boolean
          description: |
            When `true`, only existing profiles are updated. Defaults to
            `true` for `user_alias`. Set to `false` to allow creation of
            alias-only profiles.
        push_token_import:
          type: boolean
          description: |
            When `true`, import push tokens for an anonymous user. The object
            must contain `push_tokens` and must not include `external_id` or
            `braze_id`.
        first_name:
          type: string
        last_name:
          type: string
        dob:
          type: string
          description: Date of birth in `YYYY-MM-DD` format.
          example: "1988-02-14"
        gender:
          type: string
          nullable: true
          enum: [M, F, O, N, P, null]
          description: M (male), F (female), O (other), N (not applicable), P (prefer not to say).
        country:
          type: string
          description: Country code (ISO-3166-1 alpha-2).
          example: US
        home_city:
          type: string
        language:
          type: string
          description: Language code (ISO-639-1).
          example: en
        time_zone:
          type: string
          description: IANA time zone name.
          example: America/New_York
        current_location:
          $ref: '#/components/schemas/CurrentLocation'
        date_of_first_session:
          type: string
        date_of_last_session:
          type: string
        marked_email_as_spam_at:
          type: string
        push_subscribe:
          type: string
          enum: [opted_in, subscribed, unsubscribed]
        email_subscribe:
          type: string
          enum: [opted_in, subscribed, unsubscribed]
        email_open_tracking_disabled:
          type: boolean
          description: SparkPost and SendGrid only. Set to `true` to disable open tracking.
        email_click_tracking_disabled:
          type: boolean
          description: SparkPost and SendGrid only. Set to `true` to disable click tracking.
        push_tokens:
          type: array
          items:
            $ref: '#/components/schemas/PushToken'
        subscription_groups:
          type: array
          items:
            $ref: '#/components/schemas/SubscriptionGroup'
        facebook:
          $ref: '#/components/schemas/FacebookProfile'
        twitter:
          $ref: '#/components/schemas/TwitterProfile'
    Event:
      title: Event object
      type: object
      required: [name, time]
      description: |
        For the full specification, see [events object]({{site.baseurl}}/api/objects_filters/event_object).
      properties:
        external_id:
          type: string
        user_alias:
          $ref: '#/components/schemas/TrackUserAlias'
        braze_id:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        app_id:
          type: string
        name:
          type: string
          description: Custom event name.
        time:
          type: string
          description: |
            Event timestamp in ISO 8601 format or one of Braze's accepted
            datetime formats.
          example: "2026-05-12T19:20:30+01:00"
        properties:
          type: object
          additionalProperties: true
          description: |
            Custom event properties. Property names must be non-empty strings
            up to 255 characters and cannot start with `$`. The reserved
            names `time` and `event_name` cannot be used.
        _update_existing_only:
          type: boolean
    Purchase:
      title: Purchase object
      type: object
      required: [product_id, currency, price, time]
      description: |
        For the full specification, see [purchases object]({{site.baseurl}}/api/objects_filters/purchase_object).
      properties:
        external_id:
          type: string
        user_alias:
          $ref: '#/components/schemas/TrackUserAlias'
        braze_id:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        app_id:
          type: string
        product_id:
          type: string
          description: Identifier for the purchase (up to 255 characters).
          example: backpack
        currency:
          type: string
          description: ISO 4217 alphabetic currency code.
          example: USD
        price:
          type: number
          format: float
          description: Value in the base currency unit.
        quantity:
          type: integer
          maximum: 100
          default: 1
          description: |
            Number of items purchased. Defaults to 1. Braze treats a
            quantity of N as N separate purchases of quantity 1.
        time:
          type: string
          description: ISO 8601 timestamp of the purchase.
        properties:
          type: object
          additionalProperties: true
          description: Purchase event properties.
        _update_existing_only:
          type: boolean
    TrackRequest:
      title: Create and update users request
      type: object
      additionalProperties: false
      description: |
        Each object in `attributes`, `events`, and `purchases` must include one of `external_id`, `user_alias`, `braze_id`, `email`, or `phone`.

        At least one of `attributes`, `events`, or `purchases` must contain a
        valid object. The combined total across all three arrays cannot
        exceed 75 objects per request (or 75 each — 225 total — for
        customers on legacy rate limits).
      properties:
        attributes:
          type: array
          maxItems: 75
          items:
            $ref: '#/components/schemas/UserAttributes'
          description: See [user attributes object]({{site.baseurl}}/api/objects_filters/user_attributes_object).
        events:
          type: array
          maxItems: 75
          items:
            $ref: '#/components/schemas/Event'
          description: See [events object]({{site.baseurl}}/api/objects_filters/event_object).
        purchases:
          type: array
          maxItems: 75
          items:
            $ref: '#/components/schemas/Purchase'
          description: See [purchases object]({{site.baseurl}}/api/objects_filters/purchase_object).
        partner:
          type: string
          description: |
            Partner identifier for attribution. Sent by Braze technology
            partners making `/users/track` calls on behalf of customers.
        group_id:
          type: string
          minLength: 1
          maxLength: 128
          pattern: '^[A-Za-z0-9._~-]+$'
          description: |
            (Beta) An ID you choose to group this request with related
            requests so you can check their processing status with the
            `/users/track/status` endpoint. Group IDs are scoped to a
            workspace. For more information, see [Track request processing status](#track-request-processing-status).
          example: loyalty_backfill_2026-09-23
        skip_optional_event_processing:
          type: boolean
          description: |
            When `true`, skips optional downstream processing (e.g. some
            secondary event handling). Defaults to `false`.
    TrackResponse:
      title: Create and update users response
      type: object
      additionalProperties: false
      required: [message, request_id]
      description: |
        `*_processed` fields are only present when the corresponding request
        array contained at least one valid object. The `errors` array is
        present only when there were non-fatal validation issues.
      properties:
        message:
          type: string
          example: success
        request_id:
          type: string
          format: uuid
          description: Unique identifier for the request. Braze returns this field in every successful response.
          example: 3f2b8c1e-7a4d-4e9b-9c6f-1d2e3a4b5c6d
        attributes_processed:
          type: integer
          description: Number of attribute objects queued for processing. If attributes are included in the request, this returns an integer of the number of external_ids with attributes that Braze queued for processing.
        events_processed:
          type: integer
          description: Number of custom event objects queued for processing. If events are included in the request, this returns an integer of the number of events that Braze queued for processing.
        purchases_processed:
          type: integer
          description: Number of purchase objects queued for processing. If purchases are included in the request, this returns an integer of the number of purchases that Braze queued for processing.
        recommended_events_processed:
          type: integer
          description: Number of recommended event objects queued for processing.
        errors:
          type: array
          description: Non-fatal validation issues encountered while processing the request.
          items:
            $ref: '#/components/schemas/TrackError'
    TrackError:
      title: Object error
      type: object
      additionalProperties: false
      required: [type]
      description: |
        A single validation issue. `input_array` and `index` point to the
        offending entry in the request body; they are omitted for errors
        not tied to a specific input entry (e.g. an invalid credit key).
      properties:
        type:
          type: string
          description: Human-readable description of the validation issue.
        input_array:
          type: string
          enum: [attributes, events, purchases]
          description: Which top-level array the offending object came from.
        index:
          type: integer
          description: Zero-based index of the offending object within `input_array`.
    TrackErrorResponse:
      title: Error
      type: object
      additionalProperties: false
      description: |
        Returned for fatal errors. Endpoint-specific error codes (such as
        `BAD_DEVICE_ID`, `BAD_EMAIL_SUBSCRIPTION_STATE`, or
        `BAD_PUSH_TOKEN_VALUE`) are surfaced in the `errors` array. The
        top-level `error` (singular) is used for request-level failures
        such as malformed JSON.
      properties:
        message:
          type: string
          description: Human-readable error message.
        error:
          type: string
          description: Request-level error message (e.g. malformed JSON body).
        errors:
          type: array
          items:
            $ref: '#/components/schemas/TrackError'
