API Reference
Generate multi-scene videos and infographics programmatically — or connect AI agents like Claude and Cursor over MCP. The MCP connector, REST endpoints, and API keys all require Ultimate, Creator, or AppSumo Tier 5+.
Authentication
All API requests must include your API key in the Authorization header.
Authorization: Bearer mvk_your_api_key_here
Create and manage API keys from your dashboard. Keys start with mvk_ and should be kept secret. The only unauthenticated endpoint is GET /v1/health — it returns {"status":"ok"} for uptime checks.
/v1/videosCreate an async video generation job. miltos-5 (default) produces a full multi-scene video with optional narration, music and SFX; miltos-3/4 produce single-scene animations. Returns 202 with a job id immediately — poll GET /v1/videos/{id} or receive a webhook.
Request Body
| Parameter | Type | Description |
|---|---|---|
prompt* | string | What the video should be about |
model | string | 'miltos-5' (multi-scene, default), 'miltos-4', or 'miltos-3'. See GET /v1/models for capabilities. |
enhance_prompt | boolean | Rewrite the prompt with the enhancement model before generating (small extra credit cost). The enhanced prompt is returned on the job. |
aspect_ratio | string | '16:9', '9:16', '1:1', or '4:5'. Default: 9:16 (multi-scene) / 16:9 (single-scene). Ignored when dimensions is set — dimensions takes precedence. |
dimensions | {width, height} | Exact canvas size in pixels. Takes precedence over aspect_ratio when both are set. |
duration | number | Target duration in seconds. Default 30 (multi-scene) / 15 (single-scene). Max: model-dependent (miltos-5: 120s) |
animation_style | string | Visual style pack id — see GET /v1/animation-styles for the live catalog. Omit for the house default; unknown ids are rejected with 400. |
background_color | string | Hex color for the canvas background. Default: chosen by the model |
narration | boolean | AI voiceover narration. miltos-5 only (default true). Passing true on miltos-3/miltos-4 returns 400 — single-scene models have no audio. |
narration_voice | string | ElevenLabs voice id — see GET /v1/voices. Omit to auto-pick a voice fitting the content; unknown ids are rejected with 400. |
narration_language | string | Narration language code ('en', 'es', 'fr', 'de', 'pt', 'it', 'nl', 'sv', 'pl', 'ja', 'ko', 'zh', 'hi', 'ar'). Omit to auto-detect from the prompt. |
music | boolean | Background music (miltos-5 only, default true) |
sfx | boolean | Sound effects (miltos-5 only, default false) |
ai_video | string | AI-generated video clips inside scenes: 'auto' (default — only where the story calls for film), 'on' (mix clips in), 'off' (motion graphics only; cheapest), 'full' (every scene is a generated clip — an all-footage build, the most expensive; builds in one pass unless storyboard review is on). miltos-5 only; non-auto on single-scene models returns 400. |
storyboard | boolean | ai_video 'full' only. Pause the build for human review before clips are generated — first at the cast sheets, then at the storyboard stills. The job goes to status 'awaiting_approval' at each gate until POST /v1/videos/{id}/approve (or the approval card in the MotionVid app) answers it. Default off: the build runs straight through. |
plan | boolean | Plan the story arc before generating scenes (multi-scene, default true) |
brand_kit_id | string | Saved brand kit id — see GET /v1/brand-kits. Injects the full brand context (logo, colors, fonts, assets). miltos-5 only. |
brand_settings | object | Inline brand colors, fonts, name — {primary_color, secondary_color, accent_color, heading_font, body_font, visual_style, brand_name, brand_description} |
images | array | Images to include: [{url, context?}]. Max 20. |
documents | array | Source documents to ground the content: [{url, mime_type, file_name?}]. PDF/CSV/plain text. Max 5. |
webhook_url | string | Receives video.completed / video.failed / video.canceled events |
render | boolean | Auto-render an MP4 when generation finishes (uses a render from your plan; 4K uses two) |
Example
curl -X POST https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/videos \
-H "Authorization: Bearer mvk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A 60-second explainer on how solar panels work",
"model": "miltos-5",
"enhance_prompt": true,
"duration": 60,
"aspect_ratio": "16:9",
"narration": true,
"music": true,
"render": true,
"webhook_url": "https://example.com/webhooks/motionvid"
}'Response (202 Accepted)
{
"id": "vj_abc123",
"status": "queued",
"model": "miltos-5",
"estimated_credits": 21
}Errors: 402 insufficient_credits (multi-scene needs at least 7 credits to start), 409 too_many_in_flight (max 5 concurrent jobs), 400 invalid_request (e.g. narration on a single-scene model).
/v1/videos/{id}Poll a video job. Multi-scene jobs report live progress as scenes are committed. Statuses: queued → processing → rendering (when render: true) → completed | failed | canceled, with phase giving the finer step inside processing. Multi-scene jobs typically take around 5 minutes, occasionally up to 15.
Response
{
"id": "vj_abc123",
"status": "processing",
"phase": "generating",
"phase_detail": "Building scenes — 3 of 6 committed. Scenes are built one at a time, roughly 30-90 seconds each, so a steady count between polls is normal, not a stall.",
"poll_after_seconds": 30,
"model": "miltos-5",
"prompt": "A 60-second explainer on how solar panels work",
"enhanced_prompt": "Create a cinematic 60-second explainer...",
"project_id": "proj_789",
"progress": { "scenes_committed": 3, "scenes_planned": 6 },
"scenes": [
{ "id": "s1", "title": "Hook — the sun's energy", "duration_seconds": 8 },
{ "id": "s2", "title": "Photovoltaic effect", "duration_seconds": 11 }
],
"app_url": "https://motionvid.ai/motion-graphics/proj_789",
"created_at": "2026-07-07T10:00:00.000Z",
"updated_at": "2026-07-07T10:03:12.000Z"
}A job is not stuck just because it looks idle: the AI writes the script and plan before any scene exists, so phase: "planning" with zero committed scenes is normal for the first several minutes. updated_at tracks job-record writes only and stays frozen through generation — poll on phase, not on it.
When completed (and rendered), the response additionally carriescredits_cost and a download_url — a ~15-minute signed link. Re-fetch this endpoint any time for a fresh link. app_url never expires and opens the video in MotionVid, so it is the better link to show a person.
Related: GET /v1/videos lists your jobs (paginated via limit + cursor). GET /v1/videos/{id}/frames returns one base64 JPEG per scene, sampled from the rendered video — a filmstrip you can show without downloading the MP4 (max_frames, default 6, max 12); on a job paused for review it returns the storyboard stills or cast sheets instead. POST /v1/videos/{id}/approve answers a paused review gate on a storyboard: true build (empty body approves and the build continues on the same job id; {"changes": "…"} redoes the board and pauses again — jobs pause at status awaiting_approval and never expire). POST /v1/videos/{id}/render re-renders a completed job, optionally at different dimensions. POST /v1/videos/{id}/cancel cancels a job — queued and paused jobs cancel instantly, in-flight jobs cooperatively (poll until canceled; credits for scenes already generated still apply).
/v1/videos/{id}/editChange a finished video by describing the change — the same AI agent that powers in-app editing runs your instruction against the job's project, with the original build as conversation context. Edits are surgical: only what the instruction names is regenerated, so a wording or color change costs a fraction of a rebuild. Multi-scene (miltos-5) jobs only; the source job must be in a terminal state (completed, failed, or canceled — asking a failed build to finish is a valid edit).
Request body
| Parameter | Type | Description |
|---|---|---|
prompt* | string | The change, in plain words — scope it like a message to an editor ("make scene 2's map show only the EU", "change the palette to warm tones", "shorten it to 20 seconds") |
images | array | Images for the edit (max 20): { url, context?, role? } — role "import" (default, goes in the video), "style" (look only), or "reference" (model decides) |
render | boolean | Re-render the MP4 when the edit lands (uses a render from your plan; 4K uses two). Off by default — or call /render afterwards |
webhook_url | string | HTTPS endpoint notified when the edit job finishes |
Example
curl -X POST https://api.motionvid.ai/v1/videos/vj_abc123/edit \
-H "Authorization: Bearer mvk_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "The map in scene 2 is wrong — show only the EU member states, colored in our brand blue",
"render": true
}'Returns 202 with a new job id — poll it via GET /v1/videos/{id} exactly like a created job. Credits are charged for what the edit actually regenerates: text and layout changes are cheap, regenerating AI clips bills like generation.
/v1/modelsCapability matrix for the selectable generation models — use it to pick the model for POST /v1/videos.
Response
{
"models": [
{
"id": "miltos-5",
"label": "Miltos 5.0",
"description": "Our most intelligent model yet",
"multi_scene": true,
"supports": { "narration": true, "music": true, "sfx": true, "ai_video": true },
"duration_options": [15, 30, 60, 120],
"max_duration_seconds": 120,
"min_credits_to_start": 7,
"default": true
},
{
"id": "miltos-4",
"label": "Miltos 4.0",
"description": "High-quality single-scene infographics",
"multi_scene": false,
"supports": { "narration": false, "music": false, "sfx": false, "ai_video": false },
"duration_options": [15, 30, 60],
"max_duration_seconds": 60,
"flat_credit_estimate": 5,
"default": false
}
]
}/v1/brand-kitsYour saved brand kits for the brand_kit_id parameter. Read-only — create and edit kits in the MotionVid app (Brand Kits page).
Response
{
"brand_kits": [
{
"id": "aBc123",
"name": "Acme",
"description": "Bold, developer-first tone",
"website_url": "https://acme.dev",
"colors": { "primary": "#6366F1", "secondary": "#8B5CF6", "accent": "#EC4899" },
"fonts": { "heading": "Inter", "body": "Inter" },
"has_logo": true
}
]
}/v1/voicesNarration voice catalog for the narration_voice parameter (ElevenLabs premade voices). Omit narration_voice to auto-pick a voice fitting the content.
Response
{
"voices": [
{
"id": "JBFqnCBsd6RMkjVDRZzb",
"name": "George",
"description": "Warm resonance that instantly captivates listeners.",
"gender": "male",
"accent": "British",
"age": "middle_aged",
"use_case": "narration",
"languages": ["en", "es", "fr", "de"]
}
]
}/v1/animation-stylesLive catalog of visual style packs for the animation_style parameter. Styles are curated in-app, so fetch this rather than hardcoding ids. default_style is what you get when animation_style is omitted; no_style disables style guidance entirely.
Response
{
"default_style": "taste-default",
"no_style": "no-style",
"styles": [
{
"id": "editorial-print",
"name": "Editorial Print",
"vibe": "Refined magazine layouts, serif headlines",
"palette": [
{ "label": "Background", "hex": "#F7F4EE" },
{ "label": "Accent", "hex": "#C8442A" }
]
}
]
}MCP — Connect AI Agents
MotionVid ships a hosted Model Context Protocol server, so Claude, Claude Code, Cursor, and any MCP-compatible agent can generate videos for you. It exposes the same capabilities as this REST API: create_video, get_video_status, edit_video, preview_video, approve_video, render_video, cancel_video, list_videos, list_models, list_animation_styles, list_voices, list_brand_kits, enhance_prompt, get_credit_balance, and whoami.
Endpoint: https://motionvid.ai/mcp (Streamable HTTP)
1. Desktop: Claude → Settings → Connectors Web: claude.ai/customize/connectors 2. Add custom connector 3. Name: MotionVid — URL: https://motionvid.ai/mcp 4. Click Connect and sign in with your MotionVid account
Auth: OAuth 2.1 with PKCE + dynamic client registration (the “Connect” flow), or a mvk_ API key as a bearer header. MCP and REST + API keys both need Ultimate, Creator, or AppSumo Tier 5+. Both auth methods spend the same credits — and users with saved BYOK keys pay no credits for the parts a key covers.
/v1/infographics/generateGenerate a new infographic animation from a text prompt. Returns synchronously with the generated project. Pass render: true to also kick off a video render in the same call.
Request Body
| Parameter | Type | Description |
|---|---|---|
prompt* | string | Description of the infographic to generate |
dimensions | {width, height} | Canvas size in pixels. Default: 1280x720 |
duration | number | Animation duration in seconds. Default: 15 |
animationStyle | string | Animation style preset |
backgroundColor | string | Hex color for background. Default: auto-detected |
brandSettings | object | Brand colors, fonts, and visual style |
images | array | Reference images to include in the generation. Each item: {url: string, context?: string}. Max 20. |
documents | array | Source documents whose content grounds the generation. Supported mimeType values: application/pdf, text/csv, text/plain. Each item: {url, mimeType, fileName?}. Max 5. |
webhookUrl | string | URL to receive render completion webhooks |
render | boolean | If true, immediately starts a render after generation. Result is delivered via webhook. |
Brand Settings Object
| Parameter | Type | Description |
|---|---|---|
primaryColor | string | Primary brand color, e.g. "#6366F1" |
secondaryColor | string | Secondary brand color |
accentColor | string | Accent color |
headingFont | string | Google Font name for headings |
bodyFont | string | Google Font name for body text |
visualStyle | "2d" | "3d" | Visual style of the infographic |
brandName | string | Brand name to display |
Document Object
| Parameter | Type | Description |
|---|---|---|
url* | string | Publicly accessible HTTPS URL to the document |
mimeType* | string | MIME type of the document. Must be one of the supported types below. |
fileName | string | Display name for the file (e.g. "Q3-report.pdf") |
Supported mimeType values: application/pdf, text/csv, text/plain
curl -X POST https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/infographics/generate \
-H "Authorization: Bearer mvk_your_key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Monthly revenue growth showing 45% increase",
"dimensions": {"width": 1920, "height": 1080},
"duration": 12,
"brandSettings": {
"primaryColor": "#6366F1",
"headingFont": "Poppins",
"brandName": "Acme Corp"
}
}'/v1/infographics/:id/renderRender a generated infographic to video. Returns immediately with a 202 status — the result is delivered via webhook.
| Parameter | Type | Description |
|---|---|---|
versionId* | string | Version ID from the generate response |
dimensions | {width, height} | Render resolution. Default: matches generation |
fps | number | Frames per second. Default: 30 |
webhookUrl | string | Override the default webhook URL for this render |
settingsValues | object | Override individual settings for this render (colors, text, fonts, etc). Keys must match those in the version's settingsSchema. Merged with stored defaults. |
curl -X POST https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/infographics/abc123/render \
-H "Authorization: Bearer mvk_your_key" \
-H "Content-Type: application/json" \
-d '{
"versionId": "def456",
"dimensions": {"width": 1920, "height": 1080},
"webhookUrl": "https://your-server.com/webhook"
}'/v1/infographicsList your projects, newest first. Supports cursor-based pagination.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit | number | Results per page (max 100, default 20) |
cursor | string | Project ID to paginate from (use nextCursor from previous response) |
curl "https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/infographics?limit=20" \ -H "Authorization: Bearer mvk_your_key" # Next page: curl "https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/infographics?limit=20&cursor=abc123" \ -H "Authorization: Bearer mvk_your_key"
/v1/infographics/:idGet project details and all versions.
curl https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/infographics/abc123 \ -H "Authorization: Bearer mvk_your_key"
/v1/infographics/:id/versions/:versionIdGet version details including status, settings, and the full settings schema for customization.
curl https://us-central1-motionvid-ai.cloudfunctions.net/api/v1/infographics/abc123/versions/def456 \ -H "Authorization: Bearer mvk_your_key"
Webhooks
When a render completes or fails, we send a POST request to your webhook URL. Configure a default webhook URL when creating your API key, or pass one per-request.
Payload
{
"event": "render.completed",
"data": {
"projectId": "abc123",
"versionId": "def456",
"renderId": "render_789",
"url": "https://storage.googleapis.com/...",
"dimensions": { "width": 1920, "height": 1080 },
"fps": 30,
"duration": 12
},
"timestamp": "2024-12-01T10:35:00.000Z"
}Events
| Event | Description |
|---|---|
render.completed | Render finished. Includes url, dimensions, fps, and duration. |
render.failed | Render failed. Includes error plus the same projectId/versionId/renderId/dimensions/fps/duration context as completed events. Credits are automatically refunded. |
video.completed | A /v1/videos job finished. Includes videoId, projectId, creditsCost, and (when rendered) a short-lived downloadUrl — download immediately or re-fetch GET /v1/videos/{id}. |
video.failed | A /v1/videos job failed. Includes error. Only completed work is charged. |
video.canceled | A /v1/videos job was canceled via POST /v1/videos/{id}/cancel. Credits for scenes already generated still apply. |
Signature Verification
Deliveries are signed two ways with your webhook secret — use whichever is easier. Recommended: the Standard Webhooks headers (webhook-id, webhook-timestamp, webhook-signature) verified with any off-the-shelf library:
import { Webhook } from "standardwebhooks"; // npm i standardwebhooks
const wh = new Webhook(process.env.WEBHOOK_SECRET);
// Throws on bad signature or stale timestamp (replay protection):
const payload = wh.verify(rawBody, {
"webhook-id": req.headers["webhook-id"],
"webhook-timestamp": req.headers["webhook-timestamp"],
"webhook-signature": req.headers["webhook-signature"],
});Alternatively, the legacy X-MotionVid-Signature header carries a hex HMAC-SHA256 of the raw request body:
import crypto from 'crypto';
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post('/webhook', (req, res) => {
const signature = req.headers['x-motionvid-signature'];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) return res.status(401).send('Invalid signature');
const { event, data } = req.body;
if (event === 'render.completed') {
console.log('Video URL:', data.url);
}
res.status(200).send('OK');
});Retry policy: Failed deliveries are retried 3 times with exponential backoff (1s, 5s, 25s). Return a 2xx status to acknowledge receipt.
Error Codes
All errors return a consistent JSON structure:
{
"error": {
"code": "insufficient_credits",
"message": "Monthly generation limit reached.",
"status": 402
}
}| Status | Code | Description |
|---|---|---|
400 | invalid_request | Missing or invalid request parameters |
401 | unauthorized | Missing, invalid, or revoked API key |
402 | insufficient_credits | Monthly usage limit reached |
403 | forbidden | Plan does not include API access |
404 | not_found | Resource does not exist or is not yours |
409 | too_many_in_flight | Max 5 video jobs in progress — wait for one to finish |
409 | not_cancelable | The job already completed, failed, or was canceled |
429 | rate_limited | Too many requests (see Rate Limits) |
500 | internal_error | Server error — retry or contact support |
503 | voices_unavailable | Voice catalog temporarily unreachable — retry shortly |
Rate Limits
Rate limits are applied per API key. Exceeding them returns a 429 status.
| Endpoint | Limit |
|---|---|
| All endpoints | 60 requests / minute |
| POST /v1/infographics/generate | 10 requests / minute |
| POST /v1/videos | 5 requests / minute + max 5 jobs in flight |
Rate limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) are included in every response.