Get started
Overview
Send a video, describe the outcome you need, and get typed results with timecodes, evidence and review flags.
Story0 is a video API for media teams: newsrooms, aggregators, archives and anyone else with more footage than people to watch it. You send a video, describe the outcome you need, and get back structured data you can build on: time intervals in seconds, labels you defined, the evidence behind each one, and a flag on every moment a person should check.
What you can ask for
There are two processing operations today.
| Operation | Endpoint | What you get back |
|---|---|---|
Segmentation, partition mode |
POST /v1/segments |
A complete timeline. Every moment of the video, or of the range you ask about, carries exactly one of your labels, or uncertain where the footage doesn't show enough to decide. |
Segmentation, extract mode |
POST /v1/segments |
Only the moments that match an instruction, such as "moments where a person is jogging". No match is an empty list, not an error. |
| Screening | POST /v1/screenings |
A quick answer for each of up to 20 categories: match, no_match or uncertain, with the frames that support each match. |
A common pattern is to screen incoming clips to route them, then segment the ones worth a closer look. Analysis is visual: Story0 inspects frames sampled from the video and doesn't listen to the audio track.
How the pieces fit together
The API has three core objects.
- Assets hold your media. You create one by uploading a file or by giving Story0 a direct link to one. Story0 reads its media facts (duration, frame size, frame rate, codec, audio, size and SHA-256 hash) and keeps the media for 7 days.
- Jobs are requested operations on an asset. A segmentation or screening request returns its job straight away. The job moves from
queuedtorunningand ends assucceeded,failedorcancelled, reporting its stage, progress and charge along the way. - Results are what a succeeded job produced: a
segmentationor ascreening. Results are typed and immutable, and they're kept for 30 days. The job points to its result, and the result points back to its job and asset.
Processing is asynchronous by design, because analyzing video takes longer than an HTTP request should stay open. There is exactly one completion event, job.succeeded, and it fires only after the result exists, so whoever receives it can always fetch the result. You can wait for a job in three ways:
- long-poll
GET /v1/jobs/{id}?wait=20, which answers the moment the job finishes; - call an SDK helper such as
segments.createAndWaitin TypeScript orsegments.create_and_waitin Python; or - receive the
job.succeededwebhook.
Three steps
- Bring the media. Upload a file or create an asset from a URL. For one-off requests you can skip this step and pass
input.urlstraight to a processing request. - Ask for an outcome. Send
POST /v1/segmentsorPOST /v1/screeningswith an instruction, your labels or categories, abilling.max_chargethat caps what the request may cost, and anIdempotency-Keyso a retry never runs or bills the work twice. - Collect the result. When the job succeeds, fetch the typed result from
job.result.urland use its intervals, evidence and review flags in your own tools.
Here are all three steps in one call, run against a sample clip hosted for these docs:
import Story0 from "@story0/sdk";
const story0 = new Story0(); // reads STORY0_API_KEY
const result = await story0.segments.createAndWait({
input: { url: "https://dev.story0.com/demo/park-dog-demo.mp4" },
mode: "extract",
instruction: "Moments where a person is jogging",
billing: { max_charge: { currency: "USD", amount: "0.25" } },
});
for (const moment of result.segments) {
console.log(moment.start, moment.end, moment.title);
}from story0 import Story0
client = Story0() # reads STORY0_API_KEY
result = client.segments.create_and_wait(
input={"url": "https://dev.story0.com/demo/park-dog-demo.mp4"},
mode="extract",
instruction="Moments where a person is jogging",
billing={"max_charge": {"currency": "USD", "amount": "0.25"}},
)
for moment in result["segments"]:
print(moment["start"], moment["end"], moment.get("title"))curl https://api.story0.com/v1/segments \
-H "Authorization: Bearer $STORY0_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: overview-jogging-1" \
-d '{
"input": { "url": "https://dev.story0.com/demo/park-dog-demo.mp4" },
"mode": "extract",
"instruction": "Moments where a person is jogging",
"billing": { "max_charge": { "currency": "USD", "amount": "0.25" } }
}'The SDK helpers wait for the job and return the result. The cURL call returns the job with 202 Accepted; Jobs and results shows how to wait for it and fetch the result.
Live and test keys
| Live key | Test key | |
|---|---|---|
| Prefix | s0_live_ |
s0_test_ |
| Results | Analysis of your footage | Deterministic labeled fixtures, marked fixture: true |
| Billing | Charged in credits | Never billed |
Both kinds of key use the same base URL and endpoints; the key alone decides the mode. Build and test with a test key, then switch the key. See Test mode.
Conventions
- Base URL:
https://api.story0.com. Every endpoint is under/v1. - Authentication: send
Authorization: Bearer <key>with a key from the console. Keys belong on servers only. - Version: the current API version is
2026-11-01. Every response carries aStory0-Version: 2026-11-01header, and the SDKs send the same header on every request. Within a version, changes are additive: new fields can appear, so ignore fields you don't recognize. - JSON: request bodies are JSON, sent with
Content-Type: application/json. - Time: seconds from the start of the video, as numbers with up to three decimal places. Results say so with
time_unit: "seconds". - Money: decimal strings with a currency, such as
{ "currency": "USD", "amount": "0.50" }. Only USD is supported. - IDs: prefixed strings such as
asset_…,upl_…,job_…,seg_…,scr_…,whe_…andevt_…. Treat them as opaque. - Lists: newest first, with
limit,cursor,has_moreandnext_cursor. - Errors: RFC 9457 problem documents with a stable
code. See Errors. - Request IDs: every response has an
x-request-idheader. Log it next to your own request logs.
Results are made to be checked
Every result says where it's unsure. Partition timelines mark moments without enough evidence as uncertain; screenings answer uncertain instead of guessing; every interval and category carries a review object with required and reasons; and evidence points at the frames behind a label so a reviewer can jump straight to them. Screening scores are uncalibrated estimates, not probabilities.
Use results to route, prioritize and prepare work for people. Editorial, licensing and safety decisions stay with your team, and review.required: false is not a clearance for any of them.
Where to go next
- Quickstart: from a new key to a drawn timeline in about five minutes.
- Uploading media: resumable uploads, URL inputs, media facts and retention.
- Segmentation:
partitionandextract, sampling, ranges, constraints and writing labels. - Screening: fast triage across up to 20 categories.
- Webhooks: events, signatures, retries and reconciliation.
- Jobs and results: the lifecycle, waiting, cancelling and recovering after a disconnect.
- Idempotency and retries: retrying without running or paying for work twice.
- Pricing and billing: prices, credits, estimates, caps and receipts.
- Test mode: free, deterministic fixtures for development and CI.
- API reference: every endpoint and schema.
- Errors: every error code and what to do about it.
- Limits and retention: sizes, rates and how long things are kept.
- SDKs: the TypeScript and Python libraries.
The machine-readable contract is the OpenAPI 3.1 document at https://api.story0.com/v1/openapi.json. The TypeScript SDK's types are generated from it.