API reference
Every v1 endpoint, generated from the OpenAPI 3.1 spec. Read on the left, run live against your account on the right. Base URL https://postreef.com.
/v1/extractionsSubmit an extraction
Starts an extraction for a public URL: a video, a photo post/carousel, or an ordinary article/webpage (webpage runs are priced flat and read the page text and images; photo posts/carousels are priced flat and per-slide — their reported "duration" is a background track, never billed). Credits are debited up-front (a flat base fee plus download rates for the requested artifacts, plus per-second AI rates when a schema is set; use `POST /v1/probe` for the exact quote first) and refunded in full if the extraction fails. When the duration can't be determined up-front, a worst-case duration of 300s is debited and the difference is refunded on completion; the debit is never exceeded. Videos longer than 60 minutes are rejected before any debit. If an identical extraction (same URL, schema and inputs) completed within the last 30 days, the result is reused: the response has `cached: true` semantics on subsequent reads and `creditsDebited: 0`. Pass `force: true` to skip the cache and re-run. Rate limit: 6 requests per minute. At most 2 extractions run per account at once. Submissions past that limit are accepted with status `queued` (credits debited up-front) and dispatched automatically when a slot frees.
Parameters
| Field | Type | Description |
|---|---|---|
| Idempotency-Key (header) | string | Any unique string (e.g. a UUID). Retrying a submit with the same key and the same request body returns the original extraction with status 200 instead of creating a new one. Reusing a key with a *different* body returns 409 conflict. |
Request body
| Field | Type | Description |
|---|---|---|
| urlrequired | string | Public URL: a video (YouTube, Shorts, TikTok, Instagram Reels, …), a photo post or carousel, or an ordinary article/webpage. |
| inputs | "transcript" | "comments" | "audio" | "video"[] | Which modalities feed the AI extraction. Requires `schema` or `schemaId` — without one there is no AI call, and a download-only run selects its artifacts with `parts`. Defaults to ["transcript","comments"]. Determines the price. |
| schemaId | string | Id of a predefined schema (e.g. `postreef.predefined.recipe.v1`), one of your saved schemas, or `auto` to skip picking a schema entirely — the AI extracts whatever structured information is actually there, and the output is compiled into a new schema saved to your library. Mutually exclusive with `schema`. |
| schema | object | Inline JSON Schema (max 100KB) describing the structured object you want back. Omit both `schema` and `schemaId` for a download-only run (no AI, no per-second AI charge). |
| prompt | string | Extraction guidance sent to the model alongside the schema. It steers how the video is read into the schema (source priority, what to omit, common pitfalls). For a predefined `schemaId` this overrides that schema's built-in prompt; for an inline `schema` it's the prompt that pairs with it. Max 20KB. Part of the cache key, so an edited prompt re-runs instead of reusing a cached result. Ignored on download-only runs. |
| auto | boolean | Auto mode: start with the cheapest inputs (text), judge the result, and only climb to audio/video if quality is below threshold. `inputs` becomes the ceiling the climb may reach. Requires `maxSpendCredits`. Charged the ceiling at submit, refunded down to the rung actually used. Auto runs reuse a recent result on the same URL, schema and model when its inputs fit inside your `inputs` ceiling (free, `creditsDebited: 0`); pass `force: true` to re-run instead. |
| force | boolean | Skip the result cache and run the full pipeline even when an identical extraction from the last 30 days exists. The run is billed as fresh. Default false. |
| maxSpendCredits | number | Spend ceiling for an auto run, in credits. Required when `auto` is true. Must be at least the cost of the cheapest rung. |
| policy | "strict" | "fallback" | "best-effort" | What to do when a requested input isn't available for this video (e.g. no comments): `strict` fails the run, `fallback` (default) proceeds with reduced inputs, `best-effort` silently drops the missing input. |
| parts | "transcript" | "comments" | "audio" | "video"[] | Download-only runs (no schema): which artifacts to fetch. Omit for everything. Metadata, thumbnail and description are always included. Invalid alongside `schema`/`schemaId`. |
| webhookUrl | string | Per-run webhook override: an https URL (public hosts only) that receives `extraction.completed` / `extraction.failed` for this run in addition to your registered endpoints. Signed with your first registered endpoint's secret when one exists; unsigned otherwise. |
{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"inputs": [
"transcript",
"comments"
],
"schemaId": "postreef.predefined.recipe.v1",
"webhookUrl": "https://example.com/hooks/postreef"
}Response fields
| Field | Type | Description |
|---|---|---|
| idrequired | string | Extraction id, used for polling, results and files. |
| statusrequired | "queued" | "pending" | "running" | "complete" | "failed" | |
| urlrequired | string | |
| creditsDebitedrequired | integer | Credits debited up-front. 0 when the result was served from a recent identical extraction. |
| createdAtrequired | string |
Responses
200 Idempotent replay: the Idempotency-Key matched a previous submit with the same body, so the original extraction is returned.
201 Extraction created and started.
{
"id": "run_8f3a2b1c",
"status": "pending",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"creditsDebited": 127,
"createdAt": "2026-06-11T10:15:00.000Z"
}400 Invalid request: missing/invalid url (`invalid_url`), unsupported platform (`unsupported_url`), invalid options or webhookUrl (`invalid_request`).
401 Missing or invalid API key.
402 Insufficient credits (`insufficient_credits`). `details.balance` and `details.cost` carry your current balance and the quoted cost in credits.
404 Referenced schemaId not found.
409 Idempotency-Key was already used with a different request body (`conflict`).
429 `rate_limited` (per-minute window exceeded, includes Retry-After).
/v1/extractionsList extractions
Lists your extractions, newest first, with cursor pagination. Pass `next_cursor` from a previous page as `cursor` to fetch the next page.
Parameters
| Field | Type | Description |
|---|---|---|
| limit (query) | integer | Page size, 1–100. |
| cursor (query) | string | Opaque cursor from the previous page's `next_cursor`. Do not construct it yourself. |
Response fields
| Field | Type | Description |
|---|---|---|
| datarequired | Extraction[] | |
| has_morerequired | boolean | |
| next_cursorrequired | string | null | Pass as `cursor` to fetch the next page. Null on the last page. |
Responses
200 One page of extractions.
{
"data": [
{
"id": "run_8f3a2b1c",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"status": "complete",
"createdAt": "2026-06-11T10:15:00.000Z",
"completedAt": "2026-06-11T10:16:42.000Z",
"creditsDebited": 127,
"cached": false,
"outcome": "ok"
}
],
"has_more": false,
"next_cursor": null
}400 Invalid limit or cursor.
401 Missing or invalid API key.
429 Rate limit exceeded.
/v1/extractSubmit an extraction (alias)
Exact alias of `POST /v1/extractions`, with the same request, responses, rate limit and Idempotency-Key support.
Request body
| Field | Type | Description |
|---|---|---|
| urlrequired | string | Public URL: a video (YouTube, Shorts, TikTok, Instagram Reels, …), a photo post or carousel, or an ordinary article/webpage. |
| inputs | "transcript" | "comments" | "audio" | "video"[] | Which modalities feed the AI extraction. Requires `schema` or `schemaId` — without one there is no AI call, and a download-only run selects its artifacts with `parts`. Defaults to ["transcript","comments"]. Determines the price. |
| schemaId | string | Id of a predefined schema (e.g. `postreef.predefined.recipe.v1`), one of your saved schemas, or `auto` to skip picking a schema entirely — the AI extracts whatever structured information is actually there, and the output is compiled into a new schema saved to your library. Mutually exclusive with `schema`. |
| schema | object | Inline JSON Schema (max 100KB) describing the structured object you want back. Omit both `schema` and `schemaId` for a download-only run (no AI, no per-second AI charge). |
| prompt | string | Extraction guidance sent to the model alongside the schema. It steers how the video is read into the schema (source priority, what to omit, common pitfalls). For a predefined `schemaId` this overrides that schema's built-in prompt; for an inline `schema` it's the prompt that pairs with it. Max 20KB. Part of the cache key, so an edited prompt re-runs instead of reusing a cached result. Ignored on download-only runs. |
| auto | boolean | Auto mode: start with the cheapest inputs (text), judge the result, and only climb to audio/video if quality is below threshold. `inputs` becomes the ceiling the climb may reach. Requires `maxSpendCredits`. Charged the ceiling at submit, refunded down to the rung actually used. Auto runs reuse a recent result on the same URL, schema and model when its inputs fit inside your `inputs` ceiling (free, `creditsDebited: 0`); pass `force: true` to re-run instead. |
| force | boolean | Skip the result cache and run the full pipeline even when an identical extraction from the last 30 days exists. The run is billed as fresh. Default false. |
| maxSpendCredits | number | Spend ceiling for an auto run, in credits. Required when `auto` is true. Must be at least the cost of the cheapest rung. |
| policy | "strict" | "fallback" | "best-effort" | What to do when a requested input isn't available for this video (e.g. no comments): `strict` fails the run, `fallback` (default) proceeds with reduced inputs, `best-effort` silently drops the missing input. |
| parts | "transcript" | "comments" | "audio" | "video"[] | Download-only runs (no schema): which artifacts to fetch. Omit for everything. Metadata, thumbnail and description are always included. Invalid alongside `schema`/`schemaId`. |
| webhookUrl | string | Per-run webhook override: an https URL (public hosts only) that receives `extraction.completed` / `extraction.failed` for this run in addition to your registered endpoints. Signed with your first registered endpoint's secret when one exists; unsigned otherwise. |
{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"inputs": [
"transcript",
"comments"
],
"schemaId": "postreef.predefined.recipe.v1",
"webhookUrl": "https://example.com/hooks/postreef"
}Response fields
| Field | Type | Description |
|---|---|---|
| idrequired | string | Extraction id, used for polling, results and files. |
| statusrequired | "queued" | "pending" | "running" | "complete" | "failed" | |
| urlrequired | string | |
| creditsDebitedrequired | integer | Credits debited up-front. 0 when the result was served from a recent identical extraction. |
| createdAtrequired | string |
Responses
201 Extraction created and started.
/v1/extractions/{id}Get an extraction
Returns the current state of one extraction, including a coarse `phase` (`queued`, `processing`, `done`, `failed`). Poll this (or `/result`) until the status is terminal, or skip polling entirely with a webhook.
Parameters
| Field | Type | Description |
|---|---|---|
| id (path)required | string | Extraction id returned at submit time. |
Response fields
| Field | Type | Description |
|---|---|---|
| idrequired | string | |
| urlrequired | string | |
| statusrequired | "queued" | "pending" | "running" | "complete" | "failed" | |
| createdAtrequired | string | |
| completedAtrequired | string | null | |
| creditsDebitedrequired | integer | |
| cachedrequired | boolean | True when the result was reused from a recent identical extraction (no credits were debited). |
| outcome | "ok" | "no_match" | "uncertain" | "null" | Content-match verdict for an AI run. `ok` means the content matched the schema and `extraction` holds the data. `no_match` means the content is about something else, with a null `extraction`. `uncertain` means the inputs were too sparse to decide, also with a null `extraction`. Null on download-only runs and runs from before this field existed. |
| verdictReason | string | One-sentence explanation of the content-match verdict, present only when `outcome` is `no_match` or `uncertain`. |
| error | string | Present only when status is `failed`. |
| phaserequired | "queued" | "processing" | "done" | "failed" | Coarse progress phase derived from status. |
Responses
200 The extraction.
{
"id": "run_8f3a2b1c",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"status": "complete",
"createdAt": "2026-06-11T10:15:00.000Z",
"completedAt": "2026-06-11T10:16:42.000Z",
"creditsDebited": 127,
"cached": false,
"outcome": "ok",
"phase": "done"
}401 Missing or invalid API key.
404 No extraction with this id belongs to you.
429 Rate limit exceeded.
/v1/extractions/{id}/resultGet an extraction's result
Returns the final result. While the extraction is still queued or processing, responds **202** with `{ "status": "queued" | "processing" }` and keep polling. Once complete, responds 200 with the summary (metadata + available file names), `artifacts` (the same files as ready-to-fetch URLs), and, when an AI schema was supplied, the structured `extraction` object conforming to it. A failed extraction responds 200 with `status: "failed"` and a human-readable `error`.
Parameters
| Field | Type | Description |
|---|---|---|
| id (path)required | string |
Responses
200 Terminal result (complete or failed).
{
"id": "run_8f3a2b1c",
"status": "complete",
"summary": {
"title": "Weeknight pasta in 15 minutes",
"uploader": "@kitchen",
"durationSeconds": 212,
"files": [
"video.mp4",
"transcript.txt",
"comments.json"
]
},
"artifacts": {
"video": "https://postreef.com/v1/extractions/run_8f3a2b1c/files/video.mp4",
"transcript": "https://postreef.com/v1/extractions/run_8f3a2b1c/files/transcript.txt",
"comments": "https://postreef.com/v1/extractions/run_8f3a2b1c/files/comments.json",
"images": [],
"files": {
"video.mp4": "https://postreef.com/v1/extractions/run_8f3a2b1c/files/video.mp4",
"transcript.txt": "https://postreef.com/v1/extractions/run_8f3a2b1c/files/transcript.txt",
"comments.json": "https://postreef.com/v1/extractions/run_8f3a2b1c/files/comments.json"
}
},
"extraction": {
"name": "Weeknight pasta",
"servings": 2
}
}202 Still running. The body is `{ "status": "queued" | "processing" }`.
{
"status": "processing"
}401 Missing or invalid API key.
404 No extraction with this id belongs to you.
429 Rate limit exceeded.
/v1/extractions/{id}/files/{name}Download an extraction artifact
Streams one artifact produced by a completed extraction: `video.mp4`, `audio.m4a`, `transcript.txt` (the page text, for webpage runs), `comments.json`, `thumbnail.jpg`, `description.txt`, subtitle files, or the numbered `image.NN.jpg` slides of a photo post or carousel. The available names are listed in the result's `summary.files`, and the result's `artifacts` object gives you the full URL directly. Accepts either an API key or, if you're signed in to the web app, your browser session — so an `artifacts` URL also opens directly in a tab.
Parameters
| Field | Type | Description |
|---|---|---|
| id (path)required | string | |
| name (path)required | string | File name exactly as listed in `summary.files` or `artifacts.files`. |
Responses
200 The file bytes; `Content-Type` matches the artifact (video/mp4, text/plain, application/json, …).
400 Invalid file name.
401 Missing or invalid API key.
404 Extraction or file not found.
429 Rate limit exceeded.
/v1/probeProbe a URL and quote the price
Inspects a URL without starting an extraction: returns the title, duration, whether subtitles/comments exist, and the exact credit price for the run you intend to submit (a URL that resolves to an article/webpage is quoted at the flat webpage price, with `durationSec: null`). This is the canonical quote: send the same body you will submit (a schema when you want an AI extraction, `parts` when you want a download) and `POST /v1/extractions` debits exactly this amount (or the worst-case ceiling when duration is unknown). Rate limit: 10 requests per minute. Probing is free.
Request body
| Field | Type | Description |
|---|---|---|
| urlrequired | string | |
| schema | object | Quote an AI extraction. Only its presence matters — the AI rates are per-second and schema-independent, so the quote never validates it. Send the schema you intend to submit, or any object. |
| schemaId | string | Alternative to `schema` for signalling an AI extraction quote. Not resolved by the probe. |
| inputs | "transcript" | "comments" | "audio" | "video"[] | Inputs you intend to submit with. Requires `schema` or `schemaId` — without one there is no AI call to bill. Defaults to ["transcript","comments"]. Mutually exclusive with `parts`. |
| parts | "transcript" | "comments" | "audio" | "video"[] | Artifacts a download-only run should fetch: base fee + download rates, no AI component. Defaults to all four. Mutually exclusive with `inputs` and with a schema. |
{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"schemaId": "postreef.predefined.recipe.v1",
"inputs": [
"transcript",
"comments"
]
}Response fields
| Field | Type | Description |
|---|---|---|
| urlrequired | string | |
| durationSecrequired | number | null | Null when the platform hides the duration. Submitting then debits the 300s worst case, refunded down to actual on completion. |
| titlerequired | string | null | |
| hasSubtitlesrequired | boolean | |
| hasCommentsrequired | boolean | |
| pricerequired | object |
Responses
200 Probe result with price quote.
{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"durationSec": 212,
"title": "Weeknight pasta in 15 minutes",
"hasSubtitles": true,
"hasComments": true,
"price": {
"mode": "ai",
"credits": 186,
"usd": 0.0186,
"baseUsd": 0.005,
"downloadUsd": 0.003,
"downloadBreakdown": {
"transcript": 0.001,
"comments": 0.002
},
"breakdown": {
"text": 0.0106,
"audio": 0,
"video": 0
}
}
}400 Invalid url, unsupported platform, or bad inputs.
401 Missing or invalid API key.
422 The content itself is unavailable: private, removed, or login-gated (`content_unavailable`). Permanent — don't retry.
429 Rate limit exceeded.
502 The content could not be probed right now.
/v1/openapi.jsonThis document
Returns this OpenAPI 3.1 document. Public, no authentication required. Import it into Postman, Insomnia, or your codegen of choice.
Responses
200 The OpenAPI 3.1 document.
Code snippet
curl -X POST https://postreef.com/v1/extractions \
-H "x-api-key: pr_••••••••••••••••" \
-H "Content-Type: application/json" \
-d '{
"url": "<video url>"
}'