---
title: "The AI video generation API for ads."
url: "https://riffads.com/video-ad-api"
description: "The RiffAds AI video generation API makes lip-synced UGC ads with AI actors from your code. Priced first, spend caps, webhooks, same credits as the studio."
updated: "2026-09-28"
source: "RiffAds"
---

# The AI video generation API for ads.

An AI video generation API makes video from your code. The RiffAds API takes a script and an AI actor and returns a lip-synced UGC video ad, paid from the same credits as the studio.

## What is the RiffAds AI video generation API?

The RiffAds API is an AI video generation API for developers. Your code sends a script, an AI actor and a voice, and gets back a lip-synced UGC video ad as a download link. Every job is priced before it runs, capped by a number you set, and paid from the same credits as the studio.

The same API also makes images and B-roll video, runs editing tools like captions and resize, and starts ready-made workflows.

- **Base URL**: `https://app.riffads.com/api/v1`
- **Authentication**: `Authorization: Bearer sk_live_...`. Send your key as a Bearer token. The `x-api-key` header works too. Keys belong to the workspace and are shown once, when you create them in the app.
- **Scopes**: Every key can read. Tick Generate to start jobs and Workflows to run workflows.

- `read`: Browse capabilities, actors and voices, read generations back, and check the credit balance. Spends nothing.
- `generate`: Everything in Read, plus uploading files and starting generations. This one spends credits.
- `workflows`: Everything in Read, plus starting a workflow run: a script written, spoken by an actor, captions burned in, in one call. One run is several generations, so it spends more credits than a single one.

Call it from a server. The API sends no CORS headers, so a key never belongs in a web page. There is no test mode: every key is live and every call is real.

