Skip to content
Get an API key

Build

Updated

Streaming

Set stream to true to receive server-sent events as the output is produced. The event vocabulary is the Open Responses one, with exactly one terminal event per stream.

Set stream: true on POST /v1/responses and the reply is a server-sent events body instead of a single JSON object. Each event’s event: name is the same as the type field inside its data: payload, which is what the official SDKs’ stream parsers expect.

The Node and Python examples use a client set up for this API.

Terminal window
curl -N https://api.helping.io/v1/responses \
-H "Authorization: Bearer $HELPINGIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"stream": true,
"input": "Count from 1 to 5."
}'

Items nest, and each level closes only once the level inside it has. A message holds a content part, which holds the text, so the text finishes first, then its part, then the item. A response of one text message is this, captured:

The event order
[
"response.created",
"response.in_progress",
"response.output_item.added",
"response.content_part.added",
"response.output_text.delta",
"response.output_text.done",
"response.content_part.done",
"response.output_item.done",
"response.completed"
]

Each of those names is an event: line with its data: payload underneath. Items that hold no content part are shorter by two events: reasoning closes with response.reasoning_text.done and then response.output_item.done, and a function call closes with response.function_call_arguments.done and then response.output_item.done. A response carrying several items opens and closes them one at a time, between the same response.created and terminal event.

A terminal event’s payload in full, captured from the same response, is one response.completed event carrying the same response object a non-streaming call returns:

The terminal event
{
"response": {
"background": false,
"completed_at": 1767225600,
"conversation": null,
"created_at": 1767225600,
"error": null,
"frequency_penalty": 0.0,
"id": "resp_ZXhhbXBsZS9zdC9zdHJlYW0tdGVybWluYWwAAAAAAAAAAAAAAAAAAA",
"incomplete_details": null,
"instructions": null,
"max_output_tokens": null,
"max_tool_calls": null,
"metadata": null,
"model": "anthropic/claude-sonnet-5",
"object": "response",
"output": [
{
"content": [
{
"annotations": [],
"logprobs": [],
"text": "Streaming hello.",
"type": "output_text"
}
],
"id": "msg_00000000000000000000000000000001",
"role": "assistant",
"status": "completed",
"type": "message"
}
],
"parallel_tool_calls": true,
"presence_penalty": 0.0,
"previous_response_id": null,
"prompt_cache_key": null,
"reasoning": null,
"safety_identifier": null,
"service_tier": "default",
"status": "completed",
"store": true,
"temperature": 1.0,
"text": {
"format": {
"type": "text"
}
},
"tool_choice": "auto",
"tools": [],
"top_logprobs": 0,
"top_p": 1.0,
"truncation": "disabled",
"usage": {
"input_tokens": 100,
"input_tokens_details": {
"cache_write_tokens": 0,
"cached_tokens": 0
},
"output_tokens": 20,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 120
}
},
"sequence_number": 8,
"type": "response.completed"
}

Every event carries a strictly increasing sequence_number, and output_index is stable for the lifetime of an item.

EventTerminalWhen
response.createdThe response exists. The payload carries the response object with an empty output
response.in_progressThe response is producing output
response.output_item.addedAn output item (message, function call, reasoning) opened
response.content_part.addedA content part inside a message opened
response.output_text.deltaA chunk of assistant text
response.output_text.doneThe complete text of that part
response.refusal.deltaThe model declined and gave an explanation, which streams as the text of a refusal content part after any text already produced. Not sent when there is no explanation
response.refusal.doneThe refusal part is complete and carries its text, which is empty when there is no explanation. The response then completes with response.completed
response.function_call_arguments.deltaA chunk of a function call's JSON arguments
response.function_call_arguments.doneThe complete arguments for that call
response.reasoning_text.deltaA chunk of reasoning content, when the model produces reasoning
response.reasoning_text.doneThe complete reasoning content
response.content_part.doneA content part inside a message closed
response.output_item.doneAn output item closed
response.completedYesThe payload is the full response object, including usage
response.failedYesThe response object carries error.code and error.message
response.incompleteYesThe response stopped early, for example at max_output_tokens. Read incomplete_details.reason
errorYesThe final state was not known when the stream ended, with code response_unresolved. The response may still complete: retrieve it by id, or retry

Reasoning events arrive before the text they precede, when the model produces reasoning at all.

OpenAI’s Responses schema declares events beyond the ones above, and the published API reference carries the whole vocabulary, so a client generated from it can hold names this API never sends. Nothing below ever arrives, and a handler for one of them never runs. What you get instead is in the second column.

EventWhat you get instead
response.queuedA stream opens with response.created and response.in_progress, so read response.created as the start
response.output_text.annotation.addedOutput text is not annotated here, so annotations on a text part is always empty
response.reasoning_summary_part.addedReasoning is not summarized. The reasoning item opens with response.output_item.added
response.reasoning_summary_part.doneReasoning is not summarized. The reasoning item closes with response.output_item.done
response.reasoning_summary_text.deltaReasoning is not summarized. The reasoning itself streams on response.reasoning_text.delta
response.reasoning_summary_text.doneReasoning is not summarized. The complete reasoning arrives on response.reasoning_text.done
  • Exactly one terminal event. Every stream ends with one of response.completed, response.failed, response.incomplete, or error. Never a bare drop. A response that was canceled ends with response.failed whose response has status: "cancelled" and no error, so read the status rather than assuming a failure.
  • A keep-alive every 15 seconds. While the stream is quiet you get an SSE comment frame, so proxies between you and the API do not time the connection out. Standard SSE parsers ignore comments.
  • A 300 second ceiling. A stream is closed after 300 seconds with its terminal event. The response may not be finished at that point: retrieve it with GET /v1/responses/{response_id}.
  • Failures are in-band. Once the stream is open the HTTP status has already been sent. A failure after that arrives as response.failed whose error carries the same codes and messages as a non-streaming error. A failure of the stream itself, rather than of the response, arrives as an error event instead, which carries code, message and param on the event itself rather than nested: read event.message. Those codes are below.
  • Tool calls end the stream. When the model calls one of your function tools, the stream emits the function_call item and then its terminal event, carrying that item on the response. You post the tool output as the next request. See Function calling.

An error event is terminal and carries a code of its own, read the same way as the code on an HTTP error:

codeMeaning
response_unresolvedThe stream ended before a result was ready. The response may still finish: retrieve it by the id response.created carried. Retrying without the same Idempotency-Key starts a second response.

Everything else that can go wrong reaches you as a response object, on response.failed or response.incomplete. See Errors.