> ## Documentation Index
> Fetch the complete documentation index at: https://developers.teambuilder.ge/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync your employee roster

> Upsert your HR roster in one idempotent request, optionally deactivating employees who have left.

<Tip>
  Read the [roster sync guide](/guides/roster-sync) before your first run: it covers matching, departments, deactivation guards, chunking and a worked example.
</Tip>


## OpenAPI

````yaml api-reference/openapi.json PUT /v1/employees/sync
openapi: 3.0.0
info:
  title: TeamBuilder API
  description: >-
    The TeamBuilder API gives your own systems access to your company's
    gamification data (employees, leaderboards, departments and the rewards
    shop) and lets you push your employee roster from your HR system. Send every
    request to `https://api.teambuilder.ge/v1` with your API key as
    `Authorization: Bearer <key>`: live keys (`tb_live_…`) work on your real
    data, test keys (`tb_test_…`) on a sandbox company with sample data.
    Responses are JSON with snake_case fields, cursor-based pagination and
    `X-RateLimit-*` headers. For keys, scopes or help, contact
    [contact@teambuilder.ge](mailto:contact@teambuilder.ge).
  version: '1.0'
  contact:
    name: TeamBuilder support
    url: https://teambuilder.ge
    email: contact@teambuilder.ge
servers:
  - url: https://api.teambuilder.ge
    description: Production
security: []
tags:
  - name: Status
    description: Check that your API key works and see what it can access.
  - name: Employees
    description: >-
      Your active employees, their gamification stats and the badges they hold.
      Requires the `employees:read` scope.
  - name: Leaderboard
    description: >-
      XP rankings of your employees for a chosen period, company-wide or grouped
      by department. Requires the `leaderboard:read` scope.
  - name: Departments
    description: Your company's department tree. Requires the `departments:read` scope.
  - name: Shop
    description: >-
      The rewards your employees can redeem with their coins. Requires the
      `shop:read` scope.
  - name: Roster sync
    description: >-
      Keep TeamBuilder in step with your HR system by pushing your employee
      roster. Requires the `employees:write` scope, which is issued on request.
paths:
  /v1/employees/sync:
    put:
      tags:
        - Roster sync
      summary: Sync your employee roster
      description: >-
        Creates, updates and, if you ask, deactivates employees so that
        TeamBuilder matches the roster you send. Run it from your HR system,
        typically once a day.


        Each row is matched to an existing employee by `external_id`, then by
        `email` (an employee who has no `external_id` yet is linked to yours). A
        row that matches nobody adds a new employee; no invitation email is
        sent. The response counts what happened and lists every row that could
        not be applied.


        **How rows are applied**


        - Optional fields merge: omit a field to keep the current value, or send
        `null` (or an empty string) to clear it.

        - Departments are matched by `department_external_id` (an archived
        department is restored), then by `department_name`, ignoring case. An
        unknown department is created at the top level when you send its name. A
        sync never renames a department.

        - An existing person's name and email are never changed. Until a new
        employee first signs in, the API shows the name you sent.

        - A row that cannot be applied is reported in `errors` with its `index`
        and a `code`; the other rows still apply. `created + updated +
        unchanged` plus the number of `errors` always equals the number of rows
        sent.

        - Syncing is idempotent: sending the same roster again changes nothing
        and reports every row as `unchanged`.


        **Deactivating employees who left**


        Set `deactivate_missing` to `true` only when the request carries your
        complete roster. Employees linked to an `external_id` that the roster
        does not name are then deactivated. Deactivation is soft: their history
        is kept, and a later sync that names them again reactivates them. To
        protect you from a truncated export, nobody is deactivated when any row
        is rejected as malformed, duplicated or outside your SSO domains, when
        the sync stopped part-way (`batch_aborted`), or when more employees
        would be deactivated than `deactivation_limit` allows;
        `deactivation_skipped_reason` then says why. A sync never deactivates
        admins, employees with an admin-portal role, or employees without an
        `external_id`.


        **Notes**


        - Up to 5,000 employees per request (16 MB). For a larger roster, send
        several requests one after another, with `deactivate_missing` left
        `false`.

        - One sync per company runs at a time (409 otherwise), and each key may
        run 30 syncs per hour.

        - Send the body with a `Content-Length` header.

        - If your company signs in with SSO, every email must use one of your
        SSO domains (`email_domain_not_allowed` otherwise).

        - With a test key, rows sync into your sandbox (at most 500 employees,
        no SSO check), where emails are stored as non-deliverable stand-ins.
      operationId: syncEmployees
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RosterSyncRequest'
      responses:
        '200':
          description: The sync ran. Check `errors` for rows that were not applied.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RosterSyncResult'
        '400':
          description: >-
            The body is not valid JSON, or `employees`, `deactivate_missing` or
            `max_deactivations` is invalid. Invalid rows do not cause this
            error: they are reported in `errors`.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: validation_failed
                  message: employees must contain no more than 5000 elements
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '401':
          description: The API key is missing, unknown, revoked or expired.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            WWW-Authenticate:
              $ref: '#/components/headers/WWW-Authenticate'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: unauthorized
                  message: Invalid API key
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '403':
          description: Your API key does not have the `employees:write` scope.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: forbidden
                  message: 'This API key lacks the required scope: employees:write'
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '409':
          description: >-
            Another sync of your company is still running (or the service is
            running its maximum of 4 syncs). Wait `Retry-After` seconds, then
            send the same request again.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: conflict
                  message: >-
                    Another employee sync for your company is running; retry
                    when it has finished
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '411':
          description: >-
            The body was sent without a `Content-Length` header (for example
            with chunked encoding).
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: length_required
                  message: Send the body with a Content-Length header
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '413':
          description: The body is larger than 16 MB. Send fewer employees per request.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: payload_too_large
                  message: Request body is too large
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '429':
          description: >-
            Your API key ran 30 syncs in the last hour, or made more requests
            than its per-minute limit allows. Wait `Retry-After` seconds, then
            retry.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: rate_limit_exceeded
                  message: Too many employee syncs. Retry after 1800 seconds.
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
      security:
        - api-key: []
