Open a conversation session, send user messages, stream agent responses, and close the session when done — with full code examples.
The Feather conversation API is built around sessions — stateful containers that track the full message history, context variables, and session lifecycle for a single end-user interaction. Within a session you can send synchronous turns, stream tokens over SSE for real-time UIs, poll for operator replies during a human handoff, and retrieve a paginated transcript at any time. This guide walks through each operation with working curl examples.
live sessions are persisted and trigger post-session processing; test sessions are sandboxed; simulation sessions back automated scenario runs
status
active, closed
Lifecycle state of the conversation
Pass a context_variables object in the request body to seed the conversation with pre-known data — for example, {"customer_name": "Alex", "account_tier": "premium"}. These values are injected into the agent’s system prompt and tool calls automatically.
For standard request/response flows, post a turn and wait for the full agent reply.
curl -X POST https://api-sandbox.featherhq.com/v1/v2/conversations/conv_01hxabc987654321fedcba00/turns \ -H "x-api-key: $FEATHER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_message": "I need help with my order #12345" }'
{ "turn_id": "turn_01hxbcd111222333444aaaa", "conversation_id": "conv_01hxabc987654321fedcba00", "text": "I'd be happy to help with order #12345! Let me look that up for you right now.", "session_status": "active", "message_seqs": [2, 3]}
Field
Description
turn_id
Unique identifier for this turn
conversation_id
The conversation this turn belongs to
text
The agent’s final reply text
session_status
Current conversation status (active, waiting_for_human, closed)
message_seqs
Sequence numbers of the messages written in this turn — use as a since_seq watermark
If session_status changes to waiting_for_human, the agent has triggered a human handoff. No further AI replies will be generated until an operator responds or the session is returned to the bot.
For chat UIs where you want to render tokens as they arrive, use the streaming endpoint. It returns a Server-Sent Events stream with the same request body as the synchronous turn endpoint.
curl -X POST https://api-sandbox.featherhq.com/v1/v2/conversations/conv_01hxabc987654321fedcba00/turns/stream \ -H "x-api-key: $FEATHER_API_KEY" \ -H "Content-Type: application/json" \ --no-buffer \ -d '{ "user_message": "What is the status of my order?" }'
Example SSE stream:
event: deltadata: {"text":"Your order "}event: deltadata: {"text":"#12345 was "}event: deltadata: {"text":"shipped on May 13th "}event: deltadata: {"text":"and is expected to arrive by May 16th."}event: completedata: {"turn_id":"turn_01hxbcd111222333444bbbb","conversation_id":"conv_01hxabc987654321fedcba00","text":"Your order #12345 was shipped on May 13th and is expected to arrive by May 16th.","active_assistant_id":"agt_01hx3k2mz9vbqwerty123456","path":"single","decision_source":"router","message_seqs":[4,5],"session_status":"active","turn_ttft_ms":312,"perceived_ttft_ms":340,"ended":false}
Event types:Each frame carries a named SSE event: line followed by a data: line — branch on the event name, not on any field inside the payload.
SSE event:
Data payload
delta
{"text": "..."} — an incremental text token to append to the UI
complete
The final turn object with its fields inline (turn_id, conversation_id, text, session_status, message_seqs, …) — treat this as the source of truth
error
{"error": "..."} — a fatal error occurred; the stream closes immediately after this event
Always handle the error event explicitly. If the stream closes without a complete event, assume the turn failed and surface an appropriate message to the user.
When your agent is connected to an asynchronous channel (email, SMS, or a human handoff queue) you need to poll for new messages rather than waiting on an open HTTP connection.
{ "session_status": "waiting_for_human", "next_seq": 7, "messages": [ { "id": "msg_01hxbcd555666777888aaaa", "role": "assistant", "content": "I'm connecting you with a support specialist now. Please hold on.", "authored_by": "agent", "created_at": "2024-05-14T12:05:00Z" }, { "id": "msg_01hxbcd555666777888bbbb", "role": "assistant", "content": "Hi, this is Jamie from support. How can I help you today?", "authored_by": "human_operator", "created_at": "2024-05-14T12:06:45Z" } ]}
Both AI and operator messages use role: "assistant"; distinguish them by authored_by — "agent" for AI-authored replies and "human_operator" for operator relays. The role enum is user, assistant, system, tool, or system_event.Pass since_seq=-1 on the first request to retrieve all messages from the beginning. On subsequent polls, pass the next_seq value from the previous response to receive only new messages.
For human handoff workflows, poll every 2–5 seconds while session_status is waiting_for_human. Once it returns to active, the operator has handed control back to the agent and you can resume normal turn-based messaging.
Signal that the conversation is resolved by closing the session. This triggers any configured post-session webhooks, saves the conversation to long-term memory, and makes the session eligible for analytics processing.
Each element of turns is a single message row — one per message, carrying a role and content rather than a paired user/assistant turn.
{ "turns": [ { "id": "msg_01hxbcd111222333444aaaa", "session_id": "conv_01hxabc987654321fedcba00", "role": "user", "content": "I need help with my order #12345", "created_at": "2024-05-14T12:01:00Z" }, { "id": "msg_01hxbcd111222333444bbbb", "session_id": "conv_01hxabc987654321fedcba00", "role": "assistant", "content": "I'd be happy to help with order #12345! Let me look that up for you right now.", "created_at": "2024-05-14T12:01:02Z" } ], "next_cursor": "dHVybl8wMWh4YmNkMTExMjIyMzMzNDQ0YmJiYg==", "has_more": false}
Paginate by passing cursor=<next_cursor> and limit=<n> as query parameters. When has_more is false, you’ve reached the end of the transcript.