> ## 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.

# Sequences API

> Run multi-step outbound journeys over WhatsApp, voice and email from code: enroll people, author steps, launch, and read what happened to each person.

A **sequence** is a multi-step outbound journey. Each person enrolled in it walks the steps in order, on the channel each step names (WhatsApp, an AI phone call, or email), waiting the step's delay between messages, and leaves the journey the moment they engage. The engine tracks every person individually, so a sequence is both a plan and a live record of what happened to each contact.

Every endpoint here is scoped with `sequences:read` or `sequences:write`. Two exceptions are called out below: audience discovery returns contact data and needs `contacts:read`, and reply previews need `chat-sessions:read`.

## Lifecycle

A sequence has three stored statuses:

| Status   | Meaning                                                                        |
| -------- | ------------------------------------------------------------------------------ |
| `draft`  | Being authored. People can be enrolled and reviewed; nothing is sent.          |
| `active` | Launched. Eligible people are armed and sent; new enrollees arm immediately.   |
| `paused` | Sending is stopped. Everyone keeps their place and resumes from the same step. |

* [Launch](/api-reference/sequences/launch) moves `draft` to `active` and arms every eligible candidate. Launching an already active sequence is idempotent: it only arms candidates added since. Launching a paused sequence is refused, [resume](/api-reference/sequences/resume) it instead.
* [Pause](/api-reference/sequences/pause) and [resume](/api-reference/sequences/resume) only touch the sequence itself. People keep their cursors; on resume, overdue steps go first.
* There is no stored "done". `finished` on the stats is derived (nothing left in flight and something already went out), so enrolling more people revives a finished sequence with no status change.

## Recommended flow

The API is built for **enroll first, author later**: land a real audience, then write the steps for it.

<Steps>
  <Step title="Create the shell">
    [Create](/api-reference/sequences/create) with a name and a plain-text brief. Steps can wait.
  </Step>

  <Step title="Enroll people">
    [Add contacts](/api-reference/sequences/add-contacts) by contact id, phone, email or WhatsApp user id, with any facts you know about them in `extra_data`. Read the per-person outcomes; a successful call never implies everyone was enrolled. Use `batch_key` to label the wave. [List contacts](/api-reference/sequences/list-contacts) to review who landed.
  </Step>

  <Step title="Author steps and bind senders">
    [Set steps](/api-reference/sequences/set-steps) on the draft. [Set senders](/api-reference/sequences/set-senders) per channel; the [email](/api-reference/sequences/list-email-senders) and [voice](/api-reference/sequences/list-voice-senders) catalogs tell you which senders are eligible and why not.
  </Step>

  <Step title="Preview, then launch">
    [Launch preview](/api-reference/sequences/launch-preview) mirrors launch clause for clause: who would start, who is held and why, and what would refuse the launch. Then [launch](/api-reference/sequences/launch).
  </Step>

  <Step title="Keep feeding it">
    Keep calling add contacts. On an active sequence with steps, new people arm the moment they are enrolled, so a CRM can push leads without anyone pressing launch again. [Remove](/api-reference/sequences/exit-contact) a person to stop every remaining step.
  </Step>
</Steps>

## People

Every person in a sequence has one status:

| Status      | Meaning                                                                                                                      |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `candidate` | Enrolled, not armed. Drafts hold everyone here; on an active sequence this is someone held back by a qualification verdict.  |
| `active`    | Armed. The engine sends their next step when it becomes due.                                                                 |
| `parked`    | Waiting on a human. `park_reason` says what to fix; the journey resumes where it stopped.                                    |
| `exited`    | Journey over. `exit_reason` is `replied` (they engaged) or `suppressed` (every channel they could be reached on is blocked). |
| `completed` | Every step went out and nobody replied.                                                                                      |

