Start here/Quickstart

Quickstart

Send one video and a schema, poll the job, read the rows. This page is the shortest complete path from an empty terminal to validated JSON.

Updated 2 September 2026·4 minute read

Before you start

You need a key from the dashboard and a video whose captions are public. New accounts hold 100 credits, which is enough to work through this page about twenty times over.

NOTE
Captions are read, never generated. If a video has no caption track the job returns no_captions and costs nothing.

1. Submit a video

A schema is a list of columns, each with a name and a plain-language description. The description is the instruction, so write it the way you would brief a person.

node 20 · esm
// no SDK needed — plain fetch
const res = await fetch("https://api.tubeextract.dev/v1", {
method: "POST",
headers: {
"X-API-Key": process.env.TUBEEXTRACT_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
// always a list — one video here, up to 200 in the same call
videos: ["https://youtube.com/watch?v=dQw4w9WgXcQ"],
schema: {
multiple: true,
columns: [
{ name: "city",
description: "A city the speaker mentions visiting" },
{ name: "timestamp",
description: "When it is mentioned, as hh:mm:ss" }
]
}
})
});
 
const job = await res.json();
console.log(job.job_id); // 8f2c1e40-91a3-4b7e

Request body

Field
Type
Required
Description
videosstring[]yesOne to 200 entries, each a watch link or a bare video ID. Always a list, so one video and two hundred are the same call — see POST /v1.
schema.columnsarrayyesOne entry per field, each with a name and a description.
schema.multiplebooleannoReturn many rows per video instead of one. Defaults to false.
webhook_urlstringnoHTTPS endpoint called once when the job settles.

2. Read the job

Extraction is asynchronous, so the submit call answered with an id rather than rows. Poll every two seconds, or supply a webhook_url and skip polling entirely. A single video usually settles in twelve to fifteen seconds.

200 · GET /v1/8f2c1e40
{
"job_id": "8f2c1e40-91a3-4b7e",
"status": "done",
"videos": 1,
"credits_charged": 1,
"results": [
{
"video_id": "dQw4w9WgXcQ",
"status": "done",
"rows": {
"results": [
{ "city": "Lisbon", "timestamp": "00:04:12" },
{ "city": "Porto", "timestamp": "00:09:48" }
]
},
"usage": {
"credits_charged": 1,
"duration_seconds": 782,
"chunk_count": 4
}
}
]
}

results stays null until every video in the job has settled, so polling is free and destroys nothing.

CAREFUL
Rows are deleted on the first read that finds the job settled. Write them somewhere before you acknowledge the response.

3. Handle the four outcomes

Every job settles into one of these. Treat anything else as a transport error and retry with the same idempotency key.

done

Rows are ready and charged. Fetch once and store.

no_captions

Nothing to read. Not charged, and retrying will not help.

schema_invalid

A field name or description was rejected. The message names the field.

insufficient_credits

The balance reached zero, or the hard cap. Top up or raise the cap, then resubmit.

Where to go next

Was this page useful?Tell us what was missing