API & MCP

Render, from anywhere.

Everything Render can do in the editor, it can do from an AI assistant or from your own code: turn a brief into a finished ad video, keep it on brand, use your product photos, and tell you when it is ready. This page is the complete description of both.

Overview

Both ride the same pipeline as the editor. An ad is planned first — the angle, every shot, every spoken line, the exact cost — and only generated once the plan is approved. Video is billed per generated second: 25 credits a second from your plan, then $0.25 a second on your card once the credits run out. Nothing is charged for a plan.

The base URL is https://tryrender.ai/api/v1. Every response is JSON, wrapped as { success: true, data } or { success: false, error }. The machine-readable description of every endpoint is /api/v1/openapi.json, generated from the same schemas the server validates with.

Connect an AI assistant (MCP)

Render is a Model Context Protocol server. Any assistant that speaks MCP can be given its address and will then know how to list your brands, make ads, check on them, revise them and approve them — as tools it calls on your behalf. You never write code and never copy a key: the assistant sends you to Render to sign in.

Server URL:  https://tryrender.ai/api/mcp
Transport:   Streamable HTTP
Sign-in:     OAuth 2.1 — Render asks you to allow the connection

Claude

  1. 1

    Open claude.ai or the Claude desktop app and go to Settings → Connectors.

  2. 2

    Add custom connector. Name it Render, paste the server URL, click Add.

  3. 3

    Click Connect. A Render page opens; sign in and click Allow.

ChatGPT

  1. 1

    Settings → Connectors. If there is no Create button, turn on Developer mode under Advanced first.

  2. 2

    Create: name it Render, paste the server URL as the MCP server URL, leave authentication on OAuth, click Create.

  3. 3

    In a new chat, choose Render under Tools — or simply ask for an ad and let it pick the tool.

Cursor, Claude Code, VS Code and other clients

Add the URL to the client’s MCP configuration; the sign-in opens in your browser the first time. A client that only accepts a fixed header can use an API key instead of OAuth — same server, no sign-in:

{
  "mcpServers": {
    "render": {
      "url": "https://tryrender.ai/api/mcp",
      "headers": { "Authorization": "Bearer rk_live_..." }
    }
  }
}

What to say

Talk to it the way you would brief a person. The more specific the brief — what is sold, who buys it, the tone, what should be on screen — the better the ad.

  • “Save my brand from acme.com” — reads the website and stores the business, once. Every later ad is on brand.
  • “Save the lavender candle as a product, here’s the photo link” — every ad for it then uses that exact photo.
  • “Make a 15-second vertical ad for the lavender candle, warm and calm, for people who work from home”
  • “Make the opening funnier” — revises the plan, still free.
  • “Go ahead” — generates. “Is it done?” — checks. “Make three more with different openings” — variants.

It does not spend money unless you say so. Making an ad stops at a plan by default; generating is a separate approve_ad step, so the assistant shows you the plan and the cost and gets a yes first. When you tell it to just make the ad, it can pass mode: "auto" — the tool itself warns it that this charges your account.

Connected assistants appear on the API & MCP page; disconnecting one there cuts it off immediately. The tools the assistant gets are listed in the tool reference.

Quickstart (REST)

  1. 1

    Make a key on the API & MCP page. It is shown once — copy it. A key spends your credits, so treat it like a password.

  2. 2

    Check it works.

    curl https://tryrender.ai/api/v1/credits \
      -H "Authorization: Bearer rk_live_..."
  3. 3

    Make an ad in review mode. It returns at once with an id and status: "planning"; the plan is free.

    curl https://tryrender.ai/api/v1/ads \
      -H "Authorization: Bearer rk_live_..." \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{
        "brief": "30 second vertical ad for Bright Smile Dental: same-day crowns, evening hours until 8pm. Calm narration over B-roll of the practice, soft piano, the phone number on screen at the end.",
        "format": "9:16",
        "duration_seconds": 30,
        "mode": "review"
      }'
  4. 4

    Read the plan back. After about 30 seconds the status is awaiting_approval and the response carries the whole plan and its cost.

    curl https://tryrender.ai/api/v1/ads/AD_ID \
      -H "Authorization: Bearer rk_live_..."
  5. 5

    Approve it. Generation starts and the credits come out of your pool. Poll until status: "ready", then download output.url — or register a webhook and be told.

    curl -X POST https://tryrender.ai/api/v1/ads/AD_ID/approve \
      -H "Authorization: Bearer rk_live_..."

Leave mode out (or pass "auto") and the ad approves itself the moment its plan exists — one call in, one file out. The same thing with the SDK:

import { createClient } from "@render/sdk";

const render = createClient(process.env.RENDER_API_KEY!);

const ad = await render.ads.createAndWait(
  { brief: "...", format: "9:16", duration_seconds: 30 },
  {
    onPlan(ad) {
      console.log(ad.plan!.angle, "—", ad.plan!.estimated_credits, "credits");
      return true; // false walks away without paying
    },
  }
);

console.log(ad.status, ad.output?.url);

Authentication

Every request carries Authorization: Bearer with either an API key (rk_live_…) or an OAuth access token minted for an MCP client. Keys are made on the API & MCP page and shown once; the server stores only a hash. You can hold up to 20 active keys, and each can carry a per-request credit ceiling (default 2,500, at most 15,000) so a runaway brief cannot ask for a ten-minute film.

A key cannot mint another key, and revoking one stops everything using it immediately. 401 means the key is wrong, revoked, or the token expired (AUTH_EXPIRED — refresh it); 403 means the account has no active subscription or no way to bill.

How an ad progresses

review   planning ──▶ awaiting_approval ──▶ generating ──▶ ready
             │                                          └──▶ failed
             └──▶ needs_input

auto     planning ──▶ (queued) ──▶ generating ──▶ ready
                                              └──▶ failed
