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

> Enroll people by contact id, phone, email or WhatsApp user id and get one outcome per person. On an active sequence with steps they start sending right away.

# Enroll people into an outbound sequence — returns a per-person outcome



## OpenAPI

````yaml post /sequences/{sequenceId}/contacts
openapi: 3.1.0
info:
  title: OpenCX API
  description: >

    OpenCX is an AI-powered, all-in-one platform for customer support and
    outbound communications.


    Use this API to manage your OpenCX organization's AI agents, actions,
    conversations, contacts, and more.


    To get started, generate a new API key from the dashboard.


    ## Authentication

    All API endpoints require authentication using a Bearer token. You can
    generate an API key from your OpenCX dashboard.


    ## Rate Limiting

    API requests are rate limited to ensure fair usage. The current limits are:

    - 100 requests per minute for standard endpoints

    - 1000 requests per minute for streaming endpoints


    ## Error Handling

    The API uses standard HTTP status codes and returns detailed error messages
    in the response body.
  version: 1.0-beta
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
  - url: http://localhost:8080
    description: Development
  - url: https://api.open.cx
    description: Production
security:
  - bearerAuth: []
paths:
  /sequences/{sequenceId}/contacts:
    post:
      summary: Enroll people into an outbound sequence — returns a per-person outcome
      operationId: addSequenceContacts
      parameters:
        - schema:
            type: string
            format: uuid
            pattern: >-
              ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
          in: path
          name: sequenceId
          required: true
          description: The unique identifier of the sequence
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddSequenceContactsDto'
      responses:
        '201':
          description: Default Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddSequenceContactsOutputDto'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorDto'
