# Create a production

> Push a brief. Raylight writes a free pitch (script plus shot list) in the background; poll the production or receive `production.pitch_ready` on a webhook. `externalId` makes the call idempotent per key owner and workspace: repeating it with a key from the same owner, in the same workspace, answers 200 with the production the first call made and `Idempotent-Replayed: true`, and nothing new is created. `origin` is display only (the Productions pages and emails); Raylight never calls the provider.

### POST /v1/productions

**Create a production**

Push a brief. Raylight writes a free pitch (script plus shot list) in the background; poll the production or receive `production.pitch_ready` on a webhook. `externalId` makes the call idempotent per key owner and workspace: repeating it with a key from the same owner, in the same workspace, answers 200 with the production the first call made and `Idempotent-Replayed: true`, and nothing new is created. `origin` is display only (the Productions pages and emails); Raylight never calls the provider.

Auth: `Authorization: Bearer rl_live_...` with the `productions` scope.

Request body (application/json):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `workspaceId` | uuid | no |  |
| `brief` | object | yes |  |
| `brief.intent` | string | yes |  |
| `brief.context` | string \| null | no | Default `null`. |
| `brief.media` | object[] | no |  |
| `brief.media[].id` | string | yes |  |
| `brief.media[].kind` | enum | yes | One of: `image`, `video`. |
| `brief.media[].url` | url | yes |  |
| `brief.media[].name` | string | yes |  |
| `brief.media[].description` | string \| null | no | Default `null`. |
| `brief.code` | object[] | no |  |
| `brief.code[].path` | string | yes |  |
| `brief.code[].source` | string | yes |  |
| `brief.code[].status` | enum \| null | no | One of: `added`, `modified`, `removed`, `renamed`. Default `null`. |
| `brief.code[].additions` | integer | no | Default `0`. |
| `brief.code[].deletions` | integer | no | Default `0`. |
| `brief.code[].patch` | string \| null | no | Default `null`. |
| `brief.code[].digest` | string \| null | no | Default `null`. |
| `brief.code[].score` | number | no | Default `0`. |
| `brief.code[].sourceOmitted` | boolean | no |  |
| `brief.format` | object | no |  |
| `brief.format.template` | enum | no | One of: `changelog`, `launch`, `feature`, `digest`, `custom`. Default `changelog`. |
| `brief.format.durationMs` | integer | no | Default `10000`. |
| `brief.format.aspect` | enum | no | One of: `16:9`, `4:3`, `3:2`, `21:9`, `1:1`, `9:16`, `3:4`, `2:3`, `4:5`. Default `16:9`. |
| `brief.format.sound` | object | no |  |
| `brief.format.sound.voiceover` | boolean | no | Default `true`. |
| `brief.format.sound.music` | boolean | no | Default `true`. |
| `brief.format.sound.sfx` | boolean | no | Default `true`. |
| `brief.brand` | object | no |  |
| `brief.brand.name` | string \| null | no | Default `null`. |
| `brief.brand.logoUrl` | url \| null | no | Default `null`. |
| `brief.brand.primaryColor` | string \| null | no | Default `null`. |
| `brief.brand.backgroundColor` | string \| null | no | Default `null`. |
| `brief.brand.fontFamily` | string \| null | no | Default `null`. |
| `brief.brand.tone` | string \| null | no | Default `null`. |
| `greenlightMode` | enum | no | One of: `manual`, `auto`. Default `manual`. |
| `externalId` | string | no |  |
| `origin` | object | no |  |
| `origin.provider` | string | yes |  |
| `origin.label` | string | yes |  |
| `origin.url` | url \| null | no | Default `null`. |
| `origin.ref` | string \| null | no | Default `null`. |
| `origin.requestedBy` | string \| null | no | Default `null`. |

Responses:

| Status | Meaning |
| --- | --- |
| 200 | A repeat of an earlier create with the same `externalId`: the existing production. |
| 201 | The new production, queued for a pitch. |
| 400 | Invalid request. |
| 401 | Missing, invalid or revoked key. |
| 402 | The workspace owner's credits do not cover a video of the length asked for (code `credits`). A pitch is free, but it is only written when the video it asks for could be paid for. Nothing was created. |
| 403 | The key lacks the scope, or the key's owner has view-only access to the workspace (code `forbidden`). |
| 404 | Not found. |
| 429 | Rate limited, or a monthly cap reached. |
| 503 | Raylight can't write pitches right now (code `disabled`). Nothing was created. |

