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 /jobsThe 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.
| Field | Type | Required | Notes |
|---|---|---|---|
filename | string | yes | Up to 255 characters. |
contentType | string | yes | An audio/* or video/* type, such as audio/wav. The upload must be sent with the same type. |
sizeBytes | integer | yes | The exact size of the file, from 1 byte to 5 GiB. The upload must match it. |
durationSeconds | number | yes | The media's duration in seconds, above 0. Used for billing and plan limits. |
name | string | no | Up to 200 characters. Defaults to the filename without its extension. |
folderId | string | no | A folder in your organization. Its members get access to the job. |
presetId | string | no | An upload preset from your organization, to start from. |
settings | object | no | See 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.
| Setting | Type | Default | Notes |
|---|---|---|---|
language | string | auto | auto, multilingual, or a language code such as nl, en or zh-TW. |
customVocabulary | string[] | none | Names and terms to recognize. Up to 1,000 terms of up to 100 characters. |
formatSubtitles | boolean | true | AI subtitle formatting. |
maxCharsPerLine | integer | 42 | 10 to 250. |
maxLinesPerSubtitle | integer | 2 | 1 to 5. |
minDuration | number | none | Seconds, 0 to 60. Cannot exceed maxDuration. |
maxDuration | number | none | Seconds, 0.1 to 60. |
minFramesBetweenSubtitles | integer | 3 | 0 to 100. |
contentProfile | string | balanced | Reading pace: balanced, dramatic, dense or auto. |
subtitleMode | string | default | default or audiogram. |
splitOnPunctuation | boolean | false | |
emDashOnSpeakerChange | boolean | true | |
ellipsisContinuation | boolean | false | |
shiftMs | integer | 0 | −10,000 to 10,000. Usually negative, to show subtitles slightly early. |
forbiddenCharacters | array | none | Up to 100 rules, each { "chars": "…", "replacement": "…" }. |
prompt | string | none | Up to 4,000 characters of context for the AI formatting pass. |
createHighlights | boolean | false | |
createChapters | boolean | false |
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:
- The defaults in the table above.
- The upload preset named by
presetId. Anything the preset leaves unset keeps the default. - 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.
status | Meaning |
|---|---|
uploading | Waiting for the media to arrive at the upload URL. |
transcribing | Extracting audio and recognizing speech. |
formatting | Laying out the subtitles and analyzing the content. |
ready | Done. The transcript and subtitle files can be downloaded. |
failed | Done, without a result. error says why, and the charge is refunded. |
Transcripts
transcripts lists the original first, then the others, oldest first.
| Field | Values |
|---|---|
kind | original (as recognized), clean (same language, filler words removed) or translation (another language). |
status | pending, processing, ready or failed. |
label | A 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.