StatusMeaning
planningThe agent is writing the plan. About 30 seconds.
awaiting_approvalReview mode: the plan is ready in plan, with its cost, and costs nothing until you approve it. A plan expires after 24 hours.
needs_inputReview mode only: the agent asked something. The question is in question; answer with POST /v1/ads/{id}/messages. An auto-mode ad answers its own questions and never shows this.
queuedAuto mode: the plan is accepted and the ad is waiting for a generation slot behind your plan’s concurrency cap. It starts on its own; it waits up to an hour.
generatingRunning. progress counts finished pieces. Minutes, not seconds.
readyoutput.url is a signed download link, good for 24 hours — fetch the ad again for a fresh one. output also carries width, height, duration and size.
failedfailure_reason says why: a planning failure (planning_failed, planning_timeout, needs_input_unattended), an approval refusal in auto mode (credit_limit_exceeded, forbidden, concurrency_limit after an hour of waiting), or a generation or export failure. Credits already spent on shots that did generate are not refunded by a later failure.
canceledYou cancelled it. Free before generation; during generation the shots already made are still paid for.

Briefs and creative controls

A brief is a paragraph: what is being sold, who it is for, the tone, what should be on screen. Vague briefs make generic ads, and in auto mode a brief with no brand, no product and no specifics is refused up front rather than paid for.

Format and length

format is the delivered aspect: 9:16 (default), 16:9, 1:1, 4:5, 4:3, 3:4. duration_seconds is 5–120 (default 30) and it is a contract: the plan’s shots must add up to it, a plan that does not fit is sent back to the planner before you see it, and the delivered file is exactly that long — output.duration_seconds confirms it.

The controls

Everything the brief could only hint at, as fields the agent must honour. All optional; leave one out and the agent decides.

FieldWhat it does
brand_idFrom POST /v1/brands. The ad speaks in that brand's voice and about that business. Strongly recommended.
product_idFrom POST /v1/products. The ad sells this and builds shots around its exact photos.
referencesUploads with a job. role: product (show exactly this), person (cast them from the photo), style (match the look), scene (this is the setting), motion (a clip whose movement and pacing to reproduce). Each may carry a short note.
languageBCP-47 code ("es", "pt-BR", "ja") for every spoken line and on-screen word. Any language the voice model speaks.
voice_idA narration voice from GET /v1/voices — the preset roster, or your own cloned voice.
scriptThe exact spoken words, used verbatim and in order; the agent only splits them across shots and decides narration versus on-camera. A script that cannot be spoken in the time is refused with the arithmetic.
styleugc (one person to a phone camera) · cinematic (film-grade footage, narration) · product_demo (the product in use, close) · motion_graphics (typography and shapes, no live action).
captionstrue burns word-timed captions into the finished file. With a script, the captions are your exact words timed to the narration.
musicfalse for no music, or direction such as "warm acoustic, 80bpm".
presenter"me" puts the account owner on camera, cast from the photo enrolled under Settings → Your appearance.
mode"auto" (default) approves the plan the moment it exists; "review" stops at awaiting_approval.
nameA label for your own records.
metadataUp to 16 string key/values of your own. Echoed on the ad and in every webhook, so a delivery ties back to your order.
await render.ads.create({
  brief: "Talking to camera about why this candle is the one for winter nights",
  brand_id: brand.id,
  product_id: product.id,
  style: "ugc",
  language: "es",
  voice_id: "jHprmvvyQreWpRuutdmV",
  captions: true,
  music: false,
  references: [{ upload_id: heroShot, role: "product", note: "label facing camera" }],
  metadata: { order_id: "o_8812", campaign: "winter" },
});

The plan

plan is what the agent intends: the angle, the premise, the opening line, every shot with its length, description and spoken line (and whether that line is on-camera dialogue or narration), the CTA, the music, and the cost — billable_seconds, estimated_credits, and what it would cost in dollars if the pool were empty. In review mode you read it before paying; in auto mode it is still returned on the ad for your records.

Brands and products

A brand is what stops every ad inventing a new tone and a new product name. Create one per business — from its website, and the agent reads it, or from fields you supply — then pass brand_id on every ad.

curl https://tryrender.ai/api/v1/brands \
  -H "Authorization: Bearer rk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://acme.com"}'

# or, without a website:
  -d '{"name": "Cosy Nights", "description": "Hand-poured lavender soy candles for slow evenings.", "tone_of_voice": "warm, unhurried, honest"}'

A product is a specific thing you sell — name, description, and its photos as upload ids. Pass product_id on an ad and the agent names it, describes it accurately, and builds shots around those exact photos rather than inventing a look-alike.

const product = await render.products.create({
  name: "Lavender Dreams 200g",
  description: "Hand-poured soy candle, 40-hour burn",
  image_ids: [await render.uploads.fromUrl("https://acme.com/img/lavender.jpg")],
});
await render.ads.create({
  brief: "15s vertical ad, cosy evening, ends on the jar",
  brand_id: brand.id,
  product_id: product.id,
});

Your own images and footage

Two ways in. If the file is already on the web, import it by URL (up to 50MB):

const id = await render.uploads.fromUrl("https://acme.com/img/candle.jpg");

If you hold the bytes: reserve an id (you get a signed URL good for 60 minutes), PUT the file to it (up to 200MB — images or video), then optionally confirm.

const id = await render.uploads.upload({
  file_name: "candle.jpg",
  content_type: "image/jpeg",
  body: bytes,
});
await render.ads.create({
  brief: "Ad built around this exact product shot",
  references: [{ upload_id: id, role: "product" }],
});

An upload is checked the moment an ad references it: one with no bytes behind it is refused by id, and a file whose type contradicts what was reserved (video bytes under an image) is refused too. Uploads belong to your account; an id from another account is rejected.

Questions, revisions and cancelling

POST /v1/ads/{id}/messages (or /revise, the same thing) sends the agent a message in plain English: an answer to its question, or a change to the plan — “make the opening funnier”, “it’s for dog owners, not cats”. The ad goes back to planning and comes back with a new plan; nothing is charged for a re-plan. On a finished ad the same call starts a new cut.

While an ad is generating a revision is refused (409): wait for ready and revise the finished ad, or cancel it. POST /v1/ads/{id}/cancel stops it at any point; shots already generated stay paid for.

Variants of a winner

