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.
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.
Authentication
Create a key in the studio 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 <key>. X-API-Key: <key> also works. The examples read it from STAGLIGHT_API_KEY.
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, 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.
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_limitedwithretry_afterand aRetry-Afterheader. - 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.
{
"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=<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
curl -s -X POST https://staglight.ai/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)
curl -s -X POST https://staglight.ai/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
curl -s -X POST https://staglight.ai/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
curl -s "https://staglight.ai/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).
{
"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://staglight.ai/media/med_19a2b3c4d5e6f7a8b9c0d2/image.png?exp=...&sig=...",
"download_url": "https://staglight.ai/media/med_19a2b3c4d5e6f7a8b9c0d2/image.png?exp=...&sig=...&dl=1"
}
],
"error": null
}
Download the result
curl -L -o product-ad.png "<outputs[0].download_url>"
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.
curl -s -X POST https://staglight.ai/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/<id>?wait=30. When status is awaiting_approval, approve with POST /api/v1/runs/<id>/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: every endpoint with a curl example.
- MCP setup: Claude Code, Codex and Cursor.
- Agent kit: agent prompt, skill and FFmpeg recipes.
- OpenAPI document for code generators.
- llms.txt and llms-full.txt for language models.