Channelsmith docs
v1.0.0
Get help
● Connect · API

API and webhooks.
REST at /api/v1, signed deliveries.

Workspaces can work with their channels and episodes from their own software, Zapier or Make through a REST API, and get signed webhooks when something happens. Publishing to YouTube is the one thing the API never does: it always needs a person in the studio.

/api/v1Bearer msk_…HMAC-SHA256

01Turn it on

  1. Console, Settings, Public API: turn "Public API and webhooks" on. "Requests per minute per API key" (default 60) limits each key.
  2. Console, Plans: tick "REST API and webhooks" on every plan that should have it.
  3. In the studio, the workspace owner opens Developers and creates a key. The key (msk_…) is shown once and stored only as a hash; revoke it there. A key acts for its workspace, on behalf of the person who created it (credits, history).

02Endpoints

Base URL: https://your-address/api/v1. Send the key in every request: Authorization: Bearer msk_…. Bodies and answers are JSON with snake_case fields; dates are ISO 8601.

Channels and episodes

Method and pathWhat it does
GET /channelsThe workspace's channels: language, style pack, YouTube connection, autopilot, episodes per stage, next posting slots
GET /channels/{id}/episodes?stage=&limit=50A channel's episodes, newest change first (limit 1-200)
POST /channels/{id}/episodesStart an episode: {"topic": "…", "minutes": 10, "urls": ["https://…"], "notes": "…"}. Answers 201. Nothing runs or is charged yet
GET /episodes/{id}One episode: stage, every step's state and error, language, the files of its last render (links valid for one hour) and its YouTube uploads
POST /episodes/{id}/steps/{step}Run a step in the background: research, script, factcheck, produce, footage, render, package, shorts; localize on a language version. Charged like in the studio and refunded when it fails. Answers 202. publish answers 403
POST /episodes/{id}/versionsA language version: {"language": "de-DE", "channel_id": "…", "voice_id": "…"} (the last two optional). Answers 202 with the new episode

An episode looks like this (shortened):

{
  "id": "ep_…", "channel_id": "ch_…", "channel": "Harbour Lights", "topic": "How lighthouses guided ships before GPS",
  "title": "How Lighthouses Told Ships Exactly Where They Were", "stage": "review", "language": "en-US", "version_of": null,
  "steps": { "research": { "status": "done", "error": null, "note": null, "at": "2026-11-11T18:20:00.000Z" }, "…": {} },
  "project_id": "prj_…", "render_id": "rnd_…", "publish_at": null, "autopilot": false,
  "created_at": "…", "updated_at": "…",
  "render": { "id": "rnd_…", "status": "done", "files": [ { "name": "video.mp4", "url": "https://…" } ] },
  "publications": []
}

Run the steps in order. A typical client starts an episode, runs research, waits for episode.step.done, runs the next step, and so on up to shorts; then a person reviews and publishes in the studio.

Storyboards and renders

The storyboard behind an episode is a project. These endpoints work on projects directly, for example to render a storyboard you built yourself.

Method and pathWhat it does
GET /workspaceName, credits and the plan's limits
GET /projects?limit=25The workspace's storyboards, newest change first (limit 1-100)
POST /projectsCreate one from a storyboard: {"name": "…", "storyboard": { … }}. Answers 201 with status ready
GET /projects/{id}One storyboard; add ?storyboard=1 to include the storyboard JSON
GET /projects/{id}/rendersIts last 30 renders
POST /projects/{id}/rendersQueue a render (charges credits): {"formats": ["9:16"], "review": true}
GET /renders/{id}Status, progress and, when done, file links

The easiest way to get a valid storyboard is to export one from the studio's scene editor and change it. formats are extra aspect ratios next to the storyboard's own (the plan must allow several formats); review: false skips the AI check of the frames. File links download without a key for one hour; ask again for fresh links.

Errors

{"error": "what happened", "status": 4xx}: 401 wrong or revoked key, 402 not enough credits or the plan does not include it, 403 the API is turned off (or publishing was asked for), 404 not found, 409 not ready yet or already exists, 422 a field is wrong, 429 rate limit (wait a minute).

03Webhooks

On the Developers page the owner adds an https URL (public address, port 443) and picks events:

EventWhen
episode.step.doneA step of an episode finished
episode.step.failedA step failed (its credits are refunded)
episode.ready_for_reviewAn episode is rendered and waits for a person
episode.publishedAn upload to YouTube finished
render.doneA render finished; the data has the files with their links
render.failedA render failed (its credits are refunded)
project.readyA storyboard exists for the first time (an episode's Visuals step finished, or one was created)

Episode events carry { "episode": { … }, "step": "…", "error": "…", "publication": { … } } (the last three when they apply).

Each delivery is a POST with content-type: application/json and this body:

{ "id": "evt_…", "type": "episode.step.done", "created": "2026-11-11T10:00:00.000Z", "workspace": "ws_…", "data": { … } }

Headers: x-studio-event (the event) and x-studio-signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" with the webhook's secret (whsec_…, shown once when you add it). Check it before you trust a delivery, and refuse old timestamps:

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, rawBody, header) {
  const p = Object.fromEntries(header.split(",").map((x) => x.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
  const want = createHmac("sha256", secret).update(`${p.t}.${rawBody}`).digest("hex");
  return want.length === p.v1?.length && timingSafeEqual(Buffer.from(want), Buffer.from(p.v1));
}

Answer with any 2xx within 10 seconds. The Developers page has a "Test" button (sends a ping event) and the last 50 deliveries of each webhook with their result.