Live-tail a job's log as a binary frame stream
GET/api/jobs/{job_id}/logs/stream
streamJobLogs
Streams `application/octet-stream` as a sequence of length-prefixed frames: a 5-byte header `kind u8 | len u32 big-endian`, then `len` payload bytes. Kinds: `0x00 LOG` — raw log bytes, contiguous from the cursor; `0x01 STEP` — a JSON `WorkflowRunStepResponse` for a step whose state changed; `0x02 EOF` — JSON `{"exit_code": <i32|null>, "status": <string>}`, terminal, the stream ends after it; `0x03 PING` — `len == 0` keepalive on a quiet job. Frames carry NO offset: the concatenation of `LOG` payloads is exactly `log[from_offset..]`, so the client cursor is `from_offset + sum(len(LOG))` and a reconnect at that cursor is exact. Live wakeups ride Postgres NOTIFY with a 1s poll floor.
Authentication optional — this operation also accepts anonymous requests. When authenticating, use a bearer token or a session cookie.
curl
curl -X GET 'https://neosource.dev/api/jobs/JOB_ID/logs/stream' \
-H 'Authorization: Bearer $NEOSOURCE_TOKEN'fetch
fetch("https://neosource.dev/api/jobs/JOB_ID/logs/stream", {
method: "GET",
headers: {
Authorization: "Bearer $NEOSOURCE_TOKEN",
},
});Path parameters
job_idrequired
Query parameters
from_offsetByte offset to read/stream from. Defaults to 0 (the whole log).
Responses
200Job log frame stream
application/octet-stream
403Forbidden — one of: forbidden, needs_scope
application/json
Standard errors
Bodies documented once for the whole API — see standard errors.
- 400Bad Request — one of: invalid_input
- 404Not Found — one of: not_found
- 429Rate limited — retry after the `Retry-After` header
- 500Internal server error
- 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
- 504Gateway timeout — the request exceeded the server's handling budget