Subtitle AI
API

Jobs

Create a job, choose its settings and follow its status.

A job is one transcription, from upload to finished subtitles. It is also a normal project in your organization.

Create a job

POST /jobs

The response contains a presigned upload URL. You upload the media there yourself, and the upload starts transcription.

Request body

Unknown fields are rejected, not ignored. A typo such as max_chars_per_line returns an error that names it, instead of quietly using the default.

FieldTypeRequiredNotes
filenamestringyesUp to 255 characters.
contentTypestringyesAn audio/* or video/* type, such as audio/wav. The upload must be sent with the same type.
sizeBytesintegeryesThe exact size of the file, from 1 byte to 5 GiB. The upload must match it.
durationSecondsnumberyesThe media's duration in seconds, above 0. Used for billing and plan limits.
namestringnoUp to 200 characters. Defaults to the filename without its extension.
folderIdstringnoA folder in your organization. Its members get access to the job.
presetIdstringnoAn upload preset from your organization, to start from.
settingsobjectnoSee settings.

Upload audio only whenever you can. A 50 MB WAV or a small m4a is enough, and the editor shows a waveform instead of a picture. A single upload can be at most 5 GiB, so extract the audio from larger video first.

Settings

Every setting is optional, and they match the upload form in the app.

SettingTypeDefaultNotes
languagestringautoauto, multilingual, or a language code such as nl, en or zh-TW.
customVocabularystring[]noneNames and terms to recognize. Up to 1,000 terms of up to 100 characters.
formatSubtitlesbooleantrueAI subtitle formatting.
maxCharsPerLineinteger4210 to 250.
maxLinesPerSubtitleinteger21 to 5.
minDurationnumbernoneSeconds, 0 to 60. Cannot exceed maxDuration.
maxDurationnumbernoneSeconds, 0.1 to 60.
minFramesBetweenSubtitlesinteger30 to 100.
contentProfilestringbalancedReading pace: balanced, dramatic, dense or auto.
subtitleModestringdefaultdefault or audiogram.
splitOnPunctuationbooleanfalse
emDashOnSpeakerChangebooleantrue
ellipsisContinuationbooleanfalse
shiftMsinteger0−10,000 to 10,000. Usually negative, to show subtitles slightly early.
forbiddenCharactersarraynoneUp to 100 rules, each { "chars": "…", "replacement": "…" }.
promptstringnoneUp to 4,000 characters of context for the AI formatting pass.
createHighlightsbooleanfalse
createChaptersbooleanfalse

Two settings from the app are not available through the API: subtitle lead-in and creating a clean version at upload. Jobs created with the API have neither, even if the preset you name has them.

How settings combine

Settings are layered, as in the upload form:

  1. The defaults in the table above.
  2. The upload preset named by presetId. Anything the preset leaves unset keeps the default.
  3. Whatever you send in settings. This wins.

A presetId must belong to your organization. Create presets in the app, under Settings, and use the preset's id. The built-in presets, such as Broadcast, cannot be named here.

Response

201 Created:

{
  "id": "6a5f3c0e9b1d4e2a7c8b9d01",
  "status": "uploading",
  "name": "episode",
  "editorUrl": "https://app.subtitle.ai/editor/6a5f3c0e9b1d4e2a7c8b9d01",
  "upload": {
    "method": "PUT",
    "url": "https://…",
    "headers": { "Content-Type": "audio/wav", "Content-Length": "52428800" },
    "expiresAt": "2026-10-02T11:00:00.000Z"
  },
  "links": {
    "self": "https://app.subtitle.ai/api/v1/jobs/6a5f3c0e9b1d4e2a7c8b9d01",
    "transcript": "https://app.subtitle.ai/api/v1/jobs/6a5f3c0e9b1d4e2a7c8b9d01/transcript",
    "subtitles": "https://app.subtitle.ai/api/v1/jobs/6a5f3c0e9b1d4e2a7c8b9d01/subtitles?format=srt",
    "editor": "https://app.subtitle.ai/editor/6a5f3c0e9b1d4e2a7c8b9d01"
  }
}

Uploading the media

Send one PUT request to upload.url with the file as the body. The declared size and type are built into the URL, so a body of a different length, or sent as a different type, is refused. The URL expires at upload.expiresAt, one hour after creation. If it expires, create a new job.

The job stays uploading until the file arrives. A job whose file is never uploaded stays in that state.

Get a job

GET /jobs/{id}
{
  "id": "6a5f3c0e9b1d4e2a7c8b9d01",
  "name": "episode",
  "status": "ready",
  "error": null,
  "createdAt": "2026-10-02T10:01:12.000Z",
  "updatedAt": "2026-10-02T10:09:40.000Z",
  "media": {
    "filename": "episode.wav",
    "contentType": "audio/wav",
    "sizeBytes": 52428800,
    "durationSeconds": 1520.4
  },
  "language": "nl",
  "transcripts": [
    { "id": "6a5f…a1", "language": "nl", "kind": "original", "label": null, "status": "ready" },
    { "id": "6a5f…a2", "language": "nl", "kind": "clean", "label": "Clean version", "status": "ready" },
    { "id": "6a5f…a3", "language": "en", "kind": "translation", "label": null, "status": "processing" }
  ],
  "editorUrl": "https://app.subtitle.ai/editor/6a5f3c0e9b1d4e2a7c8b9d01",
  "links": { "self": "…", "transcript": "…", "subtitles": "…", "editor": "…" }
}

Status

Poll every 10 to 30 seconds until status is ready or failed. These five values will not change, even if the pipeline inside does.

statusMeaning
uploadingWaiting for the media to arrive at the upload URL.
transcribingExtracting audio and recognizing speech.
formattingLaying out the subtitles and analyzing the content.
readyDone. The transcript and subtitle files can be downloaded.
failedDone, without a result. error says why, and the charge is refunded.

Transcripts

transcripts lists the original first, then the others, oldest first.

FieldValues
kindoriginal (as recognized), clean (same language, filler words removed) or translation (another language).
statuspending, processing, ready or failed.
labelA name, such as Clean version, or null.

language on the job is the detected language once the job is transcribed. Until then it is the language you asked for, for example auto.

Translations and clean versions are created in the editor. The API lets you read them, as described in Results.

On this page