VidLabDocsDashboard
For agentsMarkdown

Agent skill

A skill is one Markdown file, SKILL.md, that teaches your agent how to use VidLab: pick a model for the task, read the price, run it, poll it, upload a file, run a format and handle each error. It uses the MCP tools when they are connected, otherwise the vidlab command or the REST API, bills your API wallet, and works without MCP.

Download SKILL.md

1. Install the skill

One command installs it for Claude Code and Codex (and connects MCP):

bash
npx @vidlab/cli setup

Or by hand. Claude Code:

bash
mkdir -p ~/.claude/skills/vidlab && curl -fsSL https://vidlab.ai/vidlab-api/SKILL.md -o ~/.claude/skills/vidlab/SKILL.md

Codex:

bash
mkdir -p ~/.codex/skills/vidlab && curl -fsSL https://vidlab.ai/vidlab-api/SKILL.md -o ~/.codex/skills/vidlab/SKILL.md

Any other agent: save the file where it reads skills, or add it to its instructions file (such as AGENTS.md). Or paste this prompt and let the agent install it:

Install the VidLab skill: download https://vidlab.ai/vidlab-api/SKILL.md, save it as vidlab/SKILL.md in your skills folder, then read it. My VidLab API key is in the VIDLAB_API_KEY environment variable: never print it or ask me to paste it.

2. Set your key

The skill reads the key from the environment, never from the chat. Add it to your shell profile (~/.zshrc or ~/.bashrc) and open a new terminal before you start the agent:

bash
export VIDLAB_API_KEY="vidlab_sk_your_key_here"

3. Check it works

Start with a free question: listing models and estimating prices cost nothing.

Use the VidLab skill: list three video models and the price of a 5 second clip at 720p.

Then a small paid one:

Use the VidLab skill to make one 1:1 image of a red sneaker on white with gpt-image-2. Tell me the price first.

What the skill tells your agent

  • Read the key from VIDLAB_API_KEY and never print it.
  • Pick a model for the task, read its inputs, and estimate the price before any paid call.
  • Start generations with max_price_usd and an idempotency key, then poll until they finish.
  • Upload a local file, and run a format through its question loop.
  • Branch on error codes: fix the input, add credit, or wait for Retry-After.
  • Never invent a model id, option or price.

The whole file:

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