VidLabDocsDashboard
GuidesMarkdown

Formats

Run one of our ad formats end to end and get the finished video back. You pick a format, send what you have, and VidLab's own agent makes the whole ad with the method and models chosen for that format. If it needs something (a product photo, what the ad should say, your yes to the price) it asks, and your agent answers.

Formats and model calls

A generation runs one model on your input. A format run makes a finished ad: script, shots, voice and edit. You see the format's name, its inputs, the questions it asks, the price and the result. The method itself (scripts, prompts, shot logic, which models run each step) stays on our side.

How a run works

  1. POST /v1/formats/{format_id}/runs with the inputs you have. You get a run_id at once.
  2. If something is missing, the run is needs_input and questions lists what to answer. Answer them all in one POST /v1/formats/runs/{run_id}/answers. Once the inputs are complete, the run is priced and asks confirm_price.
  3. After you approve, the run is queued, then running. Poll GET /v1/formats/runs/{run_id} every 10 to 20 seconds (or pass a webhook_url). When it is succeeded, output.videos holds the videos.

Nothing is charged before you approve the price. Send max_price_usd with the run to approve in advance: a quote at or below it starts without the price question.

Endpoints

MethodPathWhat it doesKey
GET/v1/formatsLists the formats you can run.No
GET/v1/formats/{format_id}One format: its inputs, questions and price rule.No
POST/v1/formats/{format_id}/runsStarts a run.Yes
GET/v1/formats/runs/{run_id}Reads a run.Yes
POST/v1/formats/runs/{run_id}/answersAnswers the open questions.Yes
POST/v1/formats/runs/{run_id}/cancelStops a run.Yes

Dashboard keys have full access. A scoped key needs the runs scope (reading a run also accepts tools or jobs). Reads are free of request limits; starts and answers count against your rate limits.

The formats at launch

idNameWhat it makesMust send or answer
talking-head-ugcTalking Head UGCA creator talks to camera about your product, shot like a phone video.What it is about: brief, script, product_image or product_name
street-interviewStreet interviewA two-person street interview about your product, in one take.What it is about: brief, product_image or product_name
drama-adDrama adA short acted drama with a twist, where your product turns the story.What it is about: brief, product_image or product_name
app-demoApp demoYour app's screen recording with a creator reacting to it.screen_recording
recipe-montageRecipe montageA cooking montage of a dish made with your product, with real kitchen sound.product_image
product-commercialProduct commercialCinematic hero shots of your product, cut to music.product_image

GET /v1/formats is the live list: read it rather than hard-coding inputs.

List formats

curl https://vidlab.ai/v1/formats

Each format in data looks like this (shortened):

json
{
  "object": "format",
  "id": "talking-head-ugc",
  "name": "Talking Head UGC",
  "description": "A creator talks to camera about your product, shot like a phone video.",
  "kind": "video",
  "minutes": 6,
  "inputs": [
    { "name": "product_image", "type": "image", "required": false, "description": "A clear photo of the product." },
    { "name": "brief", "type": "text", "required": false, "description": "What the ad should say, in a sentence or two.", "max_length": 2000 },
    { "name": "length_seconds", "type": "choice", "required": false, "options": ["15", "30", "45", "60"], "default": "30" },
    { "name": "hooks", "type": "choice", "required": false, "description": "How many opening hooks to make, one video each.", "options": ["1", "3", "5"], "default": "3" }
  ],
  "price": { "rule": "quoted_per_run", "from_usd": 7.07, "note": "Every run is quoted for its own inputs before it starts." },
  "questions_it_may_ask": [
    { "id": "brief", "text": "What is the ad about? Name the product and the one thing the creator should say.", "type": "text", "required": true },
    { "id": "confirm_price", "text": "This video costs $X. Start it?", "type": "choice", "options": ["approve", "cancel"], "required": true }
  ]
}

price.from_usd is the price of the format's default inputs, read live from the API. A run's own price depends on its inputs (length, number of hooks).

Input types

Files go in as public URLs only. VidLab downloads each one when you send it, so a URL that cannot be read is refused at once (url_unreadable).

typeSend
textA string, up to max_length characters.
choiceOne of options, as a string ("30"; a number is read as its string).
imageA public https image URL, 30 MB at most.
videoA public https video URL (MP4 or MOV), 300 MB at most.
urlA public http or https URL.

Leave out anything you do not have: the run asks for what it needs. An input the format does not take is refused with unknown_input, and the message lists its inputs.

Start a run

POST /v1/formats/{format_id}/runs

FieldTypeRequiredWhat it is
inputsobjectNoInput name to value, as GET /v1/formats lists them.
max_price_usdnumberNoThe most you approve in advance, 0 to 10000. A quote at or below it starts without the price question.
webhook_urlstringNoAn https URL VidLab POSTs to when the run needs answers or ends. See Webhooks.

