Quickstart
Base URL https://quantumplations.ai/api/v1. Everything speaks JSON, your plan decides what you may order, and your key is the only credential.
Mint a key
In your account settings, under API keys. It's shown once, so keep it on a server: a secret key in a browser is a leaked key.
Order a video
Only prompt is required.
curl -X POST https://quantumplations.ai/api/v1/videos \
-H "Authorization: Bearer $QP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"prompt": "Explain how a Kalman filter works.", "duration_seconds": 60}'
You get 202 and a render:
{
"id": "425e4bbe-5d63-4343-b0ad-8d90dc5805b4",
"status": "queued",
"duration_seconds": 60,
"videos_left": null,
"video": null
}
The Idempotency-Key is optional but worth sending. If the reply never reaches you, repeating the call with the same key hands back the render you already bought instead of ordering a second one.
Collect it
Poll the render, or give callback_url on step 2 and let a webhook tell you.
curl https://quantumplations.ai/api/v1/videos/$RENDER_ID \ -H "Authorization: Bearer $QP_API_KEY"
status runs queued to processing to one of succeeded, failed or canceled. On success video arrives on a paid plan, and its URLs are signed and expire in an hour, so fetch rather than store them. On the free plan there is nothing to fetch: video stays null, download_locked comes back true, and the video is watched signed in on the site.
"video": {
"url": "https://…/425e4bbe….mp4?token=…",
"poster_url": "https://…/425e4bbe….jpg?token=…",
"duration_seconds": 59.8,
"expires_in": 3600
}
Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /videos | Order a video, under your plan's length, concurrency and allowance limits. |
| GET | /videos/{id} | One render, with signed URLs once it's done on a paid plan. |
| GET | /videos | Your renders, newest first. ?limit= 1–100, ?before= a timestamp. |
| DELETE | /videos/{id} | Cancel one that hasn't started. A free video it took comes back. |
| GET | /options | Every voice, language and look you can ask for, and your plan's limits. |
| GET | /credits | Your plan and what is left on it. |
Render options
Everything but prompt has a default. Anything else you send is ignored.
| Field | Default | Accepts |
|---|---|---|
| prompt required | – | Up to 8,000 characters. |
| duration_seconds | 120 | A whole number, 10 to 1800, and no longer than your plan allows. |
| look | whiteboard | whiteboard, technical, sketch. |
| voice | Finn | A voice id from /options, or "none" for a silent video. |
| language | en | 74 languages, listed in /options. |
| scope | standard | standard, or socratic for a two-voice dialogue. |
| student_voice | Janet | Who plays the student in a socratic dialogue. A voice id from /options, never the same as voice. Ignored on a standard video. |
| formality | neutral | casual, neutral or formal. How the narrator talks. Never changes the length. Set for you on a plan without fine-tuning (see Plans). |
| focus | balanced | tight, balanced or exploratory. How far the video may range from the question. Set for you on a plan without fine-tuning (see Plans). |
| captions | false | Burns the narration into the frames. Not with "none". |
| personalized | true | Shapes the video around your saved profile: how you learn, what you already know, and your first name. Send false when the video is for someone else. |
| aspect_ratio | 16:9 | 16:9 or 9:16. |
| callback_url | null | An https URL on a public host. |
Errors
Every failure is {"error": "<code>", "message": "…"} with a matching status.
| Code | HTTP | When |
|---|---|---|
| auth_required | 401 | No key. |
| invalid_api_key | 401 | The key isn't ours, or it's been revoked. |
| free_videos_used | 402 | The free videos on this account are gone. Subscribe for unlimited. |
| duration_over_plan | 400 | Longer than your plan allows. The plan's ceiling is in /options. |
| too_many_in_flight | 429 | This account has as many videos open as your plan allows. One waiting on your answers counts as open, so answer it or wait for a video to finish. |
| forbidden | 403 | A browser tried to use a key. |
| invalid_request | 400 | A field is missing or out of range. The message names it. |
| render_not_found | 404 | No render with that id on your account. |
| render_not_cancelable | 409 | It already started, or it started once and is queued for another try. |
| rate_limited | 429 | Past 60 calls a minute on one key, or 240 from one address. Carries Retry-After. |
| upstream_error | 502 | Our side. Safe to repeat everywhere except POST /videos, which is what Idempotency-Key is for. |
Webhooks
Give callback_url and we post once, when the render settles: { event, id, status, error, completed_at }, with X-Quantumplations-Signature: t=<unix>,v1=<hex>. There's no retry, so keep polling as your fallback.
The signature is an HMAC-SHA256 of "<t>.<raw body>" keyed with the webhook secret shown beside your key. Verify against the raw bytes, before parsing:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhook(rawBody, header, secret,
nowS = Math.floor(Date.now() / 1000)) {
const parts = Object.fromEntries(String(header || '').split(',').map((p) => {
const i = p.indexOf('=');
return i < 0 ? ['', ''] : [p.slice(0, i).trim(), p.slice(i + 1).trim()];
}));
// Reject a replay of anything older than five minutes.
const t = Number(parts.t);
if (!/^[0-9]+$/.test(parts.t || '') || Math.abs(nowS - t) > 300) return false;
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest();
const given = Buffer.from(String(parts.v1 || ''), 'hex');
// timingSafeEqual throws on a length mismatch, so check length first.
return given.length === expected.length && timingSafeEqual(given, expected);
}
Answer with a 2xx quickly. Nothing you return is read.
Use it from an agent
There's an MCP server at https://quantumplations.ai/api/mcp, so any agent or editor that speaks MCP can order a video without leaving the conversation. It's the same API and the same key.
{
"mcpServers": {
"quantumplations": {
"type": "http",
"url": "https://quantumplations.ai/api/mcp",
"headers": { "Authorization": "Bearer qp_live_…" }
}
}
}
Five tools: generate_video, answer_questions, get_video, list_videos and get_credits. The one worth knowing about is answer_questions: the agent making your video stops and asks when a brief is ambiguous, and get_video reports awaiting_answers with the questions and how long is left to answer. Only generate_video makes a video, and it's annotated so your client asks before it does. There's deliberately no cancel tool: an agent that can cancel renders it didn't order is a footgun, and cancelling stays on the REST surface where a person presses the button.
A key in an MCP config is still a secret key. It sits on the machine running the client, which is a server-side place, but treat the config file the way you'd treat any file holding a credential.
API keys
Keys belong to your Quantumplations account and carry your plan and your beta access. Mint one per application, so you can revoke or roll one without breaking the rest. Twenty live keys is the ceiling.
Managed in your account
Minting, rolling and revoking live behind your sign-in, not on this page: open Create, click your account, and choose API keys. A key is shown once, at mint, and never again; we store a hash, not the key.
Revoking is immediate and can't be undone. Renders already queued with that key still finish, and their webhooks still arrive.
Plans
An API key orders under the same plan as the website, and the plan decides four things: how long a video may run, how many renders you may have going at once, on the free plan how many videos you get in total, and which of the render options above you may set at all. GET /options publishes those limits for the key that calls it, and GET /credits reports what is left. A video longer than the plan allows is refused with duration_over_plan before anything is queued, so nothing is half-ordered. On the free and starter plans, tone, focus, web images and clarifying questions are set for you, and so is personalizing; on the free plan captions are set for you too. An order that sends one of them is accepted all the same and the video is made with the standard value, so read plan.locked from GET /options, which names them, before you rely on one. The free plan gives three videos in total; a render that fails or is cancelled puts its video back. Downloading the finished video, its transcript and its subtitles is part of the paid plans; a free key renders and reports progress, and answers download_locked: true with no file. See the plans.