Response body (200):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `production` | object | yes |  |
| `production.id` | uuid | yes |  |
| `production.workspaceId` | uuid | yes |  |
| `production.stage` | enum | yes | One of: `queued`, `writing_pitch`, `pitch_ready`, `greenlit`, `making`, `exporting`, `delivered`, `skipped`, `declined`, `cancelled`, `failed`. |
| `production.source` | object | yes |  |
| `production.source.kind` | enum | yes | One of: `app`, `api`, `mcp`. |
| `production.source.externalId` | string \| null | yes |  |
| `production.source.origin` | object \| null | no | Default `null`. |
| `production.source.origin.provider` | string | yes |  |
| `production.source.origin.label` | string | yes |  |
| `production.source.origin.url` | url \| null | no | Default `null`. |
| `production.source.origin.ref` | string \| null | no | Default `null`. |
| `production.source.origin.requestedBy` | string \| null | no | Default `null`. |
| `production.brief` | object | yes |  |
| `production.brief.intent` | string | yes |  |
| `production.brief.context` | string \| null | no | Default `null`. |
| `production.brief.media` | object[] | no |  |
| `production.brief.media[].id` | string | yes |  |
| `production.brief.media[].kind` | enum | yes | One of: `image`, `video`. |
| `production.brief.media[].url` | url | yes |  |
| `production.brief.media[].name` | string | yes |  |
| `production.brief.media[].description` | string \| null | no | Default `null`. |
| `production.brief.code` | object[] | no |  |
| `production.brief.code[].path` | string | yes |  |
| `production.brief.code[].source` | string | yes |  |
| `production.brief.code[].status` | enum \| null | no | One of: `added`, `modified`, `removed`, `renamed`. Default `null`. |
| `production.brief.code[].additions` | integer | no | Default `0`. |
| `production.brief.code[].deletions` | integer | no | Default `0`. |
| `production.brief.code[].patch` | string \| null | no | Default `null`. |
| `production.brief.code[].digest` | string \| null | no | Default `null`. |
| `production.brief.code[].score` | number | no | Default `0`. |
| `production.brief.code[].sourceOmitted` | boolean | no |  |
| `production.brief.format` | object | no |  |
| `production.brief.format.template` | enum | no | One of: `changelog`, `launch`, `feature`, `digest`, `custom`. Default `changelog`. |
| `production.brief.format.durationMs` | integer | no | Default `10000`. |
| `production.brief.format.aspect` | enum | no | One of: `16:9`, `4:3`, `3:2`, `21:9`, `1:1`, `9:16`, `3:4`, `2:3`, `4:5`. Default `16:9`. |
| `production.brief.format.sound` | object | no |  |
| `production.brief.format.sound.voiceover` | boolean | no | Default `true`. |
| `production.brief.format.sound.music` | boolean | no | Default `true`. |
| `production.brief.format.sound.sfx` | boolean | no | Default `true`. |
| `production.brief.brand` | object | no |  |
| `production.brief.brand.name` | string \| null | no | Default `null`. |
| `production.brief.brand.logoUrl` | url \| null | no | Default `null`. |
| `production.brief.brand.primaryColor` | string \| null | no | Default `null`. |
| `production.brief.brand.backgroundColor` | string \| null | no | Default `null`. |
| `production.brief.brand.fontFamily` | string \| null | no | Default `null`. |
| `production.brief.brand.tone` | string \| null | no | Default `null`. |
| `production.pitch` | object \| null | yes |  |
| `production.pitch.version` | integer | yes |  |
| `production.pitch.verdict` | enum | no | One of: `video`, `skip`. Default `video`. |
| `production.pitch.verdictReason` | string \| null | no | Default `null`. |
| `production.pitch.title` | string | yes |  |
| `production.pitch.summary` | string | yes |  |
| `production.pitch.shots` | object[] | yes |  |
| `production.pitch.shots[].index` | integer | yes |  |
| `production.pitch.shots[].title` | string | yes |  |
| `production.pitch.shots[].durationMs` | integer | yes |  |
| `production.pitch.shots[].beat` | string \| null | no | Default `null`. |
| `production.pitch.shots[].visual` | string | yes |  |
| `production.pitch.shots[].text` | string \| null | no | Default `null`. |
| `production.pitch.shots[].voiceover` | string \| null | no | Default `null`. |
| `production.pitch.shots[].mediaIds` | string[] | no |  |
| `production.pitch.shots[].codePaths` | string[] | no |  |
| `production.pitch.music` | string \| null | no | Default `null`. |
| `production.pitch.estimatedCredits` | integer | yes |  |
| `production.pitch.createdAt` | datetime | yes |  |
| `production.greenlight` | object \| null | yes |  |
| `production.greenlight.mode` | enum | yes | One of: `manual`, `auto`. |
| `production.greenlight.actor` | string | yes |  |
| `production.greenlight.actorName` | string \| null | no |  |
| `production.greenlight.at` | datetime | yes |  |
| `production.greenlight.pitchVersion` | integer | yes |  |
| `production.greenlightMode` | enum | yes | One of: `manual`, `auto`. |
| `production.delivery` | object \| null | yes |  |
| `production.delivery.shareUrl` | url | yes |  |
| `production.delivery.projectId` | uuid | yes |  |
| `production.delivery.editUrl` | url | yes |  |
| `production.delivery.exportId` | uuid | yes |  |
| `production.delivery.deliveredAt` | datetime | yes |  |
| `production.projectId` | uuid \| null | yes |  |
| `production.creditsCharged` | integer | yes |  |
| `production.error` | string \| null | yes | Why the production failed, in words you can show a person. Never the render machine's own diagnostics. |
| `production.createdAt` | datetime | yes |  |
| `production.updatedAt` | datetime | yes |  |
| `production.terminal` | boolean | no | True once the stage is one the production never leaves (delivered, skipped, declined, cancelled, failed, or a terminal stage added later). Stop polling when it is true; it is computed by the server, so it stays right for stages your client does not know yet. Absent from payloads stored before the field existed. |
| `production.pageUrl` | url | no | The production's page in the Raylight app. Absent from payloads stored before the field existed. |

