# Staglight API > Prepaid-credit REST API and MCP server for e-commerce ads (images, video, voice, music, captions) in Spanish, English, German and Portuguese. No subscription. Base URL https://mobyvid.com/api/v1. Auth: `Authorization: Bearer ` (keys start with sl_live_). Paid POSTs take an `Idempotency-Key` header and `max_credits`. Jobs answer 202 with an id: poll GET /api/v1/jobs/?wait=30. Out of credits is 402 with needed and balance; failed jobs are refunded. MCP server: https://mobyvid.com/mcp. Spanish version: https://mobyvid.com/es/llms-full.txt ## Build with the API and MCP Make product images, video, voiceovers, music and finished ads from your own code or from an AI agent. One balance pays for the studio, the REST API and the MCP server. ### Introduction Everything you make in the studio you can make from code: product images, video clips, voiceovers, music and complete ads with review steps. Every job has a free price check first, and a job that fails is refunded automatically. Requests and answers are JSON, except uploads, which are multipart form data. Every path starts with `/api/v1`, the current version. - Base URL: `https://mobyvid.com/api/v1` - Auth: `Bearer ` - Format: `JSON` - OpenAPI: `/api/v1/openapi.json` **Get an API key in under a minute** Sign up, confirm your email, then open API keys in the studio and create a key. It is shown once, so copy it right away. You can give it a daily credit cap. [Get an API key](https://mobyvid.com/en/signup?next=/app/api) ### Authentication Create a key in [the studio](https://mobyvid.com/app/api) and give it a daily credit cap. The key is shown once. Keys start with `sl_live_`. Send it on every request as `Authorization: Bearer `. `X-API-Key: ` also works. The examples read it from `STAGLIGHT_API_KEY`. Terminal: ```bash export STAGLIGHT_API_KEY="sl_live_..." ``` > Never put a key in front-end code, a repo or a chat. If one leaks, revoke it in the studio. ### Lost your API key? Keys are stored only as a hash, so nobody can show you an existing key again, not even our support. Replace it yourself: sign in, open [API keys](https://mobyvid.com/app/api), create a new key, then revoke the old one. Your keys work side by side (up to 20), so you can switch without downtime: create the new key, put it in your app, then revoke the old one. A revoked key stops working at once. ### Credits 1 credit = US$0.01. Every job has a price before it runs, and that price is charged when the job is created. - Failed jobs are refunded automatically, in full. - Canceling a job before it starts refunds it. - Purchased credits never expire. - No subscription and no API surcharge: the API and MCP use the same prices as the studio. - A key with a daily cap stops at the cap with `402 daily_limit_reached`. Check the balance with `GET /api/v1/me`, or with its history at `GET /api/v1/credits`. Buy credits in [the studio](https://mobyvid.com/app/creditos). ### Rate limits Limits are counted per API key over a sliding minute (per account for signed-in browser sessions). All calls in a row share one count. | Limit | Counted per | Applies to | |---|---|---| | 30 per minute | API key | Calls that create: `POST /api/v1/characters`, `DELETE /api/v1/characters/{character_id}`, `PATCH /api/v1/characters/{character_id}`, `POST /api/v1/characters/{character_id}/sheet`, `POST /api/v1/formats/{format_id}/series`, `DELETE /api/v1/formats/{format_id}/series/{series_id}`, `PATCH /api/v1/formats/{format_id}/series/{series_id}`, `POST /api/v1/generate/image`, `POST /api/v1/generate/video`, `POST /api/v1/jobs`, `POST /api/v1/products`, `POST /api/v1/runs`, `POST /api/v1/runs/from-analysis`, `POST /api/v1/runs/propose`, `POST /api/v1/runs/{run_id}/approve`, `POST /api/v1/runs/{run_id}/redo`, `POST /api/v1/runs/{run_id}/scenes`, `POST /api/v1/scripts`, `POST /api/v1/scripts/{job_id}/make`, `PUT /api/v1/studio/library`, `POST /api/v1/studio/{studio_id}/helpers/{name}`, `POST /api/v1/studio/{studio_id}/helpers/{name}/cancel`, `POST /api/v1/studio/{studio_id}/runs`, `POST /api/v1/studio/{studio_id}/series`, `DELETE /api/v1/studio/{studio_id}/series/{series_id}`, `PATCH /api/v1/studio/{studio_id}/series/{series_id}`, `DELETE /api/v1/studio/{studio_id}/user-cards/{card_id}`, `POST /api/v1/uploads`, `POST /api/v1/uploads/from-library`, `POST /api/v1/uploads/from-url` | | 120 per minute | API key | Every other call with a key (41 endpoints) | | 60 per minute | IP address | Calls without a valid key | - Over the limit you get `429 rate_limited` with `retry_after` and a `Retry-After` header. - Long polls (`wait`) hold the request up to 50 s. - Uploads: images up to 20 MB, video and audio up to 1024 MB. The MCP server counts the same way: paid tools 30 per minute, free tools 120 per minute. ### Conventions The same rules hold on every endpoint. #### Errors Every error has the same shape. `code` is stable; `message` is for people. JSON: ```json { "error": { "code": "insufficient_credits", "message": "This needs 63 credits and the balance is 10. Buy credits to continue.", "needed": 63, "balance": 10 } } ``` | Status | Code | What to do | |---|---|---| | 400 | invalid_idempotency_key | Use 1 to 255 visible ASCII characters, for example a UUID. | | 401 | unauthorized | Missing, wrong or revoked key. | | 402 | insufficient_credits | Comes with `needed` and `balance`. Nothing was charged. Buy credits, then retry. | | 402 | daily_limit_reached | This key reached its daily cap. Raise the cap in the studio or wait. | | 403 | csrf_failed | Browser sessions only. API keys never see it. | | 404 | not_found | Wrong id, or it belongs to another account. | | 409 | price_above_max | The price is above your `max_credits`. Nothing was charged. | | 409 | idempotency_mismatch | This key was used with a different body. Use a new key. | | 409 | idempotency_in_progress | The first request with this key is still running. Retry in a second. | | 413 | file_too_large | Images up to 20 MB, video and audio up to 1024 MB. | | 415 | unsupported_media | Use JPG, PNG, WEBP, GIF, MP4, MOV, WEBM, MP3, WAV or M4A. | | 422 | invalid_request | Check `field` and `message`. | | 422 | content_not_allowed | The request asks for content we don't make. See the terms. | | 429 | rate_limited | Wait `retry_after` seconds (also in `Retry-After`). | | 503 | service_unavailable | Generation is paused for a few minutes. Nothing was charged. | #### Retries and idempotency Send an `Idempotency-Key` header (a UUID) on every POST that spends credits. - Same key and same body within 24 h: you get the first response again, with no new charge, and the header `Idempotent-Replayed: true`. - Same key with a different body: `409 idempotency_mismatch`. - First request still running: `409 idempotency_in_progress`. Retry in a second. - A request that ended in an error is forgotten, so you can retry it with the same key. After a timeout, don't resend blindly. Look the job up first: `GET /api/v1/jobs?idempotency_key=`. Cap the price with `max_credits` in the body. Combined with the key, a retry can never charge twice or charge more than you allowed. #### Pagination Lists take `limit` (default 20, at most 100) and answer with `next`. Pass it as `before` to get the next page. On the last page `next` is `null`. Lists that page this way: `GET /api/v1/characters`, `GET /api/v1/credits`, `GET /api/v1/jobs`, `GET /api/v1/media`, `GET /api/v1/products`, `GET /api/v1/runs`, `GET /api/v1/studio/{studio_id}/runs`. ### Your first job Generation is asynchronous. These examples use curl and read your key from `STAGLIGHT_API_KEY`. #### Upload a product photo Terminal: ```bash curl -s -X POST https://mobyvid.com/api/v1/uploads \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -F "file=@product.jpg" ``` The answer is a media object. Use its `id` (`med_...`) in job params. Images are re-encoded, which removes EXIF and GPS data. For a public link use `POST /api/v1/uploads/from-url`. #### Get the price (free) Terminal: ```bash curl -s -X POST https://mobyvid.com/api/v1/estimate \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"op": "image.nano-banana-pro", "params": {"prompt": "The red sneaker on white paper backdrop, soft morning light", "reference_images": ["med_19a2b3c4d5e6f7a8b9c0d1"], "aspect_ratio": "4:5"}}' ``` Same `op` and `params` as the job. You get `credits`, `usd`, `eta_seconds`, your `balance` and `enough_credits`. Nothing is charged. `GET /api/v1/models` lists every op with its fields. #### Create the job Terminal: ```bash curl -s -X POST https://mobyvid.com/api/v1/jobs \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"op": "image.nano-banana-pro", "params": {"prompt": "The red sneaker on white paper backdrop, soft morning light", "reference_images": ["med_19a2b3c4d5e6f7a8b9c0d1"], "aspect_ratio": "4:5"}, "max_credits": 40}' ``` The answer is `202` with the job and a `Location` header. The credits are charged now. If the price is above `max_credits` you get `409 price_above_max` and nothing is charged. #### Poll until it finishes Terminal: ```bash curl -s "https://mobyvid.com/api/v1/jobs/job_19a2b3c4d5e6f7a8b9c0d1?wait=30" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` `wait=30` holds the request until the job finishes (up to 50 s). Statuses: `queued`, `running`, `succeeded`, `failed` (refunded), `canceled` (refunded). JSON: ```json { "id": "job_19a2b3c4d5e6f7a8b9c0d1", "op": "image.nano-banana-pro", "status": "succeeded", "credits": 23, "outputs": [ { "id": "med_19a2b3c4d5e6f7a8b9c0d2", "kind": "image", "mime": "image/png", "width": 1664, "height": 2080, "url": "https://mobyvid.com/media/med_19a2b3c4d5e6f7a8b9c0d2/image.png?exp=...&sig=...", "download_url": "https://mobyvid.com/media/med_19a2b3c4d5e6f7a8b9c0d2/image.png?exp=...&sig=...&dl=1" } ], "error": null } ``` #### Download the result Terminal: ```bash curl -L -o product-ad.png "" ``` Links are signed and last about 24 h, with no auth header needed. Get the job again for fresh links. Files are kept 30 days. ### Complete ads (formats) A format run makes a finished ad in steps: script, keyframes, clips, voice, music and the final edit. It pauses at review steps so you can approve or redo a take. `GET /api/v1/formats` lists them with prices per tier. Terminal: ```bash curl -s -X POST https://mobyvid.com/api/v1/runs \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"format": "product_hero", "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1", "options": {"tier": "draft", "aspect": "9:16", "lang": "en"}}' ``` Poll `GET /api/v1/runs/?wait=30`. When `status` is `awaiting_approval`, approve with `POST /api/v1/runs//approve` or make a new take with `/redo`. Only the plan step is charged at the start; each approval charges the steps it starts. ### Webhooks There are no webhooks yet. Poll with `wait=30`: it returns as soon as the job finishes. ### Next - [API reference](https://mobyvid.com/en/docs/api): every endpoint with a curl example. - [MCP setup](https://mobyvid.com/en/docs/mcp): Claude Code, Codex and Cursor. - [Agent kit](https://mobyvid.com/en/docs/agent): agent prompt, skill and FFmpeg recipes. - [OpenAPI document](https://mobyvid.com/api/v1/openapi.json) for code generators. - [llms.txt](https://mobyvid.com/llms.txt) and [llms-full.txt](https://mobyvid.com/llms-full.txt) for language models. ## API reference Generated from the live [OpenAPI document](https://mobyvid.com/api/v1/openapi.json), so it always matches the server. - Base URL: `https://mobyvid.com/api/v1` - Auth: `Bearer ` - Format: `JSON` - OpenAPI: `/api/v1/openapi.json` New here? Start with the [quick guide](https://mobyvid.com/en/docs): keys, credits, idempotency and errors. ### Account and credits #### GET /api/v1/me Who am I and my balance ```bash curl -s "https://mobyvid.com/api/v1/me" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` The account behind this key or session - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### GET /api/v1/credits Balance and credit history | Name | In | Required | Description | |---|---|---|---| | limit | query | no | | | before | query | no | Ledger id from `next` | ```bash curl -s "https://mobyvid.com/api/v1/credits" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Newest movements first - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) ### Models and estimates #### GET /api/v1/models Models and tools you can run, with fields and prices | Name | In | Required | Description | |---|---|---|---| | kind | query | no | image \| video \| audio \| text \| edit \| analysis | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/models" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Public operations - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### GET /api/v1/presets Presets: ready-made recipes with their price Each preset names its op, the settings it fixes, the inputs it asks for, the prompt with its {detail} blank and its price at the default settings. Send it as a normal job: POST /jobs with the op and the filled params. | Name | In | Required | Description | |---|---|---|---| | page | query | no | video \| image | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/presets" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### POST /api/v1/estimate Price of a job before you run it (free) Takes the same `op` and `params` as POST /jobs. Nothing is charged. | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```json { "op": "video.kling-3", "params": { "duration": 10, "mode": "pro", "prompt": "Slow push-in" } } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/estimate" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"op": "video.kling-3", "params": {"duration": 10, "mode": "pro", "prompt": "Slow push-in"}}' ``` - `200` Credits it would cost now - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) ### Uploads #### POST /api/v1/uploads Upload a file (multipart field 'file', or the raw body) Images are re-encoded (metadata such as GPS is removed). The type is detected from the file contents. Limits: images 20 MB, video and audio MAX_UPLOAD_MB (1 GB by default). The body is streamed to a temp file on disk, never held in memory whole. | Name | In | Required | Description | |---|---|---|---| | filename | query | no | File name when sending the raw body | ```bash curl -s -X POST "https://mobyvid.com/api/v1/uploads" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -F "file=@product.jpg" ``` - `201` Stored. Use the id in job params. - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `413` file_too_large - `415` unsupported_media: use JPG, PNG, WEBP, GIF, MP4, MOV, WEBM, MP3, WAV or M4A - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### POST /api/v1/uploads/from-url Import a file from a public link Direct file links only (no social media pages). Private and local addresses and redirects are refused. ```json { "url": "https://shop.example.com/images/sneaker.jpg" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/uploads/from-url" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://shop.example.com/images/sneaker.jpg"}' ``` - `201` Stored - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `413` file_too_large - `415` unsupported_media: use JPG, PNG, WEBP, GIF, MP4, MOV, WEBM, MP3, WAV or M4A - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) - `502` fetch_failed: the link could not be downloaded #### POST /api/v1/uploads/from-library Add a motion library clip to your files ```json { "id": "" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/uploads/from-library" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"id": ""}' ``` - `201` Stored - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) ### Files #### GET /api/v1/media Your files (uploads and results) | Name | In | Required | Description | |---|---|---|---| | kind | query | no | image \| video \| audio | | origin | query | no | upload \| generated \| download | | limit | query | no | | | before | query | no | | ```bash curl -s "https://mobyvid.com/api/v1/media" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Newest first - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### GET /api/v1/media/{media_id} One file with fresh signed links | Name | In | Required | Description | |---|---|---|---| | media_id | path | yes | | ```bash curl -s "https://mobyvid.com/api/v1/media/med_19a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` The file - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### DELETE /api/v1/media/{media_id} Delete a file | Name | In | Required | Description | |---|---|---|---| | media_id | path | yes | | ```bash curl -s -X DELETE "https://mobyvid.com/api/v1/media/med_19a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Deleted; its links stop working - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) ### Jobs #### POST /api/v1/jobs Run any model or tool Validates, prices and charges the job, then queues it. If it fails, the credits come back automatically. | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Any unique string up to 255 characters (a UUID v4 is ideal). Retrying with the same key and body within 24 h returns the first response without charging again. | ```json { "max_credits": 80, "op": "video.kling-3", "params": { "aspect_ratio": "9:16", "duration": 5, "first_frame": "med_19a2b3c4d5e6f7a8b9c0d1", "prompt": "Slow push-in on the sneaker on a white table" }, "title": "Sneaker hero 9:16" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/jobs" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"max_credits": 80, "op": "video.kling-3", "params": {"aspect_ratio": "9:16", "duration": 5, "first_frame": "med_19a2b3c4d5e6f7a8b9c0d1", "prompt": "Slow push-in on the sneaker on a white table"}, "title": "Sneaker hero 9:16"}' ``` - `202` Accepted and charged. Poll the Location until it finishes. - `400` Bad Idempotency-Key - `401` Missing or invalid API key - `402` insufficient_credits (with needed and balance) or daily_limit_reached for this API key. Nothing was charged. - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) - `503` Generation is paused for a few minutes. Nothing was charged. #### GET /api/v1/jobs Your jobs, newest first | Name | In | Required | Description | |---|---|---|---| | status | query | no | queued \| running \| succeeded \| failed \| canceled | | run_id | query | no | | | op | query | no | Only jobs of this model or tool | | ops | query | no | Only jobs of these models or tools, comma separated | | idempotency_key | query | no | Find the job a keyed request created | | limit | query | no | | | before | query | no | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/jobs" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Jobs - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### GET /api/v1/jobs/{job_id} One job (optionally wait for it to finish) | Name | In | Required | Description | |---|---|---|---| | job_id | path | yes | | | wait | query | no | Hold the request up to this many seconds until the job finishes | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/jobs/job_19a2b3c4d5e6f7a8b9c0d1?wait=30" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` The job - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### POST /api/v1/jobs/{job_id}/cancel Cancel a job that has not started (refunded) | Name | In | Required | Description | |---|---|---|---| | job_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s -X POST "https://mobyvid.com/api/v1/jobs/job_19a2b3c4d5e6f7a8b9c0d1/cancel" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Canceled - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) ### Generate shortcuts #### POST /api/v1/generate/image Generate an image (shortcut for op image.) `{"model": "nano-banana-pro", "prompt": "..."}`. Fields go at the top level or inside `params`. | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Any unique string up to 255 characters (a UUID v4 is ideal). Retrying with the same key and body within 24 h returns the first response without charging again. | ```json { "aspect_ratio": "4:5", "model": "nano-banana-pro", "prompt": "The red sneaker on a white paper backdrop, soft light", "reference_images": [ "med_19a2b3c4d5e6f7a8b9c0d1" ] } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/generate/image" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"aspect_ratio": "4:5", "model": "nano-banana-pro", "prompt": "The red sneaker on a white paper backdrop, soft light", "reference_images": ["med_19a2b3c4d5e6f7a8b9c0d1"]}' ``` - `202` Accepted and charged. Poll the Location until it finishes. - `400` Bad Idempotency-Key - `401` Missing or invalid API key - `402` insufficient_credits (with needed and balance) or daily_limit_reached for this API key. Nothing was charged. - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) - `503` Generation is paused for a few minutes. Nothing was charged. #### POST /api/v1/generate/video Generate a video (shortcut for op video.) `{"model": "kling-3", "prompt": "...", "first_frame": "med_...", "duration": 5}`. | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Any unique string up to 255 characters (a UUID v4 is ideal). Retrying with the same key and body within 24 h returns the first response without charging again. | ```json { "aspect_ratio": "4:5", "model": "nano-banana-pro", "prompt": "The red sneaker on a white paper backdrop, soft light", "reference_images": [ "med_19a2b3c4d5e6f7a8b9c0d1" ] } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/generate/video" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"aspect_ratio": "4:5", "model": "nano-banana-pro", "prompt": "The red sneaker on a white paper backdrop, soft light", "reference_images": ["med_19a2b3c4d5e6f7a8b9c0d1"]}' ``` - `202` Accepted and charged. Poll the Location until it finishes. - `400` Bad Idempotency-Key - `401` Missing or invalid API key - `402` insufficient_credits (with needed and balance) or daily_limit_reached for this API key. Nothing was charged. - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) - `503` Generation is paused for a few minutes. Nothing was charged. ### Formats and runs #### GET /api/v1/formats Ad formats with their price per quality tier | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es or en for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/formats" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### GET /api/v1/formats/{format_id} One format with a step-by-step estimate | Name | In | Required | Description | |---|---|---|---| | format_id | path | yes | | | tier | query | no | draft \| standard \| premium | | aspect | query | no | | | lang | query | no | es or en for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/formats/product_hero" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### GET /api/v1/formats/{format_id}/series Your series or projects in this format, newest first | Name | In | Required | Description | |---|---|---|---| | format_id | path | yes | | ```bash curl -s "https://mobyvid.com/api/v1/formats/product_hero/series" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### POST /api/v1/formats/{format_id}/series Start a series: settings, files, people, scenes and story kept between takes | Name | In | Required | Description | |---|---|---|---| | format_id | path | yes | | ```json { "name": "La abuela olorosa" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/formats/product_hero/series" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "La abuela olorosa"}' ``` - `201` Successful Response #### GET /api/v1/formats/{format_id}/series/{series_id} One series: what it remembers and its takes | Name | In | Required | Description | |---|---|---|---| | format_id | path | yes | | | series_id | path | yes | | | lang | query | no | es or en for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/formats/product_hero/series/{series_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### PATCH /api/v1/formats/{format_id}/series/{series_id} Change a series (name, notes, what comes next, settings) | Name | In | Required | Description | |---|---|---|---| | format_id | path | yes | | | series_id | path | yes | | ```json { "next": "People want her to meet the neighbour.", "notes": "She lives alone with her cat and hates soap." } ``` ```bash curl -s -X PATCH "https://mobyvid.com/api/v1/formats/product_hero/series/{series_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"next": "People want her to meet the neighbour.", "notes": "She lives alone with her cat and hates soap."}' ``` - `200` Successful Response #### DELETE /api/v1/formats/{format_id}/series/{series_id} Delete a series: its memory goes, your files stay | Name | In | Required | Description | |---|---|---|---| | format_id | path | yes | | | series_id | path | yes | | ```bash curl -s -X DELETE "https://mobyvid.com/api/v1/formats/product_hero/series/{series_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### POST /api/v1/runs Start an ad format run (the plan step is charged now) | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es or en for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```json { "format": "problem_solution", "options": { "aspect": "9:16", "lang": "es", "tier": "draft" }, "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"format": "problem_solution", "options": {"aspect": "9:16", "lang": "es", "tier": "draft"}, "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1"}' ``` - `202` Successful Response #### GET /api/v1/runs Your format runs, newest first | Name | In | Required | Description | |---|---|---|---| | limit | query | no | | | before | query | no | | | status | query | no | running \| awaiting_approval \| needs_attention \| succeeded \| failed \| canceled | | format | query | no | Only runs of this format, e.g. motion_remix | | series | query | no | Only runs of this series | | lang | query | no | es or en for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/runs" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### POST /api/v1/runs/estimate Price a run before starting it (free) | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es or en for names and labels. Defaults to your account language. | ```json { "format": "motion_remix", "inputs": { "clip": "med_19a2b3c4d5e6f7a8b9c0d1", "mode": "restyle", "styles": [ "claymation", "watercolour" ] } } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/estimate" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"format": "motion_remix", "inputs": {"clip": "med_19a2b3c4d5e6f7a8b9c0d1", "mode": "restyle", "styles": ["claymation", "watercolour"]}}' ``` - `200` Successful Response #### POST /api/v1/formats/script_runner/parse Script Runner: split a script into scenes and price them (free) Scenes with their lines, cast (from your characters library), product flag, model, allowed lengths and price, plus the whole run's estimate. Errors (with line numbers) come back in errors with a 200; nothing is charged. Send the same script and scenes to POST /runs (options.script, options.scenes). | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es or en for names and labels. Defaults to your account language. | ```json { "aspect": "9:16", "scenes": [ { "model": "veo-fast" }, { "product": true } ], "script": "=== CHARACTER === Ana\n=== SCENE: Kitchen ===\nAna pours coffee.\n=== DIALOGUE ===\nAna: My coffee used to go cold before I sat down.\n=== SCENE: Car ===\nAna: Not any more.", "tier": "draft" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/formats/script_runner/parse" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"aspect": "9:16", "scenes": [{"model": "veo-fast"}, {"product": true}], "script": "=== CHARACTER === Ana\n=== SCENE: Kitchen ===\nAna pours coffee.\n=== DIALOGUE ===\nAna: My coffee used to go cold before I sat down.\n=== SCENE: Car ===\nAna: Not any more.", "tier": "draft"}' ``` - `200` Successful Response #### POST /api/v1/runs/propose Propose a format, tier and options from a written brief (planning step charged) | Name | In | Required | Description | |---|---|---|---| | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```json { "brief": "UGC de 15 s para mi sérum de vitamina C, mujer de 30, tono cercano, para Reels", "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/propose" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"brief": "UGC de 15 s para mi sérum de vitamina C, mujer de 30, tono cercano, para Reels", "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1"}' ``` - `200` Successful Response #### POST /api/v1/runs/from-analysis Make the ad of an analysis with your product (script_runner run, the plan step is charged now) Starts a script_runner run whose script is the analysis's replication scenes (chosen hook first) and whose product is yours. Same price, approval steps and refunds as POST /runs with format script_runner; price it first with POST /runs/estimate. | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es or en for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```json { "analysis_id": "job_19a2b3c4d5e6f7a8b9c0d1", "hook_index": 2, "options": { "tier": "draft" }, "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/from-analysis" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"analysis_id": "job_19a2b3c4d5e6f7a8b9c0d1", "hook_index": 2, "options": {"tier": "draft"}, "product_id": "prd_19a2b3c4d5e6f7a8b9c0d1"}' ``` - `202` Successful Response #### GET /api/v1/analyses/{analysis_id}/script The script that Make this ad would start with (free) {script, notes, hook, hook_index, lang, aspect, format}: the script_runner inputs built from the analysis's replication block. The block itself is in data.replication of GET /api/v1/jobs/{id} for the analysis job. | Name | In | Required | Description | |---|---|---|---| | analysis_id | path | yes | | | hook_index | query | no | 0 to 4, from replication.hooks | ```bash curl -s "https://mobyvid.com/api/v1/analyses/{analysis_id}/script" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### GET /api/v1/runs/{run_id} One run (optionally wait for a change) | Name | In | Required | Description | |---|---|---|---| | run_id | path | yes | | | wait | query | no | Hold up to this many seconds while it is running | | lang | query | no | es or en for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/runs/run_19a2b3c4d5e6f7a8b9c0d1?wait=30" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### GET /api/v1/runs/{run_id}/outputs.zip Every finished file of a run in one zip (free) The run's outputs (a Motion Remix run: one file per finished version) stored, not recompressed (they are already H.264), in run order. 404 until something is finished. | Name | In | Required | Description | |---|---|---|---| | run_id | path | yes | | ```bash curl -s "https://mobyvid.com/api/v1/runs/run_19a2b3c4d5e6f7a8b9c0d1/outputs.zip" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### POST /api/v1/runs/{run_id}/approve Approve the step under review (may start paid steps) | Name | In | Required | Description | |---|---|---|---| | run_id | path | yes | | | lang | query | no | es or en for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```json { "edits": { "cta": "Pídelo hoy" }, "step": "plan" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/run_19a2b3c4d5e6f7a8b9c0d1/approve" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"edits": {"cta": "Pídelo hoy"}, "step": "plan"}' ``` - `200` Successful Response #### POST /api/v1/runs/{run_id}/redo New take of one item (charged again) | Name | In | Required | Description | |---|---|---|---| | run_id | path | yes | | | lang | query | no | es or en for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```json { "index": 1, "prompt": "Same scene, warmer morning light", "step": "keyframes" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/run_19a2b3c4d5e6f7a8b9c0d1/redo" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"index": 1, "prompt": "Same scene, warmer morning light", "step": "keyframes"}' ``` - `200` Successful Response #### POST /api/v1/runs/{run_id}/scenes Script Runner storyboard: edit, remove, undo or add a scene While the script or the storyboard waits for approval. Free except add at the storyboard (one still, storyboard.add_credits in GET /runs/{id}). | Name | In | Required | Description | |---|---|---|---| | run_id | path | yes | | | lang | query | no | es or en for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```json { "action": "edit", "index": 1, "scene": { "model": "veo-fast", "seconds": 8 } } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/run_19a2b3c4d5e6f7a8b9c0d1/scenes" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"action": "edit", "index": 1, "scene": {"model": "veo-fast", "seconds": 8}}' ``` - `200` Successful Response #### POST /api/v1/runs/{run_id}/cancel Cancel a run (jobs not started yet are refunded) | Name | In | Required | Description | |---|---|---|---| | run_id | path | yes | | | lang | query | no | es or en for names and labels. Defaults to your account language. | ```bash curl -s -X POST "https://mobyvid.com/api/v1/runs/run_19a2b3c4d5e6f7a8b9c0d1/cancel" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response ### Products #### GET /api/v1/products Your products (brand kit) | Name | In | Required | Description | |---|---|---|---| | limit | query | no | | | before | query | no | | ```bash curl -s "https://mobyvid.com/api/v1/products" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Newest first - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### POST /api/v1/products Save a product ```json { "audience": "Corredores urbanos de 25 a 40", "benefits": [ "Pesa 210 g", "Suela que agarra en mojado" ], "description": "Zapatilla ligera de malla para correr a diario.", "lang": "es", "media_ids": [ "med_19a2b3c4d5e6f7a8b9c0d1" ], "name": "Zapatilla Runner Roja", "price_text": "59,90 €", "url": "https://shop.example.com/runner" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/products" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"audience": "Corredores urbanos de 25 a 40", "benefits": ["Pesa 210 g", "Suela que agarra en mojado"], "description": "Zapatilla ligera de malla para correr a diario.", "lang": "es", "media_ids": ["med_19a2b3c4d5e6f7a8b9c0d1"], "name": "Zapatilla Runner Roja", "price_text": "59,90 €", "url": "https://shop.example.com/runner"}' ``` - `201` Saved - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### GET /api/v1/products/{product_id} One product | Name | In | Required | Description | |---|---|---|---| | product_id | path | yes | | ```bash curl -s "https://mobyvid.com/api/v1/products/prd_19a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` The product - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### PATCH /api/v1/products/{product_id} Change some fields of a product | Name | In | Required | Description | |---|---|---|---| | product_id | path | yes | | ```json { "price_text": "49,90 €" } ``` ```bash curl -s -X PATCH "https://mobyvid.com/api/v1/products/prd_19a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"price_text": "49,90 €"}' ``` - `200` Updated - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) #### DELETE /api/v1/products/{product_id} Delete a product (its photos stay) | Name | In | Required | Description | |---|---|---|---| | product_id | path | yes | | ```bash curl -s -X DELETE "https://mobyvid.com/api/v1/products/prd_19a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Deleted - `401` Missing or invalid API key - `403` csrf_failed (browser sessions only) - `404` Not found, or it belongs to another account - `429` rate_limited: wait retry_after seconds (also in the Retry-After header) ### Ad Studio #### GET /api/v1/studio Ad Studio: every studio with its from-price | Name | In | Required | Description | |---|---|---|---| | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/studio" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### GET /api/v1/studio/library Your saved inspiration ads (Recreate, Edit Product Photo) ```bash curl -s "https://mobyvid.com/api/v1/studio/library" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### PUT /api/v1/studio/library Replace your saved inspiration ads ```bash curl -s -X PUT "https://mobyvid.com/api/v1/studio/library" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` - `200` Successful Response - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field #### GET /api/v1/studio/{studio_id} One studio: inputs, cards with price, helpers | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/studio/{studio_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### DELETE /api/v1/studio/{studio_id}/user-cards/{card_id} Delete one of your saved cards in this studio | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | card_id | path | yes | | ```bash curl -s -X DELETE "https://mobyvid.com/api/v1/studio/{studio_id}/user-cards/{card_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### GET /api/v1/studio/{studio_id}/series Your series in this studio, newest first | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | ```bash curl -s "https://mobyvid.com/api/v1/studio/{studio_id}/series" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### POST /api/v1/studio/{studio_id}/series Start a series: settings, people, scenes and story kept between takes | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | ```json { "name": "La abuela olorosa" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/studio/{studio_id}/series" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "La abuela olorosa"}' ``` - `201` Successful Response - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field #### GET /api/v1/studio/{studio_id}/series/{series_id} One series: what it remembers and its episodes | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | series_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/studio/{studio_id}/series/{series_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### PATCH /api/v1/studio/{studio_id}/series/{series_id} Change a series (name, notes, what comes next, settings) | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | series_id | path | yes | | ```json { "next": "People want her to meet the neighbour.", "notes": "She lives alone with her cat and hates soap." } ``` ```bash curl -s -X PATCH "https://mobyvid.com/api/v1/studio/{studio_id}/series/{series_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"next": "People want her to meet the neighbour.", "notes": "She lives alone with her cat and hates soap."}' ``` - `200` Successful Response - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field #### DELETE /api/v1/studio/{studio_id}/series/{series_id} Delete a series: its memory goes, your files stay | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | series_id | path | yes | | ```bash curl -s -X DELETE "https://mobyvid.com/api/v1/studio/{studio_id}/series/{series_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### POST /api/v1/studio/{studio_id}/estimate Price a click before starting it (free) | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```json { "aspect": "4:5", "card": "hero", "inputs": { "language": "es", "niche": "vitamin C serum", "rating": "4.8/5 (230 reviews)", "resolution": "2K" }, "media": { "product": [ "med_19a2b3c4d5e6f7a8b9c0d1" ] }, "quality": "final", "real": [ "rating" ], "variants": 2 } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/studio/{studio_id}/estimate" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"aspect": "4:5", "card": "hero", "inputs": {"language": "es", "niche": "vitamin C serum", "rating": "4.8/5 (230 reviews)", "resolution": "2K"}, "media": {"product": ["med_19a2b3c4d5e6f7a8b9c0d1"]}, "quality": "final", "real": ["rating"], "variants": 2}' ``` - `200` Successful Response - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field #### POST /api/v1/studio/{studio_id}/check Free checks of a click (studios that have them, e.g. Ranking Shorts) | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```json { "aspect": "4:5", "card": "hero", "inputs": { "language": "es", "niche": "vitamin C serum", "rating": "4.8/5 (230 reviews)", "resolution": "2K" }, "media": { "product": [ "med_19a2b3c4d5e6f7a8b9c0d1" ] }, "quality": "final", "real": [ "rating" ], "variants": 2 } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/studio/{studio_id}/check" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"aspect": "4:5", "card": "hero", "inputs": {"language": "es", "niche": "vitamin C serum", "rating": "4.8/5 (230 reviews)", "resolution": "2K"}, "media": {"product": ["med_19a2b3c4d5e6f7a8b9c0d1"]}, "quality": "final", "real": ["rating"], "variants": 2}' ``` - `200` Successful Response - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field #### POST /api/v1/studio/{studio_id}/runs Start a studio click (each image is a charged job) | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Any unique string up to 255 characters (a UUID v4 is ideal). Retrying with the same key and body within 24 h returns the first response without charging again. | ```json { "aspect": "4:5", "card": "hero", "inputs": { "language": "es", "niche": "vitamin C serum", "rating": "4.8/5 (230 reviews)", "resolution": "2K" }, "media": { "product": [ "med_19a2b3c4d5e6f7a8b9c0d1" ] }, "quality": "final", "real": [ "rating" ], "variants": 2 } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/studio/{studio_id}/runs" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"aspect": "4:5", "card": "hero", "inputs": {"language": "es", "niche": "vitamin C serum", "rating": "4.8/5 (230 reviews)", "resolution": "2K"}, "media": {"product": ["med_19a2b3c4d5e6f7a8b9c0d1"]}, "quality": "final", "real": ["rating"], "variants": 2}' ``` - `202` Accepted and charged. Poll GET .../runs/{run_id} or each job. - `400` Bad Idempotency-Key - `402` insufficient_credits (with needed and balance) or daily_limit_reached for this API key. Nothing was charged. - `404` Not found, or it belongs to another account - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `422` invalid_request, with the offending field - `503` Generation is paused for a few minutes. Nothing was charged. #### GET /api/v1/studio/{studio_id}/runs Your earlier clicks in this studio, newest first | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | | limit | query | no | | | before | query | no | | | series | query | no | | ```bash curl -s "https://mobyvid.com/api/v1/studio/{studio_id}/runs" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### GET /api/v1/studio/{studio_id}/runs/{run_id} One studio click with its jobs | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | run_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/studio/{studio_id}/runs/run_19a2b3c4d5e6f7a8b9c0d1" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### GET /api/v1/studio/{studio_id}/runs/{run_id}/outputs.zip Every finished file of a studio click in one zip (free) | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | run_id | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | ```bash curl -s "https://mobyvid.com/api/v1/studio/{studio_id}/runs/run_19a2b3c4d5e6f7a8b9c0d1/outputs.zip" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response - `404` Not found, or it belongs to another account #### POST /api/v1/studio/{studio_id}/helpers/{name} Run a copy helper (1 or 2 credits) | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | name | path | yes | | | lang | query | no | es, en, de or pt for names and labels. Defaults to your account language. | | Idempotency-Key | header | no | Optional. Any unique string up to 255 characters (a UUID v4 is ideal). Retrying with the same key and body within 24 h returns the first response without charging again. | ```json { "payload": { "url": "shop.example.com" } } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/studio/{studio_id}/helpers/{name}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"payload": {"url": "shop.example.com"}}' ``` - `200` The finished job; its data holds the result - `202` Still running: poll GET /api/v1/jobs/{id} - `400` Bad Idempotency-Key - `402` insufficient_credits (with needed and balance) or daily_limit_reached for this API key. Nothing was charged. - `404` Not found, or it belongs to another account - `409` idempotency_mismatch, idempotency_in_progress, price_above_max or not_cancelable - `422` invalid_request, with the offending field - `503` Generation is paused for a few minutes. Nothing was charged. #### POST /api/v1/studio/{studio_id}/helpers/{name}/cancel Cancel a helper's page read (free) Cancel pressed while a page is read (the product link scan): the read stops at its next step and charges nothing; when its 1-credit job was already made it is canceled and refunded if it has not started. | Name | In | Required | Description | |---|---|---|---| | studio_id | path | yes | | | name | path | yes | | ```json { "token": "k3v9q2x7m1p4" } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/studio/{studio_id}/helpers/{name}/cancel" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"token": "k3v9q2x7m1p4"}' ``` - `200` Successful Response - `404` Not found, or it belongs to another account - `422` invalid_request, with the offending field ### Characters #### GET /api/v1/characters Your characters | Name | In | Required | Description | |---|---|---|---| | limit | query | no | | | before | query | no | | ```bash curl -s "https://mobyvid.com/api/v1/characters" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### POST /api/v1/characters Save a character ```json { "description": "Woman in her 30s, curly dark hair, denim jacket", "images": { "front": "med_19a2b3c4d5e6f7a8b9c0d1" }, "likeness_consent": true, "name": "Ana", "voice": { "id": "hpp4J3VqNfWAUOO0d1Us", "speed": 1.0, "stability": 0.5 } } ``` ```bash curl -s -X POST "https://mobyvid.com/api/v1/characters" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"description": "Woman in her 30s, curly dark hair, denim jacket", "images": {"front": "med_19a2b3c4d5e6f7a8b9c0d1"}, "likeness_consent": true, "name": "Ana", "voice": {"id": "hpp4J3VqNfWAUOO0d1Us", "speed": 1.0, "stability": 0.5}}' ``` - `201` Successful Response #### GET /api/v1/characters/{character_id} One character | Name | In | Required | Description | |---|---|---|---| | character_id | path | yes | | ```bash curl -s "https://mobyvid.com/api/v1/characters/{character_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### PATCH /api/v1/characters/{character_id} Change some fields of a character | Name | In | Required | Description | |---|---|---|---| | character_id | path | yes | | ```bash curl -s -X PATCH "https://mobyvid.com/api/v1/characters/{character_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` - `200` Successful Response #### DELETE /api/v1/characters/{character_id} Delete a character (its pictures stay in your files) | Name | In | Required | Description | |---|---|---|---| | character_id | path | yes | | ```bash curl -s -X DELETE "https://mobyvid.com/api/v1/characters/{character_id}" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" ``` - `200` Successful Response #### POST /api/v1/characters/{character_id}/sheet Make a character sheet: front, side and close-up from one picture (charged once) Three images of the same person, priced together (sheet_credits) and charged when you click. Each image is refunded if it fails. The images replace the character's front, side and close-up when they finish. | Name | In | Required | Description | |---|---|---|---| | character_id | path | yes | | | Idempotency-Key | header | no | Optional. Same key and body within 24 h returns the first response, no new charge. | ```bash curl -s -X POST "https://mobyvid.com/api/v1/characters/{character_id}/sheet" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}' ``` - `202` Successful Response ### Other #### POST /api/v1/messages Send Message ```bash curl -s -X POST "https://mobyvid.com/api/v1/messages" \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` - `201` Successful Response ## Connect your agent over MCP Claude Code, Codex, Cursor and chat apps such as claude.ai can use Staglight as tools: estimate, generate, poll and download, from the same balance as the studio. ### Chat apps in under a minute claude.ai, Claude Desktop and other chat apps with custom connectors sign in with your Staglight account (OAuth 2.1), so nobody pastes a key. You approve each app once and give it a spending cap. #### Claude [Add to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=Staglight&connectorUrl=https%3A%2F%2Fmobyvid.com%2Fmcp) 1. Press the button above, or in Claude open Customize > Connectors > Add custom connector with the name Staglight and the URL `https://mobyvid.com/mcp`. 2. Press Continue, keep Sign in now and press Add, then sign in with your Staglight account, choose what it may do and its spending cap, then Allow. On the Free plan you can add one custom connector; on Team and Enterprise an owner adds it first and you press Connect. 3. In a chat, press + > Connectors and switch Staglight on. Attach a product photo and ask: make me an ad for this product. #### ChatGPT 1. In ChatGPT on the web, open Settings > Plugins and press + > Add custom MCP server. It needs a Business, Enterprise or Edu workspace that allows it; on Pro it can only read. 2. Name it Staglight, paste the URL `https://mobyvid.com/mcp`, choose OAuth, tick I understand and want to continue, then Create as a plugin. 3. Sign in with your Staglight account and Allow. 4. In a new chat, type @, pick Staglight and ask for your ad. > Every connection is listed in [Settings > Connections](https://mobyvid.com/app/ajustes#conexiones) with its spending cap (any 24 hours); Disconnect ends its access at once. Tools that spend, upload or change things need the Create ads and spend credits permission; a view-only connection can read but not spend. ### Server - URL: `https://mobyvid.com/mcp` - Transport: `Streamable HTTP` - Auth: `OAuth 2.1 · Bearer ` - Protocol: `2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26` Create a key with a daily cap in [the studio](https://mobyvid.com/app/api) and save it as `STAGLIGHT_API_KEY`. Terminal: ```bash export STAGLIGHT_API_KEY="sl_live_..." ``` ### Claude Code One command, for every project on this computer. The shell fills in the key and Claude Code stores it in `~/.claude.json`. Terminal: ```bash claude mcp add --transport http staglight https://mobyvid.com/mcp \ --header "Authorization: Bearer $STAGLIGHT_API_KEY" --scope user ``` > Prefer to sign in instead of using a key? Run the command without the `--header` part, then type `/mcp` in Claude Code and pick staglight to sign in with your Staglight account. Check that it connected: Terminal: ```bash claude mcp get staglight ``` To share it with a team in the repo, add `.mcp.json`. Each person keeps the key in their own `STAGLIGHT_API_KEY`. .mcp.json: ```json { "mcpServers": { "staglight": { "type": "http", "url": "https://mobyvid.com/mcp", "headers": { "Authorization": "Bearer ${STAGLIGHT_API_KEY}" }, "timeout": 600000 } } } ``` > Name the variable `STAGLIGHT_API_KEY`. Claude Code leaves some credential variables with reserved names empty in remote headers, so use this one. ### Codex Terminal: ```bash codex mcp add staglight --url https://mobyvid.com/mcp --bearer-token-env-var STAGLIGHT_API_KEY ``` Or write it by hand in the Codex config: ~/.codex/config.toml: ```toml [mcp_servers.staglight] url = "https://mobyvid.com/mcp" bearer_token_env_var = "STAGLIGHT_API_KEY" startup_timeout_sec = 20 tool_timeout_sec = 120 ``` > Codex stops a tool call after 60 s by default. Every tool answers in under 50 s, and `get_job` waits 50 s at most. ### Cursor In `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one project: ~/.cursor/mcp.json: ```json { "mcpServers": { "staglight": { "url": "https://mobyvid.com/mcp", "headers": { "Authorization": "Bearer ${env:STAGLIGHT_API_KEY}" } } } } ``` ### Claude Desktop and claude.ai claude.ai and Claude Desktop connect by signing in with your Staglight account: see [Chat apps](#chat-apps). If you'd rather use an API key there, bridge from your computer with the community package `mcp-remote` in `claude_desktop_config.json`: claude_desktop_config.json: ```json { "mcpServers": { "staglight": { "command": "npx", "args": [ "mcp-remote", "https://mobyvid.com/mcp", "--header", "Authorization:${AUTH_HEADER}", "--transport", "http-only" ], "env": { "AUTH_HEADER": "Bearer sl_live_..." } } } } ``` > `mcp-remote` is a third-party package that runs on your computer with your key. Review it before you use it. ### Tools Paid tools take `max_credits` and `idempotency_key`. Every result has a short text, the data as JSON and, for finished files, links to download. | Tool | What it does | Cost | |---|---|---| | get_balance | Balance, the key's daily cap and what is left today. | Free | | buy_credits_link | Link to buy credits in the studio. | Free | | list_models | Models and tools with fields and starting price. | Free | | estimate_cost | Exact price of a call before you make it. | Free | | upload_from_url | Imports a file from a direct link and returns its media id. | Free | | upload_file | Stores a photo or short clip attached in the chat (base64, up to 12 MB) and returns its media id. | Free | | upload_image | Same as upload_file. | Free | | list_media | Uploads and results with signed links. | Free | | generate_image | Product images with Nano Banana, GPT Image, Seedream or Flux. | Paid | | generate_video | Clips with Kling, Veo, Seedance, Hailuo or Wan, plus avatars and motion control. | Paid | | generate_voice | Voiceover with ElevenLabs. If it fails, one retry with a similar backup voice at no extra cost (no word timings); send allow_fallback: false to get a refund instead. | Paid | | generate_music | Background music with Suno. | Paid | | upscale | Upscales an image or a video. | Paid | | remove_background | Cuts the product out (transparent PNG). | Paid | | add_captions | Burns word-timed captions into a video. | Paid | | reburn_captions | Burns captions again with edited lines or a new style, without transcribing. Free, with a daily limit. | Paid | | merge_videos | Joins clips into one video at one aspect. | Paid | | clean_export | Copy without location, device, editing or AI model data; keeps an AI-generated note unless you switch it off. | Paid | | analyze_ad | Grades an ad from a file or a post link and writes new hooks and a script to remake it with your product. | Paid | | create_job | Runs any op from list_models. | Paid | | social_download | Downloads a TikTok, Instagram, Facebook or Shorts post into your library. Free, with a daily limit. | Paid | | list_characters | Lists your saved characters with their pictures and voice, and the character sheet price. | Free | | get_character | One saved character with its pictures and voice. | Free | | create_character | Saves a person once (name, 1 to 3 pictures, optional voice) to cast in Script Runner scenes. Needs the likeness confirmation. | Free | | update_character | Changes a character's name, description, pictures or voice. | Free | | delete_character | Deletes a saved character. | Free | | make_character_sheet | Draws front, side and close-up of one person from one picture. Paid, charged once. | Paid | | parse_script | Reads a script and shows each scene with its cast, model, length and price. Free. | Free | | edit_scenes | Edits, removes, restores or adds Script Runner scenes before the clips are made. | Paid | | get_job | Status and results, waits up to 50 s. | Free | | list_jobs | Jobs, also by idempotency_key after a timeout or by Ad Studio run. | Free | | cancel_job | Cancels a job that has not started (refunded). | Free | | list_formats | Ad formats with prices per tier. | Free | | list_products | Saved products to use in ads. | Free | | create_product | Saves a product from its name and 1 to 8 photos, to reuse in every ad. | Free | | get_product | One saved product with its photo links. | Free | | create_ad | Starts a complete ad from a format. With auto it runs to the end within max_credits. | Paid | | propose_ad | Proposes a format and price from a written brief. | Paid | | create_ad_from_brief | Proposes and starts the ad in one call. With auto it finishes on its own within max_credits. | Paid | | get_ad | State of an ad run, waits up to 50 s. | Free | | approve_ad_step | Approves a review step and starts the next ones, or hands the rest to auto mode. | Paid | | redo_ad_step | New take of one item (charged again). | Paid | | motion_remix | A new version of your own clip per character, product or style: drafts, pick, finish. Replace mode and a person or face as target need likeness: true (since 2026-10-31); the precise swap stops at step mask for your check. | Paid | | cancel_ad | Stops a run and refunds jobs that have not started. | Free | | studio_list | Ad Studio studios with their cards, inputs and prices. | Free | | studio_estimate | Exact price of one Ad Studio click, with the balance. Free. | Free | | studio_check | Runs a studio's free checks before you pay (Ranking Shorts: every level and the variety between them). | Free | | studio_run | Makes Ad Studio images: one charged job per image, refunded if it fails. | Paid | | studio_helper | Runs an Ad Studio helper (a small flat price, see studio_list). link_scan reads one product page and returns its photos as your uploads, the name and a one-line description. | Paid | | studio_series | A channel's series or a shop's project: what it remembers and its episodes. | Free | | studio_series_save | Start or change a series: its notes, what the audience asked for next, or go back to an earlier episode. | Free | | ad_series | A format's series or project: what it remembers and its takes. | Free | | ad_series_save | Start or change a format's series: its notes, what the audience asked for next, or go back to an earlier take. | Free | | studio_get_run | State of an Ad Studio click: every job, follow-up steps included, and the finished files. | Free | ### Money rules for agents - Call `estimate_cost` before any paid tool. It's free and can price the exact call: `tool` plus `arguments`. - Send `max_credits` and a fresh `idempotency_key` with every paid call. - Paid tools answer at once with a `job_id` or `run_id`. Poll `get_job` or `get_ad` with `wait_seconds=50`. - After a timeout, call `list_jobs` with the same `idempotency_key` before paying again. - Failed jobs are refunded automatically. Out of credits, `buy_credits_link` gives the user a link to the studio. No payments go through MCP. - Give every agent its own key with a daily cap. At the cap it gets `daily_limit_reached` and stops. - With `auto: true`, `create_ad` and `create_ad_from_brief` approve each review step on their own while the whole run stays within `max_credits`. If the next step would go over, the run waits at that step with `auto_stopped` and nothing more is charged. ### Errors Tool errors come back as a result with `isError: true` and the same codes as the REST API. Out of credits includes `needed`, `balance` and `topup_url`. A missing or wrong key is HTTP 401 with `WWW-Authenticate`. JSON: ```json { "content": [ { "type": "text", "text": "Error insufficient_credits: This needs 63 credits and the balance is 10. ..." } ], "structuredContent": { "error": { "code": "insufficient_credits", "message": "This needs 63 credits and the balance is 10. Buy credits to continue.", "status": 402, "needed": 63, "balance": 10, "charged": false, "topup_url": "https://mobyvid.com/app/creditos?need=53" } }, "isError": true } ``` ### Test with curl The server answers plain JSON over POST, with no sessions, so curl works: Terminal: ```bash curl -s https://mobyvid.com/mcp \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1.0"}}}' ``` Terminal: ```bash curl -s https://mobyvid.com/mcp \ -H "Authorization: Bearer $STAGLIGHT_API_KEY" \ -H "Content-Type: application/json" \ -H "MCP-Protocol-Version: 2025-11-25" \ -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "get_balance", "arguments": {}}}' ``` ## Agent kit A prompt to paste, a skill your agent loads by itself and FFmpeg recipes to finish ads. ### Agent prompt Paste it at the start of a Claude Code, Codex or Cursor session, after you [connect the MCP server](https://mobyvid.com/en/docs/mcp). Prompt: ```text You make e-commerce ads with Staglight through its MCP tools (server "staglight"). Docs: https://mobyvid.com/en/docs Money rules - Before anything paid, tell me the price (estimate_cost is free) and ask me once. Never spend more than I approved. - Send max_credits and a new idempotency_key (a UUID) with every paid call. - Paid tools answer at once with a job_id or run_id. Poll get_job, get_ad or studio_get_run with wait_seconds=50 until it finishes. - Just a product link ("make me an ad for https://..."): studio_helper(studio=link_ad, name=link_scan, payload.url, always with an idempotency_key that a retry reuses, or the retry reads and charges again) reads that page (the scan's small flat price) and returns the photos, name and a line; then studio_estimate and studio_run on link_ad with the best photo. - After a timeout or an error, never repeat a paid call blindly: call list_jobs with the same idempotency_key first. - If credits run out, give me the link from buy_credits_link and stop. How to work - When I attach a product photo or clip: upload_file, then create_product with its name and photos. Or use list_products, or upload_from_url with a direct file link. - A finished video ad: estimate_cost for create_ad (tier "draft" unless I ask for better), tell me the price, and after my yes call create_ad with auto=true and max_credits = the price I approved. If my request is vague, use create_ad_from_brief with auto=true and the budget I approved. - Poll get_ad until it is succeeded and give me the file. If it stops (auto_stopped), tell me why and what it costs to continue. - Product images (store carousel, static ads, UGC photos): the Ad Studio with studio_list, studio_estimate, studio_run, then studio_get_run. - Download finished files with curl -L -o into ./ads/ and finish them with ffmpeg: 9:16 at 1080x1920, captions, -14 LUFS. - Keep the AI-generated label. Never present a creator-style video as a real customer review. ``` ### Ask in your own words With the MCP server connected, attach a product photo and say what you want. The agent uploads it, saves the product, tells you the price, asks you once and hands back the finished file. For example: - "Make me a 15 second UGC ad for this serum, for Reels. Keep it under $3." - "Here are 3 photos of my ceramic mug. Save it as a product and make a vertical product video in Spanish." - "Turn this photo into a store carousel: white background, a lifestyle shot and a close-up of the label." - "Something for TikTok that sells this water bottle to gym people. Tell me the price first." Your OK sets a credit cap for the whole ad: the run never spends more, and stops to ask if the next step would go over it. ### Skill for Claude Code, Codex and Cursor The skill tells your agent when to use Staglight, the money rules and how to finish ads. Agents load it on their own when the task fits. - [SKILL.md](https://mobyvid.com/agent/staglight-ads/SKILL.md) to read it first. - [staglight-ads.zip](https://mobyvid.com/agent/staglight-ads.zip) with the skill, the FFmpeg recipes and a finishing script. - It needs the MCP server connected, plus curl and ffmpeg on the computer. #### Claude Code Terminal: ```bash mkdir -p ~/.claude/skills && curl -sL -o /tmp/staglight-ads.zip https://mobyvid.com/agent/staglight-ads.zip \ && unzip -o /tmp/staglight-ads.zip -d ~/.claude/skills/ ``` #### Codex and Cursor Terminal: ```bash mkdir -p ~/.agents/skills && curl -sL -o /tmp/staglight-ads.zip https://mobyvid.com/agent/staglight-ads.zip \ && unzip -o /tmp/staglight-ads.zip -d ~/.agents/skills/ ``` Claude Code picks up the skill in the session you have open; other agents may need a new session to see it. ### Claude Code plugin files For teams that run their own plugin marketplace: [the plugin folder](https://mobyvid.com/agent/staglight-plugin.zip) has `.claude-plugin/plugin.json`, `.mcp.json` and the skill. On install it asks for the key and keeps it in the system's secure storage. Terminal: ```bash curl -sL -o staglight-plugin.zip https://mobyvid.com/agent/staglight-plugin.zip && unzip -o staglight-plugin.zip claude plugin validate ./staglight ``` > For one person, the `claude mcp add` command and the skill above do the same job. ### Finish ads with FFmpeg Download the takes, then finish them on your computer. Output: H.264 yuv420p, AAC at 48 kHz, `+faststart` so the video plays before it fully loads. Captions need FFmpeg built with libass. [finish_ad.sh](https://mobyvid.com/agent/staglight-ads/scripts/finish_ad.sh) fits a take to the aspect, sets loudness to -14 LUFS in two passes and adds faststart: Terminal: ```bash curl -sLO https://mobyvid.com/agent/staglight-ads/scripts/finish_ad.sh bash finish_ad.sh take.mp4 final.mp4 9:16 ``` #### Probe a file Duration, size, frame rate and sample rate as JSON. Terminal: ```bash ffprobe -v error -show_entries format=duration:stream=codec_type,width,height,r_frame_rate,sample_rate -of json in.mp4 ``` #### Fit into 9:16 with bars Keeps the whole frame and pads to 1080x1920. Terminal: ```bash ffmpeg -i in.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,fps=30,format=yuv420p" \ -c:v libx264 -preset medium -crf 18 -c:a aac -b:a 192k -ar 48000 -ac 2 -movflags +faststart out_916.mp4 ``` #### Fill 9:16 with a center crop Fills the frame and crops the sides. Terminal: ```bash ffmpeg -i in.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,fps=30,format=yuv420p" \ -c:v libx264 -crf 18 -c:a aac -b:a 192k -ar 48000 -movflags +faststart out_916.mp4 ``` #### 9:16 with a blurred background For product shots made in 16:9 or 1:1. The `0:a?` map skips audio when there is none. Terminal: ```bash ffmpeg -i in.mp4 -filter_complex "[0:v]split=2[bg][fg];[bg]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=40[bg];[fg]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2,setsar=1,fps=30,format=yuv420p[v]" \ -map "[v]" -map 0:a? -c:v libx264 -crf 18 -c:a aac -b:a 192k -movflags +faststart out_916.mp4 ``` #### Join clips of different sizes One re-encode. The third clip has no sound, so it gets a silent track as long as the clip (5.04 s here, from ffprobe). Terminal: ```bash ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -filter_complex " [0:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v0]; [1:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v1]; [2:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v2]; [0:a]aresample=48000,aformat=channel_layouts=stereo[a0]; [1:a]aresample=48000,aformat=channel_layouts=stereo[a1]; anullsrc=r=48000:cl=stereo,atrim=0:5.04[a2]; [v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]" \ -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset medium -c:a aac -b:a 192k -movflags +faststart joined.mp4 ``` #### Burn styled captions (ASS) Set PlayResX 1080 and PlayResY 1920 in the .ass header so margins match 9:16. Use fonts with ñ, á, ¿ and ¡. Terminal: ```bash ffmpeg -i in.mp4 -vf "ass=captions.ass:fontsdir=./fonts" -c:v libx264 -crf 18 -c:a copy -movflags +faststart captioned.mp4 ``` #### Burn SRT captions with a style MarginV lifts the captions above the platform buttons. Terminal: ```bash ffmpeg -i in.mp4 -vf "subtitles=captions.srt:fontsdir=./fonts:force_style='FontName=Archivo,FontSize=14,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=3,Alignment=2,MarginV=160'" \ -c:v libx264 -crf 18 -c:a copy -movflags +faststart captioned.mp4 ``` #### Loudness to -14 LUFS (two passes) Pass 1 measures. Copy the measured values from loud.txt into pass 2. Terminal: ```bash ffmpeg -hide_banner -i in.mp4 -af loudnorm=I=-14:TP=-1.0:LRA=11:print_format=json -f null - 2> loud.txt ffmpeg -i in.mp4 -af "loudnorm=I=-14:TP=-1.0:LRA=11:measured_I=-20.3:measured_TP=-3.1:measured_LRA=6.2:measured_thresh=-30.6:offset=0.4:linear=true" \ -ar 48000 -c:v copy -c:a aac -b:a 192k out_loud.mp4 ``` #### Voiceover over ducked music The music drops while the voice speaks, then the mix is set to -14 LUFS. Terminal: ```bash ffmpeg -i video.mp4 -i vo.wav -stream_loop -1 -i music.mp3 -filter_complex " [1:a]aresample=48000,aformat=channel_layouts=stereo,asplit=2[vo][vokey]; [2:a]aresample=48000,aformat=channel_layouts=stereo,volume=0.35[mus]; [mus][vokey]sidechaincompress=threshold=0.03:ratio=8:attack=20:release=400[ducked]; [vo][ducked]amix=inputs=2:duration=longest:dropout_transition=0:normalize=0,loudnorm=I=-14:TP=-1.0:LRA=11[a]" \ -map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k -ar 48000 -shortest -movflags +faststart final.mp4 ``` #### Logo Bottom right, above the area where Reels and TikTok put their buttons. Adjust the margin to your placement. Terminal: ```bash ffmpeg -i in.mp4 -i logo.png -filter_complex "[1:v]scale=220:-1,format=rgba,colorchannelmixer=aa=0.85[lg];[0:v][lg]overlay=W-w-40:H-h-260" \ -c:v libx264 -crf 18 -c:a copy -movflags +faststart branded.mp4 ``` #### Trim The first command is fast and cuts on keyframes. The second is frame-accurate. Terminal: ```bash ffmpeg -ss 2.0 -i in.mp4 -t 10 -c copy cut_fast.mp4 ffmpeg -ss 2.0 -i in.mp4 -t 10 -c:v libx264 -crf 18 -c:a aac cut_exact.mp4 ``` #### Frames First frame, last frame (to continue a clip from it) and a contact sheet to review a take. Terminal: ```bash ffmpeg -i in.mp4 -frames:v 1 first.png ffmpeg -sseof -1 -i in.mp4 -update 1 -q:v 1 last.jpg ffmpeg -i in.mp4 -vf "fps=1/2,scale=270:-2,tile=4x3" -frames:v 1 contact.jpg ``` #### Hold the last frame Two extra seconds for an end card or a CTA. Terminal: ```bash ffmpeg -i in.mp4 -vf "tpad=stop_mode=clone:stop_duration=2" -af "apad=pad_dur=2" -c:v libx264 -crf 18 -c:a aac out.mp4 ``` #### Other aspects 4:5 for Feed, 1:1 and 16:9 from the same take. Terminal: ```bash ffmpeg -i in.mp4 -vf "scale=1080:1350:force_original_aspect_ratio=increase,crop=1080:1350,setsar=1" -c:v libx264 -crf 18 -c:a copy out_45.mp4 ffmpeg -i in.mp4 -vf "scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080,setsar=1" -c:v libx264 -crf 18 -c:a copy out_11.mp4 ffmpeg -i in.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1" -c:v libx264 -crf 18 -c:a copy out_169.mp4 ``` #### Remove technical metadata Removes GPS, device and encoder tags only. Keep the AI-generated label on the ad. Terminal: ```bash ffmpeg -i in.mp4 -map 0 -map_metadata -1 -map_chapters -1 -c copy -fflags +bitexact out_clean.mp4 ```