Developers

Ozor API reference.

Programmatic access to Ozor — generate, edit, and export videos, and turn documents into narrated videos, straight from your own code. JSON in, JSON out, with simple polling for long-running jobs.

Base URL

https://ozor.ai

Auth header

X-API-Key

Version

/api/v1/

01

Getting started

Create an API key

API keys are created from the dashboard at ozor.ai (Settings → 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"}'

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.

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.

Errors

400 if the 5-key limit is already reached; 401 if the Firebase token is missing or invalid.

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
  }
]

status is active or revoked. lastUsedAt is null until the key is first used.

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" }

Errors

404 if the key does not exist.

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 and a JSON detail describing the shortfall. 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.

05

Quickstart

“Generate, render, and give me 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.

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
}

Export-related fields appear only once an export has been triggered (manually via /export or implicitly via generate with export: true).

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).

Errors

404 if the video does not exist; 403 if the key does not own it.

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.

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

Errors

404, 403, 500 if the export could not be triggered.

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.

Use this to iterate: change copy, tweak pacing, swap colors, add a logo, reorder scenes, etc.

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" }

Poll GET /api/v1/videos/{videoId}/jobs/{jobId} for the result, then re-fetch GET /api/v1/videos/{videoId} for the updated state (and trigger a fresh export if needed).

Errors

404, 403, 402 if out of credits.

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'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"
}
statusMeaning
queuedJob accepted, agent not started yet.
processingAgent actively running.
completedAgent finished — response holds the reply.
failedAgent errored — error holds details. No credit charged.

Errors

404 if the video or job does not exist; 403.

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've created the opening scene...",
      "toolCalls": [
        { "name": "create_scene", "args": { "order": 1, "title": "Intro" } }
      ]
    }
  ]
}

Errors

404, 403.

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. 1Analyze — upload a file or URL. You receive a planId and a scene-by-scene plan (narration script + visual descriptions) to review.
  2. 2Update (optional) — edit the scenes' voiceover text, prompts, or voice settings.
  3. 3Generate — 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" }
  ]
}

Use any id in voiceSettings.voiceId on PATCH /api/v1/documents/plans/{planId}. If omitted, a voice is auto-selected by document language.

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".

Provide exactly one of file or url.

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'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).

Errors

  • 400 — neither/both file and url, unsupported file type, file > 50 MB, invalid aspect_ratio.
  • 402 — out of credits.
  • 500 — URL fetch failed or analysis failed.
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.

Errors

404 if the plan does not exist; 403.

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'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. Each scene needs order, sceneTitle, scenePrompt, voiceoverText.
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.

Response (200): the updated plan (same shape as analyze).

Errors

400 if the plan is already generating or generated; 404; 403.

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.

Errors

400 if the plan is not in draft/failed state; 402 out of credits; 404; 403.

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.

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"

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." }

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.

Ready to build with Ozor?

Create an API key from your dashboard and make your first call in minutes. New accounts start with 10 free credits.