Headers: Authorization (required) and Idempotency-Key (optional, see Idempotency).

curl -X POST https://vidlab.ai/v1/formats/talking-head-ugc/runs \
  -H "Authorization: Bearer $VIDLAB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-talking-head" \
  -d '{ "inputs": { "length_seconds": "15", "hooks": "1" } }'

The answer is the run: 202 while it is open (needs_input, queued, running), 200 when it is already over (a replay of a finished run).

The run object

json
{
  "object": "format_run",
  "run_id": "fmtrun_6f1c2a7d9b3e4f50a1c2d3e4f5a6b7c8",
  "format": { "id": "talking-head-ugc", "name": "Talking Head UGC" },
  "status": "needs_input",
  "questions": [
    { "id": "brief", "text": "What is the ad about? Name the product and the one thing the creator should say.", "type": "text", "required": true }
  ],
  "inputs": { "length_seconds": "15", "hooks": "1" },
  "price": { "quoted_usd": null, "max_price_usd": null, "charged_usd": null },
  "progress": null,
  "output": null,
  "error": null,
  "next_action": "Answer the questions with POST answers_url.",
  "created_at": "2026-11-01T18:20:04.112Z",
  "updated_at": "2026-11-01T18:20:04.131Z",
  "completed_at": null,
  "status_url": "/v1/formats/runs/fmtrun_6f1c2a7d9b3e4f50a1c2d3e4f5a6b7c8",
  "answers_url": "/v1/formats/runs/fmtrun_6f1c2a7d9b3e4f50a1c2d3e4f5a6b7c8/answers",
  "cancel_url": "/v1/formats/runs/fmtrun_6f1c2a7d9b3e4f50a1c2d3e4f5a6b7c8/cancel"
}
FieldWhat it is
run_idfmtrun_ and 32 hex characters.
statusneeds_input, queued, running, succeeded, failed or cancelled.
questionsThe open questions, only while needs_input.
inputsEverything sent so far, answers included.
price.quoted_usdThe quote for these inputs, once they are complete.
price.max_price_usdThe most you approved.
price.charged_usdWhat the run charged, once it is over (0 when it never started).
progressWhile queued or running: { "stage", "done", "total" }.
outputWhen succeeded: { "videos": [...], "duration_seconds" }.
error{ code, message } when the run failed or was closed, or an approved start could not go ahead yet.
next_actionOne line on what to do next.

Statuses

StatusMeaningWhat to do
needs_inputWaiting for your answers. Nothing is held or charged.Answer every required question in one call.
queuedApproved and waiting to start. The quote is held.Poll every 10 to 20 seconds.
runningBeing made. progress.stage names the step.Poll every 10 to 20 seconds.
succeededDone. output.videos holds the videos.Download them.
failedIt could not be finished. What ran is charged at cost; the rest of the hold comes back.Read error, then start a new run if you still need it.
cancelledYou cancelled it, answered cancel to the price, or did not answer within 24 hours.Nothing more happens.

A run left in needs_input for 24 hours is closed as cancelled with error.code questions_expired. Nothing was charged.

Questions

