Skip to content

Streaming

Set "stream": true to receive results as the engine decodes them, instead of waiting for the whole response. This is recommended: the production engine is CPU-only, so a full response can take seconds to tens of seconds.

{
  "stream": true,
  "schema": {"total": {"type": "number", "integer": true}, "paid": "bool"},
  "input": {"text": "Total 33000. Paid in cash."}
}

All checks that can fail before the engine runs — authentication, rate limit, request validation — still return a normal JSON error (4xx/5xx) before the stream starts. Once the stream is open, failures arrive as an error event.

Two wire formats

The events are the same; the encoding depends on the Accept header:

Accept Content-Type Format
default application/x-ndjson one JSON object per line
text/event-stream text/event-stream Server-Sent Events

Streaming responses carry Cache-Control: no-cache and X-Accel-Buffering: no so proxies do not buffer them.

Events

Only four event types are ever sent: field.delta, field.done, extraction.done, and error. Every event has a type field.

Event Payload Meaning
field.delta {type, field, value} A preview of a field as it is being written. Sent only when the preview changed.
field.done {type, field, value} A field finished; value is the final typed value. Sent once per field.
extraction.done full Extraction + {type} The complete result — same fields as Extract, including id and usage. Always sent, even for fields that never appeared earlier.
error {type, error} Something failed after streaming started. error is a {type, code, message} object.

Ordering: all field.delta events for a step come before its field.done events. A field's field.done always carries the typed value; field.delta values are raw text previews (a number may show as "3" before it becomes 33000).

Example sequence (NDJSON)

{"type":"field.delta","field":"total","value":"3"}
{"type":"field.delta","field":"total","value":"33000"}
{"type":"field.done","field":"paid","value":true}
{"type":"field.done","field":"total","value":33000}
{"type":"extraction.done","id":"req_01J…","object":"extraction","result":{"total":33000,"paid":true},"usage":{"input_tokens":41,"output_tokens":8,"fields":2,"images":0},…}

You can watch this with curl:

curl -N https://api.trusttyped.com/v1/extract \
  -H "Authorization: Bearer tt_live_…" \
  -H "Content-Type: application/json" \
  -d '{"stream": true, "schema": {"total": {"type": "number", "integer": true}}, "input": {"text": "Total 33000."}}'

For SSE, add -H "Accept: text/event-stream"; each event is then framed as event: <type> followed by data: <json>.

Reading the stream

import json, httpx

body = {"stream": True, "schema": {"total": {"type": "number", "integer": True}},
        "input": {"text": "Total 33000. Paid in cash."}}
with httpx.stream(
    "POST", "https://api.trusttyped.com/v1/extract",
    headers={"Authorization": "Bearer tt_live_…"},
    json=body, timeout=120,
) as resp:
    resp.raise_for_status()
    for line in resp.iter_lines():
        if not line:
            continue
        event = json.loads(line)
        if event["type"] == "field.delta":
            print("…", event["field"], event["value"])
        elif event["type"] == "field.done":
            print("✓", event["field"], "=", event["value"])
        elif event["type"] == "extraction.done":
            print("final:", event["result"], event["usage"])
        elif event["type"] == "error":
            raise RuntimeError(event["error"])
const resp = await fetch("https://api.trusttyped.com/v1/extract", {
  method: "POST",
  headers: { Authorization: "Bearer tt_live_…", "Content-Type": "application/json" },
  body: JSON.stringify({
    stream: true,
    schema: { total: { type: "number", integer: true } },
    input: { text: "Total 33000. Paid in cash." },
  }),
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  let nl;
  while ((nl = buffer.indexOf("\n")) >= 0) {
    const ev = JSON.parse(buffer.slice(0, nl));
    buffer = buffer.slice(nl + 1);
    if (ev.type === "extraction.done") console.log("final", ev.result);
    else if (ev.type === "error") throw new Error(ev.error.code);
    else console.log(ev.type, ev.field, ev.value);
  }
}

SSE event names

In SSE mode the event: line repeats the type (e.g. field.done), and data: holds the same JSON. The JSON always contains type too, so the same parser works for both formats.

Idempotency is not supported with streaming

Idempotency-Key cannot be combined with "stream": true; the request is rejected with 400 idempotency_not_supported_for_stream. See Idempotency.