Hand a live AI conversation to a human
Convert an in-flight conversation into a ticket and route it.
/v1/handoffSigned in- Operation id
- request_handoff
- Access
- Any signed in caller. No workspace role is required.
/v1/handoffConvert an in-flight conversation into a ticket and route it.
The channel adapter calls this. The customer-facing widget keeps its chat
open on the same session_id and starts listening on
/v1/widget/sessions/{session_id}/stream for human replies.
session_id is yours to choose and is what ties the ticket to the live
conversation, so use the same value you opened the stream with.
The new ticket is routed by the agent's own handoff configuration: to a
squad, a queue or a named member. assigned_to is null when nothing
matched, and the workspace is notified that the ticket needs an assignee
rather than the ticket being left silently unowned.
An agent whose handoff is turned off is refused with 409 handoff_disabled
and no ticket is created, so the adapter can fall back to its offline
message. An unknown agent, customer or transcript is refused before
anything is written.
Rate limited per workspace, because the caller is an adapter acting for the workspace rather than a visitor. A 429 means come back later and nothing was created.
Tenancy: an automated-actor route. It names a workspace but carries no human caller and no workspace role, so the permission matrix does not apply to it (SPEC FR-18). The same exemption covers the SLA breach scan and channel ingress.
Access: Authorization: Bearer carrying either a realm access token or an API key. A key must hold the handoff:write scope. This operation declares no role floor, so no workspace role is consulted.
A realm-issued access token, presented as Authorization: Bearer <token>. It acts as the person who signed in, and every operation resolves their workspace role live from the membership record.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/handoff" \ -H "Content-Type: application/json" \ -d '{ "session_id": "wgt_9f3a2b71c4d85e60", "agent_id": "5b2c1a90-1f2e-4a77-9c31-9e0b1d2c3f44", "transcript_id": "e3f4a5b6-c7d8-4e9f-a0b1-c2d3e4f5a6b7", "customer_id": "9f1d6c4e-6b6e-4b31-9a5b-2f1c9a4d5e60", "customer_name": "Sam Okafor", "customer_email": "sam.okafor@northwind.example", "subject": "Card declined at checkout", "summary": "Customer\'s card is declined and they were still charged. The agent could not verify the charge.", "reason": "explicit_ask", "channel": "web_widget" }'{ "ticket_id": "5e8b0c31-7a4d-4f92-b6c8-1d3e2a5f7b90", "live_session_id": "wgt_9f3a2b71c4d85e60", "assigned_to": "3fe98b76-2409-5003-b75a-93e56b450433", "reason": "explicit_ask"}Create a ticket from a public contact form
Open a ticket from a contact form on a public page.
Hold a stream open and receive each human reply as it is sent
The customer side of a handoff. The widget subscribes here once the handoff has happened and receives every human reply on the ticket without reloading.