Skip to content
VIDROOT DEVELOPER API

One POST request, one finished video.

Script, voiceover, visuals, captions, and the final render — the same pipeline that powers the Vidroot studio, behind a REST API your product can call. Create a key, POST a prompt, and get a signed webhook when the file is ready.

Prefer to let an agent drive? Connect the MCP server to Claude, Cursor, or any MCP client.

Base URL

https://api.vidroot.com/v1

60

req / min / key

2xx

or we retry 3×

0

extra API fees

AUTHENTICATION

Bearer keys, hashed at rest.

Create a key in Developer settings. Keys look like vr_live_… and are shown exactly once — we store only a SHA-256 digest, so a lost key has to be revoked and replaced.

Every key is scoped to one workspace. Requests act on that workspace's products, videos, and credit balance, and nothing else. Send the key as a bearer token on every request; never in a query string, and never from a browser.

Revoking takes effect immediately — the next request with that key gets a 401.

Request
curl https://api.vidroot.com/v1/products \
  -H "Authorization: Bearer $VIDROOT_API_KEY"
ENDPOINTS

Five endpoints. That's the whole surface.

POST

/v1/videos

Create a video and start the pipeline

GET

/v1/videos/{id}

Status, final video URL, thumbnail, credits spent

GET

/v1/videos

List videos with cursor pagination

GET

/v1/products

List the products in the workspace

POST

/v1/credits/estimate

Price a payload before you spend anything

POST

/v1/videos

Creates a video and immediately queues the script step. The response returns before generation finishes — poll the status endpoint or wait for the webhook. Credits are checked against the workspace balance before any work starts.

Request
curl -X POST https://api.vidroot.com/v1/videos \
  -H "Authorization: Bearer $VIDROOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Three reasons indie founders switch to Vidroot",
    "productId": "prod_9f2c...",
    "format": "9:16",
    "duration": 30,
    "language": "en",
    "voice": "ara",
    "webhookUrl": "https://example.com/hooks/vidroot"
  }'
201 Created
{
  "id": "6f1c9c2a-7c5a-4a0e-9d1b-2f0c8a3e77b1",
  "status": "queued",
  "estimatedCredits": 115,
  "createdAt": "2026-07-29T10:14:02.184Z"
}
promptstring, requiredWhat the video should say or sell.
productIdstringUse an existing product for brand context, voice, and reference images.
productobjectPass { name, websiteUrl } instead of productId to create the product inline.
format"9:16" | "1:1" | "16:9"Aspect ratio. Defaults to 9:16.
durationinteger 5–600Target length in seconds. Defaults to 30.
languagestringBCP-47-ish language tag. Defaults to en.
voicestringVoice id. Falls back to the product's default voice.
deliveryMode"video" | "slides"Slides skips voiceover and render and returns images.
scenesarrayYour own script: narration, visualPrompt, durationMs per scene.
webhookUrlstringPer-request override; receives the same signed payload.
Creating the product inline
{
  "prompt": "Show how the app kills manual reporting",
  "product": {
    "name": "Northwind Analytics",
    "websiteUrl": "https://northwind.example.com"
  },
  "duration": 30
}
GET

/v1/videos/{id}

Returns the current status. Statuses move draft → queued → processing → ready, or failed. videoUrl and thumbnailUrl are null until the render is ready.

Request
curl https://api.vidroot.com/v1/videos/$VIDEO_ID \
  -H "Authorization: Bearer $VIDROOT_API_KEY"
200 OK
{
  "id": "6f1c9c2a-7c5a-4a0e-9d1b-2f0c8a3e77b1",
  "status": "ready",
  "title": "Three reasons indie founders switch to Vidroot",
  "format": "9:16",
  "duration": 30,
  "language": "en",
  "productId": "prod_9f2c...",
  "videoUrl": "https://media.vidroot.com/renders/6f1c9c2a.mp4",
  "thumbnailUrl": "https://media.vidroot.com/thumbs/6f1c9c2a.jpg",
  "creditsSpent": 115,
  "error": null,
  "createdAt": "2026-07-29T10:14:02.184Z",
  "updatedAt": "2026-07-29T10:17:41.902Z"
}
GET

/v1/videos

Newest first. Pass the returned nextCursor back as ?cursor= to page; a null nextCursor means you have reached the end. limit defaults to 20 and caps at 100.

