RaylightAPI

Build

Productions

How a production moves from brief to finished video, and every field you can send.

A production is one video, from the brief you send to the share link you get back. It is the same thing whether it starts from the REST API, the SDK, the CLI, the GitHub Action, an AI assistant or the Productions page in the app. The pitch always comes before any canvas work, so a misfired call only costs a free pitch.

Lifecycle

StageIn the appMeaningCharged
queuedQueuedCreated, waiting for the pitch to be written.No
writing_pitchWriting pitchRaylight is writing the script and shot list.No
pitch_readyPitch readyWaiting for someone to greenlight it, send comments or decline.No
greenlitGreenlitCredits charged, project created, waiting for a render machine.Yes
makingIn productionRaylight is building the video in a project in your workspace.Yes
exportingExportingThe video is rendering.Yes
deliveredWrappedShare link ready and pushed to you.Yes
skippedSkippedRaylight judged the brief not worth a video. Terminal.No
declinedDeclinedSomeone declined the pitch. Terminal.No
cancelledCancelledCancelled before production started. Terminal.No, or refunded in full if it was greenlit
failedFailedProduction failed after two tries. Terminal; error says why.Refunded in full

Show the labels in the second column to people. Code should branch on the stage values, which never change. The SDK exports the same labels as PRODUCTION_STAGE_LABELS, and productionStageLabel(stage) returns the label, or the raw value for a stage newer than your SDK.

GET /v1/productions/{id} returns the production, its events (created, stage changes, each pitch version, comments, the greenlight, delivery) and, while greenlit, in production or exporting, a queue object: the phase it is in (preparing (Setting up), planning (Planning the video), building (Building scenes), reviewing (Checking the cut), polishing (Polishing), exporting (Exporting)) with lastProgress as its label to show, startedAt for when work on it started, and retrying when an earlier try failed and Raylight is trying again. Every event made by a person carries actorName, the name to show, next to the raw actor. production.pageUrl is the production's page in the app; link people there rather than building the URL yourself.

The brief

Everything the pitch step needs. Only intent is required. The quickstart example:

json
{  "brief": {    "intent": "Announce dark mode in the Acme dashboard to existing customers.",    "context": "PR #412 adds a theme toggle to Settings. Charts, tables and the sidebar all pick up the dark palette. Ships to everyone on Thursday.",    "media": [      {        "id": "dashboard-dark",        "kind": "image",        "url": "https://cdn.acme.dev/press/dashboard-dark.png",        "name": "Dashboard in dark mode",        "description": "The analytics overview with the new dark theme."      }    ],    "code": [      {        "path": "src/components/settings/theme-toggle.tsx",        "source": "export const ThemeToggle = () => { /* ... */ }"      }    ],    "format": {      "template": "feature",      "durationMs": 10000,      "aspect": "16:9",      "sound": { "voiceover": true, "music": true, "sfx": true }    },    "brand": {      "name": "Acme",      "primaryColor": "#6C5CE7",      "tone": "confident, plain"    }  },  "greenlightMode": "manual",  "externalId": "acme/web#412",  "origin": {    "provider": "github",    "label": "acme/web #412",    "url": "https://github.com/acme/web/pull/412",    "ref": "feat/dark-mode",    "requestedBy": "jane"  }}

intent

One sentence, 1 to 2,000 characters: what the video is about and who it is for. "Announce dark mode to existing customers" beats "dark mode video"; the audience changes the pacing and the copy.

context

Up to 60,000 characters: release notes, the PR body, linked issues, a diff summary, feature docs. Paste generously; the pitch only uses what the shots need. Source code goes in code, not here.

media

