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
| Field | Type | Description |
|---|---|---|
id | string | Job id (UUID). |
status | enum | queued · running · succeeded · failed. |
language | string | null | Detected or requested language. null until known. |
errorMessage | string | null | Why it failed. |
costCredits | number | null | Credits charged. null until it settles. |
createdAt | string | ISO 8601. |
POST /v1/transcriptions
Submit a job. Returns immediately with status: "running".
Body
| Name | Type | Required | Description |
|---|---|---|---|
storageId | string | yes | A committed audio or video object. See Files. |
durationSeconds | number | yes | Media length in seconds; must be positive. The charge is derived from this, not from the file — get it right. |
language | string | no | BCP-47 hint, e.g. "en", "pt-BR". Omit to auto-detect. |
projectId | string | no | Bill to a shared project — the owner pays. |
Returns { "job": TranscriptionJob }.
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
}'
{
"job": {
"id": "6b0d3f28-91ae-4c57-b840-2f7c1e6a95d3",
"status": "running",
"language": null,
"errorMessage": null,
"costCredits": null,
"createdAt": "2026-07-28T18:02:19.615Z"
}
}
Errors
| Code | When |
|---|---|
invalid_params | Missing storageId, non-positive durationSeconds, or an unknown/uncommitted object. |
insufficient_credits · owner_insufficient_credits | Balance short of the 72-credits-per-hour cost. Nothing charged. |
forbidden | Transcription is disabled for the workspace. |
GET /v1/transcriptions/:id
Poll the job. Only the submitter can read it.
Returns { "job": TranscriptionJob }.
curl -s https://api.corte.so/v1/transcriptions/6b0d3f28-… \
-H "Authorization: Bearer corte_sk_…"
{
"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.
| Field | Type | Description |
|---|---|---|
text | string | The full transcript as one string. |
language | string | Detected or requested language. |
words | object[] | { text, start?, end?, speaker? } — word-level timings in seconds. Drives karaoke-style captions. |
segments | object[] | { text, start, end, speaker? } — sentence or utterance chunks. Drives subtitle blocks. |
curl -s https://api.corte.so/v1/transcriptions/6b0d3f28-…/result \
-H "Authorization: Bearer corte_sk_…"
{
"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()
Related
- Files — getting media in as a
storageId - Captions & transcription — the same engine in the editor