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

# Create or update contacts in bulk

> Upserts up to 100 contacts in one call. Each item is matched and written exactly like `POST /v1/contacts/upsert`, in order, and reports its own result: one item failing never undoes the others.

Answers **200** whenever the batch itself was valid, even if some items failed; read each item's `status`. A malformed item fails the whole request with 422 before anything is written. Send an `Idempotency-Key` to retry a batch safely.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/contacts/batch
openapi: 3.1.0
info:
  description: >-
    The Sailer REST API for CRM, conversations, and campaigns.


    **Authentication.** Send a token as `Authorization: Bearer <token>`. A
    workspace

    token (`sk_live_...`) is created by a workspace administrator and its secret
    is

    shown once. An OAuth access token (`oat_live_...`) acts on behalf of the
    person

    who authorized it and sees only what they can see. Either way the workspace
    is

    implied by the token, so no tenant header is needed. Each token carries
    scopes;

    `GET /v1/me` lists them.


    **Pagination.** Collections return `{data, meta, links}`. Pass `links.next`
    back

    as `cursor` until it is `null`. Cursors are opaque and only valid for the
    query

    that produced them.


    **Errors.** Every non-2xx response is `{"error": {...}}` with a coarse
    `type`, a

    stable `code` and the offending `param`. Quote `error.request_id` (also the

    `X-Request-ID` header) when contacting support.


    **Retries.** Send an `Idempotency-Key` header on any write to make retrying
    it

    safe for 24 hours.


    **Rate limits.** Limits are per token. Every response carries

    `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; a
    `429`

    also carries `Retry-After`.


    **Custom fields.** Workspace-defined fields appear under `custom_fields`,
    keyed

    by field key. `GET /v1/fields` lists every field on a resource, built-in and

    custom.
  title: Sailer API
  version: 1.0.0
servers:
  - url: https://api.chatsailer.com
security:
  - SailerApiToken: []
tags:
  - description: Token introspection and workspace capabilities.
    name: Meta
  - description: >-
      People you talk to. Called *leads* in older Sailer surfaces; `contact` is
      the current name.
    name: Contacts
  - description: Companies that contacts belong to (B2B accounts).
    name: Organizations
  - description: Opportunities moving through a pipeline.
    name: Deals
  - description: Pipelines and their stages.
    name: Pipelines
  - description: Field definitions, including custom fields.
    name: Fields
  - description: Free-text annotations on CRM records.
    name: Notes
  - description: Scheduled and logged CRM activities.
    name: Activities
  - description: Per-entity label catalogs.
    name: Tags
  - description: Members of the workspace, for assigning records.
    name: Users
  - description: Message threads with contacts, across every channel.
    name: Conversations
  - description: Outbound campaigns and their participants.
    name: Campaigns
  - description: Named contact lists campaigns draw from.
    name: Audiences
  - description: The connections you talk to contacts through.
    name: Channels
  - description: Pre-approved WhatsApp message templates.
    name: Templates
  - description: >-
      Keeping another system in step with a workspace: the deletion feed.
      Changes are read from each resource's list with `updated_since`.
    name: Sync
  - description: >-
      Business outcomes your systems report — a sale closed, a meeting held —
      that your agreement may charge a success fee on. Append-only: a refund is
      a reversal, never an edit. Needs the `success_events:read` /
      `success_events:write` scopes.
    name: Success Events
paths:
  /v1/contacts/batch:
    post:
      tags:
        - Contacts
      summary: Create or update contacts in bulk
      description: >-
        Upserts up to 100 contacts in one call. Each item is matched and written
        exactly like `POST /v1/contacts/upsert`, in order, and reports its own
        result: one item failing never undoes the others.


        Answers **200** whenever the batch itself was valid, even if some items
        failed; read each item's `status`. A malformed item fails the whole
        request with 422 before anything is written. Send an `Idempotency-Key`
        to retry a batch safely.
      operationId: batch_upsert_contacts
      parameters:
        - description: >-
            Comma-separated relationships to inline in the response, e.g.
            `organization`. Unexpanded relations are still identified by their
            `*_id` field.
          in: query
          name: expand
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Comma-separated relationships to inline in the response, e.g.
              `organization`. Unexpanded relations are still identified by their
              `*_id` field.
            examples:
              - organization
            title: Expand
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactBatchUpsert'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactBatchResult'
          description: Successful Response
          headers:
            Idempotent-Replay:
              $ref: '#/components/headers/Idempotent-Replay'
            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'
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - SailerApiToken: []
components:
  parameters:
    IdempotencyKey:
      description: >-
        A unique value (a UUID works) that makes retrying this request safe. A
        repeat with the same key and body within 24 hours returns the original
        response instead of acting twice; the same key with a different body is
        a `409 idempotency_key_reused`.
      in: header
      name: Idempotency-Key
      required: false
      schema:
        maxLength: 255
        type: string
  schemas:
    ContactBatchUpsert:
      additionalProperties: false
      description: Body for `POST /v1/contacts/batch`.
      properties:
        items:
          description: >-
            Up to 100 contacts, each matched and written exactly like `POST
            /v1/contacts/upsert`.
          items:
            $ref: '#/components/schemas/ContactUpsert'
          maxItems: 100
          minItems: 1
          title: Items
          type: array
      required:
        - items
      title: ContactBatchUpsert
      type: object
    ContactBatchResult:
      description: One result per item, in request order.
      properties:
        created:
          title: Created
          type: integer
        data:
          items:
            $ref: '#/components/schemas/ContactBatchItemResult'
          title: Data
          type: array
        failed:
          title: Failed
          type: integer
        object:
          const: batch_result
          default: batch_result
          title: Object
          type: string
        updated:
          title: Updated
          type: integer
      required:
        - data
        - created
        - updated
        - failed
      title: ContactBatchResult
      type: object
    ContactUpsert:
      additionalProperties: false
      description: >-
        Body for `POST /v1/contacts/upsert`.


        Matched on `external_id` when you send one, otherwise on `phone`. Phones
        are

        unique per workspace and normalized before comparison, so `(11)
        98765-4321`

        and `+55 11 98765-4321` are the same key. A phone match without an

        `external_id` yet is given yours. A deleted contact that matches is
        restored.
      properties:
        address:
          anyOf:
            - type: string
            - type: 'null'
          title: Address
        city:
          anyOf:
            - type: string
            - type: 'null'
          title: City
        company_name:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Legacy alias for `organization`. The text is kept on the contact,
            and a contact that has no organization yet is also linked to the
            organization this name matches, or to a new one when none does. To
            say which organization a contact belongs to, send `organization`,
            which also tells you how the name was resolved.
          title: Company Name
        custom_fields:
          additionalProperties: true
          title: Custom Fields
          type: object
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
        external_id:
          anyOf:
            - description: >-
                Your own id for this record, e.g. its id in your system. Unique
                per workspace and resource; send `null` to clear it.
              examples:
                - crm-48213
              maxLength: 255
              minLength: 1
              type: string
            - type: 'null'
          title: External Id
        first_name:
          description: Required; a nameless contact is not useful.
          minLength: 1
          title: First Name
          type: string
        job_title:
          anyOf:
            - type: string
            - type: 'null'
          title: Job Title
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
        on_match:
          $ref: '#/components/schemas/OnMatch'
          default: update
          description: What to do when the contact already exists.
        organization:
          anyOf:
            - $ref: '#/components/schemas/OrganizationReference'
            - type: 'null'
          description: >-
            The organization this contact belongs to: `{"id": ...}` to link one
            you picked, or `{"name": ...}` to match or create it by name. Send
            exactly one. The response carries `organization_resolution`. Do not
            combine with `organization_id`.
        organization_id:
          anyOf:
            - description: Unique identifier for an organization.
              examples:
                - org_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
              pattern: ^org_[0-9a-f]{32}$
              type: string
            - type: 'null'
          title: Organization Id
        owner_id:
          anyOf:
            - description: Unique identifier for an user.
              examples:
                - usr_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
              pattern: ^usr_[0-9a-f]{32}$
              type: string
            - type: 'null'
          title: Owner Id
        phone:
          description: Required. Any format; normalized to E.164.
          title: Phone
          type: string
        state:
          anyOf:
            - type: string
            - type: 'null'
          title: State
        visibility:
          $ref: '#/components/schemas/RecordVisibility'
          default: company
      required:
        - first_name
        - phone
      title: ContactUpsert
      type: object
    ContactBatchItemResult:
      description: What happened to one item of a batch.
      properties:
        contact:
          anyOf:
            - $ref: '#/components/schemas/ContactWriteResult'
            - type: 'null'
          description: >-
            The contact as written, exactly as `POST /v1/contacts/upsert`
            returns it. Absent when `failed`.
        error:
          anyOf:
            - $ref: '#/components/schemas/ApiError'
            - type: 'null'
          description: Why the item failed, in the usual error shape. Absent otherwise.
        index:
          description: Position of the item in `items`, from 0.
          title: Index
          type: integer
        status:
          enum:
            - created
            - updated
            - failed
          title: Status
          type: string
      required:
        - index
        - status
      title: ContactBatchItemResult
      type: object
    ApiErrorResponse:
      description: Every non-2xx response on the public API has this body.
      properties:
        error:
          $ref: '#/components/schemas/ApiError'
      required:
        - error
      title: ApiErrorResponse
      type: object
    OnMatch:
      description: What to do when an upsert matches an existing contact.
      enum:
        - update
        - ignore
      title: OnMatch
      type: string
    OrganizationReference:
      additionalProperties: false
      description: >-
        The organization to link a contact to: `{"id": ...}` or `{"name": ...}`.


        Send exactly one. An `id` links an organization you picked, from

        `GET /v1/organizations`. A `name` is matched to an existing organization

        ignoring case and extra spaces, or creates one when none matches. A name

        that matches several organizations, or that cannot be resolved, never
        fails

        the write: the contact is saved without an organization and

        `organization_resolution` in the response says why.
      properties:
        id:
          anyOf:
            - description: Unique identifier for an organization.
              examples:
                - org_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
              pattern: ^org_[0-9a-f]{32}$
              type: string
            - type: 'null'
          description: An existing organization in this workspace.
          title: Id
        name:
          anyOf:
            - minLength: 1
              type: string
            - type: 'null'
          description: A company name. Must not be blank.
          title: Name
      title: OrganizationReference
      type: object
    RecordVisibility:
      description: >-
        Who in the workspace can see a record: only its owner, the owner's team,
        the team and its sub-teams, or everyone.
      enum:
        - private
        - team
        - team_and_subteams
        - company
      title: RecordVisibility
      type: string
    ContactWriteResult:
      description: >-
        A contact as returned by create, update and upsert.


        The same object as on reads, plus `organization_resolution` when the
        request

        carried `organization`.
      properties:
        address:
          anyOf:
            - type: string
            - type: 'null'
          title: Address
        avatar_url:
          anyOf:
            - type: string
            - type: 'null'
          description: Short-lived signed URL; do not store it.
          title: Avatar Url
        channels:
          items:
            $ref: '#/components/schemas/ContactChannel'
          title: Channels
          type: array
        city:
          anyOf:
            - type: string
            - type: 'null'
          title: City
        company_name:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Legacy alias for `organization`. The text is kept on the contact,
            and a contact that has no organization yet is also linked to the
            organization this name matches, or to a new one when none does. To
            say which organization a contact belongs to, send `organization`,
            which also tells you how the name was resolved.
          title: Company Name
        created_at:
          format: date-time
          title: Created At
          type: string
        custom_fields:
          additionalProperties: true
          description: >-
            Every custom field defined on contacts in this workspace, keyed by
            field key and typed from its definition. Unset fields are `null`, so
            reading any contact shows the full set of keys in use.
          title: Custom Fields
          type: object
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
        external_id:
          anyOf:
            - type: string
            - type: 'null'
          description: Your own id for this record, when you set one.
          title: External Id
        first_name:
          anyOf:
            - type: string
            - type: 'null'
          title: First Name
        id:
          description: Unique identifier for a contact.
          examples:
            - con_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
          pattern: ^con_[0-9a-f]{32}$
          title: Id
          type: string
        job_title:
          anyOf:
            - type: string
            - type: 'null'
          title: Job Title
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
        lost_reason:
          anyOf:
            - type: string
            - type: 'null'
          description: Name of the reason, when `status` is `lost`.
          title: Lost Reason
        name:
          anyOf:
            - type: string
            - type: 'null'
          description: Display name. Derived from `first_name` and `last_name`.
          title: Name
        object:
          const: contact
          default: contact
          title: Object
          type: string
        organization:
          anyOf:
            - $ref: '#/components/schemas/Organization'
            - type: 'null'
          description: Populated only when `organization` is in `expand`.
        organization_id:
          anyOf:
            - description: Unique identifier for an organization.
              examples:
                - org_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
              pattern: ^org_[0-9a-f]{32}$
              type: string
            - type: 'null'
          title: Organization Id
        organization_resolution:
          anyOf:
            - $ref: '#/components/schemas/ContactOrganizationResolution'
            - type: 'null'
          description: >-
            How `organization` was resolved. Present only when the request sent
            `organization`.
        owner:
          anyOf:
            - $ref: '#/components/schemas/Owner'
            - type: 'null'
          description: >-
            Whoever is responsible for this record. Always populated when the
            record has an owner — this is not an `expand` target.
        owner_id:
          anyOf:
            - description: Unique identifier for an user.
              examples:
                - usr_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
              pattern: ^usr_[0-9a-f]{32}$
              type: string
            - type: 'null'
          deprecated: true
          description: >-
            Deprecated: use `owner` instead, which also covers AI-owned records.
            Still populated for teammate-owned records; will be removed in a
            future release.
          title: Owner Id
        phone:
          anyOf:
            - type: string
            - type: 'null'
          description: E.164. Normalized on write.
          title: Phone
        score:
          anyOf:
            - $ref: '#/components/schemas/ContactScore'
            - type: 'null'
        state:
          anyOf:
            - type: string
            - type: 'null'
          title: State
        status:
          $ref: '#/components/schemas/ContactStatus'
          description: Where the contact stands.
        tags:
          items:
            $ref: '#/components/schemas/Tag'
          title: Tags
          type: array
        updated_at:
          format: date-time
          title: Updated At
          type: string
        visibility:
          $ref: '#/components/schemas/RecordVisibility'
          description: >-
            Who in the workspace can see this record. A workspace token sees
            every record regardless; an OAuth token sees what the person who
            authorized it can see.
      required:
        - id
        - status
        - visibility
        - created_at
        - updated_at
      title: ContactWriteResult
      type: object
    ApiError:
      properties:
        code:
          description: Stable, machine-readable identifier.
          title: Code
          type: string
        detail:
          anyOf:
            - items:
                $ref: '#/components/schemas/ErrorDetail'
              type: array
            - type: 'null'
          default: null
          description: Per-field errors for validation failures.
          title: Detail
        documentation_url:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Documentation Url
        message:
          description: Human-readable explanation.
          title: Message
          type: string
        param:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          description: Request field the error refers to, if any.
          title: Param
        request_id:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          description: >-
            Echoes the X-Request-ID response header. Quote it in support
            requests.
          title: Request Id
        type:
          $ref: '#/components/schemas/ErrorType'
      required:
        - type
        - code
        - message
      title: ApiError
      type: object
    ContactChannel:
      description: Where a contact is reachable. An empty list means no known channel.
      properties:
        identifier:
          description: Address on that platform.
          title: Identifier
          type: string
        platform:
          description: Channel platform, e.g. `whatsapp`.
          title: Platform
          type: string
        status:
          anyOf:
            - type: string
            - type: 'null'
          description: Reachability, when the platform reports it.
          title: Status
        username:
          anyOf:
            - type: string
            - type: 'null'
          title: Username
      required:
        - platform
        - identifier
      title: ContactChannel
      type: object
    Organization:
      description: A company a contact belongs to.
      properties:
        created_at:
          format: date-time
          title: Created At
          type: string
        custom_fields:
          additionalProperties: true
          description: >-
            Every custom field defined on organizations in this workspace, keyed
            by field key and typed from its definition. Unset fields are `null`,
            so reading any organization shows the full set of keys in use.
          title: Custom Fields
          type: object
        external_id:
          anyOf:
            - type: string
            - type: 'null'
          description: Your own id for this record, when you set one.
          title: External Id
        id:
          description: Unique identifier for an organization.
          examples:
            - org_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
          pattern: ^org_[0-9a-f]{32}$
          title: Id
          type: string
        name:
          title: Name
          type: string
        object:
          const: organization
          default: organization
          title: Object
          type: string
        owner:
          anyOf:
            - $ref: '#/components/schemas/Owner'
            - type: 'null'
          description: >-
            Whoever is responsible for this record. Always populated when the
            record has an owner — this is not an `expand` target.
        owner_id:
          anyOf:
            - description: Unique identifier for an user.
              examples:
                - usr_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
              pattern: ^usr_[0-9a-f]{32}$
              type: string
            - type: 'null'
          deprecated: true
          description: >-
            Deprecated: use `owner` instead, which also covers AI-owned records.
            Still populated for teammate-owned records; will be removed in a
            future release.
          title: Owner Id
        tags:
          items:
            $ref: '#/components/schemas/Tag'
          title: Tags
          type: array
        updated_at:
          format: date-time
          title: Updated At
          type: string
        visibility:
          $ref: '#/components/schemas/RecordVisibility'
          description: >-
            Who in the workspace can see this record. A workspace token sees
            every record regardless; an OAuth token sees what the person who
            authorized it can see.
        website:
          anyOf:
            - type: string
            - type: 'null'
          title: Website
      required:
        - id
        - name
        - visibility
        - created_at
        - updated_at
      title: Organization
      type: object
    ContactOrganizationResolution:
      description: What a write did with the `organization` it was given.
      properties:
        candidates:
          description: >-
            The organizations that share the name, when `outcome` is
            `ambiguous`.
          items:
            $ref: '#/components/schemas/OrganizationSummary'
          title: Candidates
          type: array
        organization:
          anyOf:
            - $ref: '#/components/schemas/OrganizationSummary'
            - type: 'null'
          description: The organization the contact was linked to.
        outcome:
          $ref: '#/components/schemas/OrganizationLinkOutcome'
          description: >-
            `linked`: the `id` you sent. `matched`: the name found one existing
            organization. `created`: no organization had the name, so one was
            created. `ambiguous`: several organizations share the name, so the
            contact was not linked; see `candidates`. `refused`: the name could
            not be resolved, so the contact was not linked; see `reason`.
            `already_linked`: the contact already has an organization, and a
            name never changes it; send `organization.id` to move it.
        reason:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Why a name was refused: `empty`, `create_not_allowed`,
            `local_write_disabled` or `sandbox`.
          title: Reason
      required:
        - outcome
      title: ContactOrganizationResolution
      type: object
    Owner:
      description: |-
        Whoever is responsible for a record: a teammate (`kind: user`) or an AI
        agent (`kind: agent`).
      properties:
        id:
          description: Prefixed id — `usr_…` for a teammate, `agt_…` for an agent.
          title: Id
          type: string
        kind:
          enum:
            - user
            - agent
          title: Kind
          type: string
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
        object:
          const: owner
          default: owner
          title: Object
          type: string
      required:
        - kind
        - id
      title: Owner
      type: object
    ContactScore:
      description: How warm a contact is.
      enum:
        - cold
        - warm
        - hot
      title: ContactScore
      type: string
    ContactStatus:
      description: Where a contact stands commercially.
      enum:
        - open
        - on_hold
        - won
        - lost
      title: ContactStatus
      type: string
    Tag:
      description: A CRM label. The catalogue is per entity type.
      properties:
        color:
          anyOf:
            - type: string
            - type: 'null'
          description: Hex colour used by the Sailer UI, e.g. `#FFAA00`.
          title: Color
        id:
          description: Unique identifier for a tag.
          examples:
            - tag_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
          pattern: ^tag_[0-9a-f]{32}$
          title: Id
          type: string
        name:
          title: Name
          type: string
        object:
          const: tag
          default: tag
          title: Object
          type: string
      required:
        - id
        - name
      title: Tag
      type: object
    ErrorDetail:
      properties:
        code:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          title: Code
        field:
          description: Dotted path to the offending field.
          title: Field
          type: string
        message:
          title: Message
          type: string
      required:
        - field
        - message
      title: ErrorDetail
      type: object
    ErrorType:
      description: Coarse class of failure. Stable; new codes may appear under a type.
      enum:
        - invalid_request_error
        - authentication_error
        - permission_error
        - not_found_error
        - conflict_error
        - rate_limit_error
        - api_error
      title: ErrorType
      type: string
    OrganizationSummary:
      description: An organization named in `organization_resolution`.
      properties:
        id:
          description: Unique identifier for an organization.
          examples:
            - org_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4
          pattern: ^org_[0-9a-f]{32}$
          title: Id
          type: string
        name:
          title: Name
          type: string
      required:
        - id
        - name
      title: OrganizationSummary
      type: object
    OrganizationLinkOutcome:
      enum:
        - linked
        - matched
        - created
        - ambiguous
        - refused
        - already_linked
      title: OrganizationLinkOutcome
      type: string
  headers:
    Idempotent-Replay:
      description: >-
        `true` when this response is a stored replay of an earlier request with
        the same `Idempotency-Key`.
      schema:
        enum:
          - 'true'
        type: string
    X-RateLimit-Limit:
      description: Requests allowed in the current window on this token.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Seconds until the window resets and the allowance refills.
      schema:
        type: integer
    X-Request-ID:
      description: Unique id for this request. Quote it to support.
      schema:
        type: string
    Retry-After:
      description: Seconds to wait before retrying.
      schema:
        type: integer
  responses:
    BadRequest:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: A query parameter, path segment or header is invalid.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    Unauthorized:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: The token is missing, unknown, revoked or expired.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    Forbidden:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: >-
        The token lacks a required scope, or the workspace's CRM write policy
        does not allow this change.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    Conflict:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: >-
        The request conflicts with current state, or its `Idempotency-Key` was
        reused with a different body or is still being processed.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    UnprocessableContent:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: The request body failed validation.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    RateLimited:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: Too many requests on this token. Retry after the delay.
      headers:
        Retry-After:
          $ref: '#/components/headers/Retry-After'
        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'
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
    InternalError:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
      description: Something went wrong on our side. Quote `error.request_id` to support.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/X-Request-ID'
  securitySchemes:
    SailerApiToken:
      description: >-
        A workspace API token (`sk_live_...`) or an OAuth access token
        (`oat_live_...`). Create workspace tokens in Settings > API. Send either
        as `Authorization: Bearer <token>`.
      scheme: bearer
      type: http

````

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