The reason to use the API rather than the app: one ad that works becomes ten. POST /v1/ads/{id}/variants makes up to 10 more that keep the original’s brand, format, length, cast and structure and change only the opening — or the CTA, or the angle.

curl -X POST https://tryrender.ai/api/v1/ads/AD_ID/variants \
  -H "Authorization: Bearer rk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"count": 8, "vary": "hook", "direction": "lead with the 40-hour burn", "mode": "auto"}'

Each variant is a full, independent ad with variant_of_ad_id set; cancelling or revising one does not touch the others.

Cost, credits and usage

Generation comes out of your plan credits first, at 25 credits per generated second. When the pool runs out, the remaining seconds bill to your card at $0.25 a second as a separate line on your next invoice. A single ad can be paid for both ways: a 30-second ad needs 750 credits; with 300 left, credits cover 12 seconds and the other 18 bill at $0.25 — $4.50. Nothing stops midway.

  • Only video seconds count. Images, on-screen graphics, music and narration inside an ad are included.
  • Regenerating a shot is generation, so it costs, whether or not that take survives into the final cut.
  • You do not need a card on file to use credits you have.
  • There is a ceiling of $2,000 of metered spend a month; past it, generation is refused (CREDIT_LIMIT_EXCEEDED) rather than billed, so a runaway script cannot empty your card. Ask support to raise it.

POST /v1/ads/estimate prices a batch against your actual balance before you commit — how many seconds credits cover, how many meter, and whether you are inside the monthly ceiling. GET /v1/credits is the balance; GET /v1/usage is what the API has spent in a period: seconds, credits, dollars, ads created and delivered.

curl https://tryrender.ai/api/v1/ads/estimate \
  -H "Authorization: Bearer rk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"duration_seconds": 30, "quantity": 10}'

Webhooks

Polling is fine for a handful of ads. Past that, register an endpoint (up to 5) and be told.

curl https://tryrender.ai/api/v1/webhooks \
  -H "Authorization: Bearer rk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://you.example.com/render", "events": ["ad.ready", "ad.failed"]}'
EventWhen
ad.plan_readyReview mode: a plan is waiting for approval.
ad.needs_inputThe agent asked a question.
ad.generatingApproved; generation started.
ad.readyFinished. Fetch the ad for a fresh download URL.
ad.failedFailed; failure_reason is in the payload.

Every delivery carries the same ad object GET /v1/ads/{id} returns, under data.ad, with your metadata on it. The signing secret (whsec_…) is shown once, when the endpoint is created. Each request is signed the way Stripe signs theirs:

Render-Signature: t=1757836800,v1=<hex hmac-sha256 of "{t}.{body}">

Verify the MAC over `${t}.${rawBody}` with your secret and reject a timestamp older than five minutes. The SDK does both:

import { verifyWebhook } from "@render/sdk";

const ok = await verifyWebhook({
  payload: rawBody,
  signature: request.headers.get("render-signature") ?? "",
  secret: process.env.RENDER_WEBHOOK_SECRET!,
});
if (!ok) return new Response("bad signature", { status: 400 });

A delivery that does not get a 2xx within ten seconds is retried 3 times with backoff. After 20 consecutive failures the endpoint is disabled and GET /v1/webhooks/{id} says why, with its recent deliveries.

Idempotency, limits and errors

Idempotency

Send an Idempotency-Key header on every POST. Generations take minutes and cost money, so retries happen — with a key, a retry within 24 hours returns the first answer instead of buying a second ad. Reusing a key with a different body is a CONFLICT. The SDK sends one on every write.

Pagination

Lists take limit and cursor and return data, has_more and next_cursor. Keep following next_cursor while has_more is true; a status-filtered page of ads can be short or empty while more remain.

Limits

LimitValue
Requests per minute, per key120 (20 of them ad creations). Responses carry X-RateLimit-Limit, -Remaining and -Reset.
Generations in flight3 clips on Starter, 10 on Pro, 25 on Ultimate. Approving past it is a retriable CONCURRENCY_LIMIT; an auto-mode ad queues for a slot on its own.
Request body256KB. Files go through uploads.
Credits per request2,500 by default, settable per key up to 15,000.
Ad length5–120 seconds; variants 10 per request; estimate quantity up to 500.

Errors

Failure is { success: false, error: { code, message, isRetriable, retryAfter, details } }. Branch on code, never on the message.

CodeWhat to do
AUTH_REQUIRED · AUTH_INVALID401. No key, or a wrong or revoked one.
AUTH_EXPIRED401. An OAuth access token has expired. Refresh it; MCP clients do this themselves.
FORBIDDEN403. No active subscription, or credits are used up with no card to bill the rest to.
RESOURCE_NOT_FOUND404. Not yours, or not a valid id.
VALIDATION_FAILED · INVALID_REQUEST400. error.details names the fields. Unknown fields are rejected too, so a typo cannot silently change what you are charged for.
PAYLOAD_TOO_LARGE413. Bodies are capped; upload files through /v1/uploads and pass ids.
CONFLICT409. Wrong state for that action (approving a generating ad, revising during generation), or an Idempotency-Key reused with a different body.
INSUFFICIENT_CREDITS402. Not enough credits and no way to meter the rest.
CREDIT_LIMIT_EXCEEDED402. Over the key's per-request ceiling or the account's monthly metered ceiling. Ask for less, or raise the limit.
CONCURRENCY_LIMIT429, retriable. Too many generations already running. Wait and retry; auto-mode ads queue on their own.
RATE_LIMITED429, retriable. Slow down; honour Retry-After.
DISPATCH_FAILED503, retriable. The queue was briefly unavailable. Nothing was billed.

The SDK

@render/sdk is a small TypeScript client with no dependencies that runs anywhere fetch does. It wraps the two things everyone otherwise gets wrong: waiting (waitUntilReady polls with backoff and stops on a terminal state) and retrying (every write carries an idempotency key automatically).

npm install @render/sdk

import { createClient, RenderError } from "@render/sdk";
const render = createClient({ apiKey: process.env.RENDER_API_KEY! });

render.ads        create · get · list · approve · message · revise · cancel · variants · estimate
                  waitUntilReady(id) · createAndWait(params, { onPlan })
