Developers

Ozor integration docs.

Three ways to drive Ozor from your stack — the Model Context Protocol server for AI agents, the official n8n node for no-code workflows, and the REST API for everything else. One auth model, one credit balance, one underlying engine.

Base URL

https://ozor.ai

MCP server

https://mcp.ozor.ai

API version

/api/v1/

Getting started

Welcome to Ozor for developers

Ozor turns natural-language prompts and documents into fully animated, narrated videos. This documentation covers everything you need to drive that engine from outside the web app — whether you are an AI agent, a no-code automation, or a production service.

You can mix and match the integrations. They all hit the same backend, share the same credit balance, and produce videos that all show up in your dashboard at ozor.ai.

Where to find an API key

Sign in at ozor.ai and open Settings → Developer → API Keys. The raw key (sk_live_ + 32 hex chars) is shown once at creation time — copy it into a secret manager immediately. You can have up to 5 active keys per account.

Getting started

Choose your integration

Each integration is a thin wrapper around the same engine. Pick by who is calling:

IntegrationWho it's forAuthUse it when
MCP serverLLM agents (Claude Desktop, Claude.ai, Cursor, custom MCP clients).API key or OAuthYou want a chat-driven workflow where the model decides when to generate, edit, and export.
n8n nodeOperations & automation teams using n8n.API key credentialYou want to wire video generation into a Zapier-style workflow (form → video, doc → course, etc.) without writing code.
REST APIYour own backend, scripts, or any HTTP client.X-API-Key headerYou need full programmatic control — custom polling, your own queue, batching, embedded in your product.

All three live on top of the same /api/v1/ surface. Anything you can do in the n8n node or the MCP server is also doable directly against the REST API.

Getting started

Authentication overview

There are two ways to authenticate against the Ozor engine. Every integration uses one (or both) of them.

MethodHeader / mechanismUsed by
API keyX-API-Key: sk_live_<32 hex>REST API, n8n credential, MCP stdio mode, MCP HTTP scripts.
OAuth 2.1 (PKCE)Authorization: Bearer <access_token> — issued by mcp.ozor.ai.MCP Streamable-HTTP transport (Claude.ai web, mobile, hosted clients).
Firebase ID tokenAuthorization: Bearer <firebase_id_token>API key management endpoints only — the same session your browser uses after sign-in.

API keys are scoped to the account that created them. Every resource (video, plan, export, job) can only be read or modified by the owning account. Revoke a key in the dashboard and the next request using it fails with 401 immediately.

Getting started

Credits & billing

Every generation operation deducts from your account credit balance. Credits are shared across all integrations — an MCP-driven generation pulls from the same bucket as an n8n run or a curl call.

OperationCostWhen deducted
Generate video (text prompt)1 creditOn successful job completion
Send message / edit instruction1 creditOn successful job completion
Analyze document1 creditImmediately
Generate from document planfloor(voiceover_scenes / 2)On successful completion
Export an MP40 credits
All read endpoints (list, get, jobs)0 credits
  • New accounts start with 10 free credits. Subscribe or buy a one-time top-up from the dashboard to add more.
  • When a generation fails, no credit is deducted. Retry safely.
  • If your balance is short, the endpoint returns 402 Payment Required up front — no partial work is performed.
  • There is no rate limit. Usage is governed entirely by credits.

Integration

MCP integration

MCP / 01

What Ozor MCP is

The Model Context Protocol is an open standard for connecting AI models to external tools. The Ozor MCP server turns the full Ozor video pipeline into a set of typed tools any MCP-compatible client (Claude Desktop, Claude.ai, Cursor, VS Code, custom agents) can call from natural language.

What an agent can do with Ozor MCP, in one paragraph:

  • Text → video. A short creative brief becomes a short AI-generated video. The Ozor agent picks scenes, motion, copy, and visuals.
  • Document → video. A PDF, PPTX, DOCX, or web URL becomes a narrated video plan you can review, edit, and render.
  • Conversational editing. Once a video exists, natural-language instructions (“add our logo to scene 2”, “make the outro shorter”) re-drive the same project.
  • Export & embed. A finished project can be rendered to MP4, given a permanent public share page, and converted into a ready-to-paste <iframe>.

The server is stateless — every call carries the user's API key (via Bearer auth or the MCP OAuth flow). All persistence lives in the Ozor backend.

MCP / 02

How it works

architecture
┌────────────┐   MCP   ┌──────────────┐    HTTPS    ┌────────────────┐
│  LLM /     │ ──────► │  Ozor MCP    │ ──────────► │  Ozor API      │
│  MCP host  │ ◄────── │  server      │ ◄────────── │  (ozor.ai)     │
└────────────┘  tools  └──────────────┘   JSON      └────────────────┘
                                                          │
                                                          ▼
                                          ┌─────────────────────────────┐
                                          │ Firestore + GCS + Renderer  │
                                          └─────────────────────────────┘

The MCP server is a small Node/TypeScript program that:

  • Receives MCP tool calls from the client.
  • Authenticates as the end user (API key or OAuth).
  • Calls the Ozor REST API at /api/v1/....
  • Returns structured JSON results that the LLM presents to the user.

One source of truth

Every user-facing URL (editor, watch, embed, MP4) is built by the backend and returned in the API response. The MCP server passes them through verbatim. Agents must never hand-build an Ozor URL from a videoId or shareCode — see URL fields decoded.

MCP / 03

Connect from Claude Desktop

Claude Desktop launches MCP servers as local subprocesses over stdio. The configuration lives in claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json; Windows: %APPDATA%\\Claude\\claude_desktop_config.json).

1. Get an API key

Sign in at ozor.ai, open Settings → Developer → API Keys, and create a key named e.g. claude-desktop. Copy the sk_live_... value — you only see it once.

2. Add the server to your config

json
{
  "mcpServers": {
    "ozor": {
      "command": "npx",
      "args": ["-y", "@ozor/mcp"],
      "env": {
        "OZOR_API_KEY": "sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
      }
    }
  }
}

3. Restart Claude Desktop

Quit and relaunch the app. In a new conversation, the Ozor tools (generate_video, analyze_document, export_video, etc.) appear in the tool menu. Try it with:

prompt
Make a 20-second product teaser for a wireless headphone — minimalist, dark background, calm tone.

Verify the connection

Ask Claude: “Do you have the Ozor tools available? List them.” The model should reply with at least generate_video, analyze_document, export_video, list_videos, and list_voices.

MCP / 04

Connect from Claude.ai (web & mobile)

Claude.ai connects to MCP servers over Streamable HTTP using OAuth 2.1 with PKCE. You never paste your API key into the browser — Ozor stores it server-side and exchanges access tokens for it on each call.

1. Open Settings → Connectors

In Claude.ai, open Settings → Connectors (or your workspace settings) and click Add custom connector.

2. Add the Ozor connector

FieldValue
NameOzor
Connector URLhttps://mcp.ozor.ai
Auth methodOAuth 2.1 (auto-discovered)

Claude.ai discovers the OAuth endpoints from /.well-known/oauth-authorization-server automatically.

3. Authorize

A browser window opens to ozor.ai for you to sign in and approve the connection. Approving creates a server-side OAuth client tied to your account; Claude.ai receives a short-lived access token it sends as Authorization: Bearer ... on every tool call.

Revoke access any time from Settings → Developer → Connected apps on ozor.ai. Revoked tokens fail with 401 immediately.

MCP / 05

Connect from Cursor & other IDEs

Cursor, VS Code (with the MCP extension), Zed, and Windsurf all support MCP servers. Configuration is editor-specific but follows the same shape as Claude Desktop: a command + args + environment.

Cursor

Open Cursor Settings → MCP and add:

json
{
  "ozor": {
    "command": "npx",
    "args": ["-y", "@ozor/mcp"],
    "env": { "OZOR_API_KEY": "sk_live_..." }
  }
}

