# 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://staglight.ai/api/v1`
- Auth: `Bearer <key>`
- 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://staglight.ai/en/signup?next=/app/api)

## Authentication

Create a key in [the studio](https://staglight.ai/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 <key>`. `X-API-Key: <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://staglight.ai/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://staglight.ai/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=<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://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)

Terminal:

```bash
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

Terminal:

```bash
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

Terminal:

```bash
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).

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://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

Terminal:

```bash
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.

Terminal:

```bash
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](https://staglight.ai/en/docs/api): every endpoint with a curl example.
- [MCP setup](https://staglight.ai/en/docs/mcp): Claude Code, Codex and Cursor.
- [Agent kit](https://staglight.ai/en/docs/agent): agent prompt, skill and FFmpeg recipes.
- [OpenAPI document](https://staglight.ai/api/v1/openapi.json) for code generators.
- [llms.txt](https://staglight.ai/llms.txt) and [llms-full.txt](https://staglight.ai/llms-full.txt) for language models.
