Skip to content

Stream progress

GET /{entity}/{id}/events is a text/event-stream of the dataset's metadata: one frame per recorded improvement, and one for each status change, until the status is final. It is the way to watch a solve during development, and the source for a dashboard that draws the score curve. It holds a connection open for the whole solve, which is why it is not the production way to receive a result; Receive webhooks is.

The frames

curl -sN -H "X-API-KEY: $KEY" $API/schedules/$ID/events
data: "{\"seq\":7,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_ACTIVE\",\"score\":\"-3hard/-2310soft\", …}}"
data: "{\"seq\":8,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_ACTIVE\",\"score\":\"0hard/-2085soft\", …}}"
data: "{\"heartbeat\":true}"
data: "{\"seq\":19,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_COMPLETED\",\"score\":\"0hard/-1830soft\", …}}"

Each data: payload is a JSON string, so a reader decodes it twice: the string, then the frame in it. A frame is { seq, metadata, skipped? } or { heartbeat: true }:

Field Meaning
seq the dataset's sequence number for this frame; what after resumes from
metadata the dataset's metadata: status, score, timestamps, and supersededBy when it applies
skipped present on the first frame when after named a sequence number the dataset no longer holds; how many were missed
heartbeat a keep-alive, every fifteen seconds while nothing else is sent

Frames carry metadata, never solutions. Fetch GET /{id} when the score says it is worth fetching.

The stream sends at most one frame a second, latest wins, and every frame's score is at least as good as the one before, because the worker throttles its reports and the dataset entity records only a strictly better score. The final frame is never dropped. The stream ends after the frame carrying a final status.

Resume

Every frame's sequence number is journaled, so ?after=<seq> resumes exactly after a dropped connection, a gateway timeout or an API redeploy:

curl -sN -H "X-API-KEY: $KEY" "$API/schedules/$ID/events?after=8"

The entity keeps a bounded ring of recent updates. If after names a sequence number older than the ring holds, the stream opens with the current best and says "skipped": n on that frame, so a reader knows it did not see everything and can fetch the dataset.

A dataset that is already final

Opening the stream on a final dataset answers 410 Gone with a message naming the status, unless after is given, in which case the frames after it are sent and the stream ends. Fetch the dataset instead.

Filter by status

?status=SOLVING_ACTIVE&status=SOLVING_COMPLETED, repeatable, sends only frames whose status is one of those. The final frame is sent regardless of the filter.

Follow lineage

Deriving a new dataset from one that is still solving supersedes it: the parent completes and its metadata names the child in supersededBy. With ?follow=lineage the parent's stream sends that final frame and continues with the child's frames instead of ending, so a dashboard following a plan across changes keeps one connection.

From the client

The client library's events method returns a blocking iterator of typed frames that reconnects from the last sequence number it saw, and eventsPublisher the same frames as a reactive streams publisher. Use the client shows both.

A browser

A browser's EventSource receives the frames but must parse each payload as a JSON string first, and it cannot resume automatically, because the frames carry no id: field for Last-Event-ID; pass after on reconnect instead. See Limitations.