> ## 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'
        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
                  - extra_data_too_large
                  - conflict
                  - error
                description: >-
                  enrolled: new row created · updated: existing candidate
                  refreshed — or a parked person given new data, returned to
                  candidate (the next launch resumes their step) ·
                  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 ·
                  extra_data_too_large: the extra_data bag exceeds its budget
                  (detail carries size and budget), 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
                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

````