---
name: vidlab
description: Make images, video, voice-overs, music, lip sync and upscales with the VidLab API (one key, many top models, pay only for success), or run a VidLab ad format that returns a finished video. Use when the user asks to generate or edit media with VidLab, to compare VidLab model prices, or to check a VidLab generation or format run.
---

# VidLab API

One key, every top image, video and audio model, and ready-made ad formats. Prices are US dollars. You pay only for runs that succeed. Docs: `https://vidlab.ai/vidlab-api/llms.txt`.

## The key

- The key is in the `VIDLAB_API_KEY` environment variable (or saved by `vidlab login`). Send it as `Authorization: Bearer $VIDLAB_API_KEY`.
- Never print, log or echo the key, never write it into a file or a command, never ask the user to paste it into the chat. Refer to the variable only.
- No key set: stop and tell the user to run `vidlab login`, or to make a key at `https://vidlab.ai/vidlab-api/api-keys` and set `VIDLAB_API_KEY`.
- Listing models and estimating prices need no key.

## Three ways to call it, same steps

Use the first that is available. The names are the same in all three.

| Step | MCP tool | CLI | REST |
| - | - | - | - |
| find | `list_models` | `vidlab models --kind video --search "image to video"` | `GET /v1/models?kind=video&search=` |
| read | `get_model` (`input_schema`) | `vidlab model <id>` | `GET /v1/models/{id}` |
| price | `estimate_price` | `vidlab estimate <id> --prompt ...` | `POST /v1/estimate` |
| run | `generate` | `vidlab run <id> --prompt ... --max-price 1 --wait --out file` | `POST /v1/generations` |
| poll | `get_generation` | `vidlab get <gen_id> --wait` | `GET /v1/generations/{id}` |
| money | `get_wallet` | `vidlab balance` | `GET /v1/balance` |

MCP: `https://vidlab.ai/api/developers/v1/mcp?profile=api`. REST base: `https://vidlab.ai/v1`. CLI: `npx @vidlab/cli`.

## Run a model

1. **Find** with `kind` (`video`, `image`, `audio`, `lipsync`, `upscale`) and `search` words. Prefer `status: "operational"`. Never invent an id: take it from the list.
2. **Read** the model. Send only the fields it lists under `params` (`get_model` also gives `input_schema`, the exact schema of `input`): `values` are the only allowed values, `min` and `max` are limits, `required` must be sent. Fields that are not listed are refused.
3. **Price** it. `price_usd` is what a success costs: the price of the option you chose times the units. Tell the user the price before any paid run, and ask when it is more than they expect.
4. **Run** it with `max_cost_usd` set to the estimate and an idempotency key (`idempotency_key` in MCP, the `Idempotency-Key` header in REST, a fresh UUID per new job). Reuse the key only to retry that same job. Answer: a `gen_...` id, `status: "queued"`.
5. **Poll** every 3 seconds for images and 10 for video until `status` is `succeeded` or `failed` (MCP: pass `wait: 30` to hold the call open, 60 at most). Polling is free.
6. **Deliver** `outputs[].url`: links valid for 6 hours, so download what is worth keeping. Read the generation again for fresh links.

## Read the price

`prices` maps an option to dollars per unit; `unit` says what a unit is:

| unit | counts | example |
| - | - | - |
| `per_second` | seconds of output (`duration`) | 5 s at `720p` = 5 x the `720p` price |
| `per_image` | images (`n`) | `n: 4` = 4 x the price |
| `per_job` | one run | flat |
| `per_1k_chars` | 1,000 characters of `text` | 2,500 characters = 2.5 units |
| `per_minute` | minutes of output | |

The option that sets the price is usually `resolution` (or `quality`). Models with `duration_must_cover` (lip sync, speech to video, upscales) bill the length of the input file: send `duration` at least as long, in whole seconds rounded up.

## Pick a model by task

Starting points, not rules. Search, then compare `from_usd` and the description.

| Task | Try |
| - | - |
| Video from text or an image | `seedance-2.5`, `kling-4.0` |
| Cheap video drafts | `seedance-1.0-pro-fast`, `hailuo-02`, `wan-3.0` |
| Video with sound | `veo-3.1-fast`, `veo-3.1` |
| UGC style ad clip | `minimax-h3-ugc` |
| Video from a reference video (motion) | `kling-3.0-motion-control` |
| Image from text | `nano-banana-pro`, `gpt-image-2` |
| Image with readable text, logos | `ideogram-v3` |
| Edit an image (needs `images`) | `nano-banana-2`, `gpt-image-2`, `flux-kontext-pro` |
| Many cheap images | `flux-3-klein`, `z-image` |
| Remove a background / upscale a picture | `recraft-remove-bg` / `topaz-image-upscale` |
| Upscale a video | `topaz-video-upscale` |
| Voice-over | `elevenlabs-tts`, `gpt-4o-mini-tts` |
| Song or background music | `suno-v6`, `flowmusic` |
| Talking person from a photo and audio | `omnihuman-2.0`, `kling-avatar-pro`, `infinitalk` |
| Lip sync a video to audio | `bytedance-lipsync` |

