Skip to main content
Endpoint · POST /conversation/actions
The POST /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.
Each action has its own page with its fields and an example — open the one you need from the table or the nav. To send media, first upload it with the Upload media flow.

Identify the conversation

The group runs on one conversation: you identify it with the conversation object, which carries exactly one of uuid, whatsapp_user_id, username, or phone, plus channel (required).
string
Stable key of a conversation you already know. The most direct path.
string
The contact’s BSUID (Meta’s opaque identity). Resolves by exact match; recommended identifier going forward.
string
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.
string
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.
string
required
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).
Full reference (resolution order, conversation merging, and contact ambiguities) in Identify a conversation.

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.
The 202 confirms the group was accepted, not that every action succeeded. The per-action result does not travel in the response — query it in the conversation status or in the business’s activity log.
Employees on the new engine (v2). The API runs and assigns a v2 employee the same way as a classic one; the catalog lists both (AI employees). Two cases reject the whole group with 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 the actions 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 inside conversation (channel is always included); the rest of the request does not change:
Hand off to a human — a single unassign_ai action, no fields. It’s idempotent: if the conversation already has no AI, it’s a successful no-op.
If the preflight rejects the group, the response is a synchronous 4xx and no action runs. See Errors.