Start here/Quickstart

Quickstart: extract structured data from video

Extract structured data from video with one POST, then poll the job and read typed rows. Copy-paste examples in Node, Python and cURL.

Updated 14 September 2026·4 minute read

Before you start

You need a key from the dashboard and a video whose captions are public. New accounts start with 3,000 credits. A video costs 10 credits per started minute, so that covers about 300 minutes of video, or 23 runs of the 13-minute example below.

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://youtu.be/dQw4w9WgXcQ"],
multiple: true,
schema: [
{ name: "city", type: "verbatim-string",
description: "A city the speaker mentions visiting" },
{ name: "timestamp", type: "string",
description: "When it is mentioned, as hh:mm:ss" }
]
})
});
 
const job = await res.json(); // 202 Accepted
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.
schemaarrayyesOne entry per field: a name, a type and, to steer the extraction, a description. A type ending in [], such as string[], holds a list of values.
multiplebooleannoReturn many rows per video instead of one. Defaults to false.
webhook_urlstringnoHTTPS endpoint told when the job settles, so you need not poll. Paid plans only; on Free it is refused with 403 plan_limit.

2. Read the job

Extraction is asynchronous, so the submit call answered 202 Accepted with an id rather than rows. A single video usually settles in under a minute. Poll every eight seconds or so. Every call counts against your plan’s per-minute limit, which is 10 on Free and includes the submit, and a 429 tells you how long to wait in Retry-After. On a paid plan you can pass a webhook_url and skip polling entirely.

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

Each video’s rows 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

A video in a job settles as done or failed, and these are the outcomes you will meet most. The rest of the failure codes are on error codes. A failed HTTP call is a different thing: retry that 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.

captions_fetch_failed

The caption fetch failed upstream. Not charged, and worth resubmitting.

insufficient_credits

Once the video's length was known, the balance could not pay for it. Top up or raise the hard cap, then resubmit.

Where to go next

Was this page useful?Tell us what was missing