Videoup API
Create personalized Videoups from your own code, Zapier, or AI agents like Claude. Get an API key in Settings → API & Agents.
Create personalized Videoups from your own code, Zapier/Make, or an AI agent.
A Videoup is one of your recorded campaign videos, re-voiced and lip-synced so
the greeting says the recipient's first name in your voice. Every video is
checked automatically to say the right name before it becomes ready.
Base URL: https://staging.videoup.co/api/v1
Endpoints at a glance
| Method | Path | Use it to |
|---|---|---|
GET |
/api/v1/campaigns |
See your campaigns, which can make videos, and your monthly quota |
POST |
/api/v1/videos |
Make a Videoup for one person (returns at once; renders in a few minutes) |
GET |
/api/v1/videos/{id} |
Check a video's status and get its link once it's ready |
GET |
/api/v1/videos |
List your videos, newest first |
POST |
your URL | Webhooks: we tell you when a video is ready, delivered, played, needs review or failed |
POST |
/mcp |
The same actions for Claude and other AI agents (MCP) |
Quick start
# 1. Find a campaign that can make videos
curl https://staging.videoup.co/api/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY"
# 2. Make a Videoup for someone
curl https://staging.videoup.co/api/v1/videos \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"campaign_id": 123, "first_name": "Julie", "email": "julie@acme.com"}'
# 3. A few minutes later, get the link
curl https://staging.videoup.co/api/v1/videos/9812 \
-H "Authorization: Bearer YOUR_API_KEY"
Authentication
Create an API key in Settings → API & Agents and send it as a bearer token:
Authorization: Bearer YOUR_API_KEY
Keys last a year and can be revoked at any time. Requests are limited to 60 per minute per account. Every response is JSON.
Campaigns
GET /campaigns
Your campaigns, and whether each can make videos right now.
{
"object": "list",
"data": [
{
"id": 123,
"object": "campaign",
"name": "Demo follow-up",
"can_create_videos": true,
"not_ready_reason": null,
"videos_count": 41,
"created_at": "2026-10-01T14:02:11+00:00"
}
],
"quota": { "monthly_limit": 250, "used_this_month": 37 }
}
not_ready_reason is "No master video recorded yet" or "Voice clone is not ready".
monthly_limit is null on unlimited plans.
Videos
POST /videos
Start a Videoup for one person.
| Field | Required | Notes |
|---|---|---|
campaign_id |
yes | A campaign with can_create_videos: true |
first_name |
yes | Spoken in the greeting, exactly as written |
email |
yes | Identifies the person |
last_name |
no | Not spoken |
idempotency_key |
no | Defaults to campaign + email |
Rendering takes a few minutes, so this returns at once:
202— started; the video'sstatusisprocessing.200— this person (same campaign and email, or sameidempotency_key) already has a video; you get that one. Nothing is rendered twice.404campaign_not_found422cannot_create_video— e.g.Monthly AI video quota reached,Your voice clone is not ready. Calling again later retries.422witherrors— invalid input.
Each new video uses one from your monthly AI video quota. Videoup does not
send anything to the person; you deliver the url.
GET /videos/{id}
GET /videos?campaign_id=&limit=&cursor=
Newest first, up to 100 per page; pass next_cursor back as cursor.
The video object
{
"id": 9812,
"object": "video",
"status": "ready",
"campaign_id": 123,
"recipient": { "first_name": "Julie", "last_name": "Smith", "email": "julie@acme.com" },
"url": "https://staging.videoup.co/v/aB3xK9pQ2m",
"thumbnail_url": "https://image.mux.com/…/thumbnail.jpg?…",
"gif_url": "https://image.mux.com/…/animated.gif?…",
"duration_seconds": 31.2,
"qc": { "status": "passed", "reason": null },
"error": null,
"plays": 2,
"last_played_at": "2026-10-10T15:41:09+00:00",
"source": "api",
"created_at": "2026-10-10T15:20:00+00:00",
"delivered_at": null
}
status |
Meaning |
|---|---|
processing |
Rendering, or being checked. url is null. |
needs_review |
The automatic name check wasn't sure (qc.reason says why). Approve or regenerate it on the campaign page in Videoup. |
ready |
Passed the check. Share url. |
delivered |
Sent by an integration (e.g. an Explee reply). |
failed |
See error. |
plays counts plays of the video after it was ready — not page loads, which
email link scanners trigger.
Webhooks
Set an https endpoint in Settings → API & Agents to be told instead of polling. Each event is a POST:
{
"id": "evt_01JA…",
"type": "video.ready",
"created_at": "2026-10-10T15:24:51+00:00",
"data": { …the video object… }
}
Events: video.ready, video.delivered, video.played, video.needs_review,
video.failed, and webhook.test from the settings page.
Respond with any 2xx. Anything else is retried after 1 min, 5 min, 30 min and 2 h. Endpoints must be public https URLs.
Verifying the signature
Each request has Videoup-Signature: t=<unix seconds>,v1=<hex>, where
v1 = HMAC-SHA256(secret, "<t>.<raw body>") with the endpoint's signing secret
(shown once when you save the endpoint). Reject requests whose t is more
than 5 minutes old.
const crypto = require('crypto')
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}
MCP (Claude and other agents)
The same three operations are available as an MCP server at
https://staging.videoup.co/mcp, using the same API key:
claude mcp add --transport http videoup https://staging.videoup.co/mcp \
--header "Authorization: Bearer YOUR_API_KEY"
Tools: list_campaigns, create_videoup, get_videoup. Then ask Claude
something like "Make a Videoup for Julie Smith (julie@acme.com) from my demo
campaign and draft her a follow-up email with the link."
Adding Videoup as a connector in claude.ai (which needs OAuth) is not supported yet.