ScenebrewDevelopers

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:

kindStarted byTarget
scene_stepPOST /v1/scenes/{scene_id}/stepsa scene
session_turnPOST /v1/projects/{project_id}/sessions, POST /v1/sessions/{session_id}/messagesa session
generationPOST /v1/projects/{project_id}/generationsa 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.json

A 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.

On this page