json
{ "id": "confirm_price", "text": "This video costs $X. Start it?", "type": "choice", "options": ["approve", "cancel"], "required": true }
FieldWhat it is
idWhat you answer under. For an input question it is the input's name (brief, screen_recording, product_name).
textThe question in plain words. Show it to your user or answer it from what you know.
typetext, choice (one of options) or file (a public URL of the input's kind).
requiredEvery required question must be answered in the same call.

A run can ask for:

  • a required input you left out (an App demo's screen_recording, a Product commercial's product_image);
  • what the video is about, when you sent none of the format's starting points;
  • product_name, when you sent a product photo without a name;
  • confirm_price, once the inputs are complete, unless max_price_usd already covers the quote.

Answer the questions

POST /v1/formats/runs/{run_id}/answers with answers: question id to value.

curl -X POST https://vidlab.ai/v1/formats/runs/fmtrun_6f1c2a7d9b3e4f50a1c2d3e4f5a6b7c8/answers \
  -H "Authorization: Bearer $VIDLAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "answers": { "brief": "Sleep gummies: asleep in 20 minutes, no grogginess." } }'
  • Answer every required open question in one call. An answer to an input question becomes that input.
  • confirm_price takes approve (start at the quoted price) or cancel (end the run, nothing charged).
  • The answer is the run: the next round of questions, or queued once it starts.
  • Sending the same answers again returns the run as it stands. Other answers on a run that is not waiting are refused with 409 not_waiting_for_input.

When an approved start cannot go ahead yet

If you approve but the start is refused for a reason that can pass, the run stays needs_input with confirm_price open, and error says why:

error.codeWhyFix
api_wallet_exhaustedThe API wallet cannot cover the quote.Add credit, then answer approve again.
api_key_budget_exhaustedThe key's monthly budget is used.Use another key, or wait for the next cycle.
too_many_active_runsThree format runs are already in progress on this account.Answer approve again when one finishes.

Read a run

curl https://vidlab.ai/v1/formats/runs/fmtrun_6f1c2a7d9b3e4f50a1c2d3e4f5a6b7c8 \
  -H "Authorization: Bearer $VIDLAB_API_KEY"

A finished run's output:

json
"output": {
  "videos": [
    {
      "kind": "video",
      "url": "https://...",
      "mime_type": "video/mp4",
      "duration_seconds": 15.2,
      "label": "Hook A",
      "expires_at": "2026-11-02T00:31:10.000Z"
    }
  ],
  "duration_seconds": 15.2
}

Video links last 6 hours; read the run again for fresh ones. A format with several hooks returns one video per hook.

Cancel a run

POST /v1/formats/runs/{run_id}/cancel. A run still asking ends at once with nothing charged. A started run stops: what already ran is charged at cost and the rest of the hold comes back. Safe to repeat.

Billing

  • A run pays from the prepaid API wallet, on REST with a vidlab_sk_ key and on MCP with a key or OAuth. When you approve, the quote is held. When the run ends it is charged what it actually cost, never more than the approved quote, and the rest goes back to the wallet, once.
  • A failed or cancelled run pays only for what ran.
  • Answers that start billable work carry x-wallet-remaining-cents and x-wallet-resets-at.

Idempotency

Send Idempotency-Key (1 to 128 characters of A-Z a-z 0-9 _ . : -) on POST /v1/formats/{format_id}/runs. For the rest of the UTC day, the same key with the same request returns the original run (with the header idempotent-replayed: true), never a second run or charge. The same key with a different request is refused with 409 idempotency_conflict.

Webhooks

Pass webhook_url (https) when you start a run. VidLab POSTs to it when the run needs answers and when it ends:

POST https://example.com/vidlab-hook
Content-Type: application/json
X-Vidlab-Event: format_run.succeeded

{ "event": "format_run.succeeded", "run": { "object": "format_run", "run_id": "fmtrun_...", "status": "succeeded" } }

Events: format_run.needs_input, format_run.succeeded, format_run.failed, format_run.cancelled.

Webhooks are unsigned and best effort: each is sent once, with a 5 second timeout, no redirects and no retry. Treat one as a nudge, and read the run with your key before acting on it.

Errors

Every error uses the standard envelope.

StatusCodeWhen
400invalid_jsonThe body is not a JSON object.
400unknown_fieldA body field other than inputs, max_price_usd, webhook_url (or answers).
400unknown_inputAn input the format does not take.
400invalid_inputA value of the wrong type, a choice not in options, or a URL that is not http or https.
400invalid_inputsInputs that cannot make this video together.
400url_unreadableA file URL could not be downloaded (not public, too large, unreachable).
400invalid_webhook_urlwebhook_url is not an https URL.
400invalid_max_pricemax_price_usd is not a number from 0 to 10000.
400invalid_idempotency_keyThe key is not 1 to 128 allowed characters.
400invalid_answers, unknown_question, invalid_answer, answers_incompleteThe answers do not match the open questions.
400invalid_run_idNot a fmtrun_ id.
401unauthorizedNo key, or a revoked one.
403api_key_scope_forbiddenThe key's scopes do not include runs.
404format_not_foundNo such format, or not available right now.
404run_not_foundNo run with that id on this account.
409idempotency_conflictThe Idempotency-Key was used for a different request.
409not_waiting_for_inputAnswers sent to a run that is not needs_input.
409answer_in_flightTwo answer calls at once: read the run.
413storage_fullA file could not be stored: the account's storage is full.
429rate_limited and the account limitsToo many requests. Wait for Retry-After.
503api_wallet_reconciliation_requiredThe wallet is on a billing review hold. Contact support.

MCP

The MCP endpoint has the same flow as five tools, with the same answers (a run names the tool to call next instead of a URL):

ToolWhat it does
list_formatsThe formats, or one with format_id. Free.
run_formatStarts a run: format_id, inputs, optional max_price_usd, webhook_url, idempotency_key.
answer_format_questionsrun_id and answers.
get_format_runReads a run. Free.
cancel_format_runStops a run that has not finished. Free.

On MCP, to use a local image, stage it with stage_file and pass its upload_id as the image input. Connect with ?profile=api to get just these tools and the model tools.