Park reasons are always something a person must fix: `needs_data` (a step variable has no value and no fallback), `unsendable_person` (their own data cannot go on the wire), or `template_broken` (the step's template is missing, rejected or mismatched for everyone).

A removal through [exit contact](/api-reference/sequences/exit-contact) is a deletion, not an exit reason. The person leaves every read, and adding them again later starts them fresh from the first step. What was already sent is kept.

Each person also carries a **journey**: one stamp per step (`not_reached`, `waiting`, `parked`, `deferred`, `skipped`, `sent`, `delivered`, `read`, `replied`, `answered`, `no_answer`, `busy`, `failed`) with the inbox session of the send when one exists. For the raw event log behind those stamps, read [contact events](/api-reference/sequences/get-contact-events).

### Enrollment outcomes

Add contacts answers per person, index-aligned with what you sent:

| Outcome                     | Bucket       | Meaning                                                                                                         |
| --------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
| `enrolled`                  | enrolled     | New row created.                                                                                                |
| `updated`                   | enrolled     | Existing candidate refreshed, or a parked person given new data and returned to candidate.                      |
| `already_enrolled`          | noop         | Already active or completed here, or parked with nothing new supplied.                                          |
| `skipped_previously_exited` | not enrolled | They exited this sequence before. An exit is terminal; enrolling never restarts a concluded journey.            |
| `skipped_suppressed`        | not enrolled | Every channel they could be reached on is on the [outbound blocklist](/api-reference/outbound-blocklist/index). |
| `skipped_active_elsewhere`  | not enrolled | Active in another sequence (setting `block_active_elsewhere`, on by default).                                   |
| `skipped_contacted_before`  | not enrolled | Some sequence already messaged them (setting `block_contacted_before`, on by default).                          |
| `no_identity`               | not enrolled | No valid phone, email or WhatsApp user id after normalization. `detail.dropped` names what was rejected.        |
| `extra_data_too_large`      | not enrolled | The `extra_data` bag exceeds its budget. Nothing written; trim and retry that person.                           |
| `conflict`                  | not enrolled | The handles match two different existing people. `detail.matches` carries their contact ids.                    |
| `error`                     | not enrolled | This row failed unexpectedly. The rest of the batch is unaffected; retry that person.                           |

Phone numbers must be in international format, emails are lowercased, and duplicates inside one call are reported with `detail.in_batch`.

## Steps

A step is a channel, its content, and `delay_minutes`: how long to wait after the previous step **actually sent**. The first step must have a delay of zero because launch and enrollment arm it, not a timer.

<AccordionGroup>
  <Accordion title="WhatsApp step" icon="whatsapp">
    References an approved message template by `template_name` and `template_language`. `variables` is keyed by the template's positional index (`"1"`, `"2"`), each bound to a person column (`display_name`, `phone`, `email`) with an optional `fallback`. No value and no fallback parks the person; the engine never sends generic filler. A template still under review does not block a launch: armed people wait and send the moment it is approved. A rejected or disabled template refuses the launch.
  </Accordion>

  <Accordion title="Voice step" icon="phone">
    The **call brief**: `extra_instructions` rendered per person and handed to the bound phone agent as call-only guidance. The agent carries the persona (voice, language, tone, tools); the step never overrides it. Both fields are optional for an agent whose persona already holds the whole mission. One step is one dial attempt per person, ever: to retry no-answers, author further voice steps with delays. An answered call is the reply and exits the journey.
  </Accordion>

  <Accordion title="Email step" icon="envelope">
    Inline `subject`, `html` and `text` (all three required). Every `{{name}}` placeholder must have a binding in `variables`; an unbound placeholder is refused at authoring time rather than shipped. `{{signature}}` is a system token filled from the sending mailbox and needs no binding.
  </Accordion>
</AccordionGroup>

Drafts take a full step list through [set steps](/api-reference/sequences/set-steps). Once launched, use [edit steps](/api-reference/sequences/edit-steps): content and delay updates are allowed on any step (people already past it keep what they received), appends are always allowed, and removing a step somebody already reached is refused. Changing a step's channel is a remove plus an append.

## Senders

Each channel needs at least one bound sender before launch: WhatsApp numbers, AI phone agents for calls, mailboxes for email. [Set senders](/api-reference/sequences/set-senders) replaces the binding for one channel. Unbinding the last sender of a channel while an active sequence still has sends pending on it is refused; pause first.

The [email](/api-reference/sequences/list-email-senders) and [voice](/api-reference/sequences/list-voice-senders) catalogs report eligibility and the reason when a sender cannot be bound. Email health numbers are information beside eligibility, never a gate.

## Settings

| Setting                  | Default     | Effect                                                                                     |
| ------------------------ | ----------- | ------------------------------------------------------------------------------------------ |
| `block_active_elsewhere` | on          | Skip people currently active in another sequence.                                          |
| `block_contacted_before` | on          | Skip people any sequence already messaged.                                                 |
| `respect_office_hours`   | on          | Send only inside the send window. Off means send any time.                                 |
| `office_hours_id`        | org default | A named office-hours schedule to use instead of the org default. Null on update clears it. |

Settings are read live by the engine, so a change applies to the next send decision in any status.

## Qualification

A sequence can also carry qualification criteria and run an [enrich](/api-reference/sequences/enrich) and [judge](/api-reference/sequences/judge) pass over its people. Those endpoints exist for the assistant-driven authoring flow and are not needed to run a sequence from the API: people you enroll and never judge always arm. The endpoint pages under Qualification describe them.

## Consent

The engine enforces the [outbound blocklist](/api-reference/outbound-blocklist/index) at enrollment, at launch, and again before every single send. If the blocklist cannot be read, the send is held rather than released.

## Available endpoints

<CardGroup cols={2}>
  <Card title="Create sequence" icon="plus" href="/api-reference/sequences/create">Create a draft with brief, criteria and settings</Card>
  <Card title="List sequences" icon="list" href="/api-reference/sequences/list">Every sequence with its live numbers</Card>
  <Card title="Get sequence" icon="magnifying-glass" href="/api-reference/sequences/get">One sequence's definition</Card>
  <Card title="Update sequence" icon="pen" href="/api-reference/sequences/update">Name, brief, criteria, reply instructions, settings</Card>
  <Card title="Set steps" icon="list-ol" href="/api-reference/sequences/set-steps">Replace the step list of a draft</Card>
  <Card title="Edit steps" icon="pen-to-square" href="/api-reference/sequences/edit-steps">Update, append or remove steps in any status</Card>
  <Card title="Update step delay" icon="clock" href="/api-reference/sequences/update-step-delay">Change one step's wait</Card>
  <Card title="Add contacts" icon="user-plus" href="/api-reference/sequences/add-contacts">Enroll people with per-person outcomes</Card>
  <Card title="List contacts" icon="users" href="/api-reference/sequences/list-contacts">Paginated people with status, fit and journey</Card>
  <Card title="Get contact" icon="user" href="/api-reference/sequences/get-contact">Full judgment and enrichment for one person</Card>
  <Card title="Get contact events" icon="timeline" href="/api-reference/sequences/get-contact-events">The per-person ledger</Card>
  <Card title="Update contact" icon="user-pen" href="/api-reference/sequences/update-contact">Correct a name or handle</Card>
  <Card title="Include anyway" icon="circle-check" href="/api-reference/sequences/include-contact">Override a below-cutoff judgment</Card>
  <Card title="Remove contact" icon="user-minus" href="/api-reference/sequences/exit-contact">Stop every remaining step for one person</Card>
  <Card title="Launch preview" icon="eye" href="/api-reference/sequences/launch-preview">What launching would do</Card>
  <Card title="Launch" icon="rocket" href="/api-reference/sequences/launch">Arm eligible candidates</Card>
  <Card title="Pause" icon="pause" href="/api-reference/sequences/pause">Stop sending, keep places</Card>
  <Card title="Resume" icon="play" href="/api-reference/sequences/resume">Make people sendable again</Card>
  <Card title="Get stats" icon="chart-simple" href="/api-reference/sequences/stats">Numbers plus who is stuck and why</Card>
  <Card title="List replies" icon="reply" href="/api-reference/sequences/replies">Who answered, newest first</Card>
  <Card title="List senders" icon="address-card" href="/api-reference/sequences/list-senders">Senders bound per channel</Card>
  <Card title="Set senders" icon="arrows-rotate" href="/api-reference/sequences/set-senders">Replace one channel's senders</Card>
  <Card title="List email senders" icon="envelope" href="/api-reference/sequences/list-email-senders">Mailboxes with eligibility and health</Card>
  <Card title="List voice senders" icon="phone" href="/api-reference/sequences/list-voice-senders">Phone agents with eligibility and persona</Card>
  <Card title="Enrich contacts" icon="database" href="/api-reference/sequences/enrich">Snapshot what the org knows about people</Card>
  <Card title="Judge contacts" icon="gavel" href="/api-reference/sequences/judge">Score people against the criteria</Card>
  <Card title="List qualification runs" icon="list-check" href="/api-reference/sequences/list-qualification-runs">Enrich and judge runs, latest first</Card>
  <Card title="Get qualification run" icon="chart-pie" href="/api-reference/sequences/get-qualification-run">One run's counts and aggregates</Card>
  <Card title="Set columns" icon="table-columns" href="/api-reference/sequences/set-columns">Project evidence into the People table</Card>
  <Card title="Enrichment catalog" icon="book" href="/api-reference/sequences/enrichment-catalog">Paths a column may project</Card>
  <Card title="Discover contacts" icon="wand-magic-sparkles" href="/api-reference/sequences/discover">Find an audience from what customers said</Card>
</CardGroup>
