Skip to content

Developers

API and webhooks

Do you have an API or integrations? Yes: API keys, webhooks, Slack, Discord, Premiere.

A REST API with workspace API keys, signed webhooks for notes, replies, versions and approvals, Slack and Discord channels, the Premiere Pro panel, and an MCP server for Claude.

Sign in with an API key

A workspace owner or admin makes a key in Workspace settings → API keys (paid plans). It is shown once. Send it on every call:

Authorization: Bearer cr_live_…

The API lives at https://cutroom-review.fly.dev. Every answer is JSON; an error carries a machine-readable code: { "code": "NOTE_NOT_FOUND", "error": "That note was deleted." }. A key acts as the person who made it, in their workspaces, and can be revoked from the same page.

curl https://cutroom-review.fly.dev/api/versions/VERSION_ID/notes \
  -H "Authorization: Bearer cr_live_…" \
  -H "Content-Type: application/json" \
  -d '{"t": 42.0, "text": "Music dips too hard here"}'

REST endpoints

CallWhat it does
GET /api/meWho the key belongs to, and their workspaces (ids, names, plan state).
GET /api/workspaces/{workspace_id}/projectsThe workspace's projects.
GET /api/projects/{project_id}A project's files, each with its versions (ids, numbers, approval, status) and note counts.
GET /api/versions/{version_id}/reviewOne version: its media, approval and every note with its replies, timecode, author and status.
POST /api/versions/{version_id}/notesLeave a note at a time, or on the whole cut.
{ "t": 42.5, "text": "Music dips too hard here" }
// or the whole cut: { "general": true, "text": "Great pacing overall" }
POST /api/notes/{note_id}/repliesReply in a note's thread.
{ "text": "Fixed in V3" }
POST /api/notes/{note_id}/resolveMark a note done (or open again).
{ "resolved": true }
POST /api/versions/{version_id}/approvalSet the approval.
{ "status": "approved" }  // or "needs_changes", "in_review"
GET /api/versions/{version_id}/markers?format=csvThe notes as markers: resolve-edl, premiere-csv, fcpxml, avid-txt or csv.

Timecodes are seconds from the start of the cut (t, and t2 for a range). Uploads and the full list are on the API reference.

Webhooks

Add an endpoint in Apps & integrations → Webhooks (owners and admins, paid plans): an https address, the events, and a project or every project. You get a signing secret once. Send test sends a sample event shaped like a real note.

note.createdSomeone leaves a note (team-only notes are never sent).
note.resolvedA note is marked done.
reply.createdSomeone replies in a note's thread.
version.createdAn upload finishes and starts processing.
version.readyA version has processed and plays in the review.
approval.changedApproved, Needs changes, or back to In review.
share_link.viewedSomeone opens a share link (once per person every 30 minutes).

Every event carries what a no-code tool needs, so there is no second call: the workspace, project, file and version with links, and for notes the words, the author, the timecode and whether it is about the whole cut. Team-only notes and their replies are never sent.

{
  "id": "evt_Q2x9…",
  "type": "note.created",
  "created_at": "2026-10-27T14:03:11.204Z",
  "data": {
    "workspace": { "id": "…", "name": "Northlight Studio" },
    "project": { "id": "…", "name": "Brand film", "url": "https://post-prod.io/p/…" },
    "asset": { "id": "…", "name": "Brand film_v3.mp4" },
    "version": { "id": "…", "number": 3, "status": "ready", "approval": "in_review", "url": "https://post-prod.io/r/…" },
    "actor": { "name": "Maya Chen" },
    "note": {
      "id": "…", "short_id": "k3Pz9QaB", "kind": "note",
      "text": "Cut to the product shot 2 frames earlier",
      "status": "open", "whole_cut": false,
      "timecode": "00:01:12", "at_seconds": 72.4, "to_seconds": null,
      "author": { "name": "Maya Chen", "kind": "guest" },
      "resolved_by": null, "created_at": "2026-10-27T14:03:10.981Z",
      "url": "https://post-prod.io/r/…#n-k3Pz9QaB"
    }
  }
}
X-PostProd-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with the endpoint's secret>
X-PostProd-TimestampThe same unix seconds as t. Refuse anything more than 5 minutes old (a replay).
X-PostProd-EventThe event, e.g. note.created. A test from Send test is ping.
X-PostProd-DeliveryThe event's id (evt_…). The same on every retry: use it to drop duplicates.
X-PostProd-Webhook-IdWhich of your endpoints this is.

