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. 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 error event followed by data: [DONE].
  • Kindo-managed model conversations: a live error emits an error event, then the stream ends without a response.failed event.
  • Agent invocations (model: "agent/<agent-id>"): a live step can emit an error event, followed by response.failed containing 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.

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.