components:
  schemas:
    AddSequenceContactsDto:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: '#/components/schemas/AddSequenceContactsDtoInput'
      type: object
      properties:
        contacts:
          minItems: 1
          maxItems: 10000
          type: array
          items:
            type: object
            properties:
              contact_id:
                description: >-
                  Enroll a known CRM contact directly — its handles and name
                  fill anything not provided. An id that matches no contact in
                  the org is reported in detail.dropped and the phone/email
                  fallback applies.
                anyOf:
                  - type: string
                    format: uuid
                    pattern: >-
                      ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
                  - type: 'null'
              phone_number:
                anyOf:
                  - type: string
                    maxLength: 50
                  - type: 'null'
              email:
                anyOf:
                  - type: string
                    maxLength: 320
                  - type: 'null'
              whatsapp_user_id:
                description: >-
                  WhatsApp BSUID (e.g. "EG.1050912744469111") — the reachable
                  identity of a phone-hidden contact. Business-scoped: only
                  values this org's own inbound produced can round-trip. Invalid
                  shapes are dropped (reported).
                anyOf:
                  - type: string
                    maxLength: 64
                  - type: 'null'
              timezone:
                description: >-
                  IANA zone; invalid values are dropped (reported), never
                  guessed.
                anyOf:
                  - type: string
                    maxLength: 64
                  - type: 'null'
              display_name:
                anyOf:
                  - type: string
                    maxLength: 200
                  - type: 'null'
              avatar_url:
                description: >-
                  Picture URL, snapshot onto the row. Must be http(s) — anything
                  else is dropped (reported). Omitted OR null: filled from the
                  linked contact on a new row, left untouched on an existing one
                  — null does not clear a stored picture.
                anyOf:
                  - type: string
                    maxLength: 2000
                  - type: 'null'
              extra_data:
                description: >-
                  Anything you know about this person — free-form JSON,
                  arbitrary nesting. Stored under their enrichment as
                  `extra_data`: the judge reads it as facts, and People-table
                  columns can project `$.extra_data.*` paths from it.
                  Re-enrolling merges per key (a call that only sends `plan`
                  never wipes `industry`). Rejected per person
                  (`extra_data_too_large`) when the serialized bag exceeds
                  64,000 characters, or when merging it would grow their stored
                  bag past that (detail.merged=true) — trim and retry just that
                  person; results are index-aligned with the contacts you sent.
                anyOf:
                  - type: object
                    propertyNames:
                      type: string
                    additionalProperties: {}
                  - type: 'null'
              session_custom_data:
                description: >-
                  Attributes to stamp on every session this sequence opens for
                  the person — the call session at dial, the reply session when
                  they answer an email or WhatsApp step — as `custom_data`, so
                  workflows that fire on those sessions (Call Finished, Ticket
                  Created) read them straight off the session. Same contract as
                  `sessionCustomData` on the outbound-call endpoint: flat string
                  / number / boolean values, at most 50 keys and 8,000
                  characters of JSON, no key-name rules. Not for what you know
                  ABOUT the person — that is `extra_data`. Re-enrolling merges
                  per key while they are a candidate or parked (an active or
                  completed person is unchanged, like every other field);
                  rejected per person (`session_custom_data_too_large`) when the
                  bag, or the merge with what they already hold, exceeds the
                  caps — nothing written, trim and retry just that person.
                  Write-only: read it back on the session.
                anyOf:
                  - type: object
                    propertyNames:
                      type: string
                    additionalProperties:
                      anyOf:
                        - type: string
                        - type: number
                        - type: boolean
                  - type: 'null'
        source:
          type: string
          enum:
            - companion
            - workflow
            - api
            - manual
            - csv
        source_detail:
          type: string
          maxLength: 500
        batch_key:
          description: >-
            Wave label ("csv-aug-refresh") stamped on every row this call
            creates or changes — lets enrich/judge scope exactly this wave.
            Omitted = rows keep their existing key. Never auto-generated.
          type: string
          minLength: 1
          maxLength: 200
      required:
        - contacts
    AddSequenceContactsOutputDto:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: '#/components/schemas/AddSequenceContactsOutputDto'
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              outcome:
                type: string
                enum:
                  - enrolled
                  - updated
                  - already_enrolled
                  - skipped_previously_exited
                  - skipped_suppressed
                  - skipped_active_elsewhere
                  - skipped_contacted_before
                  - no_identity
                  - skipped_cannot_create_contact
                  - extra_data_too_large
                  - session_custom_data_too_large
                  - conflict
                  - error
                description: >-
                  enrolled: new row created · updated: existing candidate
                  refreshed — or a parked person given new data, who resumes
                  their step: armed immediately on a live sequence, back to
                  candidate on a draft or when their judged fit is below the
                  cutoff; a parked person given ONLY session_custom_data keeps
                  it merged and stays parked · already_enrolled: already
                  active/completed here (or parked with nothing new supplied),
                  no-op · skipped_previously_exited: exited here — an exit is
                  terminal, enrolling never restarts a concluded journey ·
                  skipped_suppressed: on the do-not-contact list ·
                  skipped_active_elsewhere: active in another campaign
                  (block-toggle) · skipped_contacted_before: some campaign
                  already messaged them (block-toggle) · no_identity: no valid
                  phone, email, or whatsapp_user_id after normalization ·
                  skipped_cannot_create_contact: matched no existing contact and
                  the caller lacks contacts:write, so no contact was created —
                  nothing written ·extra_data_too_large: the extra_data bag
                  exceeds its budget (detail carries size and budget), nothing
                  written — trim and retry just this person ·
                  session_custom_data_too_large: the session bag — or its
                  per-key merge with what the person already holds
                  (detail.merged=true) — exceeds 50 keys / 8,000 characters
                  (detail carries keys, size and the caps), nothing written —
                  trim and retry just this person · conflict: handles match two
                  different existing people (detail.matches carries their
                  contact ids), nothing written · error: this row failed
                  unexpectedly, retry just this person
              contact_id:
                description: >-
                  The person's CRM contact — the identifier every person-scoped
                  op takes. Present whenever a row exists (enrolled, updated,
                  already_enrolled, skipped_previously_exited, and suppressed
                  people who already held a row); absent for outcomes that never
                  touched a row.
                type: string
                format: uuid
                pattern: >-
                  ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
              detail:
                description: >-
                  Machine-readable context: dropped=[fields rejected by
                  normalization] · in_batch=true (duplicate within this call) ·
                  matches=[contact ids] on conflict · per-channel suppressed
                  addresses · size/budget (and merged=true when the stored bag
                  would overflow) on extra_data_too_large ·
                  keys/max_keys/size/max_size (and merged=true) on
                  session_custom_data_too_large
                type: object
                propertyNames:
                  type: string
                additionalProperties: {}
            required:
              - outcome
            additionalProperties: false
        summary:
          type: object
          properties:
            enrolled:
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            not_enrolled:
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
            noop:
              type: integer
              minimum: -9007199254740991
              maximum: 9007199254740991
          required:
            - enrolled
            - not_enrolled
            - noop
          additionalProperties: false
      required:
        - results
        - summary
      additionalProperties: false
    ErrorDto:
      type: object
      properties:
        statusCode:
          type: integer
        message:
          type: string
        error:
          type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````

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