01 · Introduction
Octavia Documentation
Octavia turns a single video, audio file, or subtitle file into fully localized output — dubbed voice tracks, translated subtitles, lip-synced video, and narrated speech — across 60+ languages. Everything the app does through its UI is reachable through the same API: send a source file, get back a job you can poll or subscribe to with a webhook, and download finished output per language once it renders.
This reference covers both the Octavia app itself and, for teams integrating Octavia into their own product, the exact endpoints, parameters, and payloads for the API. Nothing in the API does anything the app can't already do through its interface — the API just makes it scriptable.
Two ways in: if you just need to dub, subtitle, or narrate something without writing code, skip straight to Using The App below — everything there happens in the browser. If you're integrating Octavia into your own product or pipeline, jump to Developer Quickstart instead, which walks through creating a dub end to end in four requests.
What you can build
- Automated dubbing pipelines that plug into a CMS, video host, or publishing workflow.
- Subtitle generation and translation for an existing content library, in bulk.
- Text-to-speech or subtitle-to-speech narration for voiceovers and audio articles.
- Webhook-driven systems that pick up finished renders automatically instead of polling.
- Internal review tools that edit machine translations before they render, using the same transcript endpoints the Octavia app itself uses.
API access is available on the Pro and Studio plans. Studio additionally gets priority queueing for API requests. Free, Starter, and Creator plans can use every workflow through the Octavia app, but need to upgrade to call the API directly. See Pricing for plan details.
Using The App
The dashboard
Everything described in this reference is also available directly through the Octavia app at app.octavia.lunartech.ai — no code required. After signing in, your dashboard lists every dub, subtitle, and speech job in your workspace, each with its current status, target languages, and a shortcut back into the finished file once it's ready.
A credit balance in the top corner tracks what's left in the current billing period (see Billing & plan), and a New Project button starts a fresh job on any of the six workflows covered under Core Workflows: video translation, audio translation, speech generation, subtitle generation, subtitle to audio, and subtitle translation.
Using The App
Create a project
Click New Project and pick a workflow. Each one opens a short setup form: choose or upload a source, pick your target language(s), and decide between Fast and Quality mode depending on whether you're checking a translation or rendering the version you plan to ship. Toggling manual review here does the same thing as passing review: "manual" through the API — it pauses the project after translation so you can proofread before anything renders (see Review & edit).
A project you start in the app and one you create through the API are the same object underneath — a project kicked off from the dashboard shows up if you query it with GET /v1/dubs, and one created through the API shows up on your dashboard just the same.
Using The App
Upload a source
Drag a file in directly, or paste a URL Octavia can fetch on its own. Plan tier sets how long a single source can run: Free caps out at 5 minutes and Starter at 60; Creator, Pro, and Studio raise that ceiling considerably — see Pricing for the exact limit on your plan. Once a source is accepted, the project appears on your dashboard as queued and moves through the same stages described in Job lifecycle regardless of whether it started from the app or the API.
Using The App
Review & edit
If manual review was turned on when the project was created, it pauses after translation with an editable transcript — line by line, per language, with detected speakers labeled where multi-speaker detection applies. Fix a mistranslated name, adjust phrasing, or reassign a line to the correct speaker directly in that screen, then hit Render when you're satisfied. These are the exact same edits available through the transcript endpoints in the API (see Transcript & review) — the app screen and the API are two views onto the same in-review project, so edits made in one show up in the other immediately.
Using The App
Export & download
Once a project reaches complete, every target language is downloadable from its detail page, individually or as a batch. Video dubs export as the original video with the dubbed, lip-synced audio track baked in; audio and speech jobs export as an audio file; subtitle jobs export as SRT or VTT. Starter and above export without a watermark; Free-tier exports carry one.
Using The App
Workspace & team
A workspace is shared by everyone on your plan's seat allowance — Free, Starter, and Creator include a single seat, Pro includes two, and Studio includes five (see Pricing for the full breakdown, including how many jobs can run concurrently per plan). Invite teammates and manage roles from Settings → Team. Everyone in a workspace shares the same credit balance and project history, so a teammate's finished dub shows up on your dashboard too, not just theirs.
Using The App
Billing & plan
Plans, credits, and payment details live under Settings → Billing, handled through Stripe. Billing is monthly with no long-term contract. Upgrades apply immediately, so you get the new plan's credits and limits right away; downgrades take effect at your next renewal, so you don't lose access mid-cycle. Cancel any time from the same screen — there's no cancellation fee.
Developer Quickstart
Quickstart
Everything past this point is for teams integrating Octavia into their own product or pipeline rather than using the app directly.
Base URL
All API requests are made against:
https://api.octavia.ai/v1
v1 is the current stable API version and the one every example on this page uses. New fields and endpoints are added in a backward-compatible way; nothing existing is removed or renamed without a new version prefix. If you need to track exactly what changed and when, see the Changelog.
SDKs
There's an official JavaScript/TypeScript client, OctaviaClient, that wraps the REST API with typed methods:
// npm install @octavia/sdk import { OctaviaClient } from '@octavia/sdk'; const octavia = new OctaviaClient({ apiKey: 'oct_sk_...' }); // Dub your video content const results = await octavia.dub.translate({ videoUrl: 'https://example.com/video.mp4', targetLanguages: ['es', 'fr', 'de'] }); console.log(results.dubbedVideos);
If you're not on Node, every endpoint is plain REST and a GraphQL endpoint is also available for clients that want to fetch nested resources (a dub with its transcript lines and output URLs) in a single round trip. This reference documents the REST surface; the GraphQL schema mirrors the same resources.
Create your first dub
This walks through dubbing a video into two languages using nothing but curl. It's the same sequence the OctaviaClient SDK performs under the hood.
1 · Get an API key
Create one from Settings → API Keys in the Octavia app. Keys are prefixed oct_sk_ and shown once — store it somewhere safe.
2 · Set it as an environment variable
export OCTAVIA_API_KEY="oct_sk_live_..."
3 · Create a dub
curl https://api.octavia.ai/v1/dubs \ -H "Authorization: Bearer $OCTAVIA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "videoUrl": "https://example.com/video.mp4", "targetLanguages": ["es", "fr"] }'
The response returns immediately with a queued job and its id:
{
"id": "dub_31xk9m",
"status": "queued",
"targetLanguages": ["es", "fr"],
"createdAt": "2026-08-16T09:12:03Z"
}
4 · Check status, then download each finished language
curl https://api.octavia.ai/v1/dubs/dub_31xk9m \
-H "Authorization: Bearer $OCTAVIA_API_KEY"
curl -L https://api.octavia.ai/v1/dubs/dub_31xk9m/output/es \
-H "Authorization: Bearer $OCTAVIA_API_KEY" \
-o dubbed-es.mp4
By default a dub renders immediately. Pass "review": "manual" in the create request to pause after translation, edit any line through the API, and render only once you approve — see Transcript & review. Rather than polling, you can also register a webhook and get notified the moment each language finishes.
Developer Quickstart
Authentication
Every request is authenticated with a bearer token in the Authorization header:
Authorization: Bearer oct_sk_live_...
API keys are created and revoked from Settings → API Keys and are scoped to a single workspace. A few rules worth building around:
- Keys are shown once at creation — if you lose one, revoke it and issue a new one rather than trying to recover it.
- Never embed a key in client-side code, a mobile app bundle, or a public repository. Call Octavia from your own backend, or use a short-lived, narrowly-scoped key if a request has to originate from a browser.
- Use separate keys per environment (development, staging, production) so a compromised staging key can't touch production jobs, and so usage per environment shows up separately in usage reporting.
- Keys can be created with restricted permission scopes — for example a key that can create dubs but can't read usage or manage webhooks.
Requests without a valid Authorization header return 401 Unauthorized. Requests from a plan without API access return 403 Forbidden.
Developer Quickstart
Errors & limits
Octavia uses conventional HTTP status codes: 2xx for success, 4xx when the request itself needs fixing, 5xx when something failed on our end and is usually safe to retry.
| Status | Meaning |
|---|---|
400 | The request body is malformed — invalid JSON, missing a required field. |
401 | Missing, malformed, or revoked API key. |
403 | The key is valid but the plan or scope doesn't allow this action. |
404 | The resource (dub, job, key) doesn't exist or doesn't belong to this workspace. |
409 | The resource is in a state that doesn't allow this action — e.g. rendering a dub that's already rendering. |
422 | The request is well-formed but fails validation; the error body includes the offending field. |
429 | Rate limited — back off and retry using the headers below. |
500 / 503 | Something failed on Octavia's side. Safe to retry with backoff. |
Error responses share one shape:
{
"error": {
"code": "invalid_target_language",
"message": "'xx' is not a supported language code.",
"field": "targetLanguages[0]"
}
}
Rate limits are enforced per API key and scale with plan tier — Studio keys get priority throughput over Pro. Rather than hard-coding a number, read the limit off the response headers on every call:
X-RateLimit-Limit— requests allowed in the current window.X-RateLimit-Remaining— requests left in the current window.X-RateLimit-Reset— unix timestamp the window resets at.
On a 429, respect the Retry-After header rather than retrying immediately in a loop.
Core Workflow
Video translation (dubbing)
The core workflow: send a source video, get back the same video dubbed into every target language, lip-synced by default. Under the hood a dub moves through transcription, translation, and rendering — see Job lifecycle for the exact stages.
Create one with POST /v1/dubs, supplying either a videoUrl Octavia can fetch or a direct file upload. sourceLanguage is optional — when omitted, Octavia detects it automatically from the audio. targetLanguages is always an array, even for a single language.
- Lip-sync is on by default for video sources (
lipSync: true) and maps generated speech to mouth movement frame by frame. Set it tofalseto get a dubbed audio track composited over the original video without resyncing the mouth. - Mode (
fastorquality) trades render time for fidelity — iterate infast, switch toqualityfor the version you ship. See Best practices. - Generated speech follows the pacing, tone, and delivery of each source speaker rather than reading in a flat, uniform voice — this is what keeps a dub feeling like the same person talking, not a narrator reading a translation over them.
See the full parameter list under API Reference → Create a dub.
Core Workflow
Audio translation
Audio translation is the same pipeline as video dubbing minus the video and lip-sync stages, so it's a better fit for podcasts, voice memos, lectures, and any audio-only source. Submit audioUrl instead of videoUrl on the same POST /v1/dubs endpoint — Octavia infers the workflow from which field is present. Because there's no video to render or sync, jobs move through rendering faster than a video dub of the same length.
Output is a dubbed audio file per target language, downloadable the same way as a video dub's output.
Core Workflow
Speech generation
Speech generation goes the other direction: instead of dubbing an existing recording, it turns a script you write directly into narration. Use it for voiceovers, IVR prompts, or audio versions of written articles where there's no source audio to translate. POST /v1/speech takes text, a target language, and an optional voiceId — omit the voice and Octavia picks a natural default for that language. speed adjusts pacing without changing pitch.
Speech jobs skip transcription and translation entirely, so they're the fastest workflow in the API — usually rendering in well under the time it takes to read the output back.
Core Workflow
Subtitle generation
POST /v1/subtitles/generate transcribes a video or audio source into timed subtitles — word-level timestamps, punctuation, and speaker labels when more than one voice is detected — without translating or dubbing anything. Useful when you already have the source language covered and just need accurate captions, or as the first step before running subtitle translation separately.
Output is downloadable as SRT or VTT.
Core Workflow
Subtitle to audio
If you already have a subtitle file and want narrated audio without re-transcribing a source recording, submit it directly: POST /v1/dubs with sourceType: "subtitles" and a subtitleUrl instead of a video or audio URL. Octavia uses the subtitle timing as the pacing target for generated speech, then translates into each of targetLanguages as usual. This skips the transcription stage of the job lifecycle since the timed text is already provided.
Core Workflow
Subtitle translation
POST /v1/subtitles/translate translates the text of an existing SRT or VTT file into one or more target languages while leaving the original timing untouched — no audio is generated. It's the lightest-weight localization workflow in the API and the cheapest per minute (see Credits & usage), which makes it a good fit for translating an entire back catalog of captions quickly.
Working With Jobs
How a dub moves
Every dub reports its progress through a small, fixed set of statuses. Poll GET /v1/dubs/{id} to read the current one, or subscribe to webhooks to get pushed each transition instead.
| Status | What's happening |
|---|---|
| queued | Accepted and waiting for a worker. |
| transcribing | Speech-to-text with word timestamps; speakers separated. |
| translating | Each line translated into every target language. |
| in_review | Only with review=manual: the transcript is editable until you render. |
| rendering | Voice (and lip-sync, for video) generated per language. |
| complete | Every target language rendered; outputs downloadable. |
| failed | Something broke; the error rides the status payload. |
A job in failed includes an error object with the same shape described in Errors & limits. Failed jobs don't consume credits for the languages that didn't finish rendering.
Working With Jobs
Transcript & review
By default a dub renders as soon as translation finishes. Pass "review": "manual" when creating it, and the job pauses in in_review after translation instead of moving straight to rendering — giving you a window to fix names, terminology, or phrasing before any voice is generated.
While a dub is in_review:
- Read the full transcript, line by line, per language, with
GET /v1/dubs/{id}. - Edit an individual line's text, reassign it to a different detected speaker, or nudge its timing with
PATCH /v1/dubs/{id}/transcript/{lineId}. - When every edit is in, call
POST /v1/dubs/{id}/renderto resume the job and move it intorendering.
A dub left in_review doesn't expire on any fixed timer, but it also doesn't consume rendering credits until you explicitly render it — a useful way to batch up translation review work without burning budget on drafts you might still change.
Working With Jobs
Multi-speaker detection
On Pro and above, Octavia automatically detects and separates distinct speakers in a source file during transcription, rather than treating the whole track as one voice. Each detected speaker keeps a consistent voice across the entire dub and across every target language, so a conversation between two people in the source stays a conversation between two distinct voices in the translation.
Speaker assignments are visible per transcript line and adjustable during manual review — if two speakers get merged or split incorrectly, reassign the affected lines before rendering rather than living with the mistake in the output.
Languages
Supported languages
Octavia supports 60+ target languages across every workflow, identified by standard language codes (e.g. es, fr, de, ja, ko, hi, ar, pt), with regional variants distinguished where pronunciation or vocabulary meaningfully differs (e.g. pt-BR vs. pt-PT). sourceLanguage is optional on every endpoint that accepts audio or video — when it's omitted, Octavia detects the spoken language automatically before transcribing.
The full, current list of supported codes is machine-readable rather than reproduced here, since it grows over time:
curl https://api.octavia.ai/v1/languages \
-H "Authorization: Bearer $OCTAVIA_API_KEY"
Platform
Webhooks
Polling GET /v1/dubs/{id} works fine for a one-off script, but for anything running in production, register a webhook instead and let Octavia push each state change to your endpoint the moment it happens.
Register an endpoint with POST /v1/webhooks, or from Settings → Webhooks in the app. Every delivery is a POST with a JSON body:
{
"event": "dub.completed",
"data": {
"id": "dub_31xk9m",
"status": "complete",
"targetLanguages": ["es", "fr"]
},
"timestamp": "2026-08-16T09:14:41Z"
}
Each delivery is signed — verify the X-Octavia-Signature header (an HMAC-SHA256 of the raw request body, keyed with your webhook secret) before trusting the payload, the same way you would for any inbound webhook. Reject anything that doesn't match rather than assuming requests to your endpoint can only come from Octavia.
If your endpoint doesn't return a 2xx, the delivery is retried with backoff for a limited number of attempts — treat webhook handling as idempotent so a retried delivery doesn't double-process the same event. See the full list of event types under API Reference → Event types.
Platform
Credits & usage
Credits are the single currency across every Octavia workflow — API usage draws from the same monthly balance as jobs run through the app. Each workflow costs a different amount per minute of source content processed:
| Workflow | Credits / min |
|---|---|
| Video translation | 100 |
| Audio translation | 80 |
| Subtitle to audio | 60 |
| Subtitle translation | 25 |
| Subtitle generation | 20 |
Monthly allowance scales with plan: Free includes 500 credits/month, Starter 6,000, Creator 12,000, Pro 30,000, and Studio 120,000 — see Pricing for the full breakdown, including per-plan limits on job length and concurrency. Check your remaining balance at any time with GET /v1/usage rather than tracking it manually; a job that would exceed your remaining balance is rejected with 402 before it starts, so failed credit checks never partially consume a job's allowance.
Platform
Permission scopes
API access itself is gated by plan — Pro and Studio only, with Studio getting priority throughput. Within an eligible workspace, individual keys can be scoped down further so an integration only has the access it actually needs:
| Scope | Grants |
|---|---|
dubs:read | Read dub status, transcripts, and output URLs. |
dubs:write | Create, edit, render, and cancel dubs. |
subtitles:read | Read generated or translated subtitle jobs. |
subtitles:write | Create subtitle generation and translation jobs. |
speech:write | Create speech generation jobs. |
webhooks:manage | Create, update, and delete webhook endpoints. |
usage:read | Read credit balance and usage history. |
A key created for a one-way ingest pipeline, for example, typically only needs dubs:write and webhooks:manage — it never needs to read usage or manage other keys. Scope keys to the minimum they need.
Best Practices
Building reliable integrations
- Prefer webhooks over polling. If you must poll, don't do it more than once every 5–10 seconds per job — rendering a video dub reliably takes longer than a single poll interval anyway.
- Always send
targetLanguagesas an array, even for a single language. It keeps the response shape (and your parsing code) identical regardless of how many languages you request. - Use
review=manualfor anything long-form — lectures, interviews, podcasts — where names, jargon, and brand terms are easy for machine translation to get wrong. Fixing a line before render is free; re-rendering after the fact isn't. - Pass an
idempotencyKeyonPOST /v1/dubswhen your client might retry a request after a network failure. Retrying with the same key returns the original job instead of creating a duplicate one. - Store the returned
idimmediately. It's what you'll use to poll, to correlate incoming webhook events, and to fetch output — treat it as the join key between your system and Octavia's. - Iterate in
fastmode, ship inqualitymode. Fast mode is meant for checking translation and pacing before you commit credits to a final render. - Verify webhook signatures and make your handler idempotent — see Webhooks.
Heads up: generated speech follows the tone and pacing of the source speaker but Octavia doesn't offer a standalone voice-cloning feature today — you can't submit a separate voice sample and have it applied independently of a source recording. If your workflow assumes that capability, check back or reach out via Contact before building around it.
Core
API Reference
The REST API is organized around the same resources described in the Overview: dubs, speech jobs, subtitle jobs, and webhooks. Requests and responses are JSON. Every endpoint below is relative to the base URL:
https://api.octavia.ai/v1
All endpoints live under the v1 prefix. A GraphQL endpoint at /v1/graphql exposes the same resources for clients that prefer to fetch nested data in one request.
Core
Authentication
Send your API key as a bearer token on every request. See Overview → Authentication for key management and scoping.
Authorization: Bearer oct_sk_live_...
Create a dub
Creates a video or audio dubbing job. The source is inferred from which of videoUrl, audioUrl, or subtitleUrl you provide.
| Field | Type | Description |
|---|---|---|
videoUrl | string | Publicly fetchable URL of the source video. Required unless audioUrl or subtitleUrl is set. |
audioUrl | string | Source audio URL for audio-only translation. |
subtitleUrl | string | Source SRT/VTT URL for subtitle-to-audio jobs. Pair with sourceType: "subtitles". |
targetLanguages | array<string> | Required. One or more language codes to dub into. |
sourceLanguage | string | Optional. Auto-detected from audio when omitted. |
review | "auto" | "manual" | Default "auto". Use "manual" to pause at in_review. |
lipSync | boolean | Default true for video sources. Ignored for audio-only jobs. |
mode | "fast" | "quality" | Default "fast". |
idempotencyKey | string | Optional. Safely retry the request without creating a duplicate job. |
curl https://api.octavia.ai/v1/dubs \ -H "Authorization: Bearer $OCTAVIA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "videoUrl": "https://example.com/video.mp4", "targetLanguages": ["es", "fr"], "review": "manual", "mode": "quality" }'
{
"id": "dub_31xk9m",
"status": "queued",
"sourceLanguage": null,
"targetLanguages": ["es", "fr"],
"review": "manual",
"mode": "quality",
"createdAt": "2026-08-16T09:12:03Z"
}
Retrieve a dub
Returns the current status of a dub, its transcript once available, and a per-language output URL once complete.
{
"id": "dub_31xk9m",
"status": "complete",
"targetLanguages": ["es", "fr"],
"outputs": {
"es": "https://api.octavia.ai/v1/dubs/dub_31xk9m/output/es",
"fr": "https://api.octavia.ai/v1/dubs/dub_31xk9m/output/fr"
},
"createdAt": "2026-08-16T09:12:03Z",
"completedAt": "2026-08-16T09:19:47Z"
}
List dubs
Returns dubs for the current workspace, newest first. Supports cursor pagination and filtering by status.
| Field | Type | Description |
|---|---|---|
status | string | Optional filter, e.g. complete or failed. |
limit | integer | Max results per page, default 20, max 100. |
cursor | string | Opaque pagination cursor from a prior response's nextCursor. |
Edit a transcript line
Only valid while a dub is in_review. Edits the translated text, speaker assignment, or timing of a single line without touching the rest of the transcript.
curl -X PATCH https://api.octavia.ai/v1/dubs/dub_31xk9m/transcript/ln_04 \ -H "Authorization: Bearer $OCTAVIA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Octavia, not Octavio." }'
Render a dub
Resumes a dub out of in_review and moves it into rendering. No effect on a dub that isn't currently in review.
Cancel a dub
Cancels a dub that's queued, transcribing, translating, or in_review. Returns 409 if it's already rendering or complete. Cancelled jobs are not billed for languages that hadn't started rendering.
Download output
Streams the finished file for one target language. 404 until that language reaches complete.
curl -L https://api.octavia.ai/v1/dubs/dub_31xk9m/output/es \
-H "Authorization: Bearer $OCTAVIA_API_KEY" \
-o dubbed-es.mp4
Create speech
Generates narration from plain text — no source recording required.
| Field | Type | Description |
|---|---|---|
text | string | Required. The script to narrate. |
language | string | Required. Target language code. |
voiceId | string | Optional. Omit for a natural default voice. |
speed | number | Optional. Playback rate multiplier, default 1.0. |
Generate subtitles
Transcribes videoUrl or audioUrl into timed subtitles without translating. Returns a job you poll the same way as a dub; output format defaults to srt, or set format: "vtt".
Translate subtitles
Translates the text of a subtitleUrl into each of targetLanguages, keeping the original cue timing untouched.
Register a webhook
Registers an endpoint to receive job lifecycle events. Returns a signing secret once, used to verify the X-Octavia-Signature header on every delivery.
curl https://api.octavia.ai/v1/webhooks \ -H "Authorization: Bearer $OCTAVIA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/hooks/octavia", "events": ["dub.completed", "dub.failed"] }'
Event types
| Event | Fires when |
|---|---|
dub.queued | A dub is accepted and waiting for a worker. |
dub.in_review | A manual-review dub's transcript is ready to edit. |
dub.rendering | A dub starts generating voice/lip-sync output. |
dub.completed | Every target language has finished rendering. |
dub.failed | A dub failed at any stage. |
subtitles.completed | A subtitle generation or translation job finishes. |
speech.completed | A speech generation job finishes. |
Languages
Returns the current list of supported language codes and display names, usable across every workflow.
Usage
Returns the workspace's credit balance for the current billing period and a breakdown of consumption by workflow.
{
"period": "2026-08",
"allowance": 30000,
"used": 4820,
"remaining": 25180
}
Error codes
Non-exhaustive list of error.code values you may see in a 4xx response body — see Overview → Errors & limits for the response shape.
| Code | Meaning |
|---|---|
invalid_target_language | One of targetLanguages isn't a supported code. |
source_unreachable | Octavia couldn't fetch the given videoUrl/audioUrl. |
insufficient_credits | This job would exceed the workspace's remaining balance. |
invalid_state_transition | The action doesn't apply to the job's current status (e.g. rendering a job that isn't in_review). |
scope_missing | The API key doesn't have the required permission scope. |
Changelog
Changelog
The API is currently on v1, and it's the only version every endpoint in this reference targets. Changes within v1 are additive and backward-compatible — new fields, new endpoints, new languages — nothing existing is renamed or removed without a new version prefix.
Release notes
This page is a static reference and doesn't track dated release history automatically. For the live, dated changelog — new languages, endpoint additions, and fixes as they ship — check the in-app changelog or reach out through Contact and we'll point you to what changed.
Building against the API in production? Register a webhook for dub.failed so you notice breaking changes in your own pipeline immediately, rather than discovering them from a support ticket.