Streaming
Orders take seconds to minutes; you should not poll for them in a tight loop. The API pushes lifecycle events two ways: Server-Sent Events on plain HTTP, and a multiplexed WebSocket that the SDKs manage for you.
Server-Sent Events
Three streams, all authenticated with the usual header:
| Stream | Scope | Lifetime |
|---|---|---|
GET /v1/orders/:id/events | One order: snapshot, progress, terminal state. | Closes on completed, failed, cancelled or expired. |
GET /v1/batches/:id/events | One shoot: per-order completions plus aggregate counts. | Closes when every scene has settled. |
GET /v1/events | The account: everything on your key, including intermediate frames. | Infinite — reconnect on drop. |
SSE streams send a ping every 15 seconds. Order and batch streams open with a snapshot of that resource's current state, including its terminal state when already settled. The account feed's initial snapshot lists only running orders; it does not replay missed terminal events. After a disconnect, read GET /v1/orders/:id for each order you are tracking, or reconnect to its individual stream.
WebSocket — via the SDKs
The SDKs use a WebSocket instead of SSE, and hide its lifecycle entirely (ticket authentication, reconnection with backoff, snapshot reconciliation). The design rule: one connection per client. order.wait(), batch.wait() and watch() all filter the same multiplexed socket — following fifty orders costs one connection, not fifty.
for await (const ev of bkbn.watch()) {
if (ev.type === 'order') {
console.log(ev.data.orderUuid, ev.data.status, ev.data.progressPercent)
}
} When a WebSocket cannot connect at all (proxies that strip the Upgrade header), wait() falls back to polling by itself. Mid-stream drops reconnect with backoff. The wait helpers reconcile the orders they track through HTTP snapshots; applications consuming watch() directly should perform their own reconciliation after a connection gap.
Cancelling an order
POST /v1/orders/:id/cancel cancels a pending or running order and returns its summary in data. Cancelling an already cancelled order returns that summary again. A completed, failed or expired order returns HTTP 409: read its current state instead of retrying cancellation.
const summary = await bkbn.cancelOrder(orderId)
// Or, if you have an OrderHandle: await order.cancel() Cancellation stops dispatch of subsequent steps; a worker already processing a step may still finish its work, but its later reports do not resume the order. Completion can win a race with cancellation, in which case the request returns 409 and the completed result remains available. Closing a stream or timing out in wait() does not cancel the order.
These SDK snippets use the existing client and an order ID; Kotlin calls belong in a coroutine. See Errors for the cancellation and conflict exception types, and Batches to stop a shoot.
Event shape
Order events carry the order summary — orderUuid, status (pending, running, completed, failed, cancelled, expired), progressPercent, progressStage, and outputAssetId once completed. Unknown event types may be added over time; ignore what you don't recognize. Python and Kotlin expose unknown-state/event sentinels; JavaScript consumers should keep a default branch for unrecognized values.