Create Lumen5 videos from your content. Send a script or some source text, and Lumen5's agent turns it into a finished video in one of your workspaces. You can leave the style to the agent or pin down the template, voice, brand kit, aspect ratio and length yourself.
There are three ways to connect:
- HTTP: call the API directly from your own code. Most of this page documents it, starting with the quick start below.
- Automation platforms: use Lumen5's apps on platforms like Make, Zapier and n8n to create videos from your workflows, without writing code. See "Automation platforms" below.
- MCP: connect an AI assistant such as Claude, ChatGPT or Cursor, and it can create videos for you as a tool. See "MCP" below.
Quick start
-
Create an API key at
https://lumen5.com/dashboard/api/. It is shown once, so store it somewhere safe. -
Find the workspace to create the video in:
curl https://lumen5.com/api/v1/video-creation/workspaces/ \ -H "Authorization: Bearer $LUMEN5_API_KEY" -
Ask for a video:
curl -X POST https://lumen5.com/api/v1/video-creation/requests/ \ -H "Authorization: Bearer $LUMEN5_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "workspace": "ws_123", "input_content": {"text": "Paste an article, notes or a brief here."}, "output_settings": {"aspect_ratio": "landscape", "target_duration_seconds": 60} }' -
Poll
GET /api/v1/video-creation/requests/{request_id}/untilstatusiscompletedorfailed, or register a webhook endpoint to be told. A completed request has avideo_urlthat opens the video in Lumen5.
Automation platforms
Lumen5 publishes apps on automation platforms, so you can create videos from your workflows without writing code: for example, turning each new blog post into a video.
| Platform | Status |
|---|---|
| Make | Coming soon |
| Zapier | Coming soon |
| n8n | Coming soon |
Each app connects to Lumen5 with OAuth: you sign in to Lumen5 and approve access when you add the connection, so there's no API key to create or paste. The apps are built on this API, so the options and behavior on this page apply to them too.
Until the app for your platform is available, you can use the platform's own HTTP step to call the API directly with an API key, as in the quick start above.
MCP
Lumen5 runs a Model Context Protocol (MCP) server, so AI assistants and agents can create videos as a tool. Its URL is:
https://lumen5.com/mcp
It speaks MCP's Streamable HTTP transport. Its tools work like the HTTP endpoints below, so the options, limits and errors on this page apply to it too.
Connect with OAuth
Most assistants (Claude, ChatGPT, Cursor, VS Code and others) only need the URL. Add https://lumen5.com/mcp as a custom connector or remote MCP server, and the assistant opens Lumen5 for you to sign in and approve access. Approve both permissions if you want it to create videos, not just look things up. For step-by-step setup in Claude, see Configure the Lumen5 MCP for Claude.
Lumen5 doesn't review the assistants that connect this way, so the approval screen marks them as unverified and shows where approving sends you. Only approve a connection you just started yourself.
Under the hood, the server follows the MCP authorization spec. A call without a credential gets a 401 whose WWW-Authenticate header points to https://lumen5.com/.well-known/oauth-protected-resource/mcp, which names Lumen5's authorization server (metadata at https://lumen5.com/.well-known/oauth-authorization-server). Clients register themselves, either with a client ID metadata document or through dynamic client registration at /oauth/register/, then sign the user in with the authorization-code flow and PKCE.
MCP tools
| Tool | What it does |
|---|---|
list_workspaces |
Lists the workspaces you can create videos in. |
list_recipes, list_templates, list_voices, list_blueprints, list_brand_kits, list_projects |
List the style options and projects, as the matching endpoints do. |
preview_video_request |
Checks a request without creating anything, like POST /resolve/. |
create_video |
Starts a video, like POST /requests/. Needs video:write. |
get_video_request |
Gets a request's status and, once it's done, its video_url. |
list_video_requests |
Lists a workspace's requests, newest first. |
create_upload |
Starts uploading a file, like POST /uploads/. Needs video:write. |
complete_upload |
Starts checking an uploaded file, like POST /uploads/{id}/complete/. Needs video:write. |
get_upload |
Gets an upload's status; attach it to create_video once it's ready. |
preview_video_request and create_video take the same body as POST /requests/, and create_video also takes an optional idempotency_key in place of the header. create_upload takes the same body as POST /uploads/. Lists return up to 50 results per page. When a call fails, the tool's result says why, so the assistant can correct itself.
The MCP client sends the file itself, with an HTTP PUT to the upload's upload_url, so it needs to be able to reach storage.lumen5.com. Claude Code and Cursor can. In Claude Desktop and Claude.ai, allow storage.lumen5.com in Claude's code execution network settings (Settings → Capabilities) first.
Authentication
Send a credential on every call as Authorization: Bearer <token>. Either works:
- API key: starts with
lm5_sk_. Create and delete keys athttps://lumen5.com/dashboard/api/. A key acts as the user who created it. - OAuth 2.0 access token: for apps acting on behalf of other Lumen5 users.
What a credential may do depends on its scopes:
| Scope | Allows |
|---|---|
basic:read |
Every GET, and POST /resolve/ (a dry run that changes nothing) |
video:write |
Creating video requests and managing webhook endpoints |
Workspace viewers and reviewers can read but not create videos, just as in the Lumen5 app.
Workspaces
Every call that reads or creates videos names the workspace it acts in: in the workspace query parameter for GET, and in the workspace body field for POST. There is no default workspace. Get workspace ids from GET /workspaces/.
IDs
Every id the API returns has a prefix that says what it is. Pass ids back exactly as you received them.
| Prefix | Names | Listed by |
|---|---|---|
ws_ |
Workspace | GET /workspaces/ |
tpl_ |
Template | GET /templates/ |
voice_ |
AI voice | GET /voices/ |
bp_ |
Blueprint (a workspace's saved video style) | GET /blueprints/ |
bk_ |
Brand kit | GET /brand-kits/ |
proj_ |
Project in a workspace | GET /projects/ |
req_ |
Video creation request | GET /requests/ |
whep_ |
Webhook endpoint | GET /webhook-endpoints/ |
up_ |
A file you uploaded | POST /uploads/ |
med_ |
An image or video in the workspace's media library | GET /media/ |
Recipe ids have no prefix (for example create-promo-ad); get them from GET /recipes/.
Pagination
List endpoints return 50 items per page:
{
"count": 120,
"next": "https://lumen5.com/api/v1/video-creation/templates/?page=2&workspace=ws_123",
"previous": null,
"results": []
}
Follow next until it is null.
Uploading files
Attach your own files to a video request: documents (.pdf, .docx, .pptx, .xml) are read as source material, and images and videos can appear in the video. Files go through the same checks as uploads in the Lumen5 app, and media lands in the workspace's media library.
An upload takes four calls. The first returns two values that the rest use:
| Value | Where it comes from | Used in |
|---|---|---|
id (up_…) |
The response to step 1 | Steps 3 and 4, and input_content.attachments |
upload_url |
The response to step 1 | Step 2 only |
-
Start the upload. Send the workspace and the file's name, with its extension; the extension decides what kind of file it is.
curl -X POST https://lumen5.com/api/v1/video-creation/uploads/ \ -H "Authorization: Bearer $LUMEN5_API_KEY" \ -H "Content-Type: application/json" \ -d '{"workspace": "ws_123", "file_name": "Q3 brief.docx"}'Response (
201):{ "id": "up_3f2504e0-4f89-11d3-9a0c-0305e82c3301", "name": "Q3 brief.docx", "kind": "document", "status": "awaiting_file", "error": "", "created_at": "2026-10-28T23:35:19Z", "finished_at": null, "upload_url": "https://storage.lumen5.com/…?X-Goog-Signature=…", "upload_method": "PUT", "upload_headers": {"x-goog-if-generation-match": "0"} }Keep
id,upload_urlandupload_headers. Theupload_urlworks for one hour. -
Send the file to
upload_url. PUT the file's raw bytes as the request body: not JSON and not a form. Send every header inupload_headers. This request goes to storage, not to the Lumen5 API, so send noAuthorizationheader. Useupload_urlexactly as returned; it is signed, and changing any character makes storage refuse it.curl -X PUT -H "x-goog-if-generation-match: 0" --upload-file "Q3 brief.docx" "$UPLOAD_URL"Storage answers
200with an empty body. An upload URL takes one file: once a PUT has succeeded, another is refused with412. To send a different file, start a new upload. -
Complete the upload with the
idfrom step 1. Lumen5 then checks the file and adds images and videos to the media library. A document is read when a video request that attaches it runs; if it can't be read, that request fails and names the file.curl -X POST https://lumen5.com/api/v1/video-creation/uploads/up_3f2504e0-4f89-11d3-9a0c-0305e82c3301/complete/ \ -H "Authorization: Bearer $LUMEN5_API_KEY"Response (
202): the upload, now with"status": "processing". A409means storage hasn't received the file, so do step 2 first. -
Wait for it to be ready. Get the upload with the same
iduntilstatusisready, orfailedwith the reason inerror. Documents and images usually take seconds; a long video can take a few minutes.curl https://lumen5.com/api/v1/video-creation/uploads/up_3f2504e0-4f89-11d3-9a0c-0305e82c3301/ \ -H "Authorization: Bearer $LUMEN5_API_KEY"
Time limits. Every upload ends ready or failed, so you can stop waiting when it reaches either:
- If no file is uploaded and completed within an hour of starting, the upload fails with "No file was uploaded before the upload URL expired. Start a new upload."
- Processing gets 5 minutes. A file that takes longer fails with "Processing took too long."
- If processing is interrupted on our side, the upload fails within about half an hour with "Processing was interrupted. Upload the file again."
Limits. Each user can complete 10 uploads per billing cycle on a free plan, 100 on a paid plan and 1,000 on an enterprise plan (twelve times that on an annual plan). Uploads count once completed, whether they end ready or failed; ones never completed don't count. Past the limit, starting or completing an upload returns 403.
Then attach it: put the id in input_content.attachments when you create a video request, alongside a script, text or instructions:
{
"workspace": "ws_123",
"input_content": {
"text": "Turn the attached brief into a 60-second summary.",
"attachments": ["up_3f2504e0-4f89-11d3-9a0c-0305e82c3301"]
}
}
attachments also takes med_ ids from GET /media/, to use an image or video that's already in the workspace's media library without uploading it again.
Choosing a style
Everything in output_settings is optional. Anything you leave out (or send as null) is chosen by the agent.
There are two ways to set a style:
- A blueprint (
blueprint): a workspace's saved style. It decides the template, voice, recipe, brand kit, aspect ratio and duration, so it can't be combined with any of them.video_nameandprojectcan still be set. - Individual settings: any combination of
template,voice,recipe,brand_kit,aspect_ratioandtarget_duration_seconds.
Every listing endpoint accepts search to narrow the list by keywords, and only returns options the named workspace can use.
Use POST /resolve/ to check a body before sending it: it validates exactly as POST /requests/ does and returns what would be stored, without creating anything.
Endpoints
All paths are relative to https://lumen5.com.
List workspaces
GET /api/v1/video-creation/workspaces/
The workspaces the caller can create videos in. Each result: id (ws_…), label.
List recipes
GET /api/v1/video-creation/recipes/?workspace=ws_123&search=promo
The kinds of video a request can ask for. Each result: id, label, description.
List voices
GET /api/v1/video-creation/voices/?workspace=ws_123
AI voices for narration. Each result: id (voice_…), label, attributes (for example accent and gender, each a list of strings), preview_url, is_paid.
List templates
GET /api/v1/video-creation/templates/?workspace=ws_123
Each result: id (tpl_…), label, description, aspect_ratios, preview_url.
List blueprints
GET /api/v1/video-creation/blueprints/?workspace=ws_123
Each result: id (bp_…), label, description, aspect_ratio.
List brand kits
GET /api/v1/video-creation/brand-kits/?workspace=ws_123
Each result: id (bk_…), label.
List projects
GET /api/v1/video-creation/projects/?workspace=ws_123
The workspace's projects you can put videos in. Each result: id (proj_…), label.
List media
GET /api/v1/video-creation/media/?workspace=ws_123&search=logo&page=1
The images and videos in the workspace's media library, newest first, as the app's Uploads tab shows them. search and page are optional; pages stop at 50. Each result: id (med_…), kind (image or video), name, thumbnail_url, preview_url, created_at. Pass a med_ id in input_content.attachments to use that image or video in a video.
Start an upload
POST /api/v1/video-creation/uploads/
Body: workspace (a ws_ id), file_name (with its extension) and, optionally, folder: the name of a media library folder to put an image or video in, created if the workspace has none by that name. Without folder, media goes to the library unsorted. Returns 201 with the upload: id (up_…), name, kind (document, image or video), status (awaiting_file), upload_url, upload_method (PUT), upload_headers (send these with the PUT). A file of a kind that can't be used in a video is refused with 400. See "Uploading files" above.
Complete an upload
POST /api/v1/video-creation/uploads/{upload_id}/complete/
Call once the file is PUT to upload_url. Returns 202 with the upload, now processing; 200 if it was already completed; 409 if nothing has been uploaded yet.
Get an upload
GET /api/v1/video-creation/uploads/{upload_id}/
Returns id, name, kind, status (awaiting_file, processing, ready or failed), error, created_at, finished_at. Only the user who started an upload can see or attach it.
Validate a request (dry run)
POST /api/v1/video-creation/resolve/
Takes the same body as creating a request. Returns 200 with the request as it would be stored (workspace, input_content, output_settings, instructions, with null for anything left to the agent), or 400 explaining what's wrong. Needs only basic:read.
Create a video
POST /api/v1/video-creation/requests/
Body:
{
"workspace": "ws_123",
"input_content": {
"script": null,
"text": "Source material for the agent to write a script from.",
"attachments": ["up_abc"]
},
"output_settings": {
"blueprint": null,
"template": "tpl_abc",
"voice": "voice_emma",
"recipe": "create-promo-ad",
"brand_kit": "bk_42",
"aspect_ratio": "vertical",
"target_duration_seconds": 45,
"video_name": "Q3 product update",
"project": "proj_7"
},
"instructions": "Upbeat tone. End on a call to action to book a demo."
}
| Field | Required | Description |
|---|---|---|
workspace |
Yes | A ws_ id. |
input_content.script |
See below | Narration, used word for word. |
input_content.text |
See below | Source material (an article, notes, a brief) for the agent to write a script from. |
input_content.attachments |
No | Up to 25 ids: up_ ids of your uploads that are ready (see "Uploading files" above), or med_ ids of images and videos already in the workspace's media library (see "List media"). Documents are read as source material; images and videos can appear in the video. |
output_settings.blueprint |
No | A bp_ id. Can't be combined with the settings below, apart from video_name and project. |
output_settings.template |
No | A tpl_ id. |
output_settings.voice |
No | A voice_ id. |
output_settings.recipe |
No | A recipe id. |
output_settings.brand_kit |
No | A bk_ id. |
output_settings.aspect_ratio |
No | landscape, square, vertical or fourfive (4:5). |
output_settings.target_duration_seconds |
No | 5 to 600. |
output_settings.video_name |
No | Up to 200 characters. |
output_settings.project |
No | A proj_ id: the project to put the video in. Left out, the video goes to All videos, or for an editor, their most recent project. |
instructions |
See below | Free-text direction for the agent. |
Send at least one of script, text and instructions, and never both script and text. With instructions alone, the agent writes the script from them. Attachments add to these; they aren't enough on their own.
Responses:
201: the new video request (see "The video request object" below).200: a retry of an earlier request with the sameIdempotency-Key; returns that request instead of making another video.400: the body is invalid. The response says which fields are wrong and why.409: theIdempotency-Keywas already used with a different body.429: too many requests (see "Limits" below).
Idempotency. Send an Idempotency-Key header (any unique string up to 255 characters, such as a UUID) so you can safely retry after a timeout or network error. Retrying with the same key and body returns the original request; the same key with a different body is refused with 409. Keys are scoped to the caller and workspace.
List video requests
GET /api/v1/video-creation/requests/?workspace=ws_123&status=running
The workspace's requests, newest first. status is optional: running, completed or failed.
Get a video request
GET /api/v1/video-creation/requests/{request_id}/
List webhook endpoints
GET /api/v1/video-creation/webhook-endpoints/
Add a webhook endpoint
POST /api/v1/video-creation/webhook-endpoints/
{
"url": "https://example.com/lumen5-webhook",
"events": ["request.completed", "request.failed"]
}
url must be a public https URL. Returns 201 with id (whep_…), url, events, created_at and secret (whsec_…). The secret is shown only this once; store it to verify deliveries.
An endpoint receives events for all of the caller's requests, in every workspace.
Delete a webhook endpoint
DELETE /api/v1/video-creation/webhook-endpoints/{endpoint_id}/
Returns 204.
Rotate a webhook secret
POST /api/v1/video-creation/webhook-endpoints/{endpoint_id}/rotate-secret/
Returns the endpoint with its new secret. The old secret keeps working for 24 hours, so you can update your receiver without dropping deliveries.
Get the OpenAPI schema
GET /api/v1/video-creation/schema/
No authentication needed. Use it to generate a client or to give an agent the full set of operations.
The video request object
{
"id": "req_9f1c2d3e-…",
"status": "completed",
"workspace": "ws_123",
"input_content": {"script": null, "text": "Source material…"},
"output_settings": {
"blueprint": null,
"template": "tpl_abc",
"voice": null,
"recipe": null,
"brand_kit": null,
"aspect_ratio": "vertical",
"target_duration_seconds": null,
"video_name": null,
"project": null
},
"instructions": null,
"video_url": "https://lumen5.com/app/?id=456",
"share_page_url": "https://lumen5.com/user/my-workspace/otters-abc123/",
"embed_html": "<iframe src=\"https://lumen5.com/embed/abc123/\" width=\"345\" height=\"650\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen></iframe>",
"error": null,
"created_at": "2026-10-06T18:30:00Z",
"finished_at": "2026-10-06T18:34:10Z"
}
| Field | Description |
|---|---|
status |
running, then completed or failed. |
output_settings |
What you asked for. null means you left it to the agent. |
video_url |
Opens the video in Lumen5. null until there is a video. |
share_page_url |
The video's share page, to watch or share it. null until there is a video. |
embed_html |
An <iframe> that plays the video on your own page. null until there is a video, and for a private video, which can't be embedded. |
error |
Why the request failed, when status is failed. |
finished_at |
When the request completed or failed. |
Webhooks
Lumen5 sends a POST with a JSON body to each endpoint subscribed to the event:
| Event | Sent when |
|---|---|
request.completed |
A video request finished and its video is ready. |
request.failed |
A video request failed. |
{
"event": "request.completed",
"request_id": "req_9f1c2d3e-…",
"workspace": "ws_123",
"status": "completed",
"occurred_at": "2026-10-06T18:34:10+00:00",
"video_url": "https://lumen5.com/app/?id=456",
"share_page_url": "https://lumen5.com/user/my-workspace/otters-abc123/",
"embed_html": "<iframe src=\"https://lumen5.com/embed/abc123/\" width=\"345\" height=\"650\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen></iframe>"
}
Payloads say what happened and, once there is a video, carry its video_url, share_page_url and embed_html, the same as the request (null otherwise, as for a failed request). Verify the signature before you act on a delivery, pass a link on, or put embed_html on a page. Fetch GET /requests/{request_id}/ with your own credentials for the rest of the details.
Respond with a 2xx status within 10 seconds. Redirects aren't followed. Respond with 410 Gone to unsubscribe: the endpoint is deleted and gets no further deliveries. Any other response, or no response, is retried up to 5 times over about half an hour, so the same event can arrive more than once: use request_id and event to ignore repeats.
Each delivery carries these headers:
| Header | Value |
|---|---|
X-Lumen5-Event |
The event name. |
X-Lumen5-Timestamp |
When the delivery was signed. |
X-Lumen5-Signature |
One or more comma-separated signatures, each v0= followed by a hex HMAC-SHA256 signature. |
To verify a delivery, compute HMAC-SHA256 over v0:{timestamp}:{raw request body} with the endpoint's secret, and accept the delivery if it matches any of the signatures in the header, comparing in constant time. Use the raw body bytes, before any JSON parsing.
The header usually holds one signature. For 24 hours after you rotate the secret, it holds one for the new secret and one for the old, so a receiver with either secret keeps verifying deliveries.
import hashlib
import hmac
def is_from_lumen5(secret: str, timestamp: str, raw_body: str, signature_header: str) -> bool:
expected = "v0=" + hmac.new(secret.encode(), f"v0:{timestamp}:{raw_body}".encode(), hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, signature) for signature in signature_header.split(","))
Errors
Errors use standard HTTP status codes with a JSON body:
| Status | Meaning |
|---|---|
400 |
The body or parameters are invalid. The body maps each bad field to a list of messages, and detail sums them up in one readable string. |
401 |
The credential is missing, invalid, revoked or expired. |
403 |
The credential lacks the scope, or the user's role can't make that change. |
404 |
The workspace, request or endpoint doesn't exist or isn't visible to the caller. |
409 |
The Idempotency-Key was already used with a different body, or an upload was completed before its file was uploaded. |
429 |
Rate limited. Wait for the number of seconds in the Retry-After header. |
Limits
Each user can create up to 300 video requests and start up to 300 uploads an hour, on top of Lumen5's general API rate limits.
Notes for AI agents
- If you can use MCP tools, connect to
https://lumen5.com/mcpinstead of calling HTTP. See "MCP" above. - Fetch this page as Markdown from
https://lumen5.com/developers/api.md, and the machine-readable spec fromhttps://lumen5.com/api/v1/video-creation/schema/?format=json. - Start with
GET /workspaces/. If there is more than one workspace, ask the user which to use. - Only use ids returned by the listing endpoints. Don't guess them.
- Prefer leaving style settings out unless the user asked for them; the agent picks sensible defaults.
- Call
POST /resolve/beforePOST /requests/to catch mistakes without creating anything. - Always send an
Idempotency-Key, and reuse it when retrying the same request. - To use a user's file, upload it (see "Uploading files") and wait for
readybefore attaching itsup_id. - Creating a video takes minutes. Poll no more often than every 15 seconds, or use a webhook.