Conversations, messages and events

List and create conversations, send messages and actions, read the event log as JSON or as a server-sent event stream.

All endpoints take Authorization: Bearer kst_…. A conversation is only visible to the end user who owns it; others get 404.

Conversation object

{ "id": "conv_…", "agentId": "agt_…", "title": "Flight to Osaka", "status": "open",
  "createdAt": "…", "updatedAt": "…", "lastSeq": 12 }

status is open, handoff or closed.

List and create

GET  /v1/conversations                       → 200 { "conversations": [ … ] }   // latest 50 for this user and agent
POST /v1/conversations  { "agentId"?, "title"? } → 201 { "conversation": { … } }  // emits conversation.created and the greeting
GET  /v1/conversations/:id                   → 200 { "conversation": { … } }    // lastSeq is live
POST /v1/conversations/:id/close             → 200 { "ok": true }

Messages

GET  /v1/conversations/:id/messages?limit=50 → 200 { "messages": [ { "role": "user", "text": "…", "value": null } ] }
POST /v1/conversations/:id/messages
     { "text": "Where is my order ORD-2201?", "clientId": "c_01J8CLIENT0002" }
     → 202 { "accepted": true }
  • clientId is required and deduplicates retries; a repeat is a no-op.
  • value (any JSON) may replace or accompany text for structured turns such as { "intent": "select_flight", "flightId": "NH873" }.
  • The reply arrives as events (agent.typing, message.created, message.delta, tool.*, ui.render, message.completed), never in the POST response.

Actions

POST /v1/conversations/:id/actions
     { "surfaceId": "sfc_…", "componentId": "b2", "actionId": "book",
       "value": { "intent": "book", "hotelId": "h_1" }, "clientId": "c_01J8CLIENT0003" }
     → 202 { "accepted": true }

For form submissions value is { "formId": "f_callback", "values": { … } }; for tool confirmations { "toolCallId": "call_…", "approve": true }.

Events as JSON

GET /v1/conversations/:id/events?after=41&limit=200
→ 200 { "events": [ { "id": "evt_…", "seq": 42, "type": "message.delta", "ts": "…", "conversationId": "conv_…", "turnId": "trn_…", "data": { … } } ],
        "lastSeq": 57 }

limit is capped at 1000. Poll with the last seq you saw.

Events as a stream (SSE)

GET /v1/conversations/:id/events?after=41&stream=1
Accept: text/event-stream
event: ready
data: {"seq":41,"conversationId":"conv_…"}

id: 42
event: event
data: {"id":"evt_…","seq":42,"type":"message.delta",…}

: ping

The stream replays events after after, then stays live with a keepalive comment every 20 seconds. Errors arrive as event: error with a frame body. The Flutter SDK uses this when WebSockets are blocked.

Last updated 2026-09-28 · Report an issue with this page