Stream a response

Follow native SSE events and distinguish completion from a dropped connection.

Set BASE_URL to your HTTPS API origin without a trailing slash or /v1, and pass API_KEY from your credential store. OpenAI-compatible SDKs use ${BASE_URL}/v1; Anthropic-compatible SDKs use BASE_URL.

Use streaming when the caller can process Server-Sent Events (SSE) and needs output before generation finishes. You need an active organization key, access to a model returned by the catalog, and available credits. The request can consume credits. Curl's -N option disables output buffering; -i also shows the initial status and headers.

Run a bounded stream

First, generate one random key for this logical request. This shell example uses OpenSSL; in an application, use a cryptographically secure generator and keep the key with the unchanged request body until the call reaches a terminal outcome:

bash
export UNLIMGPT_REQUEST_KEY="$(openssl rand -hex 16)"

Send the request from the same shell. The check prevents generation if no key was created:

bash
test -n "${UNLIMGPT_REQUEST_KEY:-}" \
  && printf 'Authorization: Bearer %s\n' "$API_KEY" \
  | curl -q -H @- -i -N "$BASE_URL/v1/responses" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $UNLIMGPT_REQUEST_KEY" \
    -d '{
  "model": "gpt-5.6-sol",
  "input": "Count to three.",
  "max_output_tokens": 64,
  "stream": true
}'

Replace the model ID if your catalog shows a different available model. On retry, run only the second block with the same key and body: rerunning the OpenSSL command creates a new logical request. A successful connection begins with HTTP 200. Responses then emits named events such as response.created, response.output_text.delta and response.completed. Save the public response ID from the stream. The generated words and number of delta events will vary; do not parse output by line count or assume that one network chunk equals one event.

Handle the terminal event

Read events until the protocol's terminal event, then mark the call complete. Chat Completions uses data chunks with choices and a terminal marker; Messages uses message_start, content_block_delta and message_stop. A transport close before the native terminal event does not prove completion. A terminal error may arrive after the initial HTTP 200, so an HTTP status alone is not the result of a streamed call.

ProtocolSuccessful completionPartial or failed outcome
Responsesresponse.completed, followed by data: [DONE]response.incomplete gives the limit reason; response.failed contains a safe public error code.
Chat CompletionsFinal choices[0].finish_reason chunk, followed by data: [DONE]length means partial text; after HTTP 200 an error can arrive as a separate JSON object in data.
Messagesmessage_delta with stop_reason, followed by message_stopmax_tokens means the length limit; event: error means the already open stream failed.

For Chat Completions, stream_options.include_usage: true adds a chunk with empty choices and usage before [DONE]. Messages carries usage in message_delta; Responses carries it in the terminal response object. Do not count tokens twice when both preliminary and final usage are visible. An SSE parser must assemble complete events using the blank line between them: one network read may contain multiple events or only part of one.

If the connection drops, retain the response ID and your own logical request's idempotency key, then inspect API Activity in the same organization. Avoid immediately issuing a new request with a new key: it can start a second generation. Follow safe retries and connection troubleshooting before retrying.

Cancellation and protocol differences

For an API-key client, closing the stream cancels the request. A browser chat disconnect does not cancel its run; the browser user can explicitly stop it. Responses also supports POST /v1/responses/{id}/cancel for explicit cancellation. Use the ID returned by Responses, not the HTTP request-id header. Responses explains the protocol; errors and limits explains terminal failures.