{"openapi":"3.1.0","info":{"title":"Post Reef API","version":"1.0.0","description":"Turn any public URL — a social-media video (YouTube, Shorts, TikTok, Instagram Reels and more), a photo post or carousel, or an ordinary article/webpage — into structured data: metadata, transcript, comments, images, downloadable media, plus an optional typed JSON extraction conforming to a JSON Schema you provide.\n\nAuthenticate with an API key (`x-api-key` header, or `Authorization: Bearer <key>`). Submit an extraction, then either poll for the result or receive a webhook when it completes."},"servers":[{"url":"https://postreef.com"}],"security":[{"apiKey":[]},{"bearerAuth":[]}],"tags":[{"name":"Extractions","description":"Submit, list and read extractions."},{"name":"Probe","description":"Inspect a URL and get an exact price quote before submitting."}],"paths":{"/v1/extractions":{"post":{"operationId":"createExtraction","summary":"Submit an extraction","tags":["Extractions"],"description":"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.\n\nIf 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.\n\nRate 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":[{"name":"Idempotency-Key","in":"header","required":false,"description":"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.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitExtractionRequest"},"example":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","inputs":["transcript","comments"],"schemaId":"postreef.predefined.recipe.v1","webhookUrl":"https://example.com/hooks/postreef"}}}},"responses":{"200":{"description":"Idempotent replay: the Idempotency-Key matched a previous submit with the same body, so the original extraction is returned.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitExtractionResponse"}}}},"201":{"description":"Extraction created and started.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitExtractionResponse"},"example":{"id":"run_8f3a2b1c","status":"pending","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","creditsDebited":127,"createdAt":"2026-06-11T10:15:00.000Z"}}}},"400":{"description":"Invalid request: missing/invalid url (`invalid_url`), unsupported platform (`unsupported_url`), invalid options or webhookUrl (`invalid_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"Insufficient credits (`insufficient_credits`). `details.balance` and `details.cost` carry your current balance and the quoted cost in credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Referenced schemaId not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Idempotency-Key was already used with a different request body (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"`rate_limited` (per-minute window exceeded, includes Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"operationId":"listExtractions","summary":"List extractions","tags":["Extractions"],"description":"Lists your extractions, newest first, with cursor pagination. Pass `next_cursor` from a previous page as `cursor` to fetch the next page.","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size, 1–100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous page's `next_cursor`. Do not construct it yourself.","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of extractions.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionList"},"example":{"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":{"description":"Invalid limit or cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/extract":{"post":{"operationId":"createExtractionAlias","summary":"Submit an extraction (alias)","tags":["Extractions"],"description":"Exact alias of `POST /v1/extractions`, with the same request, responses, rate limit and Idempotency-Key support.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitExtractionRequest"},"example":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","inputs":["transcript","comments"],"schemaId":"postreef.predefined.recipe.v1","webhookUrl":"https://example.com/hooks/postreef"}}}},"responses":{"201":{"description":"Extraction created and started.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitExtractionResponse"}}}}}}},"/v1/extractions/{id}":{"get":{"operationId":"getExtraction","summary":"Get an extraction","tags":["Extractions"],"description":"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":[{"name":"id","in":"path","required":true,"description":"Extraction id returned at submit time.","schema":{"type":"string"}}],"responses":{"200":{"description":"The extraction.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionDetail"},"example":{"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":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"No extraction with this id belongs to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/extractions/{id}/result":{"get":{"operationId":"getExtractionResult","summary":"Get an extraction's result","tags":["Extractions"],"description":"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":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Terminal result (complete or failed).","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionResult"},"example":{"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":{"description":"Still running. The body is `{ \"status\": \"queued\" | \"processing\" }`.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionPending"},"example":{"status":"processing"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"No extraction with this id belongs to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/extractions/{id}/files/{name}":{"get":{"operationId":"downloadExtractionFile","summary":"Download an extraction artifact","tags":["Extractions"],"description":"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":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"path","required":true,"description":"File name exactly as listed in `summary.files` or `artifacts.files`.","schema":{"type":"string"}}],"responses":{"200":{"description":"The file bytes; `Content-Type` matches the artifact (video/mp4, text/plain, application/json, …).","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid file name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Extraction or file not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/probe":{"post":{"operationId":"probeUrl","summary":"Probe a URL and quote the price","tags":["Probe"],"description":"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.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProbeRequest"},"example":{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","schemaId":"postreef.predefined.recipe.v1","inputs":["transcript","comments"]}}}},"responses":{"200":{"description":"Probe result with price quote.","headers":{"X-RateLimit-Limit":{"description":"Request ceiling for the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProbeResponse"},"example":{"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":{"description":"Invalid url, unsupported platform, or bad inputs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"The content itself is unavailable: private, removed, or login-gated (`content_unavailable`). Permanent — don't retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"502":{"description":"The content could not be probed right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenApiSpec","summary":"This document","tags":["Probe"],"description":"Returns this OpenAPI 3.1 document. Public, no authentication required. Import it into Postman, Insomnia, or your codegen of choice.","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"webhooks":{"extraction.completed":{"post":{"operationId":"webhookExtractionCompleted","summary":"Extraction completed","description":"Sent to your registered webhook endpoints (and any per-run `webhookUrl`) when an extraction finishes successfully. The body matches `GET /v1/extractions/{id}/result`. Respond with any 2xx within 10 seconds; failed deliveries are retried after 30s, 2m and 10m. Verify the `X-Postreef-Signature` header; see the webhooks documentation.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookExtractionCompleted"}}}},"responses":{"200":{"description":"Return any 2xx to acknowledge receipt."}}}},"extraction.failed":{"post":{"operationId":"webhookExtractionFailed","summary":"Extraction failed","description":"Sent when an extraction fails. Debited credits are refunded in full before this event fires.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookExtractionFailed"}}}},"responses":{"200":{"description":"Return any 2xx to acknowledge receipt."}}}},"test":{"post":{"operationId":"webhookTest","summary":"Test event","description":"Sent on demand from the dashboard to verify an endpoint. Signed and delivered exactly like real events.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestEvent"}}}},"responses":{"200":{"description":"Return any 2xx to acknowledge receipt."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Your API key, created in the dashboard."},"bearerAuth":{"type":"http","scheme":"bearer","description":"The same API key, sent as `Authorization: Bearer <key>`."}},"schemas":{"ErrorEnvelope":{"type":"object","description":"Every non-2xx response carries this envelope.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","invalid_request","invalid_url","unsupported_url","insufficient_credits","content_unavailable","rate_limited","not_found","conflict","internal_error"],"description":"Stable machine-readable error code."},"message":{"type":"string","description":"Human-readable explanation."},"details":{"type":"object","description":"Optional structured context, e.g. `balance` and `cost` on insufficient_credits.","additionalProperties":true}}}}},"ExtractionInput":{"type":"string","enum":["transcript","comments","audio","video"],"description":"An input modality for the AI extraction. `transcript` and `comments` are text; `audio` attaches the audio track; `video` attaches the full video and implies audio, so the separate audio modality is not billed when video is selected."},"SubmitExtractionRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Public URL: a video (YouTube, Shorts, TikTok, Instagram Reels, …), a photo post or carousel, or an ordinary article/webpage."},"inputs":{"type":"array","items":{"$ref":"#/components/schemas/ExtractionInput"},"description":"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."},"maxVideoDurationSec":{"type":"integer","minimum":1,"maximum":3600,"description":"Optional per-request cutoff for video AI input, in seconds. Requires schema/schemaId and at least one non-video input. After its own probe, Post Reef excludes video when the source exceeds this inclusive cutoff; video is then neither downloaded for video analysis nor sent to AI, and auto/fallback cannot restore it. When the probe has no duration (e.g. Instagram), the video is downloaded, measured, and sent to AI only if it is within the cutoff. Explicit audio input remains allowed and may require a media download: omit audio for text-only fallback. Pure photo posts and webpages are unaffected. Omit this field to preserve normal behavior. Does not change the one-hour source cap. Cutoff policies use separate extraction cache entries."},"maxComments":{"type":"integer","minimum":1,"maximum":1000,"default":1000,"description":"Maximum comments to download, including replies. Applies to both AI and download-only runs when comments are selected. Limits pagination where supported; providers may return fewer comments. AI uses up to 30 of the downloaded comments, ranked by likes. Omit comments from inputs/parts to disable them. The flat comments charge is unchanged. Different limits use separate cache entries."},"schemaId":{"type":"string","description":"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":{"type":"object","description":"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).","additionalProperties":true},"prompt":{"type":"string","description":"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":{"type":"boolean","description":"Auto mode: start with the cheapest inputs (text), judge the result, and only climb to audio/video if quality is below threshold. Auto only ever uses inputs listed in `inputs`, so it requires at least one; an empty `inputs` is a 400. 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":{"type":"boolean","description":"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":{"type":"number","description":"Spend ceiling for an auto run, in credits. Required when `auto` is true. Must be at least the cost of the cheapest rung."},"policy":{"type":"string","enum":["strict","fallback","best-effort"],"description":"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":{"type":"array","items":{"$ref":"#/components/schemas/ExtractionInput"},"description":"Download-only runs (no schema): which artifacts to fetch. Omit for everything. Metadata, thumbnail and description are always included. Invalid alongside `schema`/`schemaId`."},"webhookUrl":{"type":"string","format":"uri","description":"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."}}},"SubmitExtractionResponse":{"type":"object","required":["id","status","url","creditsDebited","createdAt"],"properties":{"id":{"type":"string","description":"Extraction id, used for polling, results and files."},"status":{"$ref":"#/components/schemas/ExtractionStatus"},"url":{"type":"string"},"creditsDebited":{"type":"integer","description":"Credits debited up-front. 0 when the result was served from a recent identical extraction."},"createdAt":{"type":"string","format":"date-time"}}},"ExtractionStatus":{"type":"string","enum":["queued","pending","running","complete","failed"]},"Extraction":{"type":"object","required":["id","url","status","createdAt","completedAt","creditsDebited","cached"],"properties":{"id":{"type":"string"},"url":{"type":"string"},"status":{"$ref":"#/components/schemas/ExtractionStatus"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"creditsDebited":{"type":"integer"},"cached":{"type":"boolean","description":"True when the result was reused from a recent identical extraction (no credits were debited)."},"outcome":{"$ref":"#/components/schemas/ExtractionOutcome"},"verdictReason":{"type":"string","description":"One-sentence explanation of the content-match verdict, present only when `outcome` is `no_match` or `uncertain`."},"error":{"type":"string","description":"Present only when status is `failed`."}}},"ExtractionOutcome":{"type":["string","null"],"enum":["ok","no_match","uncertain",null],"description":"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."},"ExtractionDetail":{"allOf":[{"$ref":"#/components/schemas/Extraction"},{"type":"object","required":["phase"],"properties":{"phase":{"type":"string","enum":["queued","processing","done","failed"],"description":"Coarse progress phase derived from status."}}}]},"ExtractionList":{"type":"object","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Extraction"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` to fetch the next page. Null on the last page."}}},"ExtractionSummary":{"type":"object","description":"Metadata and artifact listing for a completed extraction. Fields are null when the platform doesn't expose them.","properties":{"runId":{"type":"string"},"url":{"type":"string"},"title":{"type":["string","null"]},"uploader":{"type":["string","null"]},"duration":{"type":["number","null"],"description":"Seconds, as reported by the platform."},"description":{"type":["string","null"]},"thumbnail":{"type":["string","null"],"description":"Thumbnail URL on the source platform."},"webpage_url":{"type":["string","null"]},"view_count":{"type":["integer","null"]},"like_count":{"type":["integer","null"]},"comment_count":{"type":["integer","null"]},"upload_date":{"type":["string","null"],"description":"YYYYMMDD."},"durationSeconds":{"type":["number","null"],"description":"Measured duration of the downloaded media."},"videoFile":{"type":["string","null"]},"thumbnailFile":{"type":["string","null"]},"subtitleFiles":{"type":"array","items":{"type":"string"}},"audioFile":{"type":["string","null"]},"transcriptFile":{"type":["string","null"]},"commentsFile":{"type":["string","null"]},"descriptionFile":{"type":["string","null"]},"files":{"type":"array","items":{"type":"string"},"description":"Every downloadable artifact. Fetch one via `GET /v1/extractions/{id}/files/{name}`."},"startedAt":{"type":"integer","description":"Unix ms."},"finishedAt":{"type":"integer","description":"Unix ms."}}},"ExtractionArtifacts":{"type":"object","required":["images","files"],"description":"Ready-to-fetch URLs for every downloaded artifact, derived from `summary`. Each URL is `GET /v1/extractions/{id}/files/{name}` and accepts either your `x-api-key` or, if you're signed in to the web app, your browser session — so they also open directly in a tab. Typed fields are absent when that artifact wasn't produced; `images` is `[]` rather than absent.","properties":{"thumbnail":{"type":"string","format":"uri"},"comments":{"type":"string","format":"uri"},"transcript":{"type":"string","format":"uri"},"audio":{"type":"string","format":"uri"},"video":{"type":"string","format":"uri"},"description":{"type":"string","format":"uri"},"images":{"type":"array","items":{"type":"string","format":"uri"},"description":"Photo-post/carousel slide images, in order. Empty for regular videos."},"files":{"type":"object","additionalProperties":{"type":"string","format":"uri"},"description":"Every downloadable artifact by file name, matching `summary.files`."}}},"ExtractionResultComplete":{"type":"object","required":["id","status","summary","artifacts","extraction"],"properties":{"id":{"type":"string"},"status":{"type":"string","const":"complete"},"summary":{"$ref":"#/components/schemas/ExtractionSummary"},"artifacts":{"oneOf":[{"$ref":"#/components/schemas/ExtractionArtifacts"},{"type":"null"}],"description":"Null only when `summary` is also null."},"outcome":{"$ref":"#/components/schemas/ExtractionOutcome"},"verdictReason":{"type":"string","description":"One-sentence explanation of the content-match verdict, present only when `outcome` is `no_match` or `uncertain`."},"extraction":{"type":["object","null"],"description":"The structured object conforming to your JSON Schema. Null for download-only runs, and also null when `outcome` is `no_match` / `uncertain` (the content didn't match the schema).","additionalProperties":true}}},"ExtractionResultFailed":{"type":"object","required":["id","status","error"],"properties":{"id":{"type":"string"},"status":{"type":"string","const":"failed"},"error":{"type":"string","description":"Human-readable failure reason. Credits were refunded in full."}}},"ExtractionResult":{"oneOf":[{"$ref":"#/components/schemas/ExtractionResultComplete"},{"$ref":"#/components/schemas/ExtractionResultFailed"}]},"ExtractionPending":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["queued","processing"]}}},"ProbeRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"schema":{"type":"object","description":"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":{"type":"string","description":"Alternative to `schema` for signalling an AI extraction quote. Not resolved by the probe."},"inputs":{"type":"array","items":{"$ref":"#/components/schemas/ExtractionInput"},"description":"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":{"type":"array","items":{"$ref":"#/components/schemas/ExtractionInput"},"description":"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."}}},"ProbeResponse":{"type":"object","required":["url","durationSec","title","hasSubtitles","hasComments","price"],"properties":{"url":{"type":"string"},"durationSec":{"type":["number","null"],"description":"Null when the platform hides the duration. Submitting then debits the 300s worst case, refunded down to actual on completion."},"title":{"type":["string","null"]},"hasSubtitles":{"type":"boolean"},"hasComments":{"type":"boolean"},"price":{"type":"object","required":["mode","credits","usd","baseUsd","downloadUsd","downloadBreakdown","breakdown"],"properties":{"mode":{"type":"string","enum":["ai","download"],"description":"Which kind of run was quoted, decided by whether a schema was passed. `download` quotes carry no AI component."},"credits":{"type":"integer","description":"Total price in credits (1 credit = $0.0001)."},"usd":{"type":"number"},"baseUsd":{"type":"number","description":"Flat per-run base fee ($0.005) covering the probe, metadata, thumbnail and description. Cached results skip it."},"downloadUsd":{"type":"number","description":"Download charge across requested artifacts: video $0.00005/sec, audio $0.00002/sec, transcript $0.001 flat, comments $0.002 flat. Artifacts that turn out unavailable are refunded on completion."},"downloadBreakdown":{"type":"object","description":"Per-artifact USD; only requested artifacts appear.","properties":{"transcript":{"type":"number"},"comments":{"type":"number"},"audio":{"type":"number"},"video":{"type":"number"}}},"breakdown":{"type":"object","description":"AI component, per-modality USD: text $0.00005/sec, audio $0.0001/sec, video $0.0002/sec. All-zero for download-only quotes.","properties":{"text":{"type":"number"},"audio":{"type":"number"},"video":{"type":"number"}}}}}}},"WebhookExtractionCompleted":{"type":"object","required":["event","data"],"properties":{"event":{"type":"string","const":"extraction.completed"},"data":{"$ref":"#/components/schemas/ExtractionResultComplete"}}},"WebhookExtractionFailed":{"type":"object","required":["event","data"],"properties":{"event":{"type":"string","const":"extraction.failed"},"data":{"$ref":"#/components/schemas/ExtractionResultFailed"}}},"WebhookTestEvent":{"type":"object","required":["event","data"],"properties":{"event":{"type":"string","const":"test"},"data":{"type":"object","properties":{"message":{"type":"string"},"endpointId":{"type":"string"},"sentAt":{"type":"string","format":"date-time"}}}}}}}}