States
| Status | Meaning |
|---|---|
bot_active | The bot is currently handling the conversation. Visitor turns hit the model. |
agent_requested | Bot has handed off; conversation is sitting in the inbox waiting for a human reply. |
open | A human (or API caller) is actively working the thread. Bot is paused. |
resolved | Customer’s issue has been handled. End-of-conversation summary kicks off. |
closed | No further activity expected. Use when the thread is done but not necessarily solved. |
archived | Hidden from the default inbox. Reversible via PATCH back to closed. |
Transition diagram
Transitions
bot_active → agent_requested
bot_active → agent_requested
Trigger. The bot’s tool calls or the visitor explicitly asking (“speak to someone”). This transition is system-driven; it is also reachable from the API via
PATCH status=agent_requested if you want to escalate based on your own logic.Side effects.- Inserts a system marker message visible to the visitor.
- Emits a
status_changerow onconversation_events. - Fires
conversation.updated.
agent_requested → open
agent_requested → open
Trigger. The first
POST /conversations/{id}/messages (API) or the dashboard’s Take over button. Cannot be reached by PATCH directly — sending a reply is what claims the conversation.Side effects.- Inserts a “A team member has joined the conversation.” system marker.
- Emits a
human_takeoverevent row. - Fires
message.created(marker),conversation.updated(with{from: "agent_requested", to: "open"}), thenmessage.created(the agent reply).
open → resolved
open → resolved
Trigger.
PATCH /conversations/{id} with {"status": "resolved"} or the dashboard’s Resolve button.Side effects.- Stamps
resolved_at. - Inserts a “This conversation has been resolved.” system marker.
- Emits a
status_changeevent row. - Fires
message.created(marker),conversation.updated. - Schedules the end-of-conversation summary job that stamps
summaryandsentiment_score.
open → closed
open → closed
Trigger.
PATCH /conversations/{id} with {"status": "closed"}. Use when the thread is done but not necessarily solved (visitor ghosted, etc).Side effects.- Stamps
closed_at. - Inserts a “This conversation has been closed.” system marker.
- Emits a
status_changeevent row. - Fires
message.created(marker),conversation.updated. - Schedules the end-of-conversation summary job.
resolved → closed (automatic)
resolved → closed (automatic)
Trigger. Inactivity timer (default 7 days after
resolved_at). System-driven; no equivalent API call.Side effects.- Stamps
closed_at. - No system marker — the
resolvedstate already announced the end of the thread. - Fires
conversation.updated.
any → archived
any → archived
Trigger.
PATCH /conversations/{id} with {"status": "archived"}. Hides the conversation from the default inbox.Side effects.- Stamps
archived_at. - No system marker (hidden from visitor view anyway).
- Fires
conversation.updated.
Reopening (resolved | closed | archived → open)
Reopening (resolved | closed | archived → open)
Trigger.
PATCH /conversations/{id} with {"status": "open"}. The dashboard requires contact_email (to email a resume link), but the API does not — you can reopen any conversation programmatically.Side effects.- Clears
resolved_at/closed_at. - Emits a
status_changeevent row. - Fires
conversation.updated.
Webhook events per transition
| Transition | Events |
|---|---|
any → agent_requested | conversation.updated |
agent_requested → open (via agent reply) | message.created (takeover marker), conversation.updated, message.created (the reply) |
open → resolved | message.created (marker), conversation.updated |
open → closed | message.created (marker), conversation.updated |
resolved → closed (auto-closure) | conversation.updated |
any → archived | conversation.updated |
archived → closed (unarchive) | conversation.updated |
Every
PATCH /conversations/{id} that changes any field — not just status — fires conversation.updated. The webhook payload includes a changes object with {from, to} for every modified field.System marker messages
Several transitions insert a system-role message into the thread so the visitor sees what happened. These show up inGET /conversations/{id}/messages with role: "system" and metadata.event set to one of:
| Event marker | Inserted text |
|---|---|
human_takeover | ”A team member has joined the conversation.” |
status_change → resolved | ”This conversation has been resolved.” |
status_change → closed | ”This conversation has been closed.” |
status_change → archived | (hidden from the visitor) |

