REST API reference
Upload footage, request an edit, and deliver a browser view or render handoff.
Scrambo turns uploaded footage into a revisioned edit. Your integration only needs to manage a session, a few returned IDs, and the output your product needs.
Before you start
| Setting | Value |
|---|---|
| Base URL | https://api.scrambo.dev |
| Authentication | Authorization: Bearer <token> · Request API access |
| JSON requests | Content-Type: application/json |
V2 does not use the X-Scrambo-SDK-Version header.
Each authenticated identity can have one active project. Creating a session closes that identity's previous active session. Give independently operating users or integrations distinct credential identities.
Request lifecycle
One session. IDs connect each request to the next.
- 01
Start
Open a workspace for one video.
- 1Create sessionPOST
/v2/sessionsKeep sessionId
- 02
Add media
Register the file, then send its bytes.
- 2Declare assetPOST
/v2/sessions/{sessionId}/assetsKeep assetId, SHA-256, size
- 3Upload bytesPUT
/v2/sessions/{sessionId}/assets/{assetId}/contentKeep upload receipt
- 03
Make the edit
Submit direction and follow the work.
- 4Submit turnPOST
/v2/sessions/{sessionId}/turnsKeep requestId, operationId
- 5Poll progressGET
/v2/operations/{operationId}?after={cursor}Keep status, latest cursor
- 04
Deliver
Choose a browser view or render handoff.
- 6Create snapshotPOST
/v2/sessions/{sessionId}/view-edit-snapshotsKeep URL, operation, expiry
- 7Create handoffPOST
/v2/sessions/{sessionId}/render-handoffsKeep operationId
- 8Download handoffGET
/v2/sessions/{sessionId}/render-handoffs/{handoffId}Keep manifest
- 9Close sessionPOST
/v2/sessions/{sessionId}/closeKeep terminal state
The first five steps get you to a working edit. From there, create a browser snapshot for review, or create and download a render handoff for your own renderer. Close the session when the user is done.
Your first edit
1. Create a session and upload media
Create the session and keep its sessionId. For every file, declare its name,
byte size, MIME type, and SHA-256 digest; keep the returned assetId, then
upload the exact bytes.
ExampleSet up curl, create a session, and upload a file
export SCRAMBO_API_URL="https://api.scrambo.dev"
export SCRAMBO_API_TOKEN="replace-with-your-token"
export VIDEO="./interview.mp4"
api() {
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $SCRAMBO_API_TOKEN" \
-H "Content-Type: application/json" \
"$@"
}
SESSION_ID=$(api -X POST "$SCRAMBO_API_URL/v2/sessions" \
--data '{"project":"first-edit","canvas":[1080,1920]}' \
| jq -r '.sessionId')
VIDEO_SIZE=$(wc -c < "$VIDEO" | tr -d ' ')
VIDEO_SHA256=$(openssl dgst -sha256 -r "$VIDEO" | awk '{print $1}')
ASSET_ID=$(api -X POST \
"$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/assets" \
--data "$(jq -n \
--arg name "$(basename "$VIDEO")" \
--argjson size "$VIDEO_SIZE" \
--arg sha256 "$VIDEO_SHA256" \
'{name:$name,size:$size,mimeType:"video/mp4",sha256:$sha256}')" \
| jq -r '.assetId')
curl --fail-with-body --silent --show-error \
-X PUT "$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/assets/$ASSET_ID/content" \
-H "Authorization: Bearer $SCRAMBO_API_TOKEN" \
-H "Content-Type: video/mp4" \
--data-binary "@$VIDEO"2. Submit editing turns
Every instruction goes to the same /turns endpoint. A dependable first edit
uses three turns in order:
| Goal | Agent | What to send |
|---|---|---|
| Understand the footage | source.work | What to find or transcribe |
| Plan the story | planner.compile | The desired length, structure, and emphasis |
| Build the edit | timeline.author | Omit message to build the current plan |
Wait for each turn to succeed before submitting the next. Keep the returned
operationId; a successful Author result also gives you the current editId.
ExampleSubmit the three-turn editing sequence
# 1. Understand the footage.
OPERATION_ID=$(api -X POST "$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/turns" \
--data '{
"requestId":"source-1",
"mode":"edit",
"agent":"source.work",
"message":"Transcribe the dialogue and identify the strongest moments.",
"tools":["transcribe"]
}' | jq -r '.operationId')
# After source.work succeeds, submit planner.compile and keep its operation.
OPERATION_ID=$(api -X POST "$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/turns" \
--data '{
"requestId":"plan-1",
"mode":"edit",
"agent":"planner.compile",
"message":"Plan an energetic 30-second story. Open with the strongest hook."
}' | jq -r '.operationId')
# After planner.compile succeeds, build its plan and keep the operation.
OPERATION_ID=$(api -X POST "$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/turns" \
--data '{
"requestId":"author-1",
"mode":"edit",
"agent":"timeline.author"
}' | jq -r '.operationId')3. Poll the operation
Poll with the latest cursor until status is succeeded, failed, or
cancelled. Send the returned cursor as the next after value so you only
receive new progress events.
ExamplePoll for progress
api "$SCRAMBO_API_URL/v2/operations/$OPERATION_ID?after=0" | jq4. Deliver the result
Create a snapshot when a user needs a browser review. Create a render handoff when another renderer needs the edit and verified asset manifest.
ExampleCreate a browser snapshot, then close the session
api -X POST \
"$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/view-edit-snapshots" \
--data '{}'
# Open the returned viewUrl. Close only after the user is finished.
api -X POST "$SCRAMBO_API_URL/v2/sessions/$SESSION_ID/close" \
--data '{}'Concepts in plain English
| Resource | Think of it as | Keep |
|---|---|---|
| Session | One user's video workspace | sessionId, currentEditId |
| Asset | One verified media file | assetId, SHA-256, byte size |
| Turn | One question or editing instruction | Your unique requestId |
| Operation | The work running in the background | operationId, latest cursor |
| Snapshot | A temporary browser view of one edit | viewUrl, expiry |
| Render handoff | The edit and asset manifest for a renderer | handoffId |
Endpoint reference
| Method | Path | Purpose |
|---|---|---|
POST | /v2/sessions | Create an empty stateful editing session. |
GET | /v2/sessions/{sessionId} | Read session state, assets, and currentEditId. |
POST | /v2/sessions/{sessionId}/assets | Declare an asset's name, size, MIME type, and SHA-256 digest. |
PUT | /v2/sessions/{sessionId}/assets/{assetId}/content | Upload the declared asset as raw bytes. |
POST | /v2/sessions/{sessionId}/turns | Submit an idempotent ask or edit turn. |
GET | /v2/operations/{operationId}?after={cursor} | Poll status and receive events newer than cursor. |
POST | /v2/sessions/{sessionId}/view-edit-snapshots | Create a detached browser view of an edit. |
POST | /v2/sessions/{sessionId}/render-handoffs | Start a renderer-neutral handoff for an edit. |
GET | /v2/sessions/{sessionId}/render-handoffs/{handoffId} | Download an authenticated handoff manifest. |
POST | /v2/sessions/{sessionId}/close | Cancel active work and close the private session. |
JSON request objects are closed: unknown fields are rejected.
Sessions and assets
POST /v2/sessions accepts:
| Field | Required | Meaning |
|---|---|---|
project | Yes | Project name, 1–120 characters. |
canvas | No | [width, height]; each value must be a positive integer no larger than 16384. |
provider | No | Object with name and optional model and thinking; omit it to use service defaults. |
To upload a file, first declare it with name, size, sha256, and optional
mimeType. Then PUT the raw bytes to the returned asset URL. An asset is only
available to agents after its byte count and digest have been verified.
Turns
All agent work uses POST /v2/sessions/{sessionId}/turns. There are two request
modes.
An ask turn performs read-only analysis and returns text:
{
"requestId": "ask-1",
"mode": "ask",
"message": "Which moments best support a 30-second customer story?"
}An edit turn runs one allowlisted specialist:
{
"requestId": "captions-1",
"mode": "edit",
"agent": "timeline.captions",
"baseEditId": "latest-edit-id",
"message": "Add bold, readable captions timed to the dialogue."
}| Agent | What it does | Message and revision rules |
|---|---|---|
source.work | Understands uploaded media and publishes the current source brief. | Requires message; never accepts baseEditId. |
source.generate | Generates media and publishes a source brief over the results. | Requires message and at least one of img2video or voiceover; never accepts baseEditId. |
planner.compile | Turns the current source brief into a grounded plan. | Requires message; never accepts baseEditId. |
timeline.author | Creates the structural edit. | Omit message and baseEditId to build the current plan as a new root; use message for direct authoring. |
timeline.graphics | Adds shapes, panels, backings, and accents. | Requires message and the latest baseEditId. |
timeline.sound | Adds music, sound effects, balance, and ducking. | Requires message and the latest baseEditId. |
timeline.titles | Adds titles, cards, lower thirds, and typography. | Requires message and the latest baseEditId. |
timeline.captions | Adds transcript-aligned captions. | Requires message and the latest baseEditId. |
Common edit-turn fields:
| Field | Meaning |
|---|---|
requestId | Required idempotency key, 1–200 characters, scoped to the session. |
mode | edit. |
agent | One agent ID from the table above. |
message | Natural-language instruction, up to 20,000 characters. Only plan-backed Author may omit it. |
baseEditId | Revision the timeline specialist must extend. See Revision rules. |
tools | Up to four explicitly granted capabilities. Grants apply only to this turn. |
toolConfig | Per-tool configuration. Every configured tool must also appear in tools. |
source.generate additionally accepts budgetUsd (default 5.0), maxCalls
(default 4), and name (1–80 characters). The deployment's default ceilings
are $15 and 15 requested outputs; requests over the active ceiling fail with
generation_limit_exceeded.
For example:
{
"requestId": "generate-tour-1",
"mode": "edit",
"agent": "source.generate",
"message": "Create a narrated 16:9 property tour from the uploaded photos.",
"tools": ["img2video", "voiceover"],
"toolConfig": {
"img2video": {
"resolution": "720p",
"duration": 6,
"aspectRatio": "16:9"
}
},
"budgetUsd": 5,
"maxCalls": 10,
"name": "listing-tour-assets"
}Tool grants
Tools are opt-in capabilities, not separate HTTP endpoints. The specialist decides whether and how to use the capabilities granted for that turn.
| Tool | Allowed agents | Configuration |
|---|---|---|
transcribe | source.work, planner.compile, timeline.author | provider, model, language, diarize; word timestamps are always enabled. |
detect_events | source.work, planner.compile, timeline.author | sampleDensity: sparse or dense; maxFrames: 1–2400. |
detect_beats | source.work | No configuration. |
masking | Every edit agent | No caller configuration. |
img2video | source.generate | Generation settings such as vendor, resolution, duration, and aspectRatio. |
voiceover | source.generate | Generation settings such as vendor and voice. |
Operations and results
Turns, browser snapshots, and render handoffs are asynchronous. A submission
returns 202 with an operationId and its current status (normally queued or
running; an idempotent replay can already be terminal). Poll:
GET /v2/operations/{operationId}?after=0The response contains:
| Field | Meaning |
|---|---|
status | queued, running, succeeded, failed, or cancelled. |
cursor | Latest event cursor. Send it as the next after value. |
events | Progress events newer than after. |
result | Present after success. Its type selects the result shape. |
error | Present after failure or cancellation. |
Result type | Important fields |
|---|---|
answer | answer |
source | agent, artifact, editId |
plan | agent, artifact, editId |
edit | agent, editId, previousEditId, artifact |
view_edit_snapshot | editId |
render_handoff | handoffId, editId |
Artifacts include name, kind, executionId, fingerprint, result,
llmInvoked, and cached.
Every error has a stable code, a safe message, and retryable. Use
retryable rather than guessing from the message. Synchronous HTTP errors use
the same envelope:
{
"error": {
"code": "edit_conflict",
"message": "baseEditId is stale",
"retryable": false
}
}Idempotency and revision rules
requestId is an idempotency key scoped to one session. Replaying the same ID
with the same canonical body returns the original operation. Reusing it for a
different body returns 409 idempotency_conflict.
Timeline revisions form one linear chain:
- A direct first
timeline.authorturn omitsbaseEditId. - Plan-backed
timeline.authoromits bothmessageandbaseEditId; it creates a new root revision from the current plan. - Every later timeline turn sends the latest successful
editIdasbaseEditId. - A stale or missing base revision returns
409 edit_conflict. source.work,source.generate, andplanner.compilenever acceptbaseEditIdand do not advance the edit chain.
Read currentEditId from GET /v2/sessions/{sessionId} when recovering local
client state.
Browser snapshots and render handoffs
A browser snapshot is a detached view of one revision. Pass an optional
editId; omitting it selects the current revision. Snapshot creation returns a
viewUrl immediately. Open it before waiting for the operation because the
browser connection completes hydration. Later agent turns do not change the
snapshot, and manual changes in that browser are not durable API revisions.
A render handoff is an authenticated, renderer-neutral manifest for a selected revision:
{
"requestId": "render-handoff-1",
"editId": "edit-id-to-render"
}Submit it to /v2/sessions/{sessionId}/render-handoffs, poll the returned
operation, then download
/v2/sessions/{sessionId}/render-handoffs/{handoffId}. Uploaded media is
identified by asset ID, SHA-256 digest, byte size, and MIME type so an external
renderer can resolve and verify its local copy.
