Skip to main content

Transcriptions

Transcription converts a stored audio or video object into a transcript with word-level timings, segments, and speaker labels — the same data that drives captions in the editor.

It's a three-call flow: submit, poll, fetch the result. The result lives on a separate endpoint from the job because transcripts are large.

Cost: 72 credits per hour of media, billed on the durationSeconds you declare and rounded up to whole credits. Ten minutes costs 12 credits.

The transcription job object

FieldTypeDescription
idstringJob id (UUID).
statusenumqueued · running · succeeded · failed.
languagestring | nullDetected or requested language. null until known.
errorMessagestring | nullWhy it failed.
costCreditsnumber | nullCredits charged. null until it settles.
createdAtstringISO 8601.

POST /v1/transcriptions

Submit a job. Returns immediately with status: "running".

Body

NameTypeRequiredDescription
storageIdstringyesA committed audio or video object. See Files.
durationSecondsnumberyesMedia length in seconds; must be positive. The charge is derived from this, not from the file — get it right.
languagestringnoBCP-47 hint, e.g. "en", "pt-BR". Omit to auto-detect.
projectIdstringnoBill to a shared project — the owner pays.

Returns { "job": TranscriptionJob }.

Request
curl -s -X POST https://api.corte.so/v1/transcriptions \
-H "Authorization: Bearer corte_sk_…" \
-H "Content-Type: application/json" \
-d '{
"storageId": "f2c94a67-0e1b-4d38-8b52-6a7f1c09e4d2",
"durationSeconds": 612
}'
Response
{
"job": {
"id": "6b0d3f28-91ae-4c57-b840-2f7c1e6a95d3",
"status": "running",
"language": null,
"errorMessage": null,
"costCredits": null,
"createdAt": "2026-07-28T18:02:19.615Z"
}
}

Errors

CodeWhen
invalid_paramsMissing storageId, non-positive durationSeconds, or an unknown/uncommitted object.
insufficient_credits · owner_insufficient_creditsBalance short of the 72-credits-per-hour cost. Nothing charged.
forbiddenTranscription is disabled for the workspace.

GET /v1/transcriptions/:id

Poll the job. Only the submitter can read it.

Returns { "job": TranscriptionJob }.

Request
curl -s https://api.corte.so/v1/transcriptions/6b0d3f28-… \
-H "Authorization: Bearer corte_sk_…"
Response
{
"job": {
"id": "6b0d3f28-91ae-4c57-b840-2f7c1e6a95d3",
"status": "succeeded",
"language": "en",
"errorMessage": null,
"costCredits": 12,
"createdAt": "2026-07-28T18:02:19.615Z"
}
}

The transcript itself is not on the job — fetch it separately.


GET /v1/transcriptions/:id/result

Fetch the transcript. Available only once status is succeeded; anything else is not_found (404), including a job that's still running.

Returns the transcript payload directly — not wrapped in a named key.

FieldTypeDescription
textstringThe full transcript as one string.
languagestringDetected or requested language.
wordsobject[]{ text, start?, end?, speaker? } — word-level timings in seconds. Drives karaoke-style captions.
segmentsobject[]{ text, start, end, speaker? } — sentence or utterance chunks. Drives subtitle blocks.
Request
curl -s https://api.corte.so/v1/transcriptions/6b0d3f28-…/result \
-H "Authorization: Bearer corte_sk_…"
Response
{
"text": "So the thing about exporting is that it's always free. No watermark, ever.",
"language": "en",
"words": [
{ "text": "So", "start": 0.12, "end": 0.28, "speaker": "A" },
{ "text": "the", "start": 0.28, "end": 0.39, "speaker": "A" },
{ "text": "thing", "start": 0.39, "end": 0.71, "speaker": "A" },
{ "text": "about", "start": 0.71, "end": 0.98, "speaker": "A" },
{ "text": "exporting", "start": 0.98, "end": 1.54, "speaker": "A" }
],
"segments": [
{ "text": "So the thing about exporting is that it's always free.", "start": 0.12, "end": 3.44, "speaker": "A" },
{ "text": "No watermark, ever.", "start": 3.61, "end": 4.88, "speaker": "A" }
]
}

speaker is present when the provider distinguished voices; it's a label like "A" / "B", not an identity.


Full flow

import time, requests

BASE, HEAD = "https://api.corte.so", {"Authorization": f"Bearer {KEY}"}

def transcribe(storage_id, duration_seconds, language=None):
body = {"storageId": storage_id, "durationSeconds": duration_seconds}
if language:
body["language"] = language
job = requests.post(f"{BASE}/v1/transcriptions", json=body, headers=HEAD).json()["job"]

while job["status"] in ("queued", "running"):
time.sleep(3)
job = requests.get(f"{BASE}/v1/transcriptions/{job['id']}", headers=HEAD).json()["job"]

if job["status"] == "failed":
raise RuntimeError(job["errorMessage"])

return requests.get(f"{BASE}/v1/transcriptions/{job['id']}/result", headers=HEAD).json()