Response body (201):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `production` | object | yes |  |
| `production.id` | uuid | yes |  |
| `production.workspaceId` | uuid | yes |  |
| `production.stage` | enum | yes | One of: `queued`, `writing_pitch`, `pitch_ready`, `greenlit`, `making`, `exporting`, `delivered`, `skipped`, `declined`, `cancelled`, `failed`. |
| `production.source` | object | yes |  |
| `production.source.kind` | enum | yes | One of: `app`, `api`, `mcp`. |
| `production.source.externalId` | string \| null | yes |  |
| `production.source.origin` | object \| null | no | Default `null`. |
| `production.source.origin.provider` | string | yes |  |
| `production.source.origin.label` | string | yes |  |
| `production.source.origin.url` | url \| null | no | Default `null`. |
| `production.source.origin.ref` | string \| null | no | Default `null`. |
| `production.source.origin.requestedBy` | string \| null | no | Default `null`. |
| `production.brief` | object | yes |  |
| `production.brief.intent` | string | yes |  |
| `production.brief.context` | string \| null | no | Default `null`. |
| `production.brief.media` | object[] | no |  |
| `production.brief.media[].id` | string | yes |  |
| `production.brief.media[].kind` | enum | yes | One of: `image`, `video`. |
| `production.brief.media[].url` | url | yes |  |
| `production.brief.media[].name` | string | yes |  |
| `production.brief.media[].description` | string \| null | no | Default `null`. |
| `production.brief.code` | object[] | no |  |
| `production.brief.code[].path` | string | yes |  |
| `production.brief.code[].source` | string | yes |  |
| `production.brief.code[].status` | enum \| null | no | One of: `added`, `modified`, `removed`, `renamed`. Default `null`. |
| `production.brief.code[].additions` | integer | no | Default `0`. |
| `production.brief.code[].deletions` | integer | no | Default `0`. |
| `production.brief.code[].patch` | string \| null | no | Default `null`. |
| `production.brief.code[].digest` | string \| null | no | Default `null`. |
| `production.brief.code[].score` | number | no | Default `0`. |
| `production.brief.code[].sourceOmitted` | boolean | no |  |
| `production.brief.format` | object | no |  |
| `production.brief.format.template` | enum | no | One of: `changelog`, `launch`, `feature`, `digest`, `custom`. Default `changelog`. |
| `production.brief.format.durationMs` | integer | no | Default `10000`. |
| `production.brief.format.aspect` | enum | no | One of: `16:9`, `4:3`, `3:2`, `21:9`, `1:1`, `9:16`, `3:4`, `2:3`, `4:5`. Default `16:9`. |
| `production.brief.format.sound` | object | no |  |
| `production.brief.format.sound.voiceover` | boolean | no | Default `true`. |
| `production.brief.format.sound.music` | boolean | no | Default `true`. |
| `production.brief.format.sound.sfx` | boolean | no | Default `true`. |
| `production.brief.brand` | object | no |  |
| `production.brief.brand.name` | string \| null | no | Default `null`. |
| `production.brief.brand.logoUrl` | url \| null | no | Default `null`. |
| `production.brief.brand.primaryColor` | string \| null | no | Default `null`. |
| `production.brief.brand.backgroundColor` | string \| null | no | Default `null`. |
| `production.brief.brand.fontFamily` | string \| null | no | Default `null`. |
| `production.brief.brand.tone` | string \| null | no | Default `null`. |
| `production.pitch` | object \| null | yes |  |
| `production.pitch.version` | integer | yes |  |
| `production.pitch.verdict` | enum | no | One of: `video`, `skip`. Default `video`. |
| `production.pitch.verdictReason` | string \| null | no | Default `null`. |
| `production.pitch.title` | string | yes |  |
| `production.pitch.summary` | string | yes |  |
| `production.pitch.shots` | object[] | yes |  |
| `production.pitch.shots[].index` | integer | yes |  |
| `production.pitch.shots[].title` | string | yes |  |
| `production.pitch.shots[].durationMs` | integer | yes |  |
| `production.pitch.shots[].beat` | string \| null | no | Default `null`. |
| `production.pitch.shots[].visual` | string | yes |  |
| `production.pitch.shots[].text` | string \| null | no | Default `null`. |
| `production.pitch.shots[].voiceover` | string \| null | no | Default `null`. |
| `production.pitch.shots[].mediaIds` | string[] | no |  |
| `production.pitch.shots[].codePaths` | string[] | no |  |
| `production.pitch.music` | string \| null | no | Default `null`. |
| `production.pitch.estimatedCredits` | integer | yes |  |
| `production.pitch.createdAt` | datetime | yes |  |
| `production.greenlight` | object \| null | yes |  |
| `production.greenlight.mode` | enum | yes | One of: `manual`, `auto`. |
| `production.greenlight.actor` | string | yes |  |
| `production.greenlight.actorName` | string \| null | no |  |
| `production.greenlight.at` | datetime | yes |  |
| `production.greenlight.pitchVersion` | integer | yes |  |
| `production.greenlightMode` | enum | yes | One of: `manual`, `auto`. |
| `production.delivery` | object \| null | yes |  |
| `production.delivery.shareUrl` | url | yes |  |
| `production.delivery.projectId` | uuid | yes |  |
| `production.delivery.editUrl` | url | yes |  |
| `production.delivery.exportId` | uuid | yes |  |
| `production.delivery.deliveredAt` | datetime | yes |  |
| `production.projectId` | uuid \| null | yes |  |
| `production.creditsCharged` | integer | yes |  |
| `production.error` | string \| null | yes | Why the production failed, in words you can show a person. Never the render machine's own diagnostics. |
| `production.createdAt` | datetime | yes |  |
| `production.updatedAt` | datetime | yes |  |
| `production.terminal` | boolean | no | True once the stage is one the production never leaves (delivered, skipped, declined, cancelled, failed, or a terminal stage added later). Stop polling when it is true; it is computed by the server, so it stays right for stages your client does not know yet. Absent from payloads stored before the field existed. |
| `production.pageUrl` | url | no | The production's page in the Raylight app. Absent from payloads stored before the field existed. |

Example (curl):

```bash
curl -X POST https://api.raylight.app/v1/productions \
  -H "Authorization: Bearer $RAYLIGHT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "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"
  }
}'
```

## More

- Previous: [List productions](https://www.raylight.app/docs/reference/get-v1-productions.md)
- Next: [This month's usage and pitch cap](https://www.raylight.app/docs/reference/get-v1-productions-usage.md)
- All docs: https://www.raylight.app/llms.txt
- Everything in one file: https://www.raylight.app/llms-full.txt
- OpenAPI: https://api.raylight.app/v1/openapi.json
