API reference

Streaming responses with server-sent events

POST /v1/conversation/stream returns server-sent events: response.started, response.completed and error. How to consume the stream from curl, Node and the browser.

Updated

/v1/conversation/stream accepts exactly the same body as /v1/conversation and answers with text/event-stream. /v1/messages streams too when you send "stream": true. Use streaming to keep connections alive on long generations and to show progress in your UI.

terminal
curl -N -X POST "https://api.subtoapi.app/v1/conversation/stream" \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "fast", "messages": [{ "role": "user", "content": "Say hello." }] }'

Events

event: …
response.started
{ request_id }
Sent as soon as the gateway accepted the request.
response.completed
{ response }
The full normalized response — same shape as the non-streaming endpoint — including usage and latency_ms.
error
{ error, message, request_id }
A problem after the stream started. Treat it like a 5xx and retry if it is safe to do so.
stream
event: response.started
data: {"type":"response.started","request_id":"req_…"}

event: response.completed
data: {"type":"response.completed","response":{"id":"msg_…","content":[{"type":"text","text":"Hello!"}],"usage":{…},"latency_ms":412,"model":"fast","provider":"claude"}}

Consuming the stream

Each event is event: <name> followed by one data: <json> line and a blank line. The snippet below parses frames as they arrive; EventSource cannot send POST bodies, so use fetch and read the body stream.

stream.ts
const res = await fetch("https://api.subtoapi.app/v1/conversation/stream", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ model: "balanced", messages: [{ role: "user", content: "Write a haiku about APIs." }] }),
});
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  let idx;
  while ((idx = buffer.indexOf("\n\n")) >= 0) {
    const frame = buffer.slice(0, idx); buffer = buffer.slice(idx + 2);
    const event = /^event: (.*)$/m.exec(frame)?.[1];
    const data = /^data: (.*)$/m.exec(frame)?.[1];
    if (!event || !data) continue;
    const payload = JSON.parse(data);
    if (event === "response.completed") console.log(payload.response.content);
    if (event === "error") throw new Error(`${payload.error}: ${payload.message}`);
  }
}

Streaming requests count once towards your rate limit and record one usage row, just like any other request. Request ids are returned in the x-request-id header as well.

Frequently asked questions

Does /v1/messages stream?
Yes: send "stream": true in the body of /v1/messages and you receive the same event set. /v1/conversation/stream is the dedicated endpoint for multi-turn threads.
Why no token-by-token deltas?
The gateway normalises the provider stream into a small, stable event set. Your client stays simple and the response shape never changes between streaming and non-streaming calls.