VS Code (MCP extension)

Add to your workspace .vscode/mcp.json (or user settings) — same shape as above. Reload the window.

Anything else

Any client that supports the stdio transport can launch npx -y @ozor/mcp with OZOR_API_KEY in the environment. The tools are the same on every host.

MCP / 06

Connect over HTTP (scripts & service-to-service)

For custom agents or service-to-service flows you can talk to the hosted MCP server over Streamable HTTP with a static Authorization header — no OAuth round-trip needed.

bash
curl -sS https://mcp.ozor.ai/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "generate_video",
      "arguments": { "prompt": "A 15-second teaser for a coffee subscription" }
    }
  }'

The Bearer value here is your raw sk_live_...API key. Service-to-service is the simplest path if you already use the REST API and just want MCP's tool-shape framing.

MCP / 07

The two workflows

The decision tree is dictated by the input shape, not how the user phrased their ask. Pick by whether a document is involved.

Workflow A — Text → video

  • generate_video(prompt){ videoId, jobId, status: "pending" }
  • wait_for_job(videoId, jobId) — blocks until the agent finishes; returns editorUrl.
  • Present editorUrl to the user. Stop. Ask whether they want an MP4.
  • (only if they say yes) export_video(videoId) { exportId, exportStatus: "queued" }
  • wait_for_export(videoId) — blocks until render completes; returns watchUrl, mp4Url, embedUrl, downloadUrl.
  • Optional: get_embed_code(videoId) → ready-to-paste <iframe>.

Prompt limit

The prompt is capped at 2000 characters and describes one short video. Never paste a multi-scene script into it.

Workflow B — Document → video

  • Get the document to the server (URL, signed upload, or base64 — see analyze_document).
  • analyze_document(...) { planId, status: "draft", scenes, voiceSettings }
  • Present the plan as readable chat text — scene count, credit cost, numbered scenes (title + a line of voiceover). Never show raw JSON.
  • Wait for user approval. To edit, call update_plan(planId, scenes, voiceSettings?) then re-present.
  • generate_from_plan(planId) { projectId, editorUrl, title }
  • From here it's the same as Workflow A: present the editor, ask about exporting, then export_videowait_for_export.

Don't re-implement document analysis

Never read the PDF yourself, summarize it, and drop the summary into generate_video. That loses the slide visuals and overflows the 2000-character prompt cap. Always use analyze_document for document inputs.

Iterating on an existing video

  • send_message(videoId, message){ jobId }
  • wait_for_job(videoId, jobId) — returns the updated editorUrl (same URL, new content).
  • Point the user back at the editor. Ask before re-exporting.
  • (if they want a fresh MP4) export_video(videoId)wait_for_export(videoId). The cached export is reused if nothing render-relevant changed.

MCP / 08

URL fields — decoded

The Ozor API returns several URL fields. They mean different things and are not interchangeable. This is the section everyone gets wrong on the first integration.

FieldWhat it isWhen set
editorUrlThe Ozor editor — where the user previews and edits the project. https://www.ozor.ai/editor?projectId=<id>Always, as soon as a project exists.
watchUrlPublic watch page — where people open to watch the finished video. https://www.ozor.ai/share/<shareCode>After a public export completes.
embedUrl<iframe src> URL for embedding the player. https://www.ozor.ai/embed/<shareCode>After a public export completes.
mp4UrlDirect, permanent MP4 file. https://storage.googleapis.com/.../1080p.mp4After a public export completes.
downloadUrl24h-signed direct download URL (GCS signed URL).After any export completes (public or private).
shareUrlDeprecated alias of mp4Url. Kept for backwards compatibility.Same as mp4Url.
thumbnailUrlStatic poster image for the video (signed GCS URL).After export completes.

Rules

  • Never construct a URL from a videoId or shareCode. Even if you “know” the format. Hosts and query params can change.
  • watchUrl is the link to give the user. Not mp4Url. A raw storage.googleapis.com MP4 is a download, not a watch page.
  • mp4Url is for downloads and <video> embeds — not for sharing on social or sending to humans.
  • If watchUrl is null, the export is private. Re-export with isPublic: true and the watch page will appear.
  • editorUrl is always safe to present, even before any export exists — it lets the user preview the project on Ozor immediately after generation.

MCP / 09

Tool reference

Every tool returns a JSON body in the MCP content block. Errors come back with isError: true and a message field. All time fields are ISO 8601 strings unless noted.

TOOLgenerate_video

Generate a new AI video from a text prompt. Async — returns immediately with a jobId you must poll via wait_for_job.

Input

FieldTypeRequiredNotes
promptstring (1–2000 chars)YesShort creative brief for one video. Not a multi-scene script.
aspect"16:9" | "9:16"NoDefault "16:9".
exportbooleanNoIf true, auto-queue an MP4 export after the agent finishes. Leave false by default.
exportQuality"720p" | "1080p" | "4k"NoOnly used when export: true. Default 720p.
imagesarrayNoOptional reference images (URL or base64).
videosarrayNoOptional reference video clips.

Returns

json
{ "videoId": "...", "jobId": "...", "status": "pending" }

Use export: true only when the user has explicitly asked for a downloadable MP4 or share link up front (e.g. “render and send me the 1080p file”). Default is preview-first.

TOOLlist_videos

List videos created through the API by this account, newest first. Videos created inside the Ozor web app are not included.

Input

FieldTypeNotes
limitnumber (1–100)Default 20.

Returns

json
{
  "videos": [
    { "videoId": "...", "title": "...", "status": "draft",
      "editorUrl": "https://www.ozor.ai/editor?projectId=...",
      "createdAt": "2026-05-26T10:14:22Z" }
  ]
}
TOOLget_video

Full status of one video — preview URL, export status, and every URL field once an export exists. Useful when polling manually; most callers should use wait_for_export instead.

Input

FieldTypeRequired
videoIdstringYes

Returns (shape varies by export state)

json
{
  "videoId": "GhWBYCdhM4omoHRv9W7p",
  "title": "Ozor MCP launch",
  "status": "complete",
  "editorUrl": "https://www.ozor.ai/editor?projectId=GhWBYCdhM4omoHRv9W7p",
  "createdAt": "2026-05-26T10:14:22Z",

  // Only once an export has been triggered:
  "exportId": "exp_...",
  "exportStatus": "complete",
  "thumbnailUrl": "https://...",
  "downloadUrl": "https://storage.googleapis.com/.../signed.mp4",

  // Only once a PUBLIC export completes:
  "shareCode": "wuq4y9t",
  "watchUrl": "https://www.ozor.ai/share/wuq4y9t",
  "embedUrl": "https://www.ozor.ai/embed/wuq4y9t",
  "mp4Url":   "https://storage.googleapis.com/.../1080p.mp4",
  "shareUrl": "https://storage.googleapis.com/.../1080p.mp4"  // deprecated alias
}
TOOLexport_video

Trigger an MP4 export for an existing video. Every export through the MCP server is public by default — the result carries a permanent watchUrl, embedUrl, and mp4Url.

Input

FieldTypeRequiredNotes
videoIdstringYes
quality"720p" | "1080p" | "4k"NoDefault "1080p".

Returns

json
{ "videoId": "...", "editorUrl": "...", "exportId": "...",
  "exportStatus": "queued",
  "watchUrl": "...", "embedUrl": "...", "mp4Url": "...", "shareCode": "wuq4y9t" }

Content-addressed cache

If an identical export already exists (same project content, quality, fps, aspect, visibility), the cached result is returned instantly.

Until the export is complete, only editorUrl is safe to show users. Poll with wait_for_export.

TOOLsend_message

Send a natural-language edit instruction to the Ozor agent for an existing video. Async — returns a jobId.

Input

