Reference
SDKs
Install and configure the TypeScript and Python libraries, and find every method, helper and error class they provide.
Story0 has official SDKs for TypeScript and JavaScript (@story0/sdk) and for Python (story0). Both cover the whole API, add helpers for resumable uploads, waiting and webhook verification, retry safely with idempotency keys, and raise typed errors. They follow the same design, so a method you know in one has the same name in the other: camelCase in TypeScript, snake_case in Python.
Install
npm install https://dev.story0.com/sdk/story0-sdk-0.1.0.tgzpip install https://dev.story0.com/sdk/story0-0.1.0-py3-none-any.whl| TypeScript | Python | |
|---|---|---|
| Package | @story0/sdk |
story0 |
| Version | 0.1.0 | 0.1.0 |
| Runtime | Node.js 18 or later; other runtimes with fetch and Web Crypto work, except for uploading by file path |
Python 3.9 or later |
| Modules | ES modules and CommonJS | Synchronous Story0 and asynchronous AsyncStory0 clients |
| Return values | Plain objects, typed from the OpenAPI document | Plain dicts, described by TypedDict classes in story0.types |
Create a client
import Story0 from "@story0/sdk";
const story0 = new Story0({
apiKey: process.env.STORY0_API_KEY, // the default
timeoutMs: 30_000,
maxRetries: 3,
});import os
from story0 import Story0
client = Story0(
api_key=os.environ["STORY0_API_KEY"], # the default
timeout=30,
max_retries=3,
)| TypeScript | Python | Default | Description |
|---|---|---|---|
apiKey |
api_key |
The STORY0_API_KEY environment variable |
Your API key. A live or test key decides the mode. |
baseUrl |
base_url |
STORY0_BASE_URL, then https://api.story0.com |
The API's base URL. |
timeoutMs |
timeout |
60 seconds (60000 ms; 60.0 s) |
The network timeout for each request. |
maxRetries |
max_retries |
2 | Automatic retries for network errors, 429 and 5xx. |
fetch |
http_client |
The global fetch; a new httpx.Client |
A custom transport, for proxies, custom TLS or tests. |
Creating a client without a key raises an error with the code missing_api_key. Both clients send Authorization, Story0-Version: 2026-11-01 and an SDK identifier with every request.
The Python client can be used as a context manager (with Story0() as client:) so its HTTP connections are closed when you're done; client.close() does the same. AsyncStory0 takes the same arguments, with an httpx.AsyncClient for http_client, and every method is a coroutine:
import asyncio
from story0 import AsyncStory0
async def main() -> None:
async with AsyncStory0() as client:
asset = await client.assets.upload_and_wait("clip.mp4")
result = await client.segments.create_and_wait(
input={"asset_id": asset["id"]},
mode="extract",
instruction="Moments where a dog is visible",
)
print(len(result["segments"]))
asyncio.run(main())Methods
| Task | TypeScript | Python | HTTP |
|---|---|---|---|
| Upload a file | assets.upload({ file }) |
assets.upload(file) |
POST /v1/uploads, the bytes, POST /v1/uploads/{id}/complete |
| Upload and wait until ready | assets.uploadAndWait({ file }) |
assets.upload_and_wait(file) |
The above, then GET /v1/assets/{id} |
| Open an upload session | uploads.create() |
uploads.create() |
POST /v1/uploads |
| Complete an upload | uploads.complete(id) |
uploads.complete(id) |
POST /v1/uploads/{id}/complete |
| Create an asset from a URL | assets.create({ url }) |
assets.create(url) |
POST /v1/assets |
| Retrieve an asset | assets.retrieve(id) |
assets.retrieve(id) |
GET /v1/assets/{id} |
| List assets | assets.list({ limit, cursor }) |
assets.list(limit=, cursor=) |
GET /v1/assets |
| Delete an asset's media | assets.delete(id) |
assets.delete(id) |
DELETE /v1/assets/{id} |
| Wait for an asset | assets.wait(id) |
assets.wait(id) |
Polls GET /v1/assets/{id} |
| Request a segmentation | segments.create(params) |
segments.create(**params) |
POST /v1/segments |
| Segment and wait for the result | segments.createAndWait(params) |
segments.create_and_wait(**params) |
The above, a wait, then GET /v1/segments/{id} |
| Retrieve a segmentation | segments.retrieve(id) |
segments.retrieve(id) |
GET /v1/segments/{id} |
| Request a screening | screenings.create(params) |
screenings.create(**params) |
POST /v1/screenings |
| Screen and wait for the result | screenings.createAndWait(params) |
screenings.create_and_wait(**params) |
The above, a wait, then GET /v1/screenings/{id} |
| Retrieve a screening | screenings.retrieve(id) |
screenings.retrieve(id) |
GET /v1/screenings/{id} |
| Retrieve a job | jobs.retrieve(id, { wait }) |
jobs.retrieve(id, wait=) |
GET /v1/jobs/{id} |
| List jobs | jobs.list({ status, limit, cursor }) |
jobs.list(status=, limit=, cursor=) |
GET /v1/jobs |
| Wait for a job | jobs.wait(id) |
jobs.wait(id) |
Long-polls GET /v1/jobs/{id} |
| Cancel a job | jobs.cancel(id) |
jobs.cancel(id) |
POST /v1/jobs/{id}/cancel |
| Estimate a charge | estimates.create({ operation, input }) |
estimates.create(operation=, input=) |
POST /v1/estimates |
| Retrieve the balance | balance.retrieve() |
balance.retrieve() |
GET /v1/balance |
| Summarize usage | usage.retrieve({ days }) |
usage.retrieve(days) |
GET /v1/usage |
| Create a webhook endpoint | webhookEndpoints.create({ url, events }) |
webhook_endpoints.create(url, events=) |
POST /v1/webhook_endpoints |
| List webhook endpoints | webhookEndpoints.list() |
webhook_endpoints.list() |
GET /v1/webhook_endpoints |
| Disable a webhook endpoint | webhookEndpoints.delete(id) |
webhook_endpoints.delete(id) |
DELETE /v1/webhook_endpoints/{id} |
| Send a test event | webhookEndpoints.test(id) |
webhook_endpoints.test(id) |
POST /v1/webhook_endpoints/{id}/test |
| List recent deliveries | webhookEndpoints.deliveries(id) |
webhook_endpoints.deliveries(id) |
GET /v1/webhook_endpoints/{id}/deliveries |
| List events | events.list({ type, limit, cursor }) |
events.list(type=, limit=, cursor=) |
GET /v1/events |
| Record reviewer feedback | feedback.create(params) |
feedback.create(**params) |
POST /v1/feedback |
| Verify a webhook delivery | Story0.webhooks.verify(body, headers, secret) |
story0.verify_webhook(body, headers, secret) |
None: runs locally |
In TypeScript, request parameters are a plain object matching the JSON body, and per-call options go in a second argument: idempotencyKey, signal (an AbortSignal) and timeoutMs, plus waitTimeoutMs, pollIntervalMs and onProgress on the waiting helpers. In Python, everything is a keyword argument: the body fields, plus idempotency_key, timeout, and wait_timeout, poll_interval and on_progress on the waiting helpers. Python's create methods also accept extra_body, a dictionary merged into the JSON body, for fields added to the API after your SDK version.
Uploads
assets.upload and assets.uploadAndWait open an upload session, send the bytes and complete the upload, so you never handle the protocol yourself.
| TypeScript | Python | |
|---|---|---|
| Sources | A file path (Node), Blob or File, ArrayBuffer, Uint8Array, ReadableStream, or an async iterable of Uint8Array |
A path (str or pathlib.Path), bytes, a binary file object, or an iterable of bytes |
| Options | filename, contentType (default video/mp4), chunkSize, onProgress(sent, total) |
filename, content_type (default video/mp4), chunk_size, on_progress(sent, total) |
Files are sent in 8 MiB chunks by default, rounded down to a multiple of 256 KiB, and no more than a chunk or two is held in memory. Each chunk is tried up to four times. For a source that can be read again, such as a path, a buffer or a seekable file, a retry first asks the session how much it stored and resumes from there; a one-shot stream retries the chunk it holds. Upload requests go to the session's own URL with only the session's headers: the SDKs never send your API key there. For the protocol itself, see Uploading media.
Waiting
| Helper | Returns | Raises | Default limit |
|---|---|---|---|
segments.createAndWait, screenings.createAndWait (create_and_wait) |
The segmentation or screening | JobFailedError, JobCancelledError, WaitTimeoutError |
10 minutes |
jobs.wait(id) |
The job in its final state, whatever that is | WaitTimeoutError |
10 minutes |
assets.uploadAndWait, assets.wait(id) (upload_and_wait) |
The ready asset |
A Story0Error with the asset's error code if it failed, or wait_timeout |
5 minutes |
The job helpers long-poll, holding each request for up to 20 seconds, or 2 seconds when you pass a progress callback so that it sees every stage. They never hold one connection open for the whole wait. Change the limit with waitTimeoutMs in TypeScript or wait_timeout (in seconds) in Python.
Stopping a wait stops only your local waiting: aborting a TypeScript signal, or the wait timing out, leaves the job running. WaitTimeoutError carries the job's ID (jobId, job_id), so you can resume with jobs.wait instead of submitting again. To stop the work itself, call jobs.cancel. See Jobs and results.
Retries and idempotency
The SDKs retry network errors, 429 and 5xx responses up to maxRetries (max_retries) times, with exponential backoff and jitter (about 0.5, 1 and 2 seconds, up to 8), and they honor a Retry-After header of up to 20 seconds. They only retry reads and requests that carry an idempotency key.
Processing requests, upload sessions and completions, asset and webhook-endpoint creation, cancellations and test events all get an idempotency key automatically, a random UUID unless you pass idempotencyKey or idempotency_key, and the key stays the same across that call's retries. Feedback is sent with a key, and retried, only when you pass one; the API then records it once. Pass your own keys for processing requests whenever a retry might come from a new process. See Idempotency and retries.
Errors
Every error the SDKs raise extends Story0Error and carries status (0 when no response arrived), code, the request ID (requestId, request_id), detail and raw, the full problem document. Branch on the class or on code, never on the message.
| Situation | TypeScript | Python |
|---|---|---|
| 401 | AuthenticationError |
AuthenticationError |
| 403 | PermissionError |
PermissionDeniedError |
| 404 or 410 | NotFoundError |
NotFoundError |
| 400 or 422 | InvalidRequestError, with errors |
InvalidRequestError, with errors |
402 insufficient_credits |
InsufficientCreditsError |
InsufficientCreditsError |
402 charge_cap_exceeded |
ChargeCapExceededError |
ChargeCapExceededError |
| 429 | RateLimitError |
RateLimitError |
| 5xx | APIError |
APIError |
| No response | ConnectionError |
APIConnectionError |
| A waited-for job failed | JobFailedError, with job |
JobFailedError, with job |
| A waited-for job was cancelled | JobCancelledError, with job |
JobCancelledError, with job |
| A job wait ran out of time | WaitTimeoutError, with jobId |
WaitTimeoutError, with job_id |
| A webhook failed verification | WebhookVerificationError |
WebhookVerificationError |
API error codes are listed in Errors. These codes come from the SDKs themselves:
| Code | Raised when |
|---|---|
missing_api_key |
No key was passed and STORY0_API_KEY isn't set. |
timeout, connection_error |
No response arrived (ConnectionError or APIConnectionError). |
upload_failed |
The upload session refused a chunk. |
upload_connection_failed |
The network failed while sending upload bytes. |
wait_timeout |
A waiting helper ran out of time. |
job_cancelled |
The job a createAndWait call was waiting for was cancelled. |
result_missing |
A job succeeded without a result reference. This shouldn't happen; report the job ID if it does. |
webhook_headers_missing, webhook_timestamp_invalid, webhook_signature_invalid |
Webhook verification failed. |
Webhook verification
import Story0 from "@story0/sdk";
const event = await Story0.webhooks.verify(rawBody, request.headers, process.env.STORY0_WEBHOOK_SECRET!);import os
import story0
event = story0.verify_webhook(raw_body, request.headers, os.environ["STORY0_WEBHOOK_SECRET"])Pass the raw body exactly as it arrived, the request headers and the endpoint's whsec_… secret. Both helpers check the webhook-id, webhook-timestamp and webhook-signature headers, reject timestamps more than 5 minutes from now (change it with { toleranceSeconds } or tolerance=), and return the event as { id, type, timestamp, data }. TypeScript also exports the helper as verifyWebhook, and it's available on a client instance as story0.webhooks.verify. Python also offers Story0.webhooks.verify, and story0.sign_webhook(secret, msg_id, timestamp, payload) for building signed test deliveries for your own handler. See Webhooks.
Types
The TypeScript package exports types for every object, including Asset, Upload, Job, JobStatus, Segmentation, Segment, Screening, ScreeningCategory, Estimate, Balance, Usage, WebhookEndpoint, WebhookDelivery, Event, WebhookEvent, Problem, SegmentCreateParams and ScreenCreateParams. They're generated from the OpenAPI document.
The Python package describes the same shapes as TypedDict classes in story0.types, for editors and type checkers; at run time every value is a plain dict or list. Within an API version, fields can be added, so write code that tolerates keys it doesn't know.
Keep keys on the server
The SDKs are for servers, scripts, workers and command-line tools. Never ship an API key to a browser or a mobile app: anyone who has it can spend your credits and read your results. Keep it in an environment variable or a secret manager, and pass results to your front end through your own API.