API and webhooks
Keys, the /v1 routes, and verifying a delivery signature.
Everything the review UI does over an episode is available over HTTP at
/v1. Issue a key from API keys — it is shown
once, because only its hash is stored.
Authentication
curl https://your-host/v1/me \
-H "Authorization: Bearer cf_live_…"
A key carries a role, the same ladder the web routes use. /v1 accepts a
Bearer key only and never a cookie, which is what makes it safe to exempt from CSRF —
a browser session gets a 401 there.
Routes
| Method | Path | Returns |
|---|---|---|
| GET | /v1/me | Plan, seats, credits remaining. |
| GET | /v1/episodes | Your episodes. |
| GET | /v1/episodes/{id} | One episode with its status. |
| GET | /v1/episodes/{id}/clips | Candidates and renders. |
| GET | /v1/clips/{render_id} | One clip and its asset URLs. |
| GET/POST | /v1/webhooks | List or register an endpoint. |
| DELETE | /v1/webhooks/{id} | Remove an endpoint. |
| GET | /v1/webhooks/deliveries | Recent attempts and their outcomes. |
Webhooks
Register a URL and we post to it when a clip becomes ready. Delivery rides the same durable job queue as everything else, so retries, backoff and dead-lettering already apply — a receiver that is down for ten minutes does not lose the event.
Each request carries a signature header shaped
t=<unix>,v1=<hmac> — deliberately the same construction Stripe
uses, so if you already verify Stripe webhooks you can reuse that verifier. Sign
"{timestamp}.{body}" with your endpoint secret using HMAC-SHA256 and
compare in constant time. Reject anything older than a few minutes.
A registered URL is resolved
and rejected if it points at loopback, link-local or private space — the same guard
that protects URL ingest. On a cloud host, an unguarded fetch of
169.254.169.254 is a credential leak.