FieldTypeRequiredNotes
videoIdstringYes
messagestring (1–2000 chars)YesInstruction: change copy, add a logo, reorder scenes, etc.
imagesarrayNoOptional images to attach (e.g. a logo).
videosarrayNoOptional video clips to attach.

Returns

json
{ "jobId": "...", "status": "pending" }

After wait_for_job resolves, point the user back at editorUrl to preview the change and ask before re-exporting. Don't trigger a fresh export_video on your own initiative.

TOOLget_job

One-shot poll for an agent job. Most callers should use wait_for_job instead.

Input

FieldTypeRequired
videoIdstringYes
jobIdstringYes

Returns

json
{ "status": "completed",
  "response": "<agent reply>",
  "editorUrl": "...", "title": "..." }
TOOLwait_for_job

Block until an agent job is completed or failed. This is the gate before presenting anything to the user.

Input

FieldTypeRequiredNotes
videoIdstringYes
jobIdstringYes
pollIntervalSecondsnumber (1–60)NoDefault 5.
timeoutSecondsnumber (10–600)NoDefault 300.

Returns when status === "completed"

json
{
  "videoId": "...",
  "jobId": "...",
  "status": "completed",
  "editorUrl": "https://www.ozor.ai/editor?projectId=...",
  "title": "Ozor MCP launch",
  "response": "<agent's reply text>",

  // Present only if generate_video was called with export:true:
  "exportStatus": "complete",
  "watchUrl": "...", "embedUrl": "...", "mp4Url": "...", "downloadUrl": "...",

  "notes": "Agent finished. Present editorUrl to the user, then ASK before exporting an MP4."
}

editorUrl and title are part of the standard payload — you never need a follow-up get_video call just to learn the editor URL.

TOOLwait_for_export

Block until an MP4 export reaches complete or failed.

Input

FieldTypeRequiredNotes
videoIdstringYes
pollIntervalSecondsnumberNoDefault 5.
timeoutSecondsnumberNoDefault 300. Bump to 600 for 4k or large plans.

Returns when complete

json
{
  "exportStatus": "complete",
  "videoId": "...",
  "shareCode": "wuq4y9t",
  "watchUrl": "https://www.ozor.ai/share/wuq4y9t",
  "embedUrl": "https://www.ozor.ai/embed/wuq4y9t",
  "editorUrl": "https://www.ozor.ai/editor?projectId=...",
  "mp4Url": "https://storage.googleapis.com/.../1080p.mp4",
  "downloadUrl": "https://storage.googleapis.com/.../signed.mp4",
  "thumbnailUrl": "...",
  "notes": "Present watchUrl as the link people open to watch on Ozor. mp4Url is the raw MP4."
}
TOOLget_embed_code

Produce ready-to-use embed snippets — share page link, responsive <iframe>, and a <video> tag. Requires a completed public export.

Input

FieldTypeRequiredNotes
videoIdstringYesMust have a completed public export.
widthnumberNoFixed iframe width in px. Omit for responsive.
heightnumberNoFixed iframe height in px. Omit for responsive.
aspect"16:9" | "9:16"NoFor responsive embed. Default "16:9".

Returns

json
{
  "videoId": "...",
  "shareCode": "wuq4y9t",
  "watchUrl": "...",
  "embedUrl": "...",
  "iframeEmbed": "<div style=\"position:relative;...\"><iframe src=\"...\"></iframe></div>",
  "videoTagEmbed": "<video src=\"...\" controls playsinline ...></video>",
  "mp4Url": "..."
}
TOOLanalyze_document

Convert a PDF, PPTX, DOCX, or web URL into a draft video plan. Always use this for document inputs.

Input — pick one source

SourceWhenNotes
urlPublic http(s) URLBest option whenever available. No size limit, no token cost.
fileBase64 + fileNameSmall local files (< 4 MB raw)Pass the complete base64 string in one call. Truncation produces “error parsing the body”.
filePathLocal STDIO mode onlyNever works on a hosted server.
prepare_document_uploadMulti-MB local filesSee the next tool — best path for anything > 4 MB.

Additional optional fields

FieldTypeNotes
aspect"16:9" | "9:16"Default "16:9".
voiceSettingsobjectTTS voice config (use list_voices for the catalog).

Returns

json
{
  "planId": "plan_...",
  "status": "draft",
  "scenes": [
    { "order": 1, "sceneTitle": "Introduction",
      "scenePrompt": "Title card with logo fading in...",
      "voiceoverText": "Welcome to Acme — we&apos;re rethinking collaboration.",
      "imageRefs": [...], "keyPoints": ["intro", "brand"] }
  ],
  "voiceSettings": { ... },
  "creditCost": 3
}

Recommended plan presentation

Here's the video plan from your document:

  • 6 scenes, costs 3 credits to generate.
  1. Opening hook — “Imagine every onboarding moment that mattered…”
  2. Problem framing — “Most policies sit in a PDF nobody reads.”

Want me to generate this, change anything, or use a different document?

TOOLprepare_document_upload

Get a one-shot signed upload URL for a local document. Use for any document larger than ~4 MB — the file streams directly to the server over HTTP and never passes through a tool call.

Input

FieldTypeRequiredNotes
fileNamestringYesFilename — used to detect content type if not provided.
contentTypestringNoapplication/pdf

Returns

json
{
  "uploadUrl": "https://mcp.ozor.ai/upload/<ticket>",
  "expiresInSeconds": 900,
  "curlCommand": "curl -sS -F \"file=@/ABSOLUTE/PATH/document.pdf\" \"https://mcp.ozor.ai/upload/<ticket>\"",
  "instructions": "Run curlCommand in a shell..."
}

The agent should run curlCommand (or instruct the user to) — its stdout is the same JSON analyze_document would return, including the planId. The upload URL works once and expires in 15 minutes.

TOOLget_plan

Retrieve a draft plan by planId, with freshly-signed image URLs and an up-to-date credit cost.

Input

FieldTypeRequired
planIdstringYes

Same response shape as analyze_document.

TOOLupdate_plan

Edit a draft plan before generating: rewrite voiceover, reorder/drop scenes, adjust prompts, lock in a TTS voice.

Input

FieldTypeRequiredNotes
planIdstringYes
scenesarrayYesFull replacement
voiceSettingsobjectNoOptional TTS settings (voice id, speed, style).

Only valid while the plan's status is "draft" or "failed". Once you call generate_from_plan, the plan is consumed.

TOOLgenerate_from_plan

Render an approved plan into a real video project. This spends credits — only call after the user has explicitly approved the plan.

Input

FieldTypeRequiredNotes
planIdstringYes
timeoutSecondsnumber (30–900)NoDefault 600.

Returns

json
{
  "status": "done",
  "planId": "...",
  "projectId": "HuANTASx3WdqwwngHRCs",
  "title": "Ozor MCP launch",
  "editorUrl": "https://www.ozor.ai/editor?projectId=HuANTASx3WdqwwngHRCs",
  "message": "Video project created. Present editorUrl, then ASK before exporting."
}

Credit cost: floor(voiceover_scenes / 2). Use the returned projectId as videoId for every subsequent call.

TOOLlist_voices

List the TTS voices available for document-to-video narration. Use a voice id in update_plan's voiceSettings to lock it in before generation.

Returns

json
{ "voices": [
  { "id": "aria",   "name": "Aria",   "language": "en", "gender": "female",  "character": "Clear, precise" },
  { "id": "nova",   "name": "Nova",   "language": "en", "gender": "female",  "character": "Warm, natural" },
  { "id": "felix",  "name": "Felix",  "language": "en", "gender": "male",    "character": "Playful, upbeat" },
  { "id": "marcus", "name": "Marcus", "language": "en", "gender": "male",    "character": "Neutral, professional" },
  { "id": "thor",   "name": "Thor",   "language": "en", "gender": "male",    "character": "Deep, authoritative" },
  { "id": "luna",   "name": "Luna",   "language": "en", "gender": "female",  "character": "Soft, calm" },
  { "id": "owen",   "name": "Owen",   "language": "en", "gender": "male",    "character": "Steady, trustworthy" },
  { "id": "sky",    "name": "Sky",    "language": "en", "gender": "neutral", "character": "Light, friendly" }
] }