Up to 20 images or videos by URL. Each item has an id (what the shot list's mediaIds refer to), a kind (image or video), a url, a name and an optional description. Media is fetched into the project's library when the video is made, so the URL must still be reachable from Raylight's servers then: public, or signed with an expiry that outlasts the wait for a greenlight (days, if your team is slow to answer). Screenshots and screen recordings of the real product work best.

code

Up to 40 UI source files, 200 KB each. React with Tailwind in v1. Files are parsed, never run. Raylight rebuilds the component as native frames, text, shapes and icons, so the shot shows your real UI at hero scale instead of a screenshot. Each file has:

FieldTypeNotes
pathstringRepository path. Required.
sourcestringThe file contents. Required, up to 200,000 characters.
statusadded, modified, removed, renamedHow the change touched the file. Null when handed in by hand.
additions, deletionsintegerLine counts from the diff. Default 0.
patchstringThe unified diff hunks, up to 20,000 characters. Never leaves Raylight again.
digeststringA short description of how the UI looks, up to 1,500 characters.
score0 to 1How relevant the file is to a product video.

How ranking works. Files are sorted by score. The top ones are read in full when the pitch is written; the rest are known only by their digest. The highest scoring component becomes the hero shot; helpers, hooks and stylesheets score low and do not become shots. The CLI (raylight brief build) and the GitHub Action compute status, patch, digest and score from the diff on your machine, so you can read the brief before it goes. A plain API call can send only path and source; Raylight derives digest and score when it writes the pitch. Send the components that render the feature, not the whole tree.

format

FieldValuesDefault
templatechangelog, launch, feature, digest, customchangelog
durationMs5,000 to 15,00010,000
aspect16:9, 4:3, 3:2, 21:9, 1:1, 9:16, 3:4, 2:3, 4:516:9
sound.voiceover, sound.music, sound.sfxbooleanall true

The defaults are the every-release preset: a 10 second widescreen changelog with full sound. Productions are short on purpose: a feature recap or changelog explainer, delivered as a project you can open in Raylight and grow into a full launch video. launch is for a named release, feature for one capability, digest for a week of small changes, custom when the intent says it all.

brand

name, logoUrl, primaryColor, backgroundColor, fontFamily and tone (a short voice note like "dry, technical"). All optional and nullable. Missing values come from the workspace (logo, fonts) and from the code you sent (colors), and all are editable on the production before it is greenlit.

Pitches

The script and shot list for sign-off, in the production's pitch field. Each round of comments makes a new version; earlier versions stay in the production's events.

json
{  "version": 1,  "verdict": "video",  "verdictReason": null,  "title": "Dark mode, finally",  "summary": "A 10 second feature spot: the toggle flips, the dashboard follows, and an end card says it ships Thursday.",  "shots": [    {      "index": 0,      "title": "The flip",      "durationMs": 3000,      "beat": "The theme toggle flips and the whole settings page goes dark.",      "visual": "The ThemeToggle component rebuilt as layers at hero scale, centred, a slow push-in. On the flip the background sweeps from white to the brand dark.",      "text": "Dark mode",      "voiceover": "Dark mode is here.",      "mediaIds": [],      "codePaths": ["src/components/settings/theme-toggle.tsx"]    },    {      "index": 1,      "title": "The dashboard",      "durationMs": 4500,      "beat": "The analytics overview in dark, tilted, drifting left.",      "visual": "dashboard-dark on a device frame, 12 degree tilt, a lateral drift with a soft depth of field.",      "text": "Every chart. Every table.",      "voiceover": "Charts, tables and the sidebar all follow.",      "mediaIds": ["dashboard-dark"],      "codePaths": []    },    {      "index": 2,      "title": "Sign-off",      "durationMs": 2500,      "beat": "The Acme logo on the dark background with the ship date.",      "visual": "Logo centred on the brand dark, the date types on underneath, a short hold before the cut.",      "text": "Thursday, for everyone",      "voiceover": "Turn it on in Settings, Thursday.",      "mediaIds": [],      "codePaths": []    }  ],  "music": "Low, steady, electronic. No drop.",  "estimatedCredits": 600,  "createdAt": "2026-10-17T18:04:11.000Z"}
  • verdict is video or skip. A skip ends the production in skipped with verdictReason set, charges nothing, and fires production.skipped. Rework the brief and create again.
  • Each shot has a beat (the one line a reader skims), a visual (the full direction for building it), on-screen text, a voiceover line, and the mediaIds and codePaths it shows. 1 to 24 shots.
  • estimatedCredits is the price quoted with the pitch, and what the greenlight charges.
  • pitchMarkdown (on GET /v1/productions/{id}, in webhook payloads, on greenlight links and in MCP results) is the same pitch formatted for reading.

Sending comments

Send comments, get the next version, free. Be specific: shots to add or drop, copy to rewrite, pacing, tone. Pass pitchVersion so comments meant for a pitch that changed meanwhile are refused (409). A production gets 6 pitch versions in total, so 5 rounds of comments after the first pitch; after that it must be greenlit or declined, and another round answers 429 with code: "cap". Each version counts as one pitch against the monthly cap.

bash
curl -X POST https://api.raylight.app/v1/productions/6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a/comments \  -H "Authorization: Bearer $RAYLIGHT_API_KEY" \  -H "Content-Type: application/json" \  -d '{ "comment": "Open on the toggle, not the logo. Drop the pricing shot.", "pitchVersion": 1 }'

Greenlight

`greenlightMode`Behaviour
manual (default)The production waits at Pitch ready until someone greenlights it, over REST, MCP, the app or a signed link.
autoAuto-greenlight: a video verdict is greenlit as soon as the pitch lands, and credits are charged. A skip still skips. If the automatic greenlight is refused (not enough credits), the production stays at Pitch ready with a note and the normal production.pitch_ready event fires.

Pass pitchVersion when you greenlight to refuse it if the pitch changed since you read it (409 when stale). The production's greenlight records the mode, the actor (a user id, link:<email> for an email link, webhook:<endpointId> for a link from a webhook payload, or auto), an actorName to show people, the time and the pitch version greenlit. Cancel before production for a full refund; a production already being made cannot be cancelled.

Origin

Where the production came from, in your words. Display only: the Productions page shows it and the emails quote it; Raylight never calls the provider. provider is a lowercase slug (github, gitlab, vercel, linear, zapier, claude-code), label is the line to show ("acme/web #412", "v2.3.0"), url makes it a link, ref is the branch, tag or commit, and requestedBy is the handle to credit.

Idempotency

Pass externalId (up to 200 characters) on create. A repeat create with the same id from the same key owner returns the existing production with 200 instead of 201, so a retried CI step or replayed webhook never makes two pitches, and you can tell a replay from a new production by the status. Use something stable per unit of work: the PR number, the release tag, the deploy id.

Errors

Every non-2xx body is { "error": "...", "code": "..." }; validation failures add issues.

StatusCodeWhen
400Invalid body or query; issues lists the fields.
401Missing, invalid or revoked key.
402creditsThe workspace owner's credits do not cover the video: on create or comments, the length asked for; on greenlight, the pitch's estimatedCredits. Nothing changed; add credits (or ask for a shorter video) and try again.
403api_key_missing_scopeThe key lacks the productions scope.
403forbiddenView-only access to this workspace: the key's owner can read it but not change it.
404not_foundNo such production in a workspace you belong to.
409stageThe production's stage does not allow the action, or pitchVersion is stale.
429cap, rate_limitedA monthly cap is reached, the pitch is out of rounds of comments, or more than 120 requests a minute on this key. Rate limits set Retry-After.
503disabledProductions are switched off on this server.

Caps

Every plan can make productions, Free included, with no monthly limit on videos. Pitches are free but capped per calendar month, so a noisy trigger cannot pile up pitches nobody reads; the plan sets that cap. Each video costs 60 credits per second of video (300 to 900 credits); the pitch quotes the exact number as estimatedCredits. A pitch is only written when the workspace owner's credits cover the video it asks for, so a workspace with no credits gets a 402 instead of a pitch nobody can greenlight. GET /v1/productions/usage returns this month's counts and the pitch cap. Numbers on Limits and pricing.