render.brands     create · get · list · delete
render.products   create · list
render.uploads    create · complete · upload({ file_name, content_type, body }) · fromUrl(url)
render.voices     list
render.webhooks   create · list · get · delete
render.credits()  render.usage({ start, end })

try { ... } catch (e) { if (e instanceof RenderError) console.log(e.code, e.status, e.retryAfter); }

waitUntilReady treats awaiting_approval and needs_input as terminal by default, because both wait on a person; createAndWait handles the whole create → plan → approve → wait cycle with a callback where the human decision goes.

OAuth for MCP clients

MCP clients sign a person in with OAuth 2.1 rather than a pasted key. The server publishes the standard discovery documents, so a conforming client needs only the server URL:

GET /.well-known/oauth-protected-resource   (RFC 9728)
GET /.well-known/oauth-authorization-server (RFC 8414)
POST /oauth/register                         dynamic client registration (RFC 7591)
GET  /oauth/authorize                        authorization code + PKCE (S256), the person clicks Allow
POST /oauth/token                            code → tokens; refresh_token → new tokens
POST /oauth/revoke                           RFC 7009

Access tokens live 24 hours and refresh tokens 90 days; the scope is ads. Each connection is a key on the person’s account, listed as a connected assistant on the API & MCP page and revocable there.

Every endpoint

Generated from the server’s own validation schemas — the same ones behind openapi.json. Fields marked * are required. Every path is relative to https://tryrender.ai/api.

Ads

Create, track and revise ads.

GET/v1/ads

List ads

Parameters

  • limitquery · string
  • cursorquery · string
  • statusquery · "planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"Filter to one status. Status is derived per ad after the page is read, so a filtered page can be short or empty while has_more is still true — keep following next_cursor.
  • brand_idquery · string
  • created_afterquery · date-time
  • created_beforequery · date-time

Returns · AdList

  • object"list"
  • dataarray of Ad
  • has_moreboolean
  • next_cursorstring
POST/v1/ads

Create an ad

