VidReef API
Connect VidReef to your course platform, CMS, or internal tools: list videos and folders, get embed codes and transcripts, import videos from a URL, and get a webhook when a video or transcript is ready. The API is available on Business and Enterprise plans; importing through the API is Enterprise only.
Getting started
The workspace owner creates keys under Settings → API & webhooks. Send the key in the Authorization header of every request. Each key works with one workspace. Keep them on your server; never put a key in a web page or app.
curl https://vidreef.com/api/v1/videos -H "Authorization: Bearer vr_live_…"All responses are JSON: a single object in data, or a list in data with next_cursor. Times are ISO 8601 in UTC.
Limits and errors
Each key can make 60 requests a minute. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix time). Over the limit you get 429 with Retry-After in seconds. Use webhooks instead of polling where you can.
Errors share one shape, with a stable code and a readable message:
{ "error": { "code": "not_found", "message": "No video with that id in this workspace." } }401 unauthorized: missing, wrong, or revoked key403 plan_required: the plan doesn't include the API, or (for imports) isn't Enterprise403 quota_exceeded: storage or the month's processing is used up400: a bad parameter (the code says which)404 not_found,429 rate_limited,500 server_error
Endpoints
GET/videos
Videos in the workspace, newest first. Optional query parameters: folder_id, status (uploading, processing, ready, failed), limit (1–100, default 25), and cursor (the next_cursor from the previous page; null on the last page).
curl "https://vidreef.com/api/v1/videos?status=ready&limit=100" -H "Authorization: Bearer vr_live_…"GET/videos/{id}
One video, with its embed codes and share link. thumbnail_url works for public videos.
{
"data": {
"id": "k3J9xQ2mPa",
"title": "Iron condors, part 2",
"description": "",
"status": "ready",
"error": null,
"duration_seconds": 1834.2,
"width": 1920,
"height": 1080,
"folder_ids": ["fld_8Hc2kd93JsLq0aZe"],
"transcript_status": "succeeded",
"thumbnail_url": "https://vidreef.com/api/thumb/k3J9xQ2mPa.jpg",
"iframe": "<iframe src=\"https://vidreef.com/embed/k3J9xQ2mPa\" style=\"aspect-ratio:1920/1080;width:100%;border:0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen></iframe>",
"embed_url": "https://vidreef.com/embed/k3J9xQ2mPa",
"share_url": "https://vidreef.com/v/k3J9xQ2mPa",
"created_at": "2026-09-29T14:03:11.000Z",
"updated_at": "2026-09-29T14:09:52.000Z"
}
}GET/videos/{id}/transcript
The video's transcript. format is json (default: timed segments), txt (plain paragraphs), txt-timestamps (paragraphs starting with [m:ss]), vtt, or srt. Until it's ready you get 404 transcript_not_ready.
{
"data": {
"video_id": "k3J9xQ2mPa",
"language": "en",
"segments": [
{ "start_seconds": 0.48, "end_seconds": 4.1, "text": "Welcome back. Today we're adjusting the condor." }
]
}
}GET/folders
Every folder in the workspace. Folders nest: parent_id is the enclosing folder, or null at the top level.
{ "data": [ { "id": "fld_8Hc2kd93JsLq0aZe", "name": "Options 101", "parent_id": null, "created_at": "…" } ] }POST/imports
Enterprise. Imports one video from a public http(s) URL to the original file (MP4, MOV, and so on). We copy it, process it, and transcribe it, just like an upload. Body fields: url (required), title (defaults to the file name), description, folder_id, and external_id (your own reference; sending the same one again the same day returns the existing import instead of a duplicate). Returns 202.
curl -X POST https://vidreef.com/api/v1/imports \
-H "Authorization: Bearer vr_live_…" \
-H "Content-Type: application/json" \
-d '{"url": "https://files.example.com/lesson-12.mp4", "title": "Lesson 12", "external_id": "lesson-12"}'{
"data": {
"id": "imi_Qm2Jd8sLk3HvA0pZ",
"status": "pending",
"url": "https://files.example.com/lesson-12.mp4",
"external_id": "lesson-12",
"video_id": null,
"video_status": null,
"error": null,
"created_at": "…"
}
}GET/imports/{id}
Enterprise. An import's progress. status is pending, running, imported, or failed (with error). Once the file is copied, video_id is set and video_status follows processing; the video.ready webhook tells you when it can play.
Webhooks
Add a URL under Settings → API & webhooks and choose events. We POST JSON to it:
video.ready: a video finished processing and can play.data.videois the same object asGET /videos/{id}.transcript.ready: a transcript was created or replaced (including edits).datahasvideo_id,language, andsource; fetch the text from the transcript endpoint.
{ "id": "evt_…", "type": "video.ready", "created_at": "2026-09-29T14:09:52.000Z", "data": { "video": { … } } }Answer with any 2xx within 10 seconds. Otherwise we retry after 1 and 5 minutes, then 30 minutes, 2, 6, 12, and 24 hours (about three days in all). A URL that fails for three days is turned off; turn it back on in settings. The same event can arrive more than once, so use its id to skip duplicates.
Verifying the signature
Each request has a VidReef-Signature header: t=<unix time>,v1=<signature>. The signature is the hex HMAC-SHA256 of t, a period, and the raw body, keyed with the endpoint's signing secret (shown once when you add it). Reject requests that don't match or are more than 5 minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received (a string, before JSON.parse)
function verifyVidReef(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}What the API doesn't do
The API doesn't hand out original video files or streaming files; videos play through VidReef embeds and share links, which keep your domain restrictions and analytics working. To take a full copy of your library, originals included, use Export your library in settings.
Questions or need something the API doesn't cover? Email support@vidreef.com.