## Files

Images, video and audio go in as public https URLs (`images` is a list, the first is the first frame or the reference; `video`, `audio`, `end_image` are one URL each). A local file: upload it first, then use the returned `url`.

- CLI: `vidlab upload ./photo.png` prints the URL; `--image ./photo.png` uploads by itself.
- REST: `POST https://vidlab.ai/v1/uploads` with the raw file as the body, its `Content-Type`, and an `X-File-Name` header. The answer has `data.url`.
- If uploads answer 404, they are not enabled yet: ask the user for a public link.

## Formats (a finished video from a brief)

A format is our own recipe, run end to end, that returns a finished video. Tools: `list_formats`, `run_format`, `answer_format_questions`, `get_format_run`, `cancel_format_run` (CLI: `vidlab formats`, `vidlab format run`). REST: `/v1/formats`.

1. `list_formats`, pick one, read its `inputs`.
2. `run_format` with the inputs you have. Leave out what you do not know.
3. While `status` is `needs_input`, read `questions[]`. Ask the user what only they know (the product, the idea); answer the rest yourself. Call `answer_format_questions` with every required answer at once.
4. The last question is the price (`confirm_price`). It is the user's approval: send `approve` only after they agreed to the price, or pass `max_cost_usd` at the start to approve up to a cap.
5. Poll `get_format_run` every 10 to 20 seconds until `succeeded` (`output.videos[].url`), `failed` or `cancelled`. A format takes minutes.

## Errors

Errors are `{"error": {"message", "type", "code"}}` (MCP: the message ends with `[code]`). Branch on `code`:

| Code | Do |
| - | - |
| `invalid_input`, `invalid_request`, `variant_unavailable` | Fix the input against the model (`get_model`). Do not retry unchanged. |
| `max_cost_exceeded` | Re-estimate and ask the user before raising the cap. |
| `unauthorized`, `api_key_required` | The key is missing or wrong: ask the user to check `VIDLAB_API_KEY`. |
| `key_expired`, `ip_not_allowed`, `model_not_allowed`, `key_quota_exceeded` | The key's own limits (an expiry date, an address list, a model list, a spend quota) stop this call. Tell the user; they change them on the API keys page or use another key. |
| `developer_access_forbidden` | The account has no API access yet: tell the user to write to support@vidlab.ai. |
| `api_wallet_exhausted`, `api_key_budget_exhausted` | The wallet or the key's budget is used up. Tell the user to add credit at `https://vidlab.ai/vidlab-api/billing`. Do not retry. |
| `request_per_minute`, `request_per_day`, `rate_limited`, `model_unavailable`, `api_error` | Wait for `Retry-After` (or a few seconds), then retry with the same idempotency key. |
| `model_not_found`, `generation_not_found` | Wrong id: list again. A generation is visible only to the account that started it. |
| job `failed`: `invalid_input` | A file link did not open, or a file was the wrong type or too long (the message says which). Send a public https URL or upload it, then run again with a new key. |
| job `failed`: `content_policy` (with `reason`), `model_declined`, `reference_changed`, `max_cost_exceeded`, `max_wait_exceeded` (`retryable: false`) | Not charged. Change the prompt, the options or the cap as the message says; do not send the same request again. |
| job `failed`: `model_busy`, `all_routes_failed`, `timeout`, `processing_failed` (`retryable: true`) | Not charged. Run again with a new key, or pick another model. |

A failed job costs nothing; the same idempotency key returns the same failed job, so use a new key to try again.

## Price it first, cap it, download it

- `POST /v1/generations/quote` takes the same body as a generation and returns `quoted_usd`, `reserved_usd` and `enough_credit`. It is free and holds nothing. Quote the exact request before a big job.
- Add `max_cost_usd` to a generation to cap what it may cost (a dearer route is skipped; if none is left the answer is `max_cost_exceeded`) and `max_wait_s` to skip slow routes.
- Download a finished file with `GET https://vidlab.ai/v1/generations/{id}/content` (and `/content/2` ... for several images), sending the same key.
- A file on the user's computer: `POST https://vidlab.ai/v1/uploads` with the file as the body, its `Content-Type` and an `X-File-Name` header; use the returned `url` as the reference.
- A price listed "with video input" bills the reference clip's seconds too (output seconds plus reference seconds at that rate).

## Rules

- One generation per request from the user unless they ask for more. Never loop paid calls.
- Never invent a model id, option or price: read them from the API.
- Give the user the file links (or the saved file paths) and the price charged.
- There are no webhooks for single generations: poll. Format runs may take a `webhook_url`.
