Skip to main content
We version the webhook payload contract by date (api_version in every event envelope). Breaking changes are opt-in: existing integrations keep the behaviour they were built against.

2026-08-17 — Fixing footage, not just judging it

POST /v1/fix executes the fix a readiness check prescribes, and re-runs the check on the result before handing it back. Two fixes today: conforming variable frame rate footage to a constant rate, and baking a rotation flag into the pixels. See Fix Video. The claim worth reading twice is the second half. A file comes back marked succeeded only if the original failure is confirmed gone and the output still matches the source in duration, audio and orientation. If we cannot show that, the status is verification_failed, the report names the check that failed and what was measured, and you are charged nothing. Failed fixes cost nothing in all three ways one can fail — the transcode failing, never finishing, or finishing without passing verification.

Changed: readiness now reports rotation

A clip stored sideways with a rotation flag used to pass the readiness check without comment. It is now reported as rotation_metadata, and the contexts disagree about it on purpose: motion_analysis treats it as fix_first, because frame-extraction pipelines routinely decode without applying the flag and a model handed a sideways frame returns a confident wrong answer rather than an error. Everywhere else it is a note — editors and browsers apply the flag themselves. If you branch on checks_failed, expect this new value. It appears in checks_passed for every context on footage that does not have it.

Fixed: the frame rate a VFR fix asks you to conform to

The vfr fix prescribed the clip’s average frame rate, which on dropped-frame footage reads lower than the camera ever shot — a clip recorded at 30fps that averaged 20.03 was told to conform to 20.03, encoding the stutter in permanently. It now prescribes the rate the footage declares. If you execute these fixes yourself, the fps value in a vfr fix will differ from what you saw before, and the new one is the correct target.

2026-08-15 — Delivery overhaul

The theme of this release: you should not have to poll. Webhook delivery is now something you can build on, and polling — if you still prefer it — costs a fraction of what it did.

Fixed: webhook payload did not match the documentation

The docs described an envelope — {"event", "data", "timestamp"} — while the dispatcher sent a flat object with those keys absent. Anyone who followed the docs and read payload.data.job_id got undefined. Registered endpoints now receive the documented envelope, plus an id for idempotency and an api_version. Nothing you have built will break: endpoints registered before today are pinned to payload_version: "legacy" and keep receiving the flat payload indefinitely. Check payload_version on GET /v1/webhooks; register a new endpoint to move to the envelope. The test event now uses the same shape your endpoint receives for real events.

Fixed: webhook failures could be silently dropped

A single malformed payload could suppress every webhook for a job while the job still reported completed. Each dispatch is now isolated — a job-level failure no longer costs you the batch event — and failures are logged with a stack trace.

Fixed: GET /v1/webhooks returned your signing secret

The list endpoint now returns secret_prefix (8 characters) instead of the full secret. The full secret comes only from registration and the new rotate endpoint. Scope, exposure window and whether you need to rotate: FD-2026-001.

New: delivery history and replay

  • GET /v1/webhooks/deliveries — what we sent, what your server answered, and why a delivery failed. Filter by job_id, webhook_id, event, status.
  • GET /v1/webhooks/deliveries/{id} — the verbatim payload body, for diffing against what your handler parsed.
  • POST /v1/webhooks/deliveries/{id}/retry — replay a delivery. Same payload, same event_id. Costs no credits — recovering a missed event no longer means re-running the job.

New: POST /v1/webhooks/{id}/rotate-secret

Issue a new signing secret without deleting and re-registering the endpoint.

New: ?wait=N long-poll

GET /v1/classify/{job_id}, GET /v1/batch/{batch_id} and GET /mpp/classify/{job_id} accept wait (0–90 seconds). The request is held open until the job reaches a terminal state. A 30–180 second job polled every 5 seconds costs 6–36 requests; with wait it costs one or two. wait=0 is the default and is unchanged behaviour, so this is additive. Every documented example moved from an unbounded 5-second poll loop to wait. The old quickstart example issued 12 requests in the first minute — enough to exceed the Free tier’s 10/minute limit on a single video.

New: batch.progress event

Opt in via events on registration. Fires at most once per 10% of a batch, never for batches under 5 videos, and never at 100% (batch.completed covers that). Off by default.

New: webhook_secret on submissions

Pass alongside webhook_url to have per-request callbacks signed. Previously that path was always unsigned. Per-request callbacks are also now retried (30s / 120s / 480s) and appear in delivery history.

Changed: status fields are enums in the OpenAPI spec

JobStatusResponse.status and batch status were typed as bare strings, so generated SDKs emitted string and callers hardcoded values from prose. They are now proper enums. Two schemas also lost accidental module-qualified names — BatchStatusResponse was published as app__api__v1__batch__BatchStatusResponse. If you generate a client, regenerate it; the class names are now clean.

Reserved for a future release

video.transcribed, video.indexed, and readiness.flagged are reserved event names. Subscribing to them returns 400 today rather than silently never firing.