Vodmill docs
v1.0.0
Get help
● Operate · Automation

API v1
the studio flow, from your customers' own software.

A small REST API under /api/v1: bring a video in from a link, analyse it, list the suggested clips, render one, fetch the files. A key works in one workspace, with what that workspace's plan and credits allow.

/api/v1Bearer vdm_…JSONOff by default

01Switch it on

  1. Operator Console, System, Public API: "Public API and webhooks" on. Off, every key is refused and no webhook is sent, whatever the plan says. "Requests per minute per API key" is 60 by default (5 to 6000).
  2. Plan On the Plans page give the right plans "REST API and webhooks".
  3. Customer A workspace owner opens Developers in the app and creates a key. It is shown once and stored only as a hash. A workspace can have 20 keys; a key can be revoked at any time.

02Basics

ThingValue
Base addresshttps://your-domain/api/v1
AuthenticationHeader Authorization: Bearer vdm_…
Bodies and answersJSON, snake_case fields, ISO 8601 dates
Body sizeUp to 1.5 MB of JSON; larger answers 413
ErrorsAn HTTP status and {"error": "a sentence", "status": 402}
Rate limitPer key and minute; over it: 429 with Retry-After: 60
export API=https://clips.your-agency.com/api/v1
export API_KEY=vdm_your_key

curl $API/workspace -H "Authorization: Bearer $API_KEY"

The answer has the workspace's id and name, its credits, and its plan with the plan's limits.

StatusMeaning
400The body is not valid JSON
401No key, or the key is not valid or was revoked
402The plan does not include the API, a plan limit is reached, or the credits are short
403The API is turned off on the server, or a feature (such as import from a link) is switched off
404Not found, or it belongs to another workspace
409Wrong state, for example analysing a source that is still importing
413The request body is larger than 1.5 MB
422Invalid input; the sentence says what
429Slow down, or too many things are waiting for this workspace

03Endpoints

From src/server/routes/api-public.ts. The app's Developers page shows the same list, with copyable examples for the workspace's own address.

The Developers page of the app on a read-only demo: API keys, webhooks, a quick start with curl examples and the list of endpoints
Method and pathBodyAnswer
GET /workspace{ id, name, credits, plan: { name, limits } }
GET /sources?limit={ sources: [source] }, newest first (limit 1 to 200, default 50)
POST /sources{ url, rights: true, title?, analyse?, options? }201 { source }. Brings a video in from a link.
GET /sources/:id{ source } with status, step, progress and the number of clips
POST /sources/:id/analyse{ options? }202 { source, credits }. Charges the workspace.
GET /sources/:id/clips{ clips: [clip] }, best score first
GET /clips?status=&source_id=&limit={ clips: [clip] }; status is suggested, approved or rejected
GET /clips/:id{ clip }
POST /clips/:id/render{ aspects? }, any of "9:16", "1:1", "4:5", "16:9"202 { render, credits, eta_seconds }. Default: the clip's own aspect ratio.
GET /renders?limit={ renders: [render] }
GET /renders/:id{ render }; when done, files with signed links
GET /files/:render/<name>?exp=&sig=The file. No key needed: the link is signed and works for one hour.
Sources through the API come from a link

POST /sources takes a link, so it needs import from a link switched on by the operator and included in the plan (Imports; YouTube, Twitch and Kick links also need the operator's yt-dlp, a direct link to a video file does not), and "rights": true: the statement that the user owns the content or has the owner's permission to clip it. Without it the answer is 422. File uploads are made in the app; a source uploaded there can be analysed and rendered through the API.

04Objects

From src/server/public-views.ts.

// source
{ "id": "…", "title": "…", "platform": "youtube", "url": "https://…", "creator": "…",
  "status": "importing | uploaded | queued | processing | ready | failed",
  "step": "transcribe", "progress": 0.42, "error": null,
  "duration_seconds": 3541.2, "width": 1920, "height": 1080, "language": "en",
  "credits": 118, "clips": 10, "created_at": "…", "finished_at": "…" }

// clip
{ "id": "…", "source_id": "…", "title": "…", "status": "suggested | approved | rejected",
  "start_seconds": 612.4, "end_seconds": 655.1, "duration_seconds": 41.3,
  "score": 84, "why": "…", "emotion": "insight",
  "hooks": ["…"], "titles": ["…"], "hashtags": ["…"],
  "flags": [{ "kind": "ends-before-payoff", "note": "…" }],
  "aspect": "9:16", "created_at": "…", "updated_at": "…" }

// render
{ "id": "…", "clip_id": "…", "source_id": "…", "title": "…",
  "status": "queued | running | done | failed | canceled | expired",
  "step": "Rendering", "progress": 0.6, "error": null,
  "aspects": ["9:16"], "credits": 2, "duration_seconds": 41.3,
  "queue_position": null, "starts_in_seconds": null,
  "files": [{ "name": "…", "kind": "…", "aspect": "9:16", "bytes": 12345678, "url": "https://…" }],
  "created_at": "…", "started_at": "…", "finished_at": "…" }

The values above are examples of the shape, not real data.

05A complete flow

# 1. bring a video in and analyse it as soon as it arrives
curl -X POST $API/sources -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://www.youtube.com/watch?v=VIDEO_ID","rights":true,"analyse":true,"options":{"count":8}}'

# 2. poll until "status" is "ready" (or use the source.ready webhook)
curl $API/sources/SOURCE_ID -H "Authorization: Bearer $API_KEY"

# 3. the suggested clips, best first
curl $API/sources/SOURCE_ID/clips -H "Authorization: Bearer $API_KEY"

# 4. render one in two shapes
curl -X POST $API/clips/CLIP_ID/render -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"aspects":["9:16","1:1"]}'

# 5. poll until "status" is "done" (or use the clip.rendered webhook), then fetch files[].url
curl $API/renders/RENDER_ID -H "Authorization: Bearer $API_KEY"

options for an analysis: count, minLen, maxLen, genre, aspect, language, notes, cutSilence, zooms. Out-of-range values are corrected, not refused. Credits work as in the app: charged when the step is queued, returned when it fails or is canceled in the app.

Use only content the workspace owns or has permission to clip, through the API as in the app.

06What the API does not cover in 1.0.0

  • Uploading a file, editing a clip, approving or rejecting clips.
  • Campaigns and publishing.
  • Console (operator) functions.

A key acts with the editor role in its workspace, on behalf of the person who created it.