Returns immediately with `status: "planning"`. In `review` mode the ad reaches `awaiting_approval` with a plan and costs nothing until approved; in `auto` mode it is approved and billed as soon as the plan exists (`queued` while it waits for a generation slot behind the account's concurrency cap).

Request body

  • brief *string · ≤ 8000 charsWhat the ad should be.
  • brand_iduuidFrom POST /brands.
  • product_iduuidFrom POST /products. The ad sells this; its images are used.
  • format"16:9" | "9:16" | "1:1" | "4:5" | "4:3" | "3:4" · default "9:16"
  • duration_secondsinteger · default 30, 5–120How long the ad runs. The plan's shots must add up to it and the delivered file is exactly this long — a plan that does not fit is sent back to the planner before it is shown or approved. `output.duration_seconds` confirms it on the finished ad; billing is on the plan.
  • mode"review" | "auto" · default "auto"auto: plan and generate in one call. review: stop at the plan.
  • namestring · ≤ 120 chars
  • reference_media_idsarray of uuidUpload ids with no stated role. Prefer `references`.
  • referencesarray of objectUploads with a role: product | person | style | scene | motion.
    • upload_id *uuid
    • role *"product" | "person" | "style" | "scene" | "motion"
    • notestring · ≤ 200 chars
  • languagestringBCP-47 code for all speech and on-screen text, e.g. "es", "pt-BR".
  • voice_idstring · ≤ 64 charsFrom GET /voices.
  • scriptstring · ≤ 2000 charsExact spoken words, used verbatim and in order.
  • style"ugc" | "cinematic" | "product_demo" | "motion_graphics"ugc | cinematic | product_demo | motion_graphics.
  • captionsbooleanBurn word-timed captions into the finished file.
  • music"false" | stringfalse for none, or direction such as "warm acoustic, 80bpm".
  • presenter"me""me": the account owner appears on camera from their enrolled photo.
  • metadataobjectUp to 16 string key/values, echoed on the ad and in webhooks.

Returns · Ad

  • id *string
  • object *"ad"
  • status *"planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"
  • mode"review" | "auto"
  • test_modeboolean
  • briefstring
  • formatstring
  • duration_secondsinteger
  • brand_idstring
  • credits_chargedintegerCredits this ad took out of the plan pool.
  • billable_secondsinteger
  • usdnumberWhat the metered overflow added to the invoice, if any.
  • variant_of_ad_idstring
  • reference_media_idsarray of stringUpload ids attached to this ad, as accepted.
  • product_idstring
  • referencesarray of object
    • upload_idstring
    • role"product" | "person" | "style" | "scene" | "motion"
    • notestring
  • controlsobjectlanguage / voice_id / script / style / captions / music / presenter, as accepted.
  • metadataobjectYour own key/values, echoed untouched — also in every webhook.
  • planAdPlan
  • questionstringPresent when status is needs_input. Answer via /messages.
  • progressobject
    • completedinteger
    • totalinteger
  • outputobject
    • urlstringSigned; expires. GET the ad again for a fresh one.
    • formatstring
    • expires_atdate-time
    • widthinteger
    • heightinteger
    • duration_secondsnumber
    • size_bytesinteger
  • failure_reasonstringWhy status is `failed`. Planning: `dispatch_failed` (queue unavailable; nothing billed), `planning_timeout`, `planning_failed` (the model crashed twice, or could not plan the requested length after two corrections), `needs_input_unattended` (auto mode; the agent could not proceed without an answer — use review mode or a fuller brief). Auto-approval refusals: `credit_limit_exceeded`, `forbidden` (no way to bill), `concurrency_limit` (only after waiting an hour for a slot — an auto ad queues behind the plan's concurrency cap rather than failing). Generation: `generation_timeout`, `generation_failed`. Export: `export_unavailable`, `export_failed`, `export_timeout`. Credits already spent on generation are not refunded by a later export failure; open `project_id` in the editor to export by hand.
  • project_idstring
  • created_atdate-time
  • finished_atdate-time
POST/v1/ads/estimate

Estimate what an ad will cost

Prices a batch against your current balance: how much comes out of credits and how much lands on the invoice. Nothing is created and nothing is charged.

Request body

  • duration_seconds *integer · 5–120
  • quantityinteger · default 1, 1–500

Returns · AdEstimate

  • object"ad_estimate"
  • duration_secondsinteger
  • quantityinteger
  • credits_per_secondinteger
  • usd_per_secondnumber
  • billable_seconds_per_adinteger
  • credits_per_adinteger
  • billable_seconds_totalinteger
  • credits_covered_secondsintegerSeconds your current balance covers.
  • credits_chargedinteger
  • metered_secondsinteger
  • usd_totalnumber
  • credits_availableinteger
  • covered_by_creditsboolean
  • month_to_date_usdnumber
  • monthly_limit_usdnumber
  • within_monthly_limitboolean
GET/v1/ads/{id}

Fetch an ad

The polling endpoint. Carries the plan, the progress counter, the question if it is stuck, and a signed download URL once ready.

Parameters

  • id *path · string

Returns · Ad

  • id *string
  • object *"ad"
  • status *"planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"
  • mode"review" | "auto"
  • test_modeboolean
  • briefstring
  • formatstring
  • duration_secondsinteger
  • brand_idstring
  • credits_chargedintegerCredits this ad took out of the plan pool.
  • billable_secondsinteger
  • usdnumberWhat the metered overflow added to the invoice, if any.
  • variant_of_ad_idstring
  • reference_media_idsarray of stringUpload ids attached to this ad, as accepted.
  • product_idstring
  • referencesarray of object
    • upload_idstring
    • role"product" | "person" | "style" | "scene" | "motion"
    • notestring
  • controlsobjectlanguage / voice_id / script / style / captions / music / presenter, as accepted.
  • metadataobjectYour own key/values, echoed untouched — also in every webhook.
  • planAdPlan
  • questionstringPresent when status is needs_input. Answer via /messages.
  • progressobject
    • completedinteger
    • totalinteger
  • outputobject
    • urlstringSigned; expires. GET the ad again for a fresh one.
    • formatstring
    • expires_atdate-time
    • widthinteger
    • heightinteger
    • duration_secondsnumber
    • size_bytesinteger
  • failure_reasonstringWhy status is `failed`. Planning: `dispatch_failed` (queue unavailable; nothing billed), `planning_timeout`, `planning_failed` (the model crashed twice, or could not plan the requested length after two corrections), `needs_input_unattended` (auto mode; the agent could not proceed without an answer — use review mode or a fuller brief). Auto-approval refusals: `credit_limit_exceeded`, `forbidden` (no way to bill), `concurrency_limit` (only after waiting an hour for a slot — an auto ad queues behind the plan's concurrency cap rather than failing). Generation: `generation_timeout`, `generation_failed`. Export: `export_unavailable`, `export_failed`, `export_timeout`. Credits already spent on generation are not refunded by a later export failure; open `project_id` in the editor to export by hand.
  • project_idstring
  • created_atdate-time
  • finished_atdate-time
DELETE/v1/ads/{id}

Cancel an ad

Before approval this stops the ad and costs nothing. After approval the generation jobs are already running and their credits are already spent.

Parameters

  • id *path · string

Returns · Ad

  • id *string
  • object *"ad"
  • status *"planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"
  • mode"review" | "auto"
  • test_modeboolean
  • briefstring
  • formatstring
  • duration_secondsinteger
  • brand_idstring
  • credits_chargedintegerCredits this ad took out of the plan pool.
  • billable_secondsinteger
  • usdnumberWhat the metered overflow added to the invoice, if any.
  • variant_of_ad_idstring
  • reference_media_idsarray of stringUpload ids attached to this ad, as accepted.
  • product_idstring
  • referencesarray of object
    • upload_idstring
    • role"product" | "person" | "style" | "scene" | "motion"
    • notestring
  • controlsobjectlanguage / voice_id / script / style / captions / music / presenter, as accepted.
  • metadataobjectYour own key/values, echoed untouched — also in every webhook.
  • planAdPlan
  • questionstringPresent when status is needs_input. Answer via /messages.
  • progressobject
    • completedinteger
    • totalinteger
  • outputobject
    • urlstringSigned; expires. GET the ad again for a fresh one.
    • formatstring
    • expires_atdate-time
    • widthinteger
    • heightinteger
    • duration_secondsnumber
    • size_bytesinteger
  • failure_reasonstringWhy status is `failed`. Planning: `dispatch_failed` (queue unavailable; nothing billed), `planning_timeout`, `planning_failed` (the model crashed twice, or could not plan the requested length after two corrections), `needs_input_unattended` (auto mode; the agent could not proceed without an answer — use review mode or a fuller brief). Auto-approval refusals: `credit_limit_exceeded`, `forbidden` (no way to bill), `concurrency_limit` (only after waiting an hour for a slot — an auto ad queues behind the plan's concurrency cap rather than failing). Generation: `generation_timeout`, `generation_failed`. Export: `export_unavailable`, `export_failed`, `export_timeout`. Credits already spent on generation are not refunded by a later export failure; open `project_id` in the editor to export by hand.
  • project_idstring
  • created_atdate-time
  • finished_atdate-time
POST/v1/ads/{id}/approve

Approve a plan and start generating

Synchronous, so you learn here whether the account can afford it (402) or too much is already running (429).

Parameters

  • id *path · string

Returns · Ad

  • id *string
  • object *"ad"
  • status *"planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"
  • mode"review" | "auto"
  • test_modeboolean
  • briefstring
  • formatstring
  • duration_secondsinteger
  • brand_idstring
  • credits_chargedintegerCredits this ad took out of the plan pool.
  • billable_secondsinteger
  • usdnumberWhat the metered overflow added to the invoice, if any.
  • variant_of_ad_idstring
  • reference_media_idsarray of stringUpload ids attached to this ad, as accepted.
  • product_idstring
  • referencesarray of object
    • upload_idstring
    • role"product" | "person" | "style" | "scene" | "motion"
    • notestring
  • controlsobjectlanguage / voice_id / script / style / captions / music / presenter, as accepted.
  • metadataobjectYour own key/values, echoed untouched — also in every webhook.
  • planAdPlan
  • questionstringPresent when status is needs_input. Answer via /messages.
  • progressobject
    • completedinteger
    • totalinteger
  • outputobject
    • urlstringSigned; expires. GET the ad again for a fresh one.
    • formatstring
    • expires_atdate-time
    • widthinteger
    • heightinteger
    • duration_secondsnumber
    • size_bytesinteger
  • failure_reasonstringWhy status is `failed`. Planning: `dispatch_failed` (queue unavailable; nothing billed), `planning_timeout`, `planning_failed` (the model crashed twice, or could not plan the requested length after two corrections), `needs_input_unattended` (auto mode; the agent could not proceed without an answer — use review mode or a fuller brief). Auto-approval refusals: `credit_limit_exceeded`, `forbidden` (no way to bill), `concurrency_limit` (only after waiting an hour for a slot — an auto ad queues behind the plan's concurrency cap rather than failing). Generation: `generation_timeout`, `generation_failed`. Export: `export_unavailable`, `export_failed`, `export_timeout`. Credits already spent on generation are not refunded by a later export failure; open `project_id` in the editor to export by hand.
  • project_idstring
  • created_atdate-time
  • finished_atdate-time
POST/v1/ads/{id}/messages

Answer a question, or ask for a revision

Free before approval. Revising a generated ad produces a new plan that must be approved like any other.

Parameters

  • id *path · string

Request body

  • content *string · ≤ 4000 chars

Returns · Ad

  • id *string
  • object *"ad"
  • status *"planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"
  • mode"review" | "auto"
  • test_modeboolean
  • briefstring
  • formatstring
  • duration_secondsinteger
  • brand_idstring
  • credits_chargedintegerCredits this ad took out of the plan pool.
  • billable_secondsinteger
  • usdnumberWhat the metered overflow added to the invoice, if any.
  • variant_of_ad_idstring
  • reference_media_idsarray of stringUpload ids attached to this ad, as accepted.
  • product_idstring
  • referencesarray of object
    • upload_idstring
    • role"product" | "person" | "style" | "scene" | "motion"
    • notestring
  • controlsobjectlanguage / voice_id / script / style / captions / music / presenter, as accepted.
  • metadataobjectYour own key/values, echoed untouched — also in every webhook.
  • planAdPlan
  • questionstringPresent when status is needs_input. Answer via /messages.
  • progressobject
    • completedinteger
    • totalinteger
  • outputobject
    • urlstringSigned; expires. GET the ad again for a fresh one.
    • formatstring
    • expires_atdate-time
    • widthinteger
    • heightinteger
    • duration_secondsnumber
    • size_bytesinteger
  • failure_reasonstringWhy status is `failed`. Planning: `dispatch_failed` (queue unavailable; nothing billed), `planning_timeout`, `planning_failed` (the model crashed twice, or could not plan the requested length after two corrections), `needs_input_unattended` (auto mode; the agent could not proceed without an answer — use review mode or a fuller brief). Auto-approval refusals: `credit_limit_exceeded`, `forbidden` (no way to bill), `concurrency_limit` (only after waiting an hour for a slot — an auto ad queues behind the plan's concurrency cap rather than failing). Generation: `generation_timeout`, `generation_failed`. Export: `export_unavailable`, `export_failed`, `export_timeout`. Credits already spent on generation are not refunded by a later export failure; open `project_id` in the editor to export by hand.
  • project_idstring
  • created_atdate-time
  • finished_atdate-time
POST/v1/ads/{id}/revise

Same as /messages

An alias of POST /ads/{id}/messages under the name the MCP tool uses (revise_ad).

Parameters

  • id *path · string

Request body

  • content *string · ≤ 4000 chars

Returns · Ad

  • id *string
  • object *"ad"
  • status *"planning" | "needs_input" | "awaiting_approval" | "queued" | "generating" | "ready" | "failed" | "canceled"
  • mode"review" | "auto"
  • test_modeboolean
  • briefstring
  • formatstring
  • duration_secondsinteger
  • brand_idstring
  • credits_chargedintegerCredits this ad took out of the plan pool.
  • billable_secondsinteger
  • usdnumberWhat the metered overflow added to the invoice, if any.
  • variant_of_ad_idstring
  • reference_media_idsarray of stringUpload ids attached to this ad, as accepted.
  • product_idstring
  • referencesarray of object
    • upload_idstring
    • role"product" | "person" | "style" | "scene" | "motion"
    • notestring
  • controlsobjectlanguage / voice_id / script / style / captions / music / presenter, as accepted.
  • metadataobjectYour own key/values, echoed untouched — also in every webhook.
  • planAdPlan
  • questionstringPresent when status is needs_input. Answer via /messages.
  • progressobject
    • completedinteger
    • totalinteger
  • outputobject
    • urlstringSigned; expires. GET the ad again for a fresh one.
    • formatstring
    • expires_atdate-time
    • widthinteger
    • heightinteger
    • duration_secondsnumber
    • size_bytesinteger
  • failure_reasonstringWhy status is `failed`. Planning: `dispatch_failed` (queue unavailable; nothing billed), `planning_timeout`, `planning_failed` (the model crashed twice, or could not plan the requested length after two corrections), `needs_input_unattended` (auto mode; the agent could not proceed without an answer — use review mode or a fuller brief). Auto-approval refusals: `credit_limit_exceeded`, `forbidden` (no way to bill), `concurrency_limit` (only after waiting an hour for a slot — an auto ad queues behind the plan's concurrency cap rather than failing). Generation: `generation_timeout`, `generation_failed`. Export: `export_unavailable`, `export_failed`, `export_timeout`. Credits already spent on generation are not refunded by a later export failure; open `project_id` in the editor to export by hand.
  • project_idstring
  • created_atdate-time
  • finished_atdate-time
POST/v1/ads/{id}/variants

Make variants of an ad that worked

Each variant is a full independent ad inheriting this one's brand, format, length and structure, varying the hook (or CTA, or angle).

Parameters

  • id *path · string

Request body

  • countinteger · default 3, 1–10
  • vary"hook" | "cta" | "angle" · default "hook"
  • directionstring · ≤ 1000 chars
  • mode"review" | "auto" · default "auto"
  • metadataobject

Returns · AdList

  • object"list"
  • dataarray of Ad
  • has_moreboolean
  • next_cursorstring

Brands

Who the ad is for.

GET/v1/brands

List brands

Parameters

  • limitquery · string
  • cursorquery · string

Returns · BrandList

  • object"list"
  • dataarray of Brand
  • has_moreboolean
  • next_cursorstring
POST/v1/brands

Create a brand

From a website URL, or from explicit fields.

Request body

Shape 1 of 2

  • url *uri
  • namestring · ≤ 200 chars

Shape 2 of 2

  • name *string · ≤ 200 chars
  • description *string · ≤ 4000 chars
  • taglinestring · ≤ 500 chars
  • industrystring · ≤ 200 chars
  • tone_of_voicestring · ≤ 500 chars
  • target_audiencestring · ≤ 1000 chars
  • value_propositionstring · ≤ 1000 chars
  • brand_colorsarray of string
  • key_messagingarray of string

Returns · Brand

  • idstring
  • object"brand"
  • namestring
  • descriptionstring
  • taglinestring
  • industrystring
  • tone_of_voicestring
  • target_audiencestring
  • value_propositionstring
  • logo_urlstring
  • brand_colorsarray of string
  • productsarray of object
  • key_messagingarray of string
  • source_urlstring
  • created_atdate-time
GET/v1/brands/{id}

Fetch a brand

Parameters

  • id *path · string

Returns · Brand

  • idstring
  • object"brand"
  • namestring
  • descriptionstring
  • taglinestring
  • industrystring
  • tone_of_voicestring
  • target_audiencestring
  • value_propositionstring
  • logo_urlstring
  • brand_colorsarray of string
  • productsarray of object
  • key_messagingarray of string
  • source_urlstring
  • created_atdate-time
DELETE/v1/brands/{id}

Delete a brand

Parameters

  • id *path · string

Returns · Deleted

  • idstring
  • objectstring
  • deleted"true"

Products

What the ad is selling.

GET/v1/products

List products

Returns · ProductList

  • object"list"
  • dataarray of Product
  • has_moreboolean
  • next_cursorstring
POST/v1/products

Create a product

Request body

  • name *string · ≤ 200 chars
  • descriptionstring · default "", ≤ 4000 chars
  • image_idsarray of uuid

Returns · Product

  • idstring
  • object"product"
  • namestring
  • descriptionstring
  • image_idsarray of string

Uploads

Your own images and footage.

POST/v1/uploads

Start an upload

Returns a presigned URL. PUT the bytes to it with the exact `content_type`, then call the complete endpoint.

Request body

Shape 1 of 2

  • file_name *string · ≤ 200 chars
  • content_type *string

Shape 2 of 2

  • url *uri · ≤ 2000 chars
  • file_namestring · ≤ 200 chars

Returns · Upload

  • idstring
  • object"upload"
  • upload_urlstring
  • content_typestring
  • expires_atdate-time
  • status"uploading" | "ready"
  • size_bytesinteger
  • source_urlstring
POST/v1/uploads/{id}/complete

Finish an upload

Verifies the bytes arrived and marks the upload usable. An upload that is never completed cannot be used in an ad.

Parameters

  • id *path · string

Returns · UploadComplete

  • idstring
  • object"upload"
  • status"ready"
  • size_bytesinteger

Webhooks

Be told, instead of polling.

GET/v1/webhooks

List webhook endpoints

Returns · WebhookList

  • object"list"
  • dataarray of Webhook
  • has_moreboolean
  • next_cursorstring
POST/v1/webhooks

Register a webhook endpoint

The signing secret is returned once. Every delivery carries `Render-Signature: t=<unix seconds>,v1=<hex>`; verify it as HMAC-SHA256, keyed by the secret, over the timestamp followed by a dot followed by the raw request body.

Request body

  • url *uri
  • eventsarray of "ad.plan_ready" | "ad.needs_input" | "ad.generating" | "ad.ready" | "ad.failed"
  • descriptionstring · ≤ 200 chars

Returns · WebhookCreated

GET/v1/webhooks/{id}

Fetch an endpoint and its recent deliveries

Parameters

  • id *path · string

Returns · Webhook

  • idstring
  • object"webhook"
  • urlstring
  • eventsarray of "ad.plan_ready" | "ad.needs_input" | "ad.generating" | "ad.ready" | "ad.failed"
  • activeboolean
  • disabled_reasonstring
  • consecutive_failuresinteger
  • last_success_atdate-time
DELETE/v1/webhooks/{id}

Delete a webhook endpoint

Parameters

  • id *path · string

Returns · Deleted

  • idstring
  • objectstring
  • deleted"true"

Account

Credits and usage.

GET/v1/voices

Narration voices

The preset roster plus your own cloned voice when you have one. Pass an id as `voice_id` on POST /ads.

Returns · VoiceList

  • object"list"
  • dataarray of Voice
  • has_moreboolean
  • next_cursorstring
GET/v1/credits

Credit balance

Returns · CreditBalance

  • object"credit_balance"
  • creditsinteger
  • planstring
  • overage_creditsinteger
  • usage_based_pricing_enabledboolean
  • usd_per_creditnumber
GET/v1/usage

What the API has spent

Parameters

  • startquery · string
  • endquery · string

Returns · Usage

  • object"usage"
  • period_startdate-time
  • period_enddate-time
  • billable_secondsinteger
  • credit_secondsinteger
  • credits_chargedinteger
  • metered_secondsinteger
  • usdnumber
  • credits_per_secondinteger
  • usd_per_secondnumber
  • ads_createdinteger
  • ads_deliveredinteger
  • monthly_limit_usdnumber

Every MCP tool

What a connected assistant can do, as the tools it sees. The descriptions below are the ones the assistant reads, which is why they say when a tool charges.

list_brands
List brands

List the brands saved on this Render account. A brand gives an ad its tone, audience and products, so check here before making one.

create_brand_from_url
Create a brand from a website

Read a business's website and save it as a brand: name, what they do, tone, products, colours. Do this once per business, then reuse the brand id on every ad.

  • url *stringThe business's website, e.g. https://acme.com
create_brand
Save a brand from a description

Save a brand without a website: the business's name, what it does, who it is for, and its tone. Use create_brand_from_url instead when there is a site to read.

  • name *string · ≤ 200 chars
  • description *string · ≤ 4000 chars
  • taglinestring · ≤ 500 chars
  • industrystring · ≤ 200 chars
  • tone_of_voicestring · ≤ 500 chars
  • target_audiencestring · ≤ 1000 chars
  • value_propositionstring · ≤ 1000 chars
  • key_messagingarray of string
list_products
List products

The products saved on this account, with their ids. Pass a product_id to create_ad so the ad sells that exact product and uses its photos.

create_product
Save a product

Save a product so ads can sell it by id: name, a short description, and optionally its photos (upload ids from add_reference_from_url).

  • name *string · ≤ 200 chars
  • descriptionstring · default "", ≤ 4000 chars
  • image_idsarray of string
add_reference_from_url
Attach an image or clip from a URL

Fetch a public image, video or audio file by URL and store it on the account. Returns an upload id to pass in create_ad's references (with a role) or create_product's image_ids.

  • url *uriA public https URL to the file.
  • file_namestring · ≤ 200 chars
list_voices
List narration voices

The narration voices available, with ids for create_ad's voice_id. Includes the account owner's own cloned voice when they have one.

check_credits
Check the credit balance

How many credits the account has left, and what a second of generated video costs. Ads spend credits first and bill the overflow on the invoice.

estimate_ad_cost
Estimate what an ad will cost

Work out what an ad of a given length will cost — how much comes out of the credit pool and how much bills on the invoice. Use this before making anything, especially more than one.

  • duration_seconds *integer · 5–120
  • quantityinteger · default 1, 1–100
create_ad
Make an ad

Start making a video ad from a description. By default it returns a plan to review — NOTHING is generated or charged until approve_ad is called. Pass mode "auto" to skip the review and generate straight away (this CHARGES the account) — only when the person has clearly asked for that. Write the brief in as much detail as you have: what is being sold, who it is for, the tone, what should be on screen.

  • brief *string · ≤ 8000 charsWhat the ad should be. Detail helps; vague briefs make generic ads.
  • brand_idstringFrom list_brands. Strongly recommended.
  • format"16:9" | "9:16" | "1:1" | "4:5" | "4:3" | "3:4" · default "9:16"9:16 for TikTok/Reels/Shorts, 16:9 for YouTube, 1:1 or 4:5 for feed.
  • duration_secondsinteger · default 30, 5–120How long the ad runs. The plan's shots add up to it and the delivered file is exactly this long.
  • product_idstringFrom list_products. The ad sells this; its images are used.
  • referencesarray of objectAttached images/clips and what each one is: product (show exactly this), person (cast them), style, scene, motion.
    • upload_id *stringFrom add_reference_from_url.
    • role *"product" | "person" | "style" | "scene" | "motion"
    • notestring · ≤ 200 chars
  • languagestringLanguage for all speech and on-screen text, e.g. "es", "pt-BR". Default: the brief's.
  • voice_idstring · ≤ 64 charsFrom list_voices.
  • scriptstring · ≤ 2000 charsExact spoken words, used verbatim. Only when the person has given you the words.
  • style"ugc" | "cinematic" | "product_demo" | "motion_graphics"ugc: one real person talking to a handheld phone camera, natural light, unpolished, first-person and conversational — the way a customer would film a recommendation; cinematic: original film-grade footage — deliberate lenses, controlled light, a graded palette, considered composition; product_demo: the product in use, close and clear — hands, texture, the result it produces; motion_graphics: typography, shapes and animated design carry the message — no live-action people, no photographic scenes
  • captionsbooleanBurn word-timed captions into the finished video.
  • music"false" | stringfalse for no music, or direction like "upbeat pop, 120bpm".
  • presenter"me""me" puts the account owner on camera from their enrolled photo.
  • mode"review" | "auto" · default "review""review" (default): stop at a plan for the person to approve with approve_ad — nothing charged. "auto": approve the plan the moment it exists and generate — this CHARGES the account's credits (and bills the overflow). Use "auto" only when the person has clearly said to go ahead without seeing the plan.
check_ad
Check an ad

See where an ad has got to: the plan waiting for approval, how far generation has got, the finished download link, or the question it is stuck on. Ads take a few minutes — call this again rather than waiting.

  • ad_id *string
approve_ad
Approve a plan and generate the video

Generate the video. This SPENDS the account's credits, and bills the overflow on their invoice if the pool runs out. ONLY call it after showing the person the plan and the cost from check_ad and getting a clear yes — it cannot be undone.

  • ad_id *string
revise_ad
Change an ad, or answer its question

Send a note about an ad in plain English — a change to the plan, or the answer to something it asked. Free while the plan is still unapproved. Revising an already-generated ad produces a new plan that has to be approved again.

  • ad_id *string
  • message *string · ≤ 4000 charse.g. "make the opening funnier" or "it is for dog owners, not cats"
make_variants
Make variants of an ad

Take an ad that has a plan (or is finished) and make more like it, changing only the hook, the CTA, or the angle. Each variant is its own ad; by default each stops at a plan and nothing is generated or charged until approve_ad. mode "auto" generates every variant straight away and CHARGES for each — only when the person has clearly asked for that.

  • ad_id *string
  • countinteger · default 3, 1–10
  • vary"hook" | "cta" | "angle" · default "hook"
  • directionstring · ≤ 1000 chars
  • mode"review" | "auto" · default "review""review" (default): each variant stops at a plan. "auto": each generates as soon as its plan exists and CHARGES the account. Only when the person has clearly said to.
cancel_ad
Cancel an ad

Stop an ad. Free before it is approved. After approval the generation is already running and already paid for — cancelling then only stops the polling.

  • ad_id *string
list_ads
List recent ads

The ads made on this account recently, with their status. Use it to find an ad id you have lost, or to see what is still running.

  • limitinteger · default 10, 1–25