Operations
One async model for everything slow — scene steps, session turns and library generations — with wait, poll, history and cancel.
Anything that takes longer than a request should — a pipeline step, a message the agent answers, an AI-generated library entity — runs as an operation. Three kinds share one resource, one status vocabulary and one set of calls:
kind | Started by | Target |
|---|---|---|
scene_step | POST /v1/scenes/{scene_id}/steps | a scene |
session_turn | POST /v1/projects/{project_id}/sessions, POST /v1/sessions/{session_id}/messages | a session |
generation | POST /v1/projects/{project_id}/generations | a project |
{
"id": "01a072e1-b04e-7490-8941-c13fdf3e231c",
"kind": "scene_step",
"status": "succeeded",
"target": { "type": "scene", "id": "01a072e0-a18f-7dc6-a74b-93c7b32d5915" },
"input": { "step": "generate_scene_blueprint" },
"result": {
"artifacts": [
{ "kind": "blueprint", "version": 1 },
{ "kind": "layout_preview", "version": 1 }
]
},
"error": null,
"credits_charged": 0,
"model": null,
"created_by": "api-key",
"created_at": "2026-09-05T19:05:12Z",
"started_at": "2026-09-05T19:05:12Z",
"finished_at": "2026-09-05T19:05:14Z",
"cancel_requested_at": null
}status moves queued → running → succeeded | failed, or through
canceling → canceled when you stop it. A failure is data, not an HTTP
error: the operation ends failed with error.code, error.message and
error.retryable. credits_charged is final once the operation is terminal.
Start
Every write that starts work answers 202 Accepted with the operation and a
Location header naming it. Add Prefer: wait=N (RFC 7240, N ≤ 60) and the
API holds the response until the operation is terminal or N seconds have
passed: 200 with the finished operation, or the same 202 with it still
running. Preference-Applied: wait=N on the response says the hold was
honoured; Prefer: respond-async is the default and needs no header.
curl -s -X POST "https://api.scenebrew.com/v1/scenes/$SCENE_ID/steps" \
-H "Authorization: Bearer $SCENEBREW_API_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: wait=60" \
-d '{"step": "generate_scene_model"}' -D - | grep -i -E '^(HTTP|location|preference-applied)'An Idempotency-Key header on the same four calls makes a repeated request
return the operation it already created (200) instead of starting another
— use one per logical attempt so a retry after a lost response is safe.
Poll
GET /v1/operations/{operation_id} reads an operation; ?wait=N turns it
into a bounded long-poll that returns as soon as the operation reaches a
terminal state, or after N seconds with its current state. A loop therefore
makes one call a minute, not one a second:
until curl -s "https://api.scenebrew.com/v1/operations/$OP_ID?wait=60" \
-H "Authorization: Bearer $SCENEBREW_API_KEY" \
| tee /tmp/op.json \
| jq -e '.status | IN("succeeded", "failed", "canceled")' >/dev/null; do :; done
jq '{status, credits_charged, error}' /tmp/op.jsonA 202 that outran Prefer: wait and a 202 from respond-async are the
same handle; poll them the same way.
History
GET /v1/operations?target_id=… lists the operations of one target —
a scene's steps, a session's turns, a project's generations — newest first,
cursor-paginated, with kind and status filters. It is the
receipt for everything that ran: the credits ledger in
GET /v1/me/credits names the operation behind each charge.
Cancel
POST /v1/operations/{operation_id}/cancel asks the worker to stop: 202
with the operation in canceling, and it ends canceled at the worker's
next boundary (error.code is canceled). A canceled flat-priced step
charges nothing; a canceled turn keeps the reply it had produced so far and
pays for the model calls it made. Cancel on an operation that is already
terminal is a no-op 200. Asset generation cannot be stopped once it has
started and answers 409 not_cancelable.
Ordering
The API refuses a step that cannot run yet, with a 409 you can branch on
rather than a failed operation: missing_sdd, missing_peg,
missing_blueprint for the pipeline order, step_in_progress (one step at
a time per scene) and turn_in_progress (one turn at a time per session).
See Errors.
Models
AI steps, sessions and generations accept a model id from
GET /v1/models — the models your workspace may use, with their hosting
(scenebrew or external) and billing (credits or byok). The chosen
model is echoed on the operation. Today the list holds the Scenebrew default
and any bring-your-own-key asset generators configured in the app.