Endpoint ·
POST /conversation/actionsPOST /conversation/actions endpoint runs a group of up to 10 actions
on a single conversation, in the order you send them. Instead of making N
requests to label, leave a note, and run the AI, you chain them into a single
request. It’s the recommended way to mutate a conversation from an external
client.
Each element of the actions array carries a type field. send_message
unifies the four send modes (text, media, quick reply, template).
If you pass a template alongside a primary (text/media or quick reply), the
server decides by the 24h window: it sends the primary if
open, or the template if closed.
A group allows up to 10 actions, of which at most 3 can trigger the AI
or be a
send_json, whose response can continue the conversation. Exceeding it
fails with BATCH_LIMIT_EXCEEDED.Identify the conversation
The group runs on one conversation: you identify it with theconversation
object, which carries exactly one of uuid, whatsapp_user_id, username,
or phone, plus channel (required).
The contact’s BSUID (Meta’s opaque identity). Resolves by exact match;
recommended identifier going forward.
Public WhatsApp username, with or without
@ and case-insensitive. If two
distinct contacts share it in the same channel, the API returns 409
(USERNAME_AMBIGUOUS); the same username in different channels is not
ambiguous: channel resolves it.Phone in E.164 format. If it is duplicated across two distinct contacts in the
same channel, the API returns
409 (PHONE_NUMBER_AMBIGUOUS) instead of
guessing; the same number in different channels is not ambiguous: channel
resolves it.The channel’s public key — copy it from the dashboard under Settings → Channels
(the Channel key field), or list them with
GET /channels. Required in
every request: it sets the target channel. On the contact-based paths
(phone, username, whatsapp_user_id) it restricts resolution to that
channel; with uuid the engine ignores it, but the contract still requires it.
A key that does not exist → 404 (CHANNEL_NOT_FOUND).How it runs
1
Synchronous validation
The API validates the request. If the preflight rejects it (e.g. the AI has
no tokens), it returns a synchronous
4xx and no action runs.2
202 Accepted
If validation passes, it returns
202 immediately; the group runs in
the background.3
Execution in order
The actions run one by one, in the order sent.
AI_EMPLOYEE_V2_NOT_SUPPORTED (422), before running: an action that would run a v2 while the v2 engine is turned off, and a classic employee that runs the assigned one (run_assigned) when the assigned one is v2. Assigning a v2 without run is never rejected, and a v2 can process the response of a send_json. The two dynamic-target cases —a run_ai without ai_employee preceded by an assign_ai_employee in the group, and an action whose classic employee runs the assigned one (run_assigned) when, after an earlier assign_ai_employee or unassign_ai, the assigned one is a v2 (because that assign assigned it, or because it didn’t end up changing it)— fail per action, after the 202.
Stop on error
stop_on_error controls what happens when an action fails during execution:
Escalation to pending
escalate_on_error controls whether a group failure escalates the conversation to inbox_status='pending' for a human to review:
Integrator input errors (
*_NOT_FOUND, *_AMBIGUOUS, WINDOW_CLOSED, TEMPLATE_NOT_APPROVED, etc.), misused payment markers (PAYMENT_MARKER_UNKNOWN, PAYMENT_MARKER_UNSUPPORTED_FIELD, TEMPLATE_BODY_TOO_LONG — the fix belongs in the text you sent, not in the conversation), billing guards (WALLET_BLOCKED, VISION_WALLET_BLOCKED), the mark_resolved integrity guard (TRANSCRIPTION_REQUIRED_TO_RESOLVE), and the v2 target (AI_EMPLOYEE_V2_NOT_SUPPORTED, also when it fails per action after the 202) never escalate — fix them and retry without human intervention; the v2 target isn’t retried as is: with the v2 engine off, try again later; in the other cases, use a classic employee.Assembling an action list
Each action is an object in theactions array, identified by its type. A
request groups several actions on the same conversation, run in the order
sent. The conversation object always includes channel. Example — label,
leave a note, and run the AI, without halting on failures:
Identify by different fields
Change only the identifier insideconversation (channel is always included);
the rest of the request does not change:
unassign_ai action, no fields. It’s
idempotent: if the conversation already has no AI, it’s a successful no-op.