Scenes are in private beta. See Scenes to request access.
It follows the same conventions as the REST API: problem documents with a stable
code, cursor pages, Idempotency-Key, and RateLimit-* headers (600 requests a minute).
The model
A scene (idscn_…) is what a stream plays when it isn’t a video loop. Its draft holds resources, each at a path:
GET /v1/scenes/{id}/resources lists the kinds. A path ending in /* lists one kind, for example frame/* or element/*. Each answer names the paths to read next in children.
All resources go through four operations:
POST /v1/scenes/{id}/probe fetches a URL once, changing nothing, to show what a table would read from it.
Walkthrough
This builds a one-frame scene, looks at it, publishes it and puts it on a stream. Set your key and workspace first.GET /v1/workspaces lists your workspaces.
1
Create a scene
201 with the scene. Note its id (scn_…). Its version is 0: nothing is published yet.2
Write a frame
Send the frame’s code as JSX. The frame’s id is its path, here A new resource answers
frame/intro:201, with revision (also the ETag header), a diff of what changed, and draftRevision. If the code doesn’t build or check, the answer is 422 CHECK_FAILED, nothing changes, and errors lists every problem with its line.GET "$SCN/resources/element/Text" shows every prop Text takes, with an example.3
Look at it
Get a picture of the frame as it goes on air:With
Accept: image/png (or no Accept) the answer is a PNG, at most 1280 px wide. With Accept: application/json you get the picture as a data URL plus what drew (drawn) and what’s wrong (issues): text outside title-safe, overlapping or too small, or a bound table with no rows. Add overlay=grid or overlay=ids to line things up, and at=<ms> to draw a moment after the frame goes on air.4
Check, then publish
Publishing takes the Then publish:
draftRevision of the draft you checked. It’s required: GET /v1/scenes/{id} and every resource answer carry the current one, also as the Draft-Revision header. So you publish exactly the draft you saw, even if someone edits it meanwhile. A dry run checks the whole scene and lists what would change, publishing nothing:changes lists what changed since the previous version, as resource paths. If the draft changed after your draftRevision, those edits aren’t published: the answer says so with "stale": true and a note. A draft equal to what’s published makes no version ("unchanged": true).If someone published after the version your draft is based on, publishing it would take their version back. The answer is 409 STALE_PUBLISH with publishedVersion, basedOn and reverted (what would be undone). Read the draft again and publish its new draftRevision, or send "overwritePublished": <publishedVersion> to replace that version on purpose. If that version is a revert (publishedVersionIsRevert: true), reading the draft again doesn’t help: send overwritePublished to publish your draft over it, or discard the draft (POST /discard) to keep the reverted version.5
Put it on a stream
Use the REST API to make a stopped stream play the scene’s published version, then start it:The stream opens on the scene’s first frame. While the stream runs,
PUT …/scene answers 409 STREAM_LIVE: stop it first. { "sceneId": null } goes back to the stream’s video playlist.On air
Once a scene is on air:- Publishing changes what viewers see at once. So
POST /publish(andPOST /revert) on a scene a running stream plays answers409 CONFIRM_REQUIRED, naming the streams. Send it again with"confirm": trueto accept that.confirmalways means “I accept what the refusal named”. GET /v1/scenes/{id}/livesays what’s on air on each stream: underonAir, the frame on air (frame), the next one cued (next), control values and source status.POST /v1/scenes/{id}/liveacts like an operator, with the same names as the MCP’scontrol_scene:goFramewithtarget(takeframe/<target>to air, with a transition such asfade),next(cue a frame),setControlwithcontrolIdandvalue,setDatawithsourceIdandrows(replace a table’s rows) andskipwithsourceId(a playlist source’s next item);streamIdwhen several streams play it. A transition is a kind (cut,fade,slide,wipe,dip) or{ "kind": …, "ms": … }withmsa whole number of 0 or more; anything else, or astreamIdthat isn’t a string, is422 INVALID_INPUT, said before what’s published is looked at. A504 SCENE_TIMEOUTmay or may not have been applied: send anIdempotency-Key(one per action) and retry with the same key and body, and it applies at most once. Without one, don’t retry askipor a button blind: readGET …/livefirst. The action is checked first against what’s published (a frame, control, table or playlist source that isn’t there, or a toggle given"yes", is422 INVALID_INPUT); then it answers409 SCENE_OFFLINEwhen no stream plays the scene.- Any resource (or picture) reads from a published version with
?version=<n>or?version=published; the answer carries aScene-Versionheader and noDraft-Revision. GET /versionslists published versions;POST /revertwith{ "version": n, "expectedVersion": <the latest published version you saw> }publishes an old one again as the next version (409 STALE_PUBLISHif someone published since; after a revert, publishing the draft asks foroverwritePublished, or discard the draft to match);POST /discardwith{ "draftRevision": "…" }puts the draft back to what’s published and makes that version the draft’s base, so the next unchanged publish answersunchangedand an edited one is the next version. Add"dryRun": truefirst to see what would be lost (lost). If the draft changed after yourdraftRevision, nothing is discarded (409 CONFLICT). Both take the precondition asIf-Matchinstead ("<draftRevision>"on discard,"<expectedVersion>"on revert): then a mismatch is412with the same code and details.
POST /v1/scenes/{id}/ingest-keyswith{ "sourceId": "…" }gives an encoder such as OBS its RTMP, RTMPS and SRT addresses for anrtmporsrtsource. The key is in that answer only.PUT /v1/scenes/{id}/secrets/{name}saves an API key that a table sends as$secret:<name>. It’s sealed and never shown again./v1/scenes/{id}/camerasadds and tests IP cameras. See Cameras.
Concurrency
Every resource has arevision, sent as its ETag. Use it so you never overwrite someone’s newer edit, whether from another script or a person in the studio:
On
412, read the resource again, reapply your change and retry.
Idempotency
Send anIdempotency-Key header on POST and PATCH to retry safely. The first answer to a key (per caller, method and path) is replayed for 24 hours, with Idempotency-Replayed: true; a retry that arrives while the first request is still running waits for its answer instead of running again. Reusing a key with a different request body is 422 IDEMPOTENCY_KEY_REUSED. Use it on publish, revert and live in particular: after a 504 SCENE_TIMEOUT, retry with the same key.
Errors
Errors are RFC 7807 problem documents (application/problem+json) with a stable code:
The full list is in the
Problem schema of the OpenAPI document.
Scopes
With an OAuth token, everyGET and probe needs streamloop:read. Edits, publishing and live control need streamloop:write. Deleting a scene, discarding its draft, and revoking or deleting an ingest key, camera or secret need streamloop:destructive.