Build
Quickstart
From nothing to a finished video: one prompt for your coding agent, or the same five calls by hand.
1. Hand it to your agent
Fastest path: let the agent that already has your code do it. Paste this into Claude Code, Cursor, Codex or any agent that can make HTTP calls.
Read https://www.raylight.app/llms.txt and make a launch video for the feature I am working on: use the device flow to get a key if RAYLIGHT_API_KEY is missing, send the relevant UI files in the brief, wait for the pitch, show it to me, and greenlight it only when I say so.The agent reads llms.txt, gets a key through the device flow (you click approve once in the browser), builds a brief from the files it is working on, waits for the pitch and shows it to you. Nothing is charged until you greenlight it. The steps below are what the agent does.
2. Get a key
Keys look like rl_live_... and go in the Authorization: Bearer header. You need the productions scope. Two ways to get one:
In the app
In the app sidebar, open Raylight API, then Keys. Name the key, pick its scopes, and copy it once; only a hash is stored. Keys belong to your account and work on every workspace you can edit.
Device flow (no browser session needed)
For tools and agents with no session. Start a request, show the approval URL to a human, and poll until the key arrives. Starting needs no key.
curl -X POST https://api.raylight.app/v1/keys/request \ -H "Content-Type: application/json" \ -d '{ "name": "CI on acme/web" }'{ "deviceCode": "1aSs9fWUrhEUu3hOo5TzSJfWDgradXEK_k9BIV67XV8", "userCode": "ABCD2345", "approvalUrl": "https://www.raylight.app/keys/approve?code=ABCD2345", "expiresInSeconds": 600, "pollIntervalSeconds": 3}Ask your user to open approvalUrl and click Approve. Then poll every pollIntervalSeconds.
curl -X POST https://api.raylight.app/v1/keys/poll \ -H "Content-Type: application/json" \ -d '{ "deviceCode": "1aSs9fWUrhEUu3hOo5TzSJfWDgradXEK_k9BIV67XV8" }'{ "status": "pending" }{ "status": "approved", "apiKey": "rl_live_..." }{ "status": "claimed" }{ "status": "denied" }{ "status": "expired" }apiKey is returned once. Save it right away, for example as RAYLIGHT_API_KEY=rl_live_... in a gitignored .env. Device-flow keys carry every scope. If the claim response gets lost, see Authentication and keys.
3. Push a brief
One call makes a production. Only intent is required; the rest makes the pitch better. externalId makes retries safe: a repeat create answers 200 with the same production. origin tells the Productions page and emails where it came from (display only; Raylight never calls the provider).
{ "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" }}curl -X POST https://api.raylight.app/v1/productions \ -H "Authorization: Bearer $RAYLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d @brief.json{ "production": { "id": "6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a", "workspaceId": "0b9d8c7a-6e5f-4d3c-ab1a-09f8e7d6c5b4", "stage": "queued", "source": { "kind": "api", "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" } }, "brief": { "intent": "Announce dark mode in the Acme dashboard to existing customers.", "...": "..." }, "pitch": null, "greenlight": null, "greenlightMode": "manual", "delivery": null, "projectId": null, "creditsCharged": 0, "error": null, "createdAt": "2026-10-17T18:02:40.000Z", "updatedAt": "2026-10-17T18:02:40.000Z", "terminal": false, "pageUrl": "https://www.raylight.app/api/productions/6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a" }}production.pageUrl is the production's page in the app. Every brief field is explained on Productions.
4. Read the pitch
The stage moves queued, writing_pitch, pitch_ready (Queued, Writing pitch, Pitch ready in the app), usually within a couple of minutes. Poll the production every few seconds, or register a webhook for production.pitch_ready.
curl https://api.raylight.app/v1/productions/6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a \ -H "Authorization: Bearer $RAYLIGHT_API_KEY"The response has the production, its events, the pitch as pitchMarkdown, and a queue status (null until the production is greenlit). At pitch_ready, production.pitch holds the pitch, a script and shot list:
{ "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"}estimatedCredits is what the greenlight will charge: 60 credits per second of video. A revised pitch is priced again from its own length. A skip verdict means Raylight judged the brief not worth a video: the production ends in skipped, nothing is charged, and you can rework the brief and create again.
5. Greenlight
Greenlighting is POST /v1/productions/{id}/greenlight. Pass pitchVersion so a greenlight can never land on a pitch you did not read. This is the only call that charges credits: the pitch's estimatedCredits, once. Greenlight only at pitch_ready: a skipped production answers 409.
curl -X POST https://api.raylight.app/v1/productions/6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a/greenlight \ -H "Authorization: Bearer $RAYLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "pitchVersion": 1 }'Want changes first? POST /v1/productions/{id}/comments with a comment sends the production back to writing_pitch; the next version arrives the same way. 6 pitch versions per production, so 5 rounds of comments. You can also greenlight from the app, MCP, or the signed links in the Pitch ready email; see Greenlight links.
6. Receive the video
After the greenlight, the stage moves greenlit, making, exporting, delivered (Greenlit, In production, Exporting, Wrapped). Keep polling, or listen for production.delivered:
{ "id": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d", "type": "production.delivered", "createdAt": "2026-10-17T18:21:05.000Z", "workspaceId": "0b9d8c7a-6e5f-4d3c-ab1a-09f8e7d6c5b4", "data": { "production": { "id": "6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a", "stage": "delivered", "creditsCharged": 600, "delivery": { "shareUrl": "https://www.raylight.app/v/dark-mode-finally-k3x9", "projectId": "4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a", "editUrl": "https://www.raylight.app/editor/4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a", "exportId": "8e7d6c5b-4a39-4281-9c0b-7a6f5e4d3c2b", "deliveredAt": "2026-10-17T18:21:05.000Z" }, "...": "..." }, "pitchMarkdown": "## Dark mode, finally ...", "status": "Wrapped: https://www.raylight.app/v/dark-mode-finally-k3x9", "links": { "page": "https://www.raylight.app/api/productions/6f1c2b7e-3d4a-4c8e-9a2b-1f0e5d6c7b8a", "greenlight": null, "comment": null, "decline": null, "share": "https://www.raylight.app/v/dark-mode-finally-k3x9", "edit": "https://www.raylight.app/editor/4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a" } }}shareUrl is the public share page; editUrl opens the project in the editor. The project lands in the workspace's Raylight folder, so anyone on the team can keep editing.