sequences:read or sequences:write. Three exceptions are called out below: audience discovery returns contact data and needs contacts:read, reply previews need chat-sessions:read, and adding someone who is not a contact yet creates one, which also needs contacts:write.
Lifecycle
A sequence has three stored statuses:- Launch moves
drafttoactiveand arms every eligible candidate. Launching an already active sequence is idempotent: it only arms candidates added since. Launching a paused sequence is refused, resume it instead. - Pause and resume only touch the sequence itself. People keep their cursors; on resume, overdue steps go first.
- There is no stored “done”.
finishedon 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.1
Create the shell
Create with a name and a plain-text brief. Steps can wait.
2
Enroll people
Add contacts by contact id, phone, email or WhatsApp user id, with any facts you know about them in
extra_data. To hand your own identifiers to the sessions the sequence opens — the call, or the session their reply lands in — put them in session_custom_data; they come back on the session as custom data, so a workflow on Call Finished or Ticket Created can update the record they point at. Read the per-person outcomes; a successful call never implies everyone was enrolled. Use batch_key to label the wave. List contacts to review who landed.3
Author steps and bind senders
Set steps on the draft. Set senders per channel; the email and voice catalogs tell you which senders are eligible and why not.
4
Preview, then launch
Launch preview mirrors launch clause for clause: who would start, who is held and why, and what would refuse the launch. Then launch.
5
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 a person to stop every remaining step.
People
Every person in a sequence has one status:
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 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.
Skip a step for one contact
Skip Sequence Step omits the next pending step, the next pending step on a channel, or a specific step for one contact. It preserves intervening outreach, personalization, and all waits. It does not resume a reply-exited contact or release an existing park. After a reply such as “I cannot talk now,” skip the next Phone step and then continue the same enrollment. Continuation counts the next pending step’s wait and preceding skipped waits from resume time. A send already being dispatched cannot be skipped. See the workflow setup.Enrollment outcomes
Add contacts answers per person, index-aligned with what you sent:
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, anddelay_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.
WhatsApp step
WhatsApp step
References an approved message template by Supply
template_name and template_language. variables is keyed by the template’s body parameter: its positional index ("1", "2") or exact name ("first_name", "course_name"), each bound to a source (see Variables) 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.For an existing named template, bind every distinct body parameter by name, without the braces:course_name and brand_name through each person’s attached data. Template sample values are only examples; they are not used as recipient data or automatic fallbacks. Missing or extra parameter bindings prevent sending. Repeated placeholders need only one binding.Named body parameters work with static text headers, static footers, and quick-reply buttons. Variable headers and media headers remain unsupported in sequences. Existing URL-suffix and coupon button bindings still use the button index.Per-person personalization uses the same exact keys in values, for example {"first_name": "Tom", "course_name": "Biblical Hebrew", "brand_name": "Our team"}. Include every body parameter with no extras; previews and sent-message history preserve these names. Positional templates continue to use numeric keys.The companion can use existing named templates. When creating a new WhatsApp template through the companion, use numbered placeholders such as {{1}} and {{2}}; its creation tool rejects named placeholders.Voice step
Voice step
The call brief is
extra_instructions rendered for each person and passed to the phone agent for that call. The agent’s instructions and outbound settings still apply. The brief and variables are optional.Select the phone agent as the sequence’s voice sender. A single-number or SIP-trunk selection makes one call per step. A BYOC pool uses the agent’s attempt limit and retry delay, with a different number for each attempt. The pool needs verified BYOC numbers and a SIP trunk from the same workspace. If the pool’s trunk is unavailable or invalid, the call cannot start.A human answer stops the remaining sequence steps for that person. Voicemail, machine answers, and other unsuccessful outcomes can lead to another attempt. Sequence Step Failed runs only after the step finishes unsuccessfully, not after each retry. Configure the pool and retry settings on the agent, and read its current selection with list voice senders.Voice step called by your team
Voice step called by your team
Set
handled_by: "human" to have a team of agents call instead of the AI. The step needs team_id (the team whose agents take the calls) and trunk_id, one of your own SIP trunks from list voice trunks. caller_id is optional and must be one of your verified phone numbers, in E.164 format (+12025550100); without it the trunk sends its own number. No phone agent is bound for a team step, and the brief is not used. To change a team step, send handled_by with the update: "ai" gives it to your AI phone agent, and an update that leaves handled_by out is refused.Every few seconds, people due on the step ring for the team’s free agents: available, taking calls, and not on a call. numbers_per_free_agent (1 to 5, default 1) sets how many people ring at once for each free agent. A pickup goes to one free agent and answers itself in their dialer. Above 1, someone who picks up while every agent is busy is missed.When an agent takes the call, the person’s remaining steps stop, as with an AI call a person answered. No answer, busy, a failed call, and a missed pickup move the person on: the next step is the retry. The agent who took the call can mark it as Voicemail, and the person continues with the sequence, or as Wrong number, which adds the number to the outbound blocklist and ends their journey. List call attempts shows every team call and how it ended.A team step whose team or trunk is deleted blocks launch. If, while the sequence runs, the trunk or the team is deleted, its caller ID stops being a verified number, telephony is turned off, or most of its recent team calls fail, the sequence pauses itself with paused_reason trunk_missing, team_missing, caller_id_missing, telephony_off or calls_failing; people keep their place, and resuming clears the reason. Resuming is refused while telephony is off for the workspace, or while a team step’s team or trunk is gone.Email step
Email step
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.Variables
Every variable binding is{ "source", "fallback"?, "type"? }. The source names where the value comes from for each person:
display_name,phone,emailread the person’s own columns, as always.- Any other string is the exact name of a key in the data attached to the person at enrollment (
extra_dataon add contacts): a spreadsheet column, a field a workflow or an integration sent. The key is matched verbatim, top level only. The three column names above always win over a same-named key.
fallback; no fallback parks the person on needs_data, naming the source. Attached data is rendered by its type, the same vocabulary columns use: without a type, text and numbers pass through and anything else counts as missing; with one, text is parsed where it is unambiguous ("250" as a number, "true" as a boolean, an ISO-shaped date such as 2026-03-12; other date spellings and epoch numbers count as missing), a date reads as a plain day in the person’s timezone (12 Mar 2026), a list must actually be a list in the data and joins with commas, a boolean reads Yes/No, and a value that cannot be read as the declared type counts as missing.
variable_coverage: for every bound source, how many enrolled people have a value, over a bounded sample. Zero on a populated sequence is almost always a misspelled key; a partial count means the variable needs a fallback or should go. Data keys lists every key the sequence’s people carry, most-covered first and up to 200, for a sequence whose people you did not enroll yourself. A value that cannot go on the wire for its channel — a line break, or, on WhatsApp, values that together push the filled-in message past its length limit — parks that person as unsendable_person rather than being altered; the largest value is named.
Senders
Each channel needs at least one bound sender before launch: WhatsApp numbers, AI phone agents for calls, mailboxes for email. 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 and voice catalogs report eligibility and the reason when a sender cannot be bound. Email health numbers are information beside eligibility, never a gate.Settings
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 and 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 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
Create sequence
Create a draft with brief, criteria and settings
List sequences
Every sequence with its live numbers
Get sequence
One sequence’s definition
Update sequence
Name, brief, criteria, reply instructions, settings
Set steps
Replace the step list of a draft
Edit steps
Update, append or remove steps in any status
Update step delay
Change one step’s wait
Add contacts
Enroll people with per-person outcomes
List contacts
Paginated people with status, fit and journey
Get contact
Full judgment and enrichment for one person
Get contact events
The per-person ledger
Update contact
Correct a name or handle
Include anyway
Override a below-cutoff judgment
Remove contact
Stop every remaining step for one person
Launch preview
What launching would do
Launch
Arm eligible candidates
Pause
Stop sending, keep places
Resume
Make people sendable again
Get stats
Numbers plus who is stuck and why
List replies
Who answered, newest first
List senders
Senders bound per channel
Set senders
Replace one channel’s senders
List email senders
Mailboxes with eligibility and health
List voice senders
Phone agents with eligibility and persona
List voice trunks
Your own SIP trunks for team calls
List call attempts
Every team call and how it ended
Enrich contacts
Snapshot what the org knows about people
Judge contacts
Score people against the criteria
List qualification runs
Enrich and judge runs, latest first
Get qualification run
One run’s counts and aggregates
Set columns
Project evidence into the People table
Enrichment catalog
Paths a column may project
Data keys
Attached-data keys a variable can bind to
Discover contacts
Find an audience from what customers said