Request
curl "https://api.vidroot.com/v1/videos?limit=20" \
  -H "Authorization: Bearer $VIDROOT_API_KEY"
200 OK
{
  "data": [
    {
      "id": "6f1c9c2a-7c5a-4a0e-9d1b-2f0c8a3e77b1",
      "title": "Three reasons indie founders switch to Vidroot",
      "status": "ready",
      "format": "9:16",
      "duration": 30,
      "language": "en",
      "createdAt": "2026-07-29T10:14:02.184Z",
      "updatedAt": "2026-07-29T10:17:41.902Z"
    }
  ],
  "nextCursor": "2026-07-29T10:14:02.184Z"
}
POST

/v1/credits/estimate

Free — no credits are charged and nothing is created. Use it to show a price before you commit, or to check headroom in a scheduler.

Request
curl -X POST https://api.vidroot.com/v1/credits/estimate \
  -H "Authorization: Bearer $VIDROOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "duration": 30, "deliveryMode": "video" }'
200 OK
{
  "totalCredits": 115,
  "sceneCount": 6,
  "breakdown": { "voiceover": 5, "visuals": 60, "render": 50 },
  "availableCredits": 4820,
  "sufficient": true
}
WEBHOOKS

Signed, retried, replay-proof.

Add an endpoint in Developer settings and subscribe it to video.completed and video.failed. Each delivery is a JSON POST with three headers: X-Vidroot-Event, X-Vidroot-Delivery, and X-Vidroot-Signature.

Retries. Anything outside 2xx — including a timeout past 10 seconds — is retried up to three times, after 10 seconds, 60 seconds, and 5 minutes. Deliveries are not ordered, so treat X-Vidroot-Delivery as an idempotency key.

Per-request webhookUrl. A URL passed on POST /v1/videos receives the same payload, signed with your workspace's default signing secret — the secret of the oldest active endpoint. If the workspace has no endpoints configured, the delivery is sent with X-Vidroot-Signature: unsigned, so add an endpoint if you need verifiable deliveries.

video.completed payload
{
  "id": "evt_2b8f...",
  "event": "video.completed",
  "createdAt": "2026-07-29T10:17:42.005Z",
  "data": {
    "id": "6f1c9c2a-7c5a-4a0e-9d1b-2f0c8a3e77b1",
    "projectId": "6f1c9c2a-7c5a-4a0e-9d1b-2f0c8a3e77b1",
    "status": "ready",
    "title": "Three reasons indie founders switch to Vidroot",
    "videoUrl": "https://media.vidroot.com/renders/6f1c9c2a.mp4",
    "thumbnailUrl": "https://media.vidroot.com/thumbs/6f1c9c2a.jpg",
    "creditsSpent": 115,
    "error": null,
    "createdAt": "2026-07-29T10:14:02.184Z",
    "completedAt": "2026-07-29T10:17:42.005Z"
  }
}

Verifying the signature

The header is t=<unix seconds>,v1=<hex>. Sign the exact raw body — parse it only after the signature checks out, and always compare in constant time.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto"

const TOLERANCE_SECONDS = 300

export function verifyVidrootSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((part) => part.trim().split("="))
  )
  const timestamp = Number(parts.t)
  if (!Number.isFinite(timestamp)) return false

  // Reject replays of a captured request.
  const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp)
  if (age > TOLERANCE_SECONDS) return false

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex")

  const a = Buffer.from(expected, "hex")
  const b = Buffer.from(parts.v1 ?? "", "hex")
  return a.length === b.length && timingSafeEqual(a, b)
}
MCP SERVER

Hand the whole pipeline to an agent.

Vidroot speaks the Model Context Protocol over Streamable HTTP at https://api.vidroot.com/mcp. Point Claude, Claude Code, Cursor, or any MCP client at it and the agent can script, price, generate, and poll videos on its own — the same pipeline and the same credits as the REST API.

Interactive clients: OAuth. The endpoint is an OAuth 2.1 protected resource. On the first call it returns a 401 with a WWW-Authenticate challenge; the client discovers the authorization server, registers itself dynamically (RFC 7591 — no manual app setup), and runs the authorization-code flow with PKCE against your normal Vidroot login. No key to paste, and access follows your account.

Headless clients: API key. Servers, CI, and cron jobs can skip the browser entirely — send a vr_live_… key as a bearer token and the endpoint accepts it directly.

