# Streaming (/docs/api/streaming)

Stream live job progress and the final result over Server-Sent Events.



## `GET /v1/jobs/{id}/stream` [#get-v1jobsidstream]

Opens a `text/event-stream` SSE connection for one job. The stream replays
prior events, then tails live until terminal (`completed` or `failed`), then
closes.

```bash
curl -N https://api.orakle.xyz/v1/jobs/<job_id>/stream \
  -H "X-Orakle-API-Key: $ORAKLE_API_KEY"
```

`-N` disables curl buffering. Unknown or unowned jobs return `404
job_not_found`.

## Wire format [#wire-format]

Standard SSE frames: `event:`, `id:` (monotonic integer), `data:` JSON, blank
line.

```
event: stage
id: 3
data: {"event_type":"stage","job_id":"b7c1...","stage":"planning","message":"Planning strategy...","timestamp":"2026-05-15T12:00:03Z"}

```

See [Events](/docs/api/events) for every event type and payload.

## Replay with Last-Event-Id [#replay-with-last-event-id]

Send `Last-Event-Id` with the last `id` you processed. The server replays
only higher sequences then continues live — so a dropped connection resumes
without gaps or duplicates.

```bash
curl -N https://api.orakle.xyz/v1/jobs/<job_id>/stream \
  -H "X-Orakle-API-Key: $ORAKLE_API_KEY" \
  -H "Last-Event-Id: 3"
```

## Keepalive [#keepalive]

When idle, the server emits SSE comments to hold the connection open. Skip
lines starting with `:`.

```
: keepalive

```

## Consuming in Node [#consuming-in-node]

```ts
const res = await fetch(
  `https://api.orakle.xyz/v1/jobs/${jobId}/stream`,
  { headers: { "X-Orakle-API-Key": process.env.ORAKLE_API_KEY! } },
);

const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buf = "";

for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });

  let i: number;
  while ((i = buf.indexOf("\n\n")) !== -1) {
    const frame = buf.slice(0, i);
    buf = buf.slice(i + 2);
    if (frame.startsWith(":")) continue; // keepalive

    const lines = frame.split("\n");
    const event = lines.find((l) => l.startsWith("event: "))?.slice(7);
    const data = lines.find((l) => l.startsWith("data: "))?.slice(6);
    if (data) handle(event, JSON.parse(data));
  }
}
```

<Callout title="Prefer the SDK">
  The [SDKs](/docs/sdks/typescript) handle streaming, reconnection, and
  `Last-Event-Id` replay, resolving a single typed result. Hand-roll SSE only
  for raw progress events or non-JS/Python clients.
</Callout>