Transcription API

The Online Transcript API lets you transcribe YouTube videos, audio files and video files from your own code, with the same engine and automatic punctuation as the website. It is included in the Pro and Max plans at no extra cost; every request uses the same monthly minutes as the website.

The API is asynchronous: you submit a job, receive an id immediately, and then ask for the result. A short file is usually ready in a few seconds; a one-hour video in about a minute.

Authentication

Create a key in My profile → API access on the website. Keys start with ot_live_ and are shown only once: store them as a secret on your server and never put them in browser or mobile app code. You can have up to 5 active keys and revoke any of them at any time.

Send the key in every request, in either of these headers:

Authorization: Bearer ot_live_xxxxxxxxxxxxxxxx
X-API-Key: ot_live_xxxxxxxxxxxxxxxx

Base URL: https://api.online-transcript.com/api/v1. All responses are JSON in UTF-8.

Quick start

# 1. Submit a YouTube video
curl -X POST https://api.online-transcript.com/api/v1/transcriptions \
  -H "Authorization: Bearer $OT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw"}'

# → 202 {"id": "tr_8f3c...", "status": "queued", ...}

# 2. Ask for the result until status is "completed" or "failed"
curl https://api.online-transcript.com/api/v1/transcriptions/tr_8f3c... \
  -H "Authorization: Bearer $OT_API_KEY"

# 3. Or download it straight as plain text
curl "https://api.online-transcript.com/api/v1/transcriptions/tr_8f3c...?format=txt" \
  -H "Authorization: Bearer $OT_API_KEY" -o transcript.txt

POST /transcriptions — create a job

From a YouTube URL (JSON)

POST /api/v1/transcriptions
Content-Type: application/json

{
  "url": "https://www.youtube.com/watch?v=VIDEO_ID",
  "punctuate": true
}

From a file (multipart/form-data)

curl -X POST https://api.online-transcript.com/api/v1/transcriptions \
  -H "Authorization: Bearer $OT_API_KEY" \
  -F "file=@interview.mp4" \
  -F "punctuate=true"
FieldTypeDescription
urlstringYouTube link (youtube.com, youtu.be, m.youtube.com). Use this or file.
filefileAudio: mp3, wav, m4a, ogg, opus, flac, aac. Video: mp4, mov, mkv, webm, avi, m4v, 3gp, mpeg, mpg, wmv, flv, ts. Up to 1 GB. Videos are converted to audio on our server and deleted right away.
punctuatebooleanOptional, default true. Adds punctuation and paragraphs without changing words. Send false to get the raw engine output only (slightly faster).

Response: 202 Accepted with the transcription object in status queued and a Location header pointing to it. The transcript keeps the language spoken in the audio; it is never translated.

GET /transcriptions/{id} — status and result

GET /api/v1/transcriptions/tr_8f3c...            → JSON object
GET /api/v1/transcriptions/tr_8f3c...?format=txt → text/plain (only when completed)

Statuses: queued → processing → completed or failed. Poll every 5–10 seconds; there is no benefit in polling faster, and it counts towards the rate limit.

GET /transcriptions — list jobs

Returns your most recent jobs, newest first, without the text. Parameters: limit (1–100, default 20) and status (queued, processing, completed, failed).

GET /api/v1/transcriptions?limit=10&status=completed

{"object": "list", "data": [ {...}, {...} ]}

DELETE /transcriptions/{id} — delete a job

Deletes a completed or failed job and its text from our servers. Jobs still running cannot be deleted (409). Results are deleted automatically 7 days after they are created.

DELETE /api/v1/transcriptions/tr_8f3c...

{"id": "tr_8f3c...", "object": "transcription", "deleted": true}

GET /usage — monthly allowance

{
  "object": "usage",
  "plan": "pro",
  "period": "2026-10",
  "unlimited": false,
  "limit_minutes": 300,
  "used_minutes": 42,
  "used_seconds": 2533,
  "remaining_minutes": 257,
  "transcriptions": 18,
  "active_jobs": 1,
  "max_active_jobs": 3,
  "rate_limit_per_minute": 60
}

On Max, unlimited is true and limit_minutes / remaining_minutes are null. The allowance resets on the first day of each calendar month (UTC) and is shared with the website.

The transcription object

{
  "id": "tr_8f3c1d0e9a7b6c5d4e3f2a1b",
  "object": "transcription",
  "status": "completed",
  "source": {"type": "youtube", "url": "https://www.youtube.com/watch?v=..."},
  "title": "Me at the zoo",
  "duration_seconds": 19.0,
  "punctuate": true,
  "created_at": "2026-10-01T21:24:14+00:00",
  "started_at": "2026-10-01T21:24:14+00:00",
  "completed_at": "2026-10-01T21:24:31+00:00",
  "text": "Alright, so here we are, in front of the elephants...",
  "raw_text": "Alright so here we are in front of the elephants..."
}