MCP / 10

Polling patterns

Two endpoints take real time and must be polled. Both use a wait_* tool that does the polling for you.

OperationTypical durationPolling tool
Agent jobs (generate_video, send_message)30s – 3 minwait_for_job
MP4 exports (export_video, or generate_video with export:true)30s – 2 minwait_for_export
  • Poll every 5 seconds by default.
  • Time out after 5 minutes by default.
  • Bump timeoutSeconds to 600 if you're rendering 4k or processing a 50-scene document plan.
  • Both wait_* tools return { status: "timeout", ... } when the deadline passes — the underlying job continues, the agent can re-poll via get_job / get_video.
flow
generate_video ────► wait_for_job ────► [present editorUrl, ASK about export]
                                                            │
                                                            ▼
                                                     export_video
                                                            │
                                                            ▼
                                                     wait_for_export
                                                            │
                                                            ▼
                                                    [present watchUrl, etc.]

MCP / 11

Presenting results to users

The MCP server returns structured JSON. The LLM's job is to translate that into something a human wants to read.

Three rules

  • Never paste raw JSON. No code blocks of API responses, no curly braces.
  • Never construct or guess Ozor URLs. Copy values verbatim from tool results.
  • Never auto-export. After generation, stop, show the editor, ask.

After wait_for_job (just generated, no export)

Your video is ready to preview.

A 4-scene cut with a deep-indigo “Ozor Tech” look — opens with a kinetic title, moves into the core pitch, runs through a feature list with checkmarks, and closes on mcp.ozor.ai.

Watch and edit it on Ozor: https://www.ozor.ai/editor?projectId=…

Want me to export it as an MP4 for sharing? If so, tell me the quality (720p, 1080p, or 4k).

After wait_for_export (export complete)

Done — your 1080p export is ready.

  • Watch / share: https://www.ozor.ai/share/wuq4y9t
  • Direct MP4 (download or embed): https://storage.googleapis.com/.../1080p.mp4
  • Edit on Ozor: https://www.ozor.ai/editor?projectId=…

Want the responsive <iframe> snippet for your landing page?

MCP / 12

Error handling

All tools return { isError: true, ...json } on failure. The body always contains a human-readable message and, where relevant, a status field.

HTTP / statusMeaningAgent response
401Bad / expired API keyTell the user to refresh their API key. Don&apos;t retry.
402Out of creditsTell the user to top up. Don&apos;t retry.
403API key doesn&apos;t own that resourceDon&apos;t retry. Don&apos;t guess other IDs.
404videoId / jobId / planId not foundRe-check the ID. Don&apos;t fabricate one.
429Rate-limitedWait, then retry once. Don&apos;t loop.
5xxBackend errorRetry once after 5–10s; otherwise surface the error.
status: "timeout"The wait_* tool's deadline passedThe work continues. Re-poll with get_job / get_video later.
status: "failed"Agent or export job itself failedShow the error / exportError field; suggest a retry or fix.

The MCP server maps common backend errors (e.g. validation failures) into structured isError responses rather than transport-level errors — the LLM sees them as tool results, not exceptions, which makes recovery much easier.

Integration

n8n integration

n8n / 01

What the Ozor n8n node does

The official Ozor node on n8n.io wraps the same /api/v1/ surface as the REST API and the MCP server, but exposes every operation as a clickable action inside an n8n workflow. No code, no polling boilerplate — drag, drop, configure.

Why use the n8n node

  • You already run your automations in n8n (form intake, ticket routing, content ingestion) and want video generation slotted into the same workflow.
  • You don't want to write a polling loop in code — the node has a polling mode that waits for jobs and exports to finish automatically.
  • You want non-engineers on your team to configure video automations without touching code.

When to choose something else

  • You need request-level control (custom retries, branching mid-job, embedded in a high-traffic backend) → use the REST API directly.
  • You want an AI agent (not a workflow) to make decisions about when to generate or export → use the MCP server.

n8n / 02

Install the node

Self-hosted n8n

  • Open Settings → Community nodes.
  • Click Install a community node and enter n8n-nodes-ozor.
  • Accept the prompt and reload — the “Ozor” node now appears in the node picker.

Community-node setting