Answer with any 2xx within 10 seconds. Anything else is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours (6 tries). Redirects are not followed. After 3 events in a row fail every try, or at once on 410 Gone, the endpoint turns itself off and the workspace owner gets an email; turn it back on in Apps & integrations → Webhooks. Each endpoint keeps a log of its last 50 deliveries, with Send again.

Check the signature

HMAC-SHA256 of the timestamp, a dot, and the raw body, with your endpoint's secret, compared in constant time. Refuse anything older than 5 minutes.

Node.js

import crypto from "node:crypto";
import express from "express";

export function verifyPostProd(rawBody, header, secret, toleranceS = 300) {
  const parts = Object.fromEntries(String(header).split(",").map((p) => p.split("=", 2)));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceS) return false; // too old: a replay
  const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const got = String(parts.v1 || "");
  return got.length === want.length && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(got));
}

const app = express();
// the raw bytes, exactly as sent: never JSON.parse and re-serialise before checking
app.post("/postprod", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyPostProd(raw, req.get("X-PostProd-Signature"), process.env.POSTPROD_WEBHOOK_SECRET)) return res.sendStatus(400);
  const event = JSON.parse(raw);
  // drop duplicates by event.id (X-PostProd-Delivery), then act on event.type
  res.sendStatus(204);
});

Python

import hashlib, hmac, time

def verify_postprod(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    try:
        t = int(parts.get("t", ""))
    except ValueError:
        return False
    if abs(time.time() - t) > tolerance:  # too old: a replay
        return False
    want = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, parts.get("v1", ""))

# Flask: verify_postprod(request.get_data(), request.headers["X-PostProd-Signature"], SECRET)

Make

  1. In Make, create a scenario and add the Webhooks app → Custom webhook. Press Add, name it, and copy the address it gives you.
  2. In post-prod.io: Apps & integrations → Webhooks → paste the address, tick the events, and press Add webhook.
  3. Back in Make, press Run once. Then in post-prod.io press Send test: Make learns every field from it (note text, timecode, author, links).
  4. Add the next module (Trello, Google Sheets, Slack, Notion…) and map the fields. Turn the scenario on.

Make answers every delivery with “Accepted”. A Make webhook with no scenario attached for 5 days answers 410 Gone, and post-prod.io turns that endpoint off and emails the owner.

Zapier

  1. Webhooks by Zapier needs a paid Zapier plan. Make a Zap with the trigger Webhooks by Zapier → Catch Hook, and copy its URL.
  2. In post-prod.io: Apps & integrations → Webhooks → paste it, tick the events, and press Add webhook.
  3. Press Send test here, then Test trigger in Zapier. The note's fields appear under data → note.
  4. Add your action and map the fields (for one part of the event only, set Pick off a child key to data).

Make and Zapier cannot check the signature without a code step. Treat the hook's address as a secret, and use your own server (below) where you need the check.

Slack, Discord, Premiere, MCP

Post notes, replies, approvals and new versions to a Slack or Discord channel, put every note on your Premiere Pro sequence as a marker with the panel, or connect Claude and other assistants over MCP with an API key. All of it is in Integrations.

Something missing? Email contact@post-prod.io.

Get started

Every feature. $10 per admin a month ($8 billed yearly). Unlimited guests. Cancel any time.

Get started