- [Create an API key](https://app.riffads.com/api-keys)
- [API quickstart](https://docs.riffads.com/api/setup)
- [OpenAPI spec](https://riffads.com/openapi.json)

## Make your first ad in three calls

Price the job, start it, then collect the file. Every step is plain HTTP, so curl, a backend or an automation tool can make it.

### 1. Price the job

`POST /api/v1/estimates`

The estimate is free and needs only the read scope. It answers `credits`, `is_ceiling` and `max_credits_needed`.

A talking actor is priced by the second of audio. Before the voice-over exists, the estimate prices the longest audio the model takes, so `is_ceiling` is true and the real price will be lower.

```bash
curl -s -X POST https://app.riffads.com/api/v1/estimates \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capability_id": "actor_ultra",
    "config": { "script": "Two weeks of battery, in a case this small." }
  }' | jq '{credits, is_ceiling, max_credits_needed}'
```

### 2. Start it with a spending cap

`POST /api/v1/generations`

This needs a key with the `generate` scope. It holds the credits and answers `201` with a `generation_id`.

`max_credits` caps what the job may hold, and a job holds its price plus 10 percent. A job over the cap is refused, never trimmed. Send the estimate's `max_credits_needed` as `$MAX`: the hold is measured from the finished voice-over, so it comes in at or under that number.

`approved_voice_generation_id` is the finished voice-over: a `tts` job you run first with the same script, actor and voice. Actor and voice ids come from `GET /api/v1/actors` and `GET /api/v1/voices`.

The workspace limit applies too: 600 credits per job by default, which fits about 19 seconds of OmniHuman 1.5 video. For a longer ad, an owner or admin raises the limit in the app first.

```bash
MAX=...   # max_credits_needed from step 1
curl -s -X POST https://app.riffads.com/api/v1/generations \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capability_id": "actor_ultra",
    "config": { "script": "Two weeks of battery, in a case this small." },
    "actor_id": "act_0193c8f0a1b24e7f9d3c5a6b7e8f0011",
    "voice_id": "voc_0193c8f0a1b24e7f9d3c5a6b7e8f0022",
    "approved_voice_generation_id": "gen_0193c8f0a1b24e7f9d3c5a6b7e8f0033",
    "max_credits": '"$MAX"'
  }' | jq -r '.generation_id'
```

### 3. Get the file

`GET /api/v1/generations/{id}/wait`

Each call waits up to 20 seconds. Call it again while `still_running` is true. When the job is done, `outputs[0].url` is a signed link that lasts 10 minutes: download the file, do not store the link.

Rather not loop? Register a webhook and read the generation when `generation.completed` arrives.

```bash
GEN=gen_...   # the generation_id from step 2
curl -s https://app.riffads.com/api/v1/generations/$GEN/wait \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  | jq '{still_running, status: .generation.status, url: .generation.outputs[0].url}'
```

- [Talking actor guide](https://docs.riffads.com/models#talking-actors)
- [Create an API key](https://app.riffads.com/api-keys)

## Video generation API endpoints

Every path below is on https://app.riffads.com. The read scope comes with every key. The OpenAPI document is the full contract.

### Make and track ads

| Method | Path | What it does | Scope |
| --- | --- | --- | --- |
| POST | `/api/v1/estimates` | Price an exact job against the published rate card. Free. | read |
| POST | `/api/v1/generations` | Start a job. It holds the credits and answers 201 with the job's id. | generate |
| GET | `/api/v1/generations` | Your history, filtered by status, capability or date. | read |
| GET | `/api/v1/generations/{id}` | One job: its status, its cost and download links that last 10 minutes. | read |
| GET | `/api/v1/generations/{id}/wait` | Waits up to 20 seconds for a job to finish, then answers. | read |
| GET | `/api/v1/batches/{id}` | Every take of a request that asked for variants. | read |

### Actors, voices and models

| Method | Path | What it does | Scope |
| --- | --- | --- | --- |
| GET | `/api/v1/capabilities` | Everything the API can make: talking actors, video, images and tools. | read |
| GET | `/api/v1/capabilities/{id}` | The JSON Schema one capability's config must match, with a valid example. | read |
| GET | `/api/v1/actors` | The actors this workspace can cast: the library plus its own. | read |
| GET | `/api/v1/voices` | The voices this workspace can use: the library plus its own. | read |
| GET | `/api/v1/presets` | Ready-made ad formats and their templates. | read |
| GET | `/api/v1/presets/{id}/templates` | One preset's full template gallery. | read |

### Files

| Method | Path | What it does | Scope |
| --- | --- | --- | --- |
| POST | `/api/v1/uploads` | Reserve an upload and get a link to send the file to. | generate |
| POST | `/api/v1/uploads/{assetId}/finalize` | Finish an upload. The file is checked before it can be used. | generate |
| POST | `/api/v1/uploads/from-url` | Import an image, video or audio file from a public link. | generate |
| GET | `/api/v1/assets` | Search the workspace library: uploads, imports and renders. | read |
| GET | `/api/v1/assets/{id}` | One file's details. | read |
| GET | `/api/v1/assets/{id}/download` | A signed download link that lasts 10 minutes. | read |

### Workflows

| Method | Path | What it does | Scope |
| --- | --- | --- | --- |
| GET | `/api/v1/workflows/templates` | Ready-made workflows and the inputs each one takes. | read |
| POST | `/api/v1/workflows/templates/{key}/invoke` | Run a template in one call, with one max_credits for every step. | workflows |
| POST | `/api/v1/workflows/{id}/invoke` | Run a workflow your workspace already has. | workflows |
| GET | `/api/v1/workflows/{id}/runs` | One workflow's runs, newest first. | read |
| GET | `/api/v1/workflow-runs` | Every run in the workspace, with its cost so far. | read |
| GET | `/api/v1/workflow-runs/{id}` | One run: its current step, its outputs and its cost. | read |

### Credits and webhooks

| Method | Path | What it does | Scope |
| --- | --- | --- | --- |
| GET | `/api/v1/credits/balance` | Your balance, and what this key may still spend under its limits. | read |
| GET | `/api/v1/webhooks` | Your webhook endpoints. | read |
| POST | `/api/v1/webhooks` | Add an endpoint. Its signing secret is shown once. | generate |
| DELETE | `/api/v1/webhooks/{id}` | Remove an endpoint and its pending deliveries. | generate |

### Status and spec

| Method | Path | What it does | Scope |
| --- | --- | --- | --- |
| GET | `/api/v1/health` | Checks that the API is up. No key needed. | No key |
| GET | `/api/v1/health/ready` | Checks that the API can reach its database. No key needed. | No key |
| GET | `/api/v1/openapi.json` | The OpenAPI 3.1 document. No key needed. | No key |

- [OpenAPI spec](https://riffads.com/openapi.json)
- [API reference](https://docs.riffads.com/api/generations)

## What the API costs

There is no separate API plan. API, MCP, Agent Skills and CLI are in every plan, and every call spends credits from the same balance as the studio, at the same prices. Credits never expire.

- **848** credits for a 30 second talking actor ad, voice included
- **$49** a month for Launch, the entry plan: 4,500 credits and the full API
- **$0.01** per credit. Estimates are free and credits never expire.

### What credits buy

| What | Model | Credits | Per |
| --- | --- | --- | --- |
| Talking actor | OmniHuman 1.5 | 28 | sec |
| Video | Veo 3.1 | from 35 | sec |
| Video | Kling 3 | from 14.7 | sec |
| Video | MiniMax H3 Max | from 8.8 | sec |
| Image | Nano Banana 2 | from 14 | image |
| Image | GPT Image 2.5 | from 11.2 | image |
| Image | Grok Image | from 3.5 | image |
| Captions | Burned in | 5.3 | min |
| Camera angle | Reshoot a photo | 14 | call |

A talking actor ad made from a script is two jobs: the voice-over, then the video. At 30 seconds that is about 8 credits of voice and 840 credits of video. Over the API, a video that long is above the default per-job limit of 600 credits, so an owner or admin raises the limit in the app first.

A job is charged at or below what it held, and a job that fails releases its hold.

[See plans and pricing](https://riffads.com/pricing)

## Webhooks when the video is ready

Skip the wait loop. Register an https endpoint and RiffAds tells your server when work settles.

### Events you can subscribe to

- `generation.completed`: A job finished. Read it for its files.
- `generation.failed`: A job failed. Its hold is released.
- `batch.settled`: Every take of a variants request has finished.
- `workflow_run.completed`: A workflow run finished, even if only part of it delivered.
- `workflow_run.failed`: A workflow run failed or was canceled.
- `credits.low`: Available credits fell below 1,000. Sent once per refill.

### How a delivery is signed

Every delivery follows the Standard Webhooks convention, so the `standardwebhooks` library checks it as it is.

- `webhook-id`: The delivery id. It stays the same on every retry, so dedupe on it.
- `webhook-timestamp`: Unix seconds of this attempt. Reject anything older than 5 minutes.
- `webhook-signature`: `v1,` then a base64 HMAC SHA-256 of `{id}.{timestamp}.{body}`, made with your `whsec_` secret.

### Retries

5 attempts. The first goes out at once, then after 5 minutes, 30 minutes, 2 hours and 12 hours, so the last lands about 14.5 hours after the event. Each attempt times out after 10 seconds.

### No download link in the event

An event carries ids and credits, never a download link: a link would expire long before the last retry. Read the generation with `GET /api/v1/generations/{id}` for a fresh one.

Up to 10 endpoints per workspace, https only. Manage them with the API or in the app.

`POST /api/v1/webhooks`

```bash
curl -s -X POST https://app.riffads.com/api/v1/webhooks \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/riffads-webhook",
    "events": ["generation.completed", "generation.failed"]
  }' | jq -r '.secret'
```

What your endpoint receives:

```http
POST https://example.com/riffads-webhook
User-Agent: RiffAds-Webhooks/1.0
webhook-id: {delivery id}
webhook-timestamp: {unix seconds}
webhook-signature: v1,{base64 signature}

{
  "id": "{delivery id}",
  "type": "generation.completed",
  "created_at": "{when it happened}",
  "data": { ... }
}
```

- [Webhooks in the app](https://app.riffads.com/settings/webhooks)
- [Webhooks reference](https://docs.riffads.com/api/webhooks)

## Built to be safe to automate

Code runs with nobody watching. These rules stop a loop, a typo or a retry from spending more than you meant.

### Priced before it runs

The estimate is free and uses the same rate card as the charge. It tells you the most a job can cost before anything is held.

### A cap on every job

`max_credits` is required on every call that spends. If the job's hold, its price plus 10 percent, is over it, the job is refused with `402 max_credits_exceeded`. It is never trimmed to fit, and a refusal charges nothing.

### Limits a person sets

On top of `max_credits`, the workspace caps API and agent spend: 600 credits per job and 2,000 per rolling 24 hours by default, plus an optional 24 hour budget for each key. An owner or admin changes them in the app. The default per-job limit fits about 19 seconds of OmniHuman 1.5 video.

### Retries that do not pay twice

There is no idempotency key to manage. Each key runs one job at a time, so a resend while the first job runs is refused with `409 submission_in_flight` and the id of the job to wait for, and nothing is charged. The same request after the first one finished is a new take.

### Rate limits you can read

Per key, per minute: 60 estimates, 20 new jobs and 120 reads. Each key also has an allowance of 120 requests that resets after a quiet minute. A `429` carries `Retry-After`.

### Errors you can branch on

Every error is JSON under `error`, with a `code`, a `message`, `retryable` and `credits_charged`. A spend refusal adds `limit.bound_by`: `max_credits`, `org_per_generation`, `api_key_budget` or `org_daily`.

- **Checked before it spends**: Scripts, prompts and uploads are moderated before any credits are held, the same way on every path.
- **Files, never posts**: The API returns a download link. It never posts to Meta, TikTok, YouTube or X.
- **Server side only**: No CORS, so a key stays on your server. Every key is live: there is no test mode.

- [Set agent limits in the app](https://app.riffads.com/connections)
- [Limits reference](https://docs.riffads.com/reference/limits)

## Video ad APIs compared

How each tool sells API access, as its own pages showed it on September 25, 2026. Prices change, so check the source before you decide.

| API | Separate API plan | How it is priced | Official MCP server | Sources |
| --- | --- | --- | --- | --- |
| RiffAds API | No. API, MCP, Agent Skills and CLI are in every plan. | Credits from one balance, 1 credit is $0.01, on a public rate card. | Yes, on the same credits and limits. | [RiffAds pricing](https://riffads.com/pricing) (Sep 25, 2026) |
| Arcads API | Not published. arcads.ai shows no price list. | Credits from the Arcads plan. The API has an endpoint for the credit balance. | Yes, at mcp.arcads.ai, with an active subscription. | [arcads.ai](https://www.arcads.ai/) (Sep 25, 2026); [Arcads API docs](https://external-api.arcads.ai/docs) (Sep 25, 2026); [Arcads help: Arcads MCP](https://intercom.help/arcads/en/articles/15655699-arcads-mcp) (Jun 26, 2026) |
| Creatify API | Yes. API Starter $99 a month for 500 credits, API Pro $299 a month for 2,000 credits. | Credits per feature, for example AI Avatar at 5 credits per 30 seconds. | Yes, as a feature of the Pro app plan. | [Creatify API docs: Billing](https://docs.creatify.ai/billing) (Sep 25, 2026); [Creatify MCP](https://creatify.ai/features/mcp) (Sep 25, 2026) |
| HeyGen API | Yes. Pay-as-you-go API credits, which expire after 12 months. | US dollars by type and length of output. Per-operation prices are shown in the API dashboard. | Yes, with OAuth, on the plan's credits. HeyGen sizes it for trials and points to an API key for scale. | [HeyGen help: API pricing explained](https://help.heygen.com/en/articles/10060327-heygen-api-pricing-explained) (Sep 16, 2026); [HeyGen developer llms.txt](https://developers.heygen.com/llms.txt) (Sep 25, 2026); [HeyGen MCP overview](https://developers.heygen.com/mcp/overview) (Sep 25, 2026) |
| MakeUGC API | Yes. API Starter $99 a month for 2,000 credits, API Pro $299 a month for 6,000 credits. | Credits. App plans are separate and start at $59 a month for 500 credits. | No official MCP. A community one drives the web app. | [MakeUGC pricing](https://makeugc.ai/pricing) (Sep 25, 2026); [Lobehub: makeugc-mcp](https://lobehub.com/mcp/hvmudvlab-makeugc-mcp) (Sep 25, 2026) |
| Synthesia API | No. API access starts on the Creator plan: $89 a month, or $64 a month billed yearly. | Up to 360 minutes of video a year, taken from the plan's limits. | Yes, in public beta. | [Synthesia pricing](https://www.synthesia.io/pricing) (Sep 25, 2026); [Synthesia MCP docs](https://docs.synthesia.io/reference/synthesia-mcp.md) (Sep 24, 2026) |

[The RiffAds MCP server](https://riffads.com/mcp)

## AI video API questions

### Is there a lip sync API for UGC ads?

Yes. Send a talking actor job to POST /api/v1/generations and RiffAds makes a new video of an AI actor whose lips match the audio. The audio can be a voice-over made from your script, in 74 voice languages, or your own recording. It makes a new video. It does not dub an existing one.

### How much does a video ad API cost?

On RiffAds there is no API fee. Jobs spend credits at the published rate card, and 1 credit is $0.01. A 30 second talking actor ad is about 848 credits, voice included. Plans start at $49 a month for 4,500 credits. The estimate endpoint prices any job for free before you spend.

### Does the API cost extra?

No. API, MCP, Agent Skills and CLI are included in every plan. Every API call spends credits from the same balance as the studio, at the same prices, so there is no separate API plan to buy. Credits never expire.

### Can I get a webhook when a video is ready?

Yes. Register an https endpoint with POST /api/v1/webhooks or in the app, and choose events such as generation.completed and generation.failed. Each delivery is signed with Standard Webhooks headers and tried up to 5 times over about 14.5 hours. It carries ids, not a download link, so read the generation for a fresh link.

### Can the API post my ads to TikTok or Meta?

No. RiffAds never posts to Meta, TikTok, YouTube, X or any other platform, and it does not connect to an ad account. The API returns a signed link to the finished file. You download it and publish it wherever you want.

### Should I use the API or the MCP server?

Use the REST API when your own code runs the job: a backend, a script, or a tool like n8n, Make or Zapier. Use the MCP server when an AI agent such as Claude, ChatGPT or Cursor should make the ad from a chat. Both spend the same credits under the same limits.

### What limits protect my credits?

Every job must name max_credits, and a job that would hold more is refused, never trimmed. A person also sets workspace limits: 600 credits per job and 2,000 credits per rolling 24 hours by default, plus an optional daily budget for each API key. The default per-job limit fits about 19 seconds of OmniHuman 1.5 video, so an owner or admin raises it in the app for a longer ad. Each refusal names the limit that stopped it and charges nothing.

## Create a key. Make the first ad.

A key comes with every plan. Price a job for free, then run it on the credits you already have.

- [Create an API key](https://app.riffads.com/api-keys)
- [Read the API quickstart](https://docs.riffads.com/api/setup)
- [Using an AI agent instead? Claude video generation](https://riffads.com/mcp)