Self-hosted instances must have community nodes enabled — set N8N_COMMUNITY_PACKAGES_ENABLED=truein your env (it's on by default in most installs).

n8n Cloud

n8n Cloud users can install the verified Ozor node directly from Workflow editor → + (add node) → search “Ozor” or via the integration page. No extra setup.

n8n / 03

Create credentials

The node uses a single Ozor API credential. Set it up once, reuse across every workflow.

  • Generate a key at ozor.ai → Settings → Developer → API Keys. Name it e.g. n8n-production so you can revoke it independently.
  • In n8n, click + Add credentialOzor API.
  • Paste the sk_live_... value into API Key.
  • Click Save — n8n stores the credential encrypted and tests it against /api/v1/videos?limit=1.
If the test fails, double-check that you copied the full key (40 characters total) and that it hasn't been revoked.

n8n / 04

Quickstart workflow

Goal: every time someone submits a form, generate a 30-second product video and email the share link.

workflow
Form Trigger
   │  (collects: prompt, recipient_email)
   ▼
Ozor — Video / Generate
   │  Prompt:  ={{ $json.prompt }}
   │  Aspect:  16:9
   │  Wait for completion: ✓
   ▼
Ozor — Video / Export
   │  Video ID:    ={{ $json.videoId }}
   │  Quality:     1080p
   │  Public:      true
   │  Wait for completion: ✓
   ▼
Send Email (SMTP)
   To:       ={{ $('Form Trigger').item.json.recipient_email }}
   Subject:  Your Ozor video is ready
   Body:     "Watch it here: ={{ $json.watchUrl }}"

Test as you build

After each Ozor step, run the workflow in test mode and inspect the node's output panel — every URL field documented in the URL fields section shows up there.

n8n / 05

Resources & operations

Pick a Resource first (Video, Document Plan, Job, Course, etc.) — the operation picker updates with the actions that apply to that resource.

OPResource: Video
OperationWhat it doesRequired fields
GenerateCreate a new video from a prompt.prompt
GetFetch one video, including export URLs.videoId
ListList API-created videos, newest first.
UpdateEdit title / description.videoId, fields to update
DeleteRemove a video and its assets.videoId
Send MessageSend a natural-language edit instruction to the agent.videoId, message
ExportTrigger an MP4 render.videoId
OPResource: Document Plan
OperationWhat it doesRequired fields
Analyze DocumentConvert a file or URL into a draft plan.One of file / url
Get PlanFetch a plan with fresh image URLs.planId
Update PlanReplace scenes or change voice settings.planId, scenes
Generate From PlanRender the plan into a video project (consumes SSE internally).planId
List VoicesReturns the available TTS voices.
OPResource: Course

Build full multi-lesson courses on top of the Ozor video pipeline — every lesson is its own narrated video, wrapped in a hosted student portal.

OperationWhat it doesRequired fields
Generate CourseCreate a multi-lesson course from a document, prompt, transcript, or URL. Auto-generates lesson breakdown, narration, visuals, and quizzes.One of file / url / prompt
Get CourseReturns metadata, lesson breakdown, completion analytics, and public URL.courseId
List CoursesReturns the workspace&apos;s courses (filter by status, date, tag).
Update CourseEdit metadata, branding, visibility.courseId
Delete CourseRemove a course and its associated assets.courseId
Regenerate LessonRebuild a single lesson inside an existing course.courseId, lessonId
OPResource: Job
OperationWhat it doesRequired fields
Get StatusPoll a generation or edit job. Used internally when &ldquo;Wait for completion&rdquo; is on.videoId, jobId
CancelStop an in-progress generation. (Best-effort — once the renderer starts an MP4, cancellation is not guaranteed.)videoId, jobId
OPResource: Distribution
OperationWhat it does
Get Share LinkReturns the public or token-gated viewer URL for a finished export.
Get Embed CodeReturns an iframe snippet for external embedding or LMS platforms.
Get Download AssetsReturns signed URLs for MP4, captions, transcript, and thumbnail.
OPResource: Workspace
OperationWhat it does
Get WorkspaceReturns plan, credit balance, and usage stats.
List Brand KitsReturns available brand kits — pass a brand kit id when generating videos to apply your colors, fonts, and logo.

n8n / 06

Field reference (Generate Video)

Every operation maps cleanly to one REST endpoint. The Generate operation on the Video resource is the most-used — its fields are the same as POST /api/v1/videos/generate.

FieldTypeRequiredNotes
PromptstringYes1–2000 chars. The creative brief.
Aspect16:9 | 9:16NoDefault 16:9.
Brand KitselectNoPopulated from List Brand Kits. Applies your colors, fonts, logo.
Wait for completionbooleanNoWhen on, the node blocks until the job finishes — no separate polling step needed.
Auto-exportbooleanNoWhen on, an MP4 render is triggered automatically once generation finishes.
Export Quality720p | 1080p | 4kNoUsed when Auto-export is on. Default 720p.
Export Is PublicbooleanNoWhen on, the export gets a permanent watchUrl + shareCode.
ImagesarrayNoReference images (URL or binary from previous node).
VideosarrayNoReference video clips.

Binary data

For Images and Videos, you can either point at a URL or feed binary data from a previous node (e.g. HTTP Request downloading a file). The Ozor node uploads it on your behalf.

n8n / 07

Common workflow patterns

1. Document → training video

workflow
Google Drive — Trigger (new file in /Policies/)
   ▼
Ozor — Document / Analyze Document
   File: ={{ $binary.data }}
   ▼
Ozor — Document / Generate From Plan
   Plan ID: ={{ $json.planId }}
   Wait for completion: ✓
   ▼
Ozor — Video / Export   (1080p, public)
   ▼
Slack — Send message    "New training video for {{ $('Drive Trigger').item.json.name }}: {{ $json.watchUrl }}"

2. Support ticket → tutorial

workflow
Zendesk — Trigger (ticket marked &ldquo;needs-tutorial&rdquo;)
   ▼
Set — Build prompt
   prompt = "60-second tutorial answering: " + {{ ticket.subject }} + "\nDetails: " + {{ ticket.body }}
   ▼
Ozor — Video / Generate (Wait for completion ✓)
   ▼
Ozor — Video / Export (Wait ✓, Public ✓)
   ▼
Zendesk — Reply to ticket
   "We turned this into a short video — {{ $json.watchUrl }}"

3. Weekly product update

workflow
Schedule Trigger — every Monday 09:00
   ▼
Linear — List recently shipped issues (since last week)
   ▼
Set — Build a single multiline prompt of all the issues
   ▼
Ozor — Video / Generate (Wait ✓, Auto-export ✓, Quality 1080p, Public ✓)
   ▼
Notion — Append block with iframe of {{ $json.embedUrl }}

4. Course generation from PDF library

workflow
Google Drive — Trigger (new file in /Course inputs/)
   ▼
Ozor — Course / Generate Course
   Source file: ={{ $binary.data }}
   Title:       ={{ $json.name.replace('.pdf','') }}
   ▼
HTTP — Notify your LMS with the new course URL
   URL: ={{ $json.publicUrl }}

5. Bulk localized renders

workflow
Google Sheets — Read rows (each row = one language target)
   ▼
Loop Over Items
   ▼
Ozor — Video / Send Message
   Message: "Translate the voiceover into ={{ $json.language }}"
   Wait for completion: ✓
   ▼
Ozor — Video / Export (Public ✓, Quality 1080p)
   ▼
Google Sheets — Update row with {{ $json.watchUrl }}

n8n / 08

Polling jobs in n8n

Most Ozor operations are async, but the node handles polling for you whenever the Wait for completion toggle is on. Behind the scenes it polls the relevant get_video / get_job endpoint at a 5-second cadence until the operation finishes or times out.

ToggleBehavior
Wait for completion ONNode blocks for up to 10 minutes. On success, the output item contains the full result (URLs, response text, etc.). On failure or timeout, the node throws — you can catch it with an Error Trigger workflow.
Wait for completion OFFNode returns immediately with the videoId + jobId. Use a separate Job / Get Status node in a loop (or n8n's Wait + IF combo) to poll manually.

Long renders

For 4k exports or 50-scene document plans, set the operation's Timeout field above the 10-minute default — otherwise the node will throw before the render finishes (the work continues server-side regardless; you can still find the video by ID).

n8n / 09

Tips & troubleshooting

Expressions you'll use a lot

ExpressionWhat it returns
={{ $json.videoId }}The ID of the video just generated by the previous node.
={{ $json.watchUrl }}Public watch page after an export finishes.
={{ $json.editorUrl }}Always present, even before exporting.
={{ $('Ozor Generate').item.json.videoId }}Reference an earlier Ozor node by name.
={{ $binary.data }}Pass binary file data from a Google Drive / HTTP Download node.

Out of credits

The node throws with a clear 402 Payment Required message. Wire an Error Triggerworkflow to surface this in Slack / email — n8n won't auto-retry credit failures.

Multiple inputs → one video

Use n8n's Merge or Aggregate nodes upstream — the Ozor node processes one item at a time. To combine multiple documents, merge them into a single PDF (or concatenate text into a single prompt) before calling Analyze Document.

Looping over many items

Put Loop Over Items upstream of the Ozor node and set the loop to Wait between batches: 1s — credits are the limit, not request rate, but small breathing room keeps the UI responsive.

Credentials test fails

  • Re-copy the key from ozor.ai → Settings → Developer → API Keys — no leading/trailing whitespace.
  • Confirm the key is active (not revoked) in the dashboard.
  • Make sure your n8n instance can reach https://ozor.ai — proxies and corporate firewalls sometimes block it.

Integration

REST API

API / 01

Getting started

Create an API key

API keys are created from the dashboard at ozor.ai (Settings → Developer → API Keys) or programmatically through the key-management endpoints. Those management endpoints are authenticated with your Firebase ID token — the same session your browser uses after logging in — not with an API key.

Key format: sk_live_ followed by 32 hex characters (40 characters total). The raw key is shown once, at creation time. Ozor stores only a SHA-256 hash and can never show it again — save it to a secret manager immediately.

Limit

A maximum of 5 active keys per account. Revoke unused keys before creating new ones.

Make your first call

bash
curl -X POST https://ozor.ai/api/v1/videos/generate \
  -H "X-API-Key: sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A 15-second ad for a minimalist desk lamp"}'

API / 02

Authentication

All public endpoints live under /api/v1/* and require the X-API-Key header:

http
X-API-Key: sk_live_<32-hex-chars>
StatusMeaning
401 UnauthorizedHeader missing, key not recognized, or key revoked.
403 ForbiddenKey is valid but does not own the resource you are accessing.

Keys are scoped to the account that created them. Every resource (video, plan, export, job) can only be read or modified by the owning account.

There is no rate limiting — usage is governed entirely by your credit balance.

API / 03

API key management

These endpoints manage API keys themselves. They are authenticated with a Firebase ID token (Authorization: Bearer <firebase_id_token>), not an API key.

POST/api/api-keys

Create a new API key.

Request

json
{ "name": "Production" }

Parameters

FieldTypeRequiredNotes
namestringYes1–64 chars. Shown in the dashboard to identify the key.

Response (200)

json
{
  "keyId": "abc123xyz",
  "name": "Production",
  "prefix": "sk_live_a1b2",
  "rawKey": "sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "createdAt": 1705312200000
}

rawKey is returned only in this response. All later list/read operations return only prefix.

GET/api/api-keys

List all keys (active and revoked) for the current account.

Response (200)

json
[
  {
    "keyId": "abc123xyz",
    "name": "Production",
    "prefix": "sk_live_a1b2",
    "status": "active",
    "createdAt": 1705312200000,
    "lastUsedAt": 1705400100000
  }
]
DELETE/api/api-keys/{keyId}

Revoke an API key. Takes effect immediately — the next request using this key fails with 401.

Response (200)

json
{ "message": "API key abc123xyz revoked" }

API / 04

Credits

Every generation operation deducts from your account's credit balance.

EndpointCostWhen deducted
POST /api/v1/videos/generate1 creditOn successful job completion
POST /api/v1/videos/{videoId}/message1 creditOn successful job completion
POST /api/v1/videos/{videoId}/export0 credits
POST /api/v1/documents/analyze1 creditImmediately
POST /api/v1/documents/plans/{planId}/generatefloor(voiceover_scenes / 2)On successful completion

If your balance is insufficient for an operation, the endpoint responds with 402 Payment Required. No partial work is performed, and no credit is deducted on failure.

New accounts receive 10 free credits. You can buy more — a subscription or a one-time credit top-up — from the dashboard.

API / 05

Quickstart

Generate, render, and get a shareable link:

bash
# Step 1 — Kick off generation + auto-export as public
curl -X POST https://ozor.ai/api/v1/videos/generate \
  -H "X-API-Key: $OZOR_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "prompt": "A 20s product teaser for a wireless headphone",
    "aspect": "16:9",
    "export": true,
    "exportQuality": "1080p",
    "exportIsPublic": true
  }'
# -> { "videoId": "abc123", "jobId": "job_...", "status": "pending" }

# Step 2 — Poll until the export is done
curl https://ozor.ai/api/v1/videos/abc123 \
  -H "X-API-Key: $OZOR_API_KEY"
# -> when exportStatus == "complete":
#    { "shareUrl": "...", "downloadUrl": "...", "shareCode": "a3kf92p", ... }

See Polling patterns for the full set of workflows.

API / 06

Video endpoints

All video endpoints live under /api/v1/videos/* and use X-API-Key auth.

POST/api/v1/videos/generate

Create a new video from a natural-language prompt. Asynchronous — the agent runs in the background. The response returns immediately with a jobId to poll and a videoId referencing the newly created project.

Deducts 1 credit on successful completion. If you pass export: true, an MP4 render is automatically triggered once the agent finishes.

Request

json
{
  "prompt": "Create a 30-second product showcase for a wireless headphone",
  "aspect": "16:9",
  "export": true,
  "exportQuality": "1080p",
  "exportIsPublic": true,
  "images": [
    { "url": "https://example.com/product.jpg" }
  ],
  "videos": [
    { "url": "https://example.com/clip.mp4", "mimeType": "video/mp4", "durationSec": 8.5 }
  ]
}

Parameters

FieldTypeRequiredDefaultNotes
promptstringYes1–2000 chars.
aspect"16:9" | "9:16"No"16:9"Landscape or portrait.
exportboolNofalseAuto-trigger an MP4 render after the agent finishes.
exportQuality"720p" | "1080p" | "4k"No"720p"Applied only when export=true.
exportIsPublicboolNofalsetrue → the auto-export gets a permanent shareUrl + shareCode.
imagesChatImage[]NoSee Media Attachments.
videosChatVideo[]NoSee Media Attachments.

Response (200)

json
{ "videoId": "abc123", "jobId": "job_s0m3R4nd0mId", "status": "pending" }

Next steps

  • Poll GET /api/v1/videos/{videoId}/jobs/{jobId} until status is "completed" to get the agent's reply.
  • If export=true, also poll GET /api/v1/videos/{videoId} until exportStatus is "complete" to get the rendered MP4.
GET/api/v1/videos

List all videos created via the public API by the current account, newest first.

Query parameters

NameTypeDefaultRange
limitint201–100

Response (200)

json
{
  "videos": [
    {
      "videoId": "abc123",
      "title": "Product showcase for a wirel...",
      "status": "draft",
      "editorUrl": "https://ozor.ai/editor?projectId=abc123",
      "createdAt": "2026-04-20T10:30:00Z"
    }
  ]
}

Videos created inside the web app do not appear here — this list is scoped to API-originated videos.

GET/api/v1/videos/{videoId}

Full status for a video, including its latest export. This is the endpoint you poll while an export renders.

Response (200)

json
{
  "videoId": "abc123",
  "title": "Product showcase...",
  "status": "draft",
  "editorUrl": "https://ozor.ai/editor?projectId=abc123",
  "createdAt": "2026-04-20T10:30:00Z",

  "exportId": "exp_xyz789",
  "exportStatus": "complete",
  "downloadUrl": "https://storage.googleapis.com/.../video.mp4",
  "shareUrl":    "https://storage.googleapis.com/.../video.mp4",
  "shareCode":   "a3kf92p",
  "thumbnailUrl":"https://storage.googleapis.com/.../thumb.jpg",
  "exportError": null
}
exportStatusMeaning
queuedRender accepted, not yet picked up by the renderer.
processingRenderer actively working.
completedownloadUrl is set; if public, shareUrl + shareCode are too.
failedRender failed — exportError contains the reason.

URL lifetimes

  • downloadUrl — signed URL, valid ~24 hours. Re-fetch this endpoint for a fresh one.
  • shareUrl — permanent and public, never expires (only set when the export was public).
POST/api/v1/videos/{videoId}/export

Manually trigger an MP4 render for an existing video. Use this when you generated without export: true, or when you want a second render at a different quality or visibility.

Request

json
{ "quality": "1080p", "isPublic": true }

Parameters

FieldTypeRequiredDefaultNotes
quality"720p" | "1080p" | "4k"No"1080p"
isPublicboolNofalsetrue → returns a permanent shareUrl + shareCode.

Response (200)

json
{
  "videoId": "abc123",
  "exportId": "exp_xyz789",
  "exportStatus": "queued",
  "shareCode": "a3kf92p"
}

Caching

If an identical export (same scene content + quality + visibility) already exists, the cached result is returned immediately with exportStatus: "complete" and its URLs already populated.
POST/api/v1/videos/{videoId}/message

Send a natural-language edit instruction to the agent. Asynchronous — a jobId is returned immediately. Deducts 1 credit on successful completion.

Request

json
{
  "message": "Add our logo in the top-right corner of every scene",
  "images": [ { "base64": "<base64-encoded-png>", "mimeType": "image/png" } ]
}

Parameters

FieldTypeRequiredNotes
messagestringYes1–2000 chars.
imagesChatImage[]NoSee Media Attachments.
videosChatVideo[]NoSee Media Attachments.

Response (200)

json
{ "videoId": "abc123", "jobId": "job_s0m3R4nd0mId", "status": "queued" }
GET/api/v1/videos/{videoId}/jobs/{jobId}

Poll the status of an agent job (from generate or message).

Response — running

json
{
  "videoId": "abc123",
  "jobId": "job_s0m3R4nd0mId",
  "status": "processing",
  "createdAt": "2026-04-20T10:30:00Z"
}

Response — completed

json
{
  "videoId": "abc123",
  "jobId": "job_s0m3R4nd0mId",
  "status": "completed",
  "response": "I&apos;ve added the logo to the top-right corner of all 4 scenes.",
  "createdAt": "2026-04-20T10:30:00Z"
}

Response — failed

json
{
  "videoId": "abc123",
  "jobId": "job_s0m3R4nd0mId",
  "status": "failed",
  "error": "Agent encountered an error: ...",
  "createdAt": "2026-04-20T10:30:00Z"
}
GET/api/v1/videos/{videoId}/conversations/{conversationId}/tool-trace

Diagnostic endpoint. Returns a chronological trace of the tools the agent invoked in a conversation — useful for debugging and observability. Read-only.

Response (200)

json
{
  "videoId": "abc123",
  "conversationId": "conv_xyz",
  "totalAssistantTurns": 3,
  "totalToolCalls": 11,
  "messages": [
    {
      "messageId": "msg_1",
      "createdAt": "2026-04-20T10:30:05Z",
      "textPreview": "I&apos;ve created the opening scene...",
      "toolCalls": [
        { "name": "create_scene", "args": { "order": 1, "title": "Intro" } }
      ]
    }
  ]
}

API / 07

Document-to-video endpoints

Turn a PDF, PowerPoint, Word document, or web URL into a narrated video with an auto-selected voice, scenes, and images extracted from the source.

The flow

  1. Analyze — upload a file or URL. You receive a planId and a scene-by-scene plan (narration script + visual descriptions) to review.
  2. Update (optional) — edit the scenes' voiceover text, prompts, or voice settings.
  3. Generate — render the plan. Progress streams over Server-Sent Events; the final event carries a projectId you can then use with the /api/v1/videos/* endpoints (e.g. to export).
GET/api/v1/documents/voices

List the available text-to-speech voices.

Response (200)

json
{
  "voices": [
    { "id": "aria",   "label": "Aria",   "gender": "female",  "character": "Clear, precise" },
    { "id": "nova",   "label": "Nova",   "gender": "female",  "character": "Warm, natural" },
    { "id": "felix",  "label": "Felix",  "gender": "male",    "character": "Playful, upbeat" },
    { "id": "marcus", "label": "Marcus", "gender": "male",    "character": "Neutral, professional" },
    { "id": "thor",   "label": "Thor",   "gender": "male",    "character": "Deep, authoritative" },
    { "id": "luna",   "label": "Luna",   "gender": "female",  "character": "Soft, calm" },
    { "id": "owen",   "label": "Owen",   "gender": "male",    "character": "Steady, trustworthy" },
    { "id": "sky",    "label": "Sky",    "gender": "neutral", "character": "Light, friendly" }
  ]
}
POST/api/v1/documents/analyze

Extract scenes and narration from a document or web URL. Deducts 1 credit immediately.

Content type: multipart/form-data (not JSON).

Form fields

FieldTypeRequiredNotes
filefileOne of file / urlPDF, PPTX, or DOCX. Max 50 MB.
urlstringOne of file / urlhttp(s):// URL — its readable text is analyzed.
target_duration_secintNoTarget output duration. Default 30. Drives scene count.
promptstringNoOptional guidance (tone, focus, audience, language).
aspect_ratio"16:9" | "9:16"NoDefault "16:9".

Supported file MIME types

  • application/pdf
  • application/vnd.openxmlformats-officedocument.presentationml.presentation (.pptx)
  • application/vnd.openxmlformats-officedocument.wordprocessingml.document (.docx)

Response (200)

json
{
  "plan": {
    "planId": "plan_a1b2c3d4e5f6",
    "fileName": "pitch-deck.pdf",
    "fileType": "pdf",
    "sourceUrl": null,
    "aspectRatio": "16:9",
    "status": "draft",
    "language": "en",
    "style": {
      "colors": ["#0F172A", "#3B82F6"],
      "fonts": ["Inter"],
      "tone": "professional",
      "layout": "slide"
    },
    "scenes": [
      {
        "order": 1,
        "sceneTitle": "Introduction",
        "scenePrompt": "A blue-tinted title card with the company logo fading in...",
        "voiceoverText": "Welcome to Acme — we&apos;re rethinking how teams collaborate.",
        "imageRefs": [
          { "storagePath": "users/uid/plans/plan_.../page_0_img_0.png",
            "url": "https://storage.googleapis.com/..." }
        ],
        "keyPoints": ["intro", "brand"]
      }
    ],
    "projectId": null,
    "error": null,
    "createdAt": 1705312200,
    "updatedAt": 1705312200,
    "createdVia": "api",
    "creditsCost": 3
  }
}

creditsCost is the future cost of calling /generate on this plan — it equals floor(count_of_scenes_with_voiceover / 2).

GET/api/v1/documents/plans/{planId}

Retrieve a plan. Image URLs are refreshed on every read (they are short-lived signed URLs), so always fetch immediately before displaying or editing.

Response (200): same shape as analyze.

PATCH/api/v1/documents/plans/{planId}

Edit the plan before generating — rewrite voiceover, reorder scenes, drop scenes, or lock in a voice.

Valid only while the plan's status is draft or failed.

Request

json
{
  "scenes": [
    {
      "order": 1,
      "sceneTitle": "Introduction",
      "scenePrompt": "A blue-tinted title card...",
      "voiceoverText": "Welcome to Acme — we&apos;re reimagining collaboration.",
      "imageRefs": [
        { "storagePath": "users/uid/plans/plan_.../page_0_img_0.png" }
      ],
      "keyPoints": ["intro"]
    }
  ],
  "voiceSettings": {
    "voiceId": "nova",
    "speakingStyle": "warm",
    "speakingRate": 1.0
  }
}

Parameters

FieldTypeNotes
scenesscene[]Full replacement — send the complete list.
voiceSettings.voiceIdstringA voice id from /documents/voices. Omit to auto-select by language.
voiceSettings.speakingStylestringFree-text style hint ("formal", "energetic", "calm").
voiceSettings.speakingRatefloat0.5–2.0. Default 1.0.
POST/api/v1/documents/plans/{planId}/generate

Render the plan into a real video project. Valid only while plan status is draft or failed.

Credit cost: floor(voiceover_scenes / 2) — e.g. a 6-scene plan costs 3 credits. The balance is checked before streaming begins; if you are short you receive 402 up front and no work is done.

Response: text/event-stream (SSE). Each event is a line:

sse
data: {"step": "...", "detail": "...", "pct": 0-100}

Typical sequence

sse
data: {"step": "init",     "detail": "Starting generation",     "pct": 0}
data: {"step": "tts",      "detail": "Generating voiceover 1/6", "pct": 15}
data: {"step": "scenes",   "detail": "Rendering scene 3/6",      "pct": 55}
data: {"step": "assembly", "detail": "Assembling project",       "pct": 90}
data: {"step": "done",     "detail": "Video project ready!",     "pct": 100, "projectId": "abc123"}

On error

sse
data: {"step": "error", "detail": "TTS failed: ...", "pct": -1}

Consume with any SSE-capable client. Once you receive the done event, use the returned projectId as videoId with the rest of the API — e.g. POST /api/v1/videos/{projectId}/export to render the MP4.

API / 08

Media attachments

Both POST /api/v1/videos/generate and POST /api/v1/videos/{videoId}/message accept optional images and videos arrays. Each item may be supplied as a URL, a base64 payload, or both.

Image object (ChatImage)

FieldTypeNotes
urlstringPublicly accessible HTTPS URL.
base64stringRaw base64 (no data: prefix needed).
mimeTypestringimage/jpeg, image/png, or image/webp. Recommended with base64.

Video object (ChatVideo)

FieldTypeNotes
urlstringPublicly accessible HTTPS URL.
base64stringRaw base64.
mimeTypestringvideo/mp4 or video/webm. Recommended with base64.
durationSecfloatClip duration — helps the agent reason about pacing.
thumbnailUrlstringOptional thumbnail URL.
When base64 is provided it takes precedence — the data is uploaded to Ozor storage and url is ignored.

API / 09

Polling patterns

Generate + auto-export

flow
POST /api/v1/videos/generate   { ..., "export": true, "exportIsPublic": true }
  -> { videoId, jobId, status: "pending" }

Poll GET /api/v1/videos/{videoId}
  - wait until "exportStatus" appears (agent finished, export queued)
  - wait until "exportStatus" === "complete"
  -> { shareUrl, downloadUrl, shareCode, thumbnailUrl, ... }

Suggested cadence: 2–3 seconds between polls for the first minute, 5–10 seconds after that. Typical end-to-end time ranges from tens of seconds to a few minutes, depending on prompt complexity and export quality.

Generate, then export separately

flow
POST /api/v1/videos/generate              { prompt: "..." }
  -> { videoId, jobId, status: "pending" }

Poll GET /api/v1/videos/{videoId}/jobs/{jobId}   until status === "completed"

POST /api/v1/videos/{videoId}/export      { quality: "1080p", isPublic: true }
  -> { exportId, exportStatus: "queued", shareCode }

Poll GET /api/v1/videos/{videoId}         until exportStatus === "complete"

Iterative edit

flow
POST /api/v1/videos/{videoId}/message     { message: "Change background to dark blue" }
  -> { jobId, status: "queued" }

Poll GET /api/v1/videos/{videoId}/jobs/{jobId}   until status === "completed"

POST /api/v1/videos/{videoId}/export      { quality: "1080p" }
  -> poll GET /api/v1/videos/{videoId}   until exportStatus === "complete"

Document → video

flow
POST /api/v1/documents/analyze            (multipart: file + target_duration_sec + ...)
  -> { plan: { planId, scenes: [...], creditsCost } }

(optional) PATCH /api/v1/documents/plans/{planId}   { scenes, voiceSettings }

POST /api/v1/documents/plans/{planId}/generate      (consume SSE stream)
  -> final event: { step: "done", projectId }

POST /api/v1/videos/{projectId}/export    { quality: "1080p", isPublic: true }
  -> poll GET /api/v1/videos/{projectId}  until exportStatus === "complete"

API / 10

Error responses

All errors are JSON with a detail field.

HTTPWhen it happens
400 Bad RequestValidation failed — missing field, value out of range, unsupported file type, file too large, invalid aspect_ratio, plan not in draft/failed state, etc.
401 UnauthorizedX-API-Key missing, unrecognized, or revoked. For key-management endpoints: Firebase token missing/expired.
402 Payment RequiredCredit balance insufficient for this operation. detail includes the cost when relevant.
403 ForbiddenThe API key is valid but does not own the target video/plan/job.
404 Not FoundThe videoId, jobId, planId, or exportId does not exist.
500 Internal Server ErrorUpstream failure — document fetch, renderer, or analysis error. Safe to retry after a short delay.

Example

json
{ "detail": "You are out of credits. Please add more to continue chatting." }

API / 11

Reference tables

Aspect ratios

ValueUse case
16:9Desktop, YouTube, landing pages (1920×1080 at 1080p).
9:16Mobile, TikTok, Reels, Shorts (1080×1920 at 1080p).

Export qualities

ValueResolution (16:9 / 9:16)
720p1280×720 / 720×1280
1080p1920×1080 / 1080×1920
4k3840×2160 / 2160×3840

Output is always mp4 at 30 fps.

Share code

When an export is public, a 7-character alphanumeric shareCode (e.g. a3kf92p) is generated. shareUrl is the permanent public URL to the MP4; shareCode is the handle used internally and in the web UI to look up the shared video.

Video statuses

status (project)Meaning
draftProject exists and is editable.
renderingA render is in progress.
completedA render has completed at least once.

Export statuses

exportStatusMeaning
queuedAccepted, not yet picked up by the renderer.
processingActively rendering.
completeDone. downloadUrl (and shareUrl if public) are set.
failedRender failed. exportError contains the reason.

Agent job statuses

status (job)Meaning
queuedJob accepted, agent not yet started.
pendingReturned synchronously by /generate; equivalent to queued.
processingAgent running.
completedDone. response is set.
failedErrored. error is set. No credit deducted.

TTS voices

idgendercharacter
ariafemaleClear, precise
novafemaleWarm, natural
felixmalePlayful, upbeat
marcusmaleNeutral, professional
thormaleDeep, authoritative
lunafemaleSoft, calm
owenmaleSteady, trustworthy
skyneutralLight, friendly

Document formats

FormatMIME
PDFapplication/pdf
PPTXapplication/vnd.openxmlformats-officedocument.presentationml.presentation
DOCXapplication/vnd.openxmlformats-officedocument.wordprocessingml.document

Max file size: 50 MB. A http(s):// URL may be supplied instead of a file.

Resources

Glossary

TermMeaning
Project / videoSame thing seen from two angles: a video is a project the user is building in the Ozor editor. The videoId (also projectId for document-mode outputs) is the canonical identifier.
PlanA draft of a document-to-video script — scenes, voiceover, images, voice settings. Lives in "draft" status until generate_from_plan consumes it.
JobAn async agent run (generation or message). Has a jobId scoped to the project. Poll with wait_for_job / GET /jobs/{jobId}.
ExportAn MP4 render of a project's current state. Has an exportId, a quality, and a visibility (public / private). The public ones get a shareCode and the /share/<code> + /embed/<code> pages.
Share codeThe short URL-safe token (e.g. wuq4y9t) that maps to a public export. Used in watchUrl and embedUrl. Treat as opaque.
Editor URLhttps://www.ozor.ai/editor?projectId=<id>. Always safe to present, even before any export. Lets the user preview, scrub, and tweak.
Watch URLhttps://www.ozor.ai/share/<shareCode>. Public watch page. The link to give people to watch the finished video.
Embed URLhttps://www.ozor.ai/embed/<shareCode>. The <iframe src>. Not for humans to open directly.
MP4 URL (raw)https://storage.googleapis.com/.../1080p.mp4. The actual file. Use for downloads or <video> tags, not as the “watch” link.
CreditThe unit Ozor bills generation in. 1 credit ≈ 1 generation step. See the credits table.
Brand kitA saved bundle of colors, fonts, logo, and tone-of-voice that Ozor applies to new videos automatically.

Resources

FAQ

Can I use Ozor without an API key?

Yes — the web app at ozor.ai works without one. API keys, the n8n node, and the MCP server are only needed when you want to drive Ozor from outside the web app.

Are videos generated through the API visible in the web app?

Yes — every video shows up in your dashboard regardless of which integration created it. The reverse is not true: GET /api/v1/videos only lists API-originated videos (this scoping is intentional, to keep the list deterministic for scripts).

How do I include reference images?

Pass them in the images array of either POST /videos/generate or POST /videos/{videoId}/message. URL or base64 both work — see Media attachments.

How do I make the same video in 16:9 and 9:16?

Generate twice with different aspectvalues. Scenes are computed per-aspect, so a single project doesn't hold both — separate videoIds. Tip: drop the same prompt + brand kit into both calls.

Can I keep my export private?

Yes. Omit isPublic (or set it to false) on POST /videos/{videoId}/export. You'll get a 24h-signed downloadUrl but no watchUrl, embedUrl, or shareCode — no public page exists.

What happens if a render fails?

exportStatus becomes failed and exportError contains the reason. No credit is deducted. You can re-run POST /export as many times as needed.

Is there a webhook for “export complete”?

Not yet — polling is the supported pattern today. The MCP server and n8n node both wrap that polling for you; for direct API integrations, see Polling patterns. Outbound webhooks are on the roadmap.

How do I rotate a leaked key?

Open the dashboard, deletethe compromised key (it's revoked instantly), and create a new one. Update wherever it's stored (env var, n8n credential, MCP config).

Where do I report a bug or request a feature?

Email support@ozor.ai with reproduction steps. For API issues, include the X-Request-ID header from your response — it helps us trace the call end-to-end.

Ready to build with Ozor?

Create an API key from your dashboard and you can drive Ozor from MCP, n8n, or plain HTTP in minutes. New accounts start with 10 free credits.