Skip to main content
ReCut

Developer getting-started guide

Everything needed to put ReCut videos, watch tracking, training reports and the editor into your site. Code samples are minimal and meant to be adapted.

Step 1: Create a workspace API key

Sign in as a workspace owner or admin, open Settings and find API keys. Create a key and name it after the system using it. The full key (starting rck_live_) is shown once — store it in your server's secret store.

All API calls are server to server. Browsers cannot call the API directly, and the key must never reach a web page.

# Store the key in your server's environment, never in browser code
RECUT_API_KEY=rck_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Step 2: Mint a viewing pass

When a signed-in user opens a page with a video, your server asks for a pass. Name the hosts allowed to show it (up to 10, wildcards like *.example.com allowed) and a lifetime between 60 seconds and 24 hours (default 1 hour). The response includes a ready embed link.

Optionally add brand (accent, theme, logo_url, name — the same options as the editor pass) to match your site. The branding is sealed into the embed link and only works for that video; editing it in the page has no effect.

// Node 18+ (server only)
const res = await fetch("https://recut.stream/api/public/v1/playback-tokens", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RECUT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    video_slug: "onboarding-policy",
    allowed_domains: ["hr.example.com", "*.staging.example.com"],
    ttl_seconds: 3600,
    brand: { accent: "#0a84ff", theme: "dark", logo_url: "https://hr.example.com/logo.png", name: "Acme HR" },
  }),
});
const { token, embed_url, expires_at } = await res.json();

Step 3: Show the player

Put the embed link in an iframe. Add &ref= with your own viewer reference (letters, numbers and _ . : @ - up to 128 characters) so watch updates come back tagged with it.

<iframe
  id="recut-player"
  src="EMBED_URL&ref=emp_1234"
  width="960" height="540"
  allow="autoplay; fullscreen; picture-in-picture"
  allowfullscreen
  title="Training video"></iframe>

Step 4: Receive watch updates

Load the helper script and listen. Events are ready, play, pause, seek, progress (milestone 25, 50, 75 or 90), heartbeat (every 10 seconds of real watching) and complete (at the end or 95%). Each carries time, duration, percent and watched seconds, where skipping doesn't count. Send them to your own server to store.

<script src="https://recut.stream/api/public/v1/embed-sdk.js"></script>
<script>
  const frame = document.getElementById("recut-player");
  const stop = ReCut.onWatch(frame, (msg) => {
    // msg: { event, slug, ref, time, duration, percent, watched, milestone? }
    navigator.sendBeacon("/my-api/watch-events", JSON.stringify(msg));
  });
</script>

Step 5: Send training reports (xAPI)

The helper turns each update into an xAPI statement with your viewer reference as the actor. Heartbeats are skipped. Post statements from your server to your training system's statement endpoint, so its credentials stay private.

ReCut.onWatch(frame, (msg) => {
  const statement = ReCut.toXapi(msg, {
    homePage: "https://hr.example.com",
    activityBase: "https://hr.example.com/videos",
    title: "Onboarding policy",
  });
  if (statement) fetch("/my-api/xapi", { method: "POST", body: JSON.stringify(statement) });
});
// Your server then forwards it to the LMS statement endpoint (POST {LRS}/statements)

Step 7: Mint an editor pass

Your server requests a pass for one project with scopes view, edit or submit (submit includes edit, edit includes view), the hosts allowed to frame it, an optional editor_ref for audit logs, and a lifetime between 5 minutes and 8 hours (default 2 hours).

Optionally add brand to match your site: accent (a hex colour like #0a84ff), theme (light or dark), logo_url (an https image) and name (up to 80 characters). Button text colour is picked automatically for readable contrast. Branding is sealed inside the pass, so nobody can change it from the page; an invalid colour or logo returns invalid_brand.

const res = await fetch("https://recut.stream/api/public/v1/editor-tokens", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RECUT_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    project_id: "00000000-0000-4000-8000-000000000000",
    scopes: ["submit"],
    allowed_domains: ["hr.example.com"],
    editor_ref: "emp_1234",
    ttl_seconds: 7200,
    brand: { accent: "#0a84ff", theme: "dark", logo_url: "https://hr.example.com/logo.png", name: "Acme HR" },
  }),
});
const { token, scopes, expires_at } = await res.json();

Step 8: Open the embedded editor

Frame the editor with the pass in the et parameter. The page must be opened from an allowed host. Your page can tell the editor to save or submit, and receives ready, dirty, saved, submitted and error events. Only messages from the framing page are accepted.

<iframe id="recut-editor"
  src="https://recut.stream/embed-editor/PROJECT_ID?et=EDITOR_TOKEN"
  width="100%" height="800" title="Video editor"></iframe>
<script>
  const ed = document.getElementById("recut-editor");
  const ORIGIN = "https://recut.stream";
  window.addEventListener("message", (e) => {
    if (e.origin !== ORIGIN || e.source !== ed.contentWindow) return;
    if (e.data?.source !== "recut:editor") return;
    console.log(e.data.type, e.data); // ready | dirty | saved | submitted | error
  });
  function save() { ed.contentWindow.postMessage({ source: "recut:host", v: 1, type: "save" }, ORIGIN); }
  function submit() {
    ed.contentWindow.postMessage({ source: "recut:host", v: 1, type: "submit",
      releaseTitle: "Q4 update", releaseNotes: "New policy section" }, ORIGIN);
  }
</script>

Launch checklist

Key stored server-side only. Allowed hosts match your real domains, including staging. Videos set to the right visibility. Your page checks event.origin and the source field before trusting messages. A workspace owner or admin is ready to approve submissions from the embedded editor.