postreef
View as Markdown

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.json

JavaScript

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 object

Python

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 object

Not 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.