Errors

Every error response has the same shape:

{"error": {"code": "QUOTA_EXCEEDED", "message": "You reached your monthly limit of 300 minutes."}}

Errors when calling the API (HTTP status)

HTTPCodeMeaning
400MISSING_INPUTNo 'url' and no 'file' were sent.
400INVALID_URLThe URL is not a YouTube link.
400UNSUPPORTED_FORMATThe file extension is not supported.
401INVALID_API_KEYMissing, malformed or revoked key.
403PLAN_REQUIREDThe account is no longer on Pro or Max.
404NOT_FOUNDThe transcription does not exist or belongs to another account.
409NOT_READYYou asked for ?format=txt before the job completed.
409JOB_IN_PROGRESSYou tried to delete a job that is still running.
413FILE_TOO_LARGEThe upload is larger than 1 GB.
429RATE_LIMITEDMore than 60 requests in a minute. Honour the Retry-After header.
429TOO_MANY_ACTIVE_JOBSYou already have 3 jobs queued or processing.
429QUOTA_EXCEEDEDYour monthly minutes are used up.
503API_DISABLEDThe API is temporarily switched off for maintenance.

Reasons a job ends in status "failed"

error.codeMeaning
QUOTA_EXCEEDEDThe audio turned out longer than the minutes you have left.
DOWNLOAD_FAILEDThe YouTube video is private, removed, age- or region-restricted.
SERVICE_MAINTENANCEYouTube downloads are temporarily unavailable. Upload the file instead.
NO_AUDIO_TRACKThe uploaded video has no audio.
INVALID_MEDIAThe file could not be decoded.
TRANSCRIPTION_FAILEDThe engine failed. Retry in a few minutes.
SERVER_RESTARTEDThe server restarted while the job was running. Submit it again.
INTERNAL_ERRORUnexpected error. Submit it again; if it persists, contact us.

Minutes are only charged for jobs that complete. A failed job costs nothing.

Limits

Examples

Python (requests)

import os, time, requests

API = "https://api.online-transcript.com/api/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["OT_API_KEY"]}

def transcribe(url=None, path=None, punctuate=True):
    if path:
        with open(path, "rb") as f:
            r = requests.post(API + "/transcriptions", headers=HEADERS,
                              files={"file": f}, data={"punctuate": str(punctuate).lower()})
    else:
        r = requests.post(API + "/transcriptions", headers=HEADERS,
                          json={"url": url, "punctuate": punctuate})
    r.raise_for_status()
    job_id = r.json()["id"]

    while True:
        time.sleep(5)
        job = requests.get(API + "/transcriptions/" + job_id, headers=HEADERS).json()
        if job["status"] == "completed":
            return job["text"]
        if job["status"] == "failed":
            raise RuntimeError(job["error"]["code"] + ": " + job["error"]["message"])

print(transcribe(url="https://www.youtube.com/watch?v=jNQXAC9IVRw"))
# print(transcribe(path="meeting.mp3"))

Node.js 18+ (fetch)

import { readFile } from "node:fs/promises";

const API = "https://api.online-transcript.com/api/v1";
const headers = { Authorization: "Bearer " + process.env.OT_API_KEY };

async function transcribe({ url, path }) {
  let body;
  if (path) {
    body = new FormData();
    body.append("file", new Blob([await readFile(path)]), path.split("/").pop());
  } else {
    headers["Content-Type"] = "application/json";
    body = JSON.stringify({ url });
  }
  const res = await fetch(API + "/transcriptions", { method: "POST", headers, body });
  const job = await res.json();
  if (!res.ok) throw new Error(job.error.code + ": " + job.error.message);
  delete headers["Content-Type"];

  for (;;) {
    await new Promise((r) => setTimeout(r, 5000));
    const j = await (await fetch(API + "/transcriptions/" + job.id, { headers })).json();
    if (j.status === "completed") return j.text;
    if (j.status === "failed") throw new Error(j.error.code + ": " + j.error.message);
  }
}

console.log(await transcribe({ url: "https://www.youtube.com/watch?v=jNQXAC9IVRw" }));

Check your remaining minutes

curl https://api.online-transcript.com/api/v1/usage -H "Authorization: Bearer $OT_API_KEY"

Support

Questions or a use case that needs higher limits? Write to support@online-transcript.com. Use of the API is subject to the Terms of Service and the Privacy Policy.