components:
  schemas:
    RosterSyncRequest:
      type: object
      properties:
        employees:
          minItems: 1
          maxItems: 5000
          description: >-
            The employees to sync, at most 5,000 per request. Each row is
            checked on its own: an invalid row is reported in `errors` and does
            not stop the others.
          type: array
          items:
            $ref: '#/components/schemas/RosterEmployee'
        deactivate_missing:
          type: boolean
          default: false
          description: >-
            Set to `true` to deactivate the employees this request does not
            name. Send it only when the request carries your complete roster;
            leave it `false` when you split a roster across requests. Only
            employees linked to an `external_id` can be deactivated (the others
            are counted in `unlinked_members`), and never admins. Nobody is
            deactivated when any row is rejected as malformed, duplicated or
            outside your SSO domains, when the sync stopped part-way
            (`batch_aborted`), or when more employees would be deactivated than
            `deactivation_limit` allows (see `max_deactivations`). Deactivation
            is soft: history is kept, and a later sync that names the employee
            again reactivates them.
          example: true
        max_deactivations:
          type: integer
          minimum: 1
          maximum: 100000
          description: >-
            Confirms a large deactivation. Without it, one sync deactivates at
            most 10 employees or 20% of your active employees linked to an
            `external_id` (admins excluded), whichever is more; with it, at most
            this many. Send it only for a deliberate reorganisation. If more
            employees would be deactivated, nobody is, and
            `deactivation_skipped_reason` says why.
          example: 25
      description: Your employee roster, or one part of it.
      required:
        - employees
    RosterSyncResult:
      type: object
      properties:
        created:
          type: integer
          minimum: 0
          description: Employees added to your company.
          example: 3
        updated:
          type: integer
          minimum: 0
          description: >-
            Existing employees whose `external_id`, department or job title
            changed, or who were reactivated.
          example: 5
        unchanged:
          type: integer
          minimum: 0
          description: >-
            Rows that already matched TeamBuilder. Sending the same roster again
            reports every row here.
          example: 205
        reactivated:
          type: integer
          minimum: 0
          description: >-
            Of `updated`: deactivated employees this roster named again, now
            active.
          example: 1
        deactivated:
          type: integer
          minimum: 0
          description: >-
            Employees deactivated because `deactivate_missing` was `true`. 0
            otherwise.
          example: 2
        departments_created:
          type: integer
          minimum: 0
          description: >-
            Departments created, at the top level, because a row named an
            unknown one.
          example: 1
        departments_restored:
          type: integer
          minimum: 0
          description: >-
            Archived departments restored because a row named their
            `department_external_id`.
          example: 0
        deactivation_candidates:
          example:
            - employee_id: 2a9c4e6b-7d1f-4b3a-9e5c-8f0d2b4a6c13
              external_id: EMP-00017
              email: luka.tsiklauri@example.com
            - employee_id: 6f1b3d5a-9c2e-4a7b-8d4f-0e3c5a7b9d26
              external_id: EMP-00093
              email: mariam.lomidze@example.com
          description: >-
            Active employees linked to an `external_id` that this roster does
            not name, ordered by `employee_id`, at most 1,000. They were
            deactivated if `deactivate_missing` was `true` and
            `deactivation_skipped_reason` is null; otherwise they are only
            reported. Admins and employees with an admin-portal role are never
            candidates.
          type: array
          items:
            $ref: '#/components/schemas/DeactivationCandidate'
        deactivation_candidates_total:
          type: integer
          minimum: 0
          description: All deactivation candidates, including any beyond the ones listed.
          example: 2
        unlinked_members:
          type: integer
          minimum: 0
          description: >-
            Active employees without an `external_id` (added in TeamBuilder,
            never synced) that this roster does not name. A sync never
            deactivates them. To link one, send their email with an
            `external_id`.
          example: 4
        deactivation_limit:
          type: integer
          minimum: 0
          description: >-
            The most employees this sync could deactivate: `max_deactivations`
            if you sent it, else the default limit.
          example: 25
        deactivation_skipped_reason:
          nullable: true
          description: >-
            Why `deactivate_missing` deactivated nobody. Null when it was not
            requested or nothing stopped it.

            - `rejected_rows`: some rows were malformed, duplicated or outside
            your SSO domains, or the sync stopped part-way, so the roster may be
            incomplete.

            - `limit_exceeded`: more employees would have been deactivated than
            `deactivation_limit` allows; send `max_deactivations` to confirm.
          example: null
          allOf:
            - $ref: '#/components/schemas/DeactivationSkipReason'
        errors:
          description: Rows that were not applied, in request order.
          type: array
          items:
            $ref: '#/components/schemas/RosterRowError'
        warnings:
          description: Rows that were applied but deserve a look, in request order.
          type: array
          items:
            $ref: '#/components/schemas/RosterRowWarning'
      description: >-
        What the sync did. `created + updated + unchanged` plus the number of
        `errors` equals the rows sent.
      required:
        - created
        - updated
        - unchanged
        - reactivated
        - deactivated
        - departments_created
        - departments_restored
        - deactivation_candidates
        - deactivation_candidates_total
        - unlinked_members
        - deactivation_limit
        - deactivation_skipped_reason
        - errors
        - warnings
    Error:
      type: object
      properties:
        error:
          description: What went wrong, and the id that traces the request.
          allOf:
            - $ref: '#/components/schemas/ErrorDetail'
      description: Every error response has this shape, whatever its status code.
      required:
        - error
    RosterEmployee:
      type: object
      properties:
        external_id:
          type: string
          maxLength: 255
          description: >-
            Your HR system's stable key for the employee. Rows are matched on it
            first. A number is accepted and read as text.
          example: EMP-00042
        email:
          type: string
          maxLength: 256
          format: email
          description: >-
            Work email address, matched ignoring case. It links the row to an
            existing employee who has no `external_id` yet. A sync never changes
            the email of an existing employee.
          example: nino.beridze@example.com
        first_name:
          type: string
          maxLength: 50
          description: Given name. A sync never renames an existing TeamBuilder account.
          example: Nino
        last_name:
          type: string
          maxLength: 50
          description: >-
            Family name. Send an empty string for an employee who has a single
            name.
          example: Beridze
        department_external_id:
          type: string
          nullable: true
          maxLength: 255
          description: >-
            The department's key in your HR system, matched first. An archived
            department with this key is restored. Send `department_name` as
            well, so that an unknown department can be created.
          example: D-ENG
        department_name:
          type: string
          nullable: true
          maxLength: 150
          description: >-
            Department name, matched ignoring case when no department has the
            `department_external_id`. An unknown department is created at the
            top level. A sync never renames a department.
          example: Engineering
        job_title:
          type: string
          nullable: true
          maxLength: 150
          description: Job title.
          example: Front-End Developer
      description: >-
        One employee of your roster. Optional fields merge: omit a field to keep
        the current value, or send `null` (or an empty string) to clear it.
      required:
        - external_id
        - email
        - first_name
        - last_name
    DeactivationCandidate:
      type: object
      properties:
        employee_id:
          type: string
          format: uuid
          description: >-
            TeamBuilder id of the employee. Stable for as long as they are in
            your company.
          example: 2a9c4e6b-7d1f-4b3a-9e5c-8f0d2b4a6c13
        external_id:
          type: string
          nullable: true
          description: The employee's key in your HR system.
          example: EMP-00017
        email:
          type: string
          format: email
          description: Work email address.
          example: luka.tsiklauri@example.com
      description: An active employee your roster does not name.
      required:
        - employee_id
        - external_id
        - email
    DeactivationSkipReason:
      type: string
      enum:
        - rejected_rows
        - limit_exceeded
      description: >-
        Why `deactivate_missing` deactivated nobody. Null when it was not
        requested or nothing stopped it.

        - `rejected_rows`: some rows were malformed, duplicated or outside your
        SSO domains, or the sync stopped part-way, so the roster may be
        incomplete.

        - `limit_exceeded`: more employees would have been deactivated than
        `deactivation_limit` allows; send `max_deactivations` to confirm.
    RosterRowError:
      type: object
      properties:
        index:
          type: integer
          minimum: 0
          description: Zero-based position of the row in the `employees` array you sent.
          example: 41
        external_id:
          type: string
          nullable: true
          description: The row's `external_id`. Null if the row had no usable one.
          example: EMP-00107
        code:
          description: >-
            Why the row was not applied:

            - `validation_failed`: a field is missing or invalid; `message`
            names it.

            - `duplicate_external_id`: another row has the same `external_id`.
            None of those rows is applied.

            - `duplicate_email`: another row has the same `email`. None of those
            rows is applied.

            - `external_id_mismatch`: the email belongs to an employee of your
            company with a different `external_id`.

            - `managed_in_portal`: the employee is a deactivated admin or holds
            an admin-portal role; reactivate them in the portal.

            - `deactivated_in_portal`: the employee was deactivated in the admin
            portal; only the portal can reactivate them.

            - `email_domain_not_allowed`: your company signs in with SSO and the
            email is not on one of your SSO domains.

            - `sandbox_limit_reached`: your sandbox is full (test keys only).

            - `unknown_department`: no department has this
            `department_external_id`, and no `department_name` was sent to
            create it.

            - `department_conflict`: the department cannot be matched
            unambiguously: its name or external id belongs to a different
            department.

            - `department_limit_reached`: creating or restoring the department
            would exceed your department limit.

            - `conflict`: the employee changed while the sync ran. Sync again to
            apply the row.

            - `batch_aborted`: not attempted, because too many rows of this
            request failed. Fix them and sync again.
          example: external_id_mismatch
          allOf:
            - $ref: '#/components/schemas/RosterRowErrorCode'
        message:
          type: string
          description: >-
            What to fix, for a person to read. It never repeats the values you
            sent.
          example: >-
            this email belongs to an employee of your company with a different
            external_id
      description: A row that was not applied. The other rows were.
      required:
        - index
        - external_id
        - code
        - message
    RosterRowWarning:
      type: object
      properties:
        index:
          type: integer
          minimum: 0
          description: Zero-based position of the row in the `employees` array you sent.
          example: 7
        external_id:
          type: string
          nullable: true
          description: The row's `external_id`.
          example: EMP-00058
        code:
          description: >-
            What to check:

            - `email_matches_other_employee`: the row matched by `external_id`,
            but its email belongs to another employee of your company who has no
            `external_id`.

            - `department_moved_to_top_level`: the restored department's parent
            is archived, so it was restored at the top level.
          example: department_moved_to_top_level
          allOf:
            - $ref: '#/components/schemas/RosterRowWarningCode'
        message:
          type: string
          description: What happened, for a person to read.
          example: >-
            the department's parent is archived, so it was restored at the top
            level
      description: A row that was applied but deserves a look.
      required:
        - index
        - external_id
        - code
        - message
    ErrorDetail:
      type: object
      properties:
        code:
          description: >-
            Machine-readable error type. Branch on this, not on `message`:

            - `validation_failed` (400): a parameter, cursor or body field is
            invalid.

            - `unauthorized` (401): the API key is missing, unknown, revoked or
            expired.

            - `forbidden` (403): the key does not have the scope this endpoint
            requires.

            - `not_found` (404): the resource does not exist in your company.

            - `conflict` (409): the request clashes with work in progress, such
            as another roster sync.

            - `length_required` (411): the request body was sent without a
            `Content-Length` header.

            - `payload_too_large` (413): the request body is larger than the
            endpoint accepts.

            - `rate_limit_exceeded` (429): the key made too many requests; wait
            for `Retry-After` seconds.

            - `bad_request` (other 4xx): the request could not be accepted as
            sent.

            - `internal_error` (500): something failed on our side; contact
            support with the `request_id`.
          example: validation_failed
          allOf:
            - $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          description: >-
            Human-readable explanation for developers. It may change; do not
            parse it.
          example: limit must not be greater than 100
        request_id:
          type: string
          description: >-
            Unique id of the request, also sent as the `X-Request-Id` header.
            Quote it when you contact support.
          example: req_7Hq2LmX9vT4kPz1RbN8sWc
      required:
        - code
        - message
        - request_id
    RosterRowErrorCode:
      type: string
      enum:
        - validation_failed
        - duplicate_external_id
        - duplicate_email
        - external_id_mismatch
        - managed_in_portal
        - deactivated_in_portal
        - email_domain_not_allowed
        - sandbox_limit_reached
        - unknown_department
        - department_conflict
        - department_limit_reached
        - conflict
        - batch_aborted
      description: >-
        Why the row was not applied:

        - `validation_failed`: a field is missing or invalid; `message` names
        it.

        - `duplicate_external_id`: another row has the same `external_id`. None
        of those rows is applied.

        - `duplicate_email`: another row has the same `email`. None of those
        rows is applied.

        - `external_id_mismatch`: the email belongs to an employee of your
        company with a different `external_id`.

        - `managed_in_portal`: the employee is a deactivated admin or holds an
        admin-portal role; reactivate them in the portal.

        - `deactivated_in_portal`: the employee was deactivated in the admin
        portal; only the portal can reactivate them.

        - `email_domain_not_allowed`: your company signs in with SSO and the
        email is not on one of your SSO domains.

        - `sandbox_limit_reached`: your sandbox is full (test keys only).

        - `unknown_department`: no department has this `department_external_id`,
        and no `department_name` was sent to create it.

        - `department_conflict`: the department cannot be matched unambiguously:
        its name or external id belongs to a different department.

        - `department_limit_reached`: creating or restoring the department would
        exceed your department limit.

        - `conflict`: the employee changed while the sync ran. Sync again to
        apply the row.

        - `batch_aborted`: not attempted, because too many rows of this request
        failed. Fix them and sync again.
    RosterRowWarningCode:
      type: string
      enum:
        - email_matches_other_employee
        - department_moved_to_top_level
      description: >-
        What to check:

        - `email_matches_other_employee`: the row matched by `external_id`, but
        its email belongs to another employee of your company who has no
        `external_id`.

        - `department_moved_to_top_level`: the restored department's parent is
        archived, so it was restored at the top level.
    ErrorCode:
      type: string
      enum:
        - bad_request
        - validation_failed
        - unauthorized
        - forbidden
        - not_found
        - conflict
        - length_required
        - payload_too_large
        - rate_limit_exceeded
        - internal_error
      description: >-
        Machine-readable error type. Branch on this, not on `message`:

        - `validation_failed` (400): a parameter, cursor or body field is
        invalid.

        - `unauthorized` (401): the API key is missing, unknown, revoked or
        expired.

        - `forbidden` (403): the key does not have the scope this endpoint
        requires.

        - `not_found` (404): the resource does not exist in your company.

        - `conflict` (409): the request clashes with work in progress, such as
        another roster sync.

        - `length_required` (411): the request body was sent without a
        `Content-Length` header.

        - `payload_too_large` (413): the request body is larger than the
        endpoint accepts.

        - `rate_limit_exceeded` (429): the key made too many requests; wait for
        `Retry-After` seconds.

        - `bad_request` (other 4xx): the request could not be accepted as sent.

        - `internal_error` (500): something failed on our side; contact support
        with the `request_id`.
  headers:
    X-Request-Id:
      description: Unique id of this request. Quote it when you contact support.
      schema:
        type: string
        example: req_7Hq2LmX9vT4kPz1RbN8sWc
    X-RateLimit-Limit:
      description: Requests your API key may make per minute.
      schema:
        type: integer
        example: 60
    X-RateLimit-Remaining:
      description: Requests left in the current one-minute window.
      schema:
        type: integer
        example: 57
    X-RateLimit-Reset:
      description: When the current window ends, as a Unix timestamp in seconds.
      schema:
        type: integer
        example: 1792483260
    WWW-Authenticate:
      description: The authentication scheme to use.
      schema:
        type: string
        example: Bearer realm="TeamBuilder API"
    Retry-After:
      description: Seconds to wait before you retry.
      schema:
        type: integer
        example: 30
  securitySchemes:
    api-key:
      scheme: bearer
      bearerFormat: API key
      type: http
      description: API key — `tb_live_…` for production data, `tb_test_…` for the sandbox.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.