API quickstart
Submit an extraction, wait for it, print the structured result. Grab an API key from the dashboard and export it as VIDEXTRACT_API_KEY. Every snippet below is copy-paste runnable.
Submit → poll → result
POST /v1/extractions returns an id immediately. The result endpoint answers 202 while the extraction is running and 200 once it's done.
curl
# 1. Submit
ID=$(curl -s -X POST https://postreef.com/v1/extractions \
-H "x-api-key: $VIDEXTRACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"inputs": ["transcript", "comments"],
"schemaId": "vidextract.predefined.recipe.v1"}' | jq -r .id)
# 2. Poll until done (202 = still running)
until curl -s -o result.json -w "%{http_code}" \
-H "x-api-key: $VIDEXTRACT_API_KEY" \
https://postreef.com/v1/extractions/$ID/result | grep -q 200; do
sleep 5
done
# 3. The structured extraction
jq .extraction result.jsonJavaScript
const BASE = "https://postreef.com";
const headers = {
"x-api-key": process.env.VIDEXTRACT_API_KEY,
"Content-Type": "application/json",
};
const { id } = await fetch(`${BASE}/v1/extractions`, {
method: "POST",
headers,
body: JSON.stringify({
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
inputs: ["transcript", "comments"],
schemaId: "vidextract.predefined.recipe.v1",
}),
}).then((r) => r.json());
let res;
do {
await new Promise((r) => setTimeout(r, 5000));
res = await fetch(`${BASE}/v1/extractions/${id}/result`, { headers });
} while (res.status === 202);
const result = await res.json();
console.log(result.extraction); // your schema-shaped objectPython
import os, time, requests
BASE = "https://postreef.com"
headers = {"x-api-key": os.environ["VIDEXTRACT_API_KEY"]}
run = requests.post(f"{BASE}/v1/extractions", headers=headers, json={
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"inputs": ["transcript", "comments"],
"schemaId": "vidextract.predefined.recipe.v1",
}).json()
while True:
res = requests.get(f"{BASE}/v1/extractions/{run['id']}/result",
headers=headers)
if res.status_code != 202:
break
time.sleep(5)
result = res.json()
print(result["extraction"]) # your schema-shaped objectNot a match?
A completed run isn't always a hit. If the video's content doesn't match your schema, outcome is "no_match" (or "uncertain" when the inputs were too thin to decide), extraction is null, and verdictReason says why in one sentence. Check it before treating a null extraction as an error:
const { outcome, verdictReason, extraction } = result;
if (outcome === "no_match" || outcome === "uncertain") {
console.log(`Not extracted: ${verdictReason}`);
} else {
console.log(extraction); // outcome "ok" (or null on download-only / legacy runs)
}The same outcome / verdictReason fields appear on GET /v1/extractions/:id, the list endpoint, and the extraction.completed webhook. Billing is unchanged: a no_match is a real run, not a failure, so it isn't refunded.
Skip polling with a webhook
Register an endpoint in the dashboard (or pass a per-run webhookUrl at submit) and we POST the full result to you when the extraction completes or fails. Verify the X-Vidextract-Signature header before trusting the payload:
Next.js route
// app/api/postreef/route.ts
import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.VIDEXTRACT_WEBHOOK_SECRET!;
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get("x-vidextract-signature") ?? "";
const t = sig.match(/t=(\d+)/)?.[1];
const v1 = sig.match(/v1=([0-9a-f]+)/)?.[1];
if (!t || !v1) return new Response("bad signature", { status: 400 });
const expected = createHmac("sha256", SECRET)
.update(`${t}.${body}`)
.digest("hex");
if (!timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
return new Response("bad signature", { status: 400 });
}
const { event, data } = JSON.parse(body);
if (event === "extraction.completed") {
console.log(data.id, data.extraction);
}
return new Response("ok"); // any 2xx acknowledges
}Node/Express
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const app = express();
const SECRET = process.env.VIDEXTRACT_WEBHOOK_SECRET;
app.post("/hooks/postreef", express.raw({ type: "*/*" }), (req, res) => {
const body = req.body.toString("utf8");
const sig = req.get("x-vidextract-signature") ?? "";
const t = sig.match(/t=(\d+)/)?.[1];
const v1 = sig.match(/v1=([0-9a-f]+)/)?.[1];
const expected = createHmac("sha256", SECRET)
.update(`${t}.${body}`)
.digest("hex");
if (!t || !v1 || !timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
return res.status(400).send("bad signature");
}
const { event, data } = JSON.parse(body);
if (event === "extraction.completed") console.log(data.id, data.extraction);
res.send("ok"); // any 2xx acknowledges
});Events, payloads and retries are covered in full in the webhooks guide. For every endpoint and field, see the API reference.