Either way the connection is scoped to one workspace, rate limited at 60 requests per minute, and billed from the same balance as everything else.

Client config — OAuth (recommended)
{
  "mcpServers": {
    "vidroot": {
      "type": "http",
      "url": "https://api.vidroot.com/mcp"
    }
  }
}

Tools

create_videoStart a video from a prompt. Returns immediately with an id and status.
get_video_statusPoll one video for status, final URLs, credits spent, or error.
list_videosPage through the workspace's videos, newest first.
list_productsList products, so the agent can pick brand context by id.
estimate_creditsPrice a request before spending anything. Creates nothing.

Failures come back as MCP tool errors, not transport errors, so the agent can read them and correct itself. Running out of credits returns the required and available amounts rather than a bare rejection.

Client config — API key (headless)
{
  "mcpServers": {
    "vidroot": {
      "type": "http",
      "url": "https://api.vidroot.com/mcp",
      "headers": {
        "Authorization": "Bearer ${VIDROOT_API_KEY}"
      }
    }
  }
}
Insufficient credits, as the agent sees it
{
  "content": [
    {
      "type": "text",
      "text": "This video needs about 115 credits and the workspace has 40.\n\n{\n  \"availableCredits\": 40,\n  \"requiredCredits\": 115\n}"
    }
  ],
  "isError": true
}
Or add it from the CLI
# Claude Code — browser opens once for the OAuth consent step
claude mcp add --transport http vidroot https://api.vidroot.com/mcp

# Headless: authenticate with a workspace API key instead
claude mcp add --transport http vidroot https://api.vidroot.com/mcp \
  --header "Authorization: Bearer $VIDROOT_API_KEY"
RATE LIMITS & ERRORS

Predictable failure modes.

400invalid_requestThe body failed validation
401unauthorizedMissing, unknown, or revoked key
402insufficient_creditsIncludes requiredCredits and availableCredits
403forbiddenThe key cannot act on this data
404not_foundNo such video or endpoint
429rate_limited60 requests per minute, per key
500internal_errorSomething broke on our side

The limit is 60 requests per minute per key, counted in a fixed window. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; a 429 adds Retry-After.

Every error, same shape
{
  "error": {
    "code": "insufficient_credits",
    "message": "This video needs about 115 credits and the workspace has 40.",
    "requiredCredits": 115,
    "availableCredits": 40
  }
}
CREDIT COSTS

You pay for steps, not for calls.

API videos draw from the same workspace balance as the studio, at the same prices. A typical 30-second, six-scene short costs 115 credits: 5 for voiceover, 60 for visuals, and 50 for the render.

5

per video

Voiceover

10

per scene

Still visuals

13

per second

Generated clips

20

+ 1/sec

Render

Failed steps are refunded automatically. Slides delivery skips voiceover and render, so it only pays for visuals.

FAQ

Questions we actually get.

How long does a video take to generate?

A 30-second short typically finishes in two to four minutes. Script and voiceover complete in seconds, image or clip generation dominates the wall clock, and the final render adds roughly 20 to 40 seconds. Poll GET /v1/videos/{id} or subscribe to the video.completed webhook instead of blocking a request.

Are API videos billed differently from videos made in the app?

No. The API charges the same credits from the same workspace balance, using the same per-step pricing. There is no API surcharge and no separate plan required.

What happens if the workspace runs out of credits mid-render?

POST /v1/videos checks the estimated cost against the balance up front and returns a 402 with requiredCredits and availableCredits before any work starts. If a later step still fails to charge, the job is marked failed, previously charged steps are refunded, and a video.failed webhook fires.

Can I send my own script instead of generating one?

Yes. Pass a scenes array with narration and optional visualPrompt and durationMs per scene. Vidroot switches to user-script mode and generates visuals, voiceover, and captions around your copy instead of writing its own.

How do I verify that a webhook really came from Vidroot?

Every delivery carries an X-Vidroot-Signature header of the form t=<unix seconds>,v1=<hex>. Recompute HMAC-SHA256 over the exact string `${timestamp}.${rawBody}` using your endpoint secret, compare it with a constant-time function, and reject anything with a timestamp older than five minutes.

What is the rate limit?

60 requests per minute per API key, applied as a fixed window. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Create more keys to raise a specific integration's headroom, or contact us for a higher ceiling.

Start with a key and a curl.

Free credits on signup — enough to generate your first videos through the API before you decide anything.