Skip to content

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.

Terminal window
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.

Each event has a named event: line and a JSON data: payload. The event names match OpenAI’s Responses streaming spec:

EventMeaning
response.createdInitial response object is allocated.
response.in_progressGeneration has started.
response.output_item.addedA new output item (message, function call, etc.) has begun.
response.output_item.doneAn output item has completed.
response.content_part.addedA new content part within a message has begun.
response.content_part.doneA content part has completed.
response.output_text.deltaIncremental text chunk for an output_text content part.
response.output_text.doneThe full text for a content part is now stable.
response.function_call_arguments.deltaIncremental JSON for a function-call’s arguments.
response.function_call_arguments.doneFunction-call arguments are complete.
response.completedThe whole response is finished.
response.failedThe response terminated in an error state.
errorMid-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:

EventMeaning
response.reasoning_summary_part.added / .doneA reasoning summary part within a reasoning item.
response.reasoning_summary_text.delta / .doneIncremental reasoning-summary text.
response.mcp_call_arguments.delta / .doneIncremental JSON for an mcp_call item’s arguments.
response.kindo_call_arguments.delta / .doneIncremental 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.

event: response.created
data: {"id":"resp_abc123","status":"in_progress","object":"response"}
event: response.output_text.delta
data: {"item_id":"msg_xyz","delta":"Logs flow through"}
event: response.output_text.delta
data: {"item_id":"msg_xyz","delta":" the gate;\nMetrics rise like steam"}
event: response.output_text.done
data: {"item_id":"msg_xyz","text":"Logs flow through the gate;\nMetrics rise like steam,\ntraces find their way."}
event: response.completed
data: {"id":"resp_abc123","status":"completed","object":"response"}

Standard OpenAI SDK stream=True works as expected:

import os
from 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);
}
}

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.

Once the first SSE byte has been written, the outer HTTP status is already committed at 200. Errors that arise after that point arrive as an error event followed by the appropriate terminal event:

event: error
data: {"error":{"message":"upstream stream interrupted","type":"server_error","code":"stream_error"}}
event: response.failed
data: {"id":"resp_abc123","status":"failed","object":"response"}

Treat any error SSE event as terminal — the response will not resume.

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: 4
event: response.output_text.delta
data: {"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:

Terminal window
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:

Terminal window
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.