Responses API streaming
Set "stream": true on POST /v1/responses to receive Server-Sent
Events. Kindo emits the standard OpenAI Responses stream — your
existing parser works without changes.
Request
Section titled “Request”curl -N https://api.kindo.ai/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $KINDO_API_KEY" \ -d '{ "model": "claude-sonnet-4-5-20250929", "stream": true, "input": "Write a haiku about observability." }'stream is consumed by the Kindo route handler to flip the response
into Content-Type: text/event-stream mode. It is not forwarded as
a model parameter.
Event types
Section titled “Event types”Each event has a named event: line and a JSON data: payload. The
event names match OpenAI’s Responses streaming spec:
| Event | Meaning |
|---|---|
response.created | Initial response object is allocated. |
response.in_progress | Generation has started. |
response.output_item.added | A new output item (message, function call, etc.) has begun. |
response.output_item.done | An output item has completed. |
response.content_part.added | A new content part within a message has begun. |
response.content_part.done | A content part has completed. |
response.output_text.delta | Incremental text chunk for an output_text content part. |
response.output_text.done | The full text for a content part is now stable. |
response.function_call_arguments.delta | Incremental JSON for a function-call’s arguments. |
response.function_call_arguments.done | Function-call arguments are complete. |
response.completed | The whole response is finished. |
response.failed | The response terminated in an error state. |
error | Mid-stream error. See “Mid-stream errors” below. |
When the response produces reasoning or server-side tool activity (Kindo-hosted tools, MCP tools, agent runs), the same protocol carries those items too:
| Event | Meaning |
|---|---|
response.reasoning_summary_part.added / .done | A reasoning summary part within a reasoning item. |
response.reasoning_summary_text.delta / .done | Incremental reasoning-summary text. |
response.mcp_call_arguments.delta / .done | Incremental JSON for an mcp_call item’s arguments. |
response.kindo_call_arguments.delta / .done | Incremental JSON for a kindo_call item’s arguments. |
The corresponding output items (reasoning, mcp_call, kindo_call,
shell_call, web_search_call) open and close with the same
response.output_item.added / response.output_item.done pairs as
messages and function calls. Agent-run invocations
(model: "agent/<agent-id>") stream this full vocabulary live as the
run executes — see
Invoke Kindo agents.
Example stream
Section titled “Example stream”event: response.createddata: {"id":"resp_abc123","status":"in_progress","object":"response"}
event: response.output_text.deltadata: {"item_id":"msg_xyz","delta":"Logs flow through"}
event: response.output_text.deltadata: {"item_id":"msg_xyz","delta":" the gate;\nMetrics rise like steam"}
event: response.output_text.donedata: {"item_id":"msg_xyz","text":"Logs flow through the gate;\nMetrics rise like steam,\ntraces find their way."}
event: response.completeddata: {"id":"resp_abc123","status":"completed","object":"response"}SDK consumption
Section titled “SDK consumption”Standard OpenAI SDK stream=True works as expected:
import osfrom openai import OpenAI
client = OpenAI(base_url="https://api.kindo.ai/v1", api_key=os.environ["KINDO_API_KEY"])
stream = client.responses.create( model="claude-sonnet-4-5-20250929", input="Write a haiku about observability.", stream=True,)
for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="", flush=True)import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://api.kindo.ai/v1', apiKey: process.env.KINDO_API_KEY});
const stream = await client.responses.create({ model: 'claude-sonnet-4-5-20250929', input: 'Write a haiku about observability.', stream: true});
for await (const event of stream) { if (event.type === 'response.output_text.delta') { process.stdout.write(event.delta); }}Pre-stream errors
Section titled “Pre-stream errors”If the upstream request fails before streaming begins (for example, auth fails, the model isn’t found, the body is invalid), Kindo returns a normal HTTP error response with a JSON envelope — no SSE switch. See Errors for shapes.
Mid-stream errors
Section titled “Mid-stream errors”Once the first SSE byte has been written, the outer HTTP status is
already committed at 200. Error events depend on the request path:
- Stateless model requests: upstream SSE events pass through unchanged.
If Kindo cannot read the upstream stream, it emits an
errorevent followed bydata: [DONE]. - Kindo-managed model conversations: a live error emits an
errorevent, then the stream ends without aresponse.failedevent. - Agent invocations (
model: "agent/<agent-id>"): a live step can emit anerrorevent, followed byresponse.failedcontaining the final response object.
Live step failures, such as a budget refusal, stop generation. For agent
invocations, continue reading for a final lifecycle event and its response
object, but accept data: [DONE] or stream closure without
response.failed. An agent stream_timeout error ends the stream while
the run continues; poll GET /v1/responses/{id} for its status. Managed
model conversations and stateless requests do not require waiting for
response.failed. See Errors
for request routing and error shapes.
Resuming a dropped stream
Section titled “Resuming a dropped stream”If the SSE connection drops mid-stream (a flaky network, a proxy
timeout), you can reconnect to the same response and replay the
events you missed instead of issuing a new POST — which would start a
new response. Every streamed event carries its sequence_number both in
the data: payload and as the SSE id: line:
id: 4event: response.output_text.deltadata: {"item_id":"msg_xyz","delta":" the gate","sequence_number":4}Reconnect with GET on the response id, passing stream=true and the
last sequence_number you received as starting_after:
curl -N "https://api.kindo.ai/v1/responses/resp_abc123?stream=true&starting_after=4" \ -H "Authorization: Bearer $KINDO_API_KEY"Kindo replays every event after sequence_number 4 with continuous
numbering, then continues live until the response completes (or emits
response.completed immediately if it already finished). No new response
is created. Omit starting_after to replay from the beginning. A negative
or non-integer starting_after returns HTTP 400.
Browser EventSource clients resume automatically: the runtime resends
the last id: it saw as the Last-Event-ID request header, which Kindo
honors when starting_after is absent.
The resume window is time-bounded (the same window over which a
response is retained for polling). After it elapses, a streamed resume
falls back to the response’s final items followed by response.completed
— the per-token deltas are no longer available. Retrieve the final object
with a plain GET (no stream) instead.
Agent-run responses replay from the start only
Section titled “Agent-run responses replay from the start only”Responses backed by an agent run (ids of the form resp_run_<runId>)
support full replay only — not incremental resume. Reconnect with
stream=true and starting_after=0 (or omit starting_after) to replay
the run’s events from the beginning and continue live:
curl -N "https://api.kindo.ai/v1/responses/resp_run_abc123?stream=true&starting_after=0" \ -H "Authorization: Bearer $KINDO_API_KEY"A positive starting_after on a resp_run_* id returns HTTP 400. An
agent run streams many steps, and a step that is still live emits per-token
deltas while a step already finished replays only its completed items, so the
event numbering is not stable enough to splice a partial reconnect onto
reliably. Replaying from the start avoids any gap or duplication.
See also
Section titled “See also”- Quickstart — non-streaming round trip.
- Request shape — every field Kindo honors.
- Tool use — streaming tool-call events.
- Errors — pre-stream error envelopes.
