API reference
All six endpoints in the public API, with exact request and response shapes.
Base URL is your vdoX API host, for example https://api.vdox.example.com. Every endpoint requires Authorization: Bearer vdx_... and every authenticated response carries an X-RateLimit-Remaining header. Any endpoint can return unauthorized (401) or rate_limited (429); those aren't repeated below. See Authentication for the full error table.
Create an upload
POST /api/v1/uploads. Body: filename (string, required, 1 to 200 characters, extension must be mp4, mov, mkv, webm, or m4v). Creates the video row and a presigned S3 POST for the source file.
POST /api/v1/uploads
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e
Content-Type: application/json
{
"filename": "lecture.mp4"
}201 Created
{
"videoId": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"upload": {
"url": "https://vdox-uploads.s3.ap-south-1.amazonaws.com/",
"fields": {
"bucket": "vdox-uploads",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "AKIAEXAMPLE/20260713/ap-south-1/s3/aws4_request",
"X-Amz-Date": "20260713T090000Z",
"key": "01H8Y3ZK5XG5W3E5V8N7Q2R4T6/01ARZ3NDEKTSV4RRFFQ69G5FAV/original.mp4",
"Policy": "eyJleHhwaXJhdGlvbiI6IjIwMjYtMDctMTNUMDk6MzA6MDBaIiwiY29uZGl0aW9ucyI6W119",
"X-Amz-Signature": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
}
}
}upload.fields is generated per request; use whatever keys and values the response actually returns. See Upload API for the full upload flow.
Errors: invalid_request, invalid_file, workspace_suspended, quota_upload_size, quota_videos, quota_storage. See Authentication for what each code means and its HTTP status.
List videos
GET /api/v1/videos. Query params, all optional: status (one of uploading, uploaded, processing, ready, failed, cancelled), page (positive integer), limit (positive integer).
GET /api/v1/videos?status=ready&page=1&limit=20
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e200 OK
{
"items": [
{
"id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"title": "lecture.mp4",
"status": "ready",
"stage": null,
"progress": 100,
"durationSeconds": 754,
"createdAt": "2026-07-13T09:00:00.000Z"
}
],
"total": 1
}Response fields
stageis the current processing stage (for example"transcoding"), ornullwhen not processing.durationSecondsisnulluntil it's known.totalis the total number of videos matching the filter, not just the ones on this page.
Errors: invalid_request. See Authentication for what each code means and its HTTP status.
Get a video
GET /api/v1/videos/:id. Full video state, including playback and download links once it's ready.
GET /api/v1/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e200 OK
{
"video": {
"id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"tenantId": "01H8Y3ZK5XG5W3E5V8N7Q2R4T6",
"title": "lecture.mp4",
"displayName": "lecture.mp4",
"status": "ready",
"stage": null,
"progress": 100,
"orientation": "landscape",
"protection": "aes_128",
"watermark": "none",
"allowOffline": false,
"captionsLangs": ["en"],
"sourceBytes": 104857600,
"outputPrefix": "01H8Y3ZK5XG5W3E5V8N7Q2R4T6/01ARZ3NDEKTSV4RRFFQ69G5FAV",
"outputBytes": 52428800,
"error": null,
"createdAt": "2026-07-13T09:00:00.000Z",
"updatedAt": "2026-07-13T09:06:40.000Z",
"readyAt": "2026-07-13T09:06:40.000Z",
"download": "/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/download",
"playback": {
"hlsAes": "/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/hls/master.m3u8",
"poster": "https://cdn.vdox.example.com/01ARZ3NDEKTSV4RRFFQ69G5FAV/poster.jpg",
"thumbnailsVtt": "https://cdn.vdox.example.com/01ARZ3NDEKTSV4RRFFQ69G5FAV/thumbnails.vtt",
"manifestUrl": "https://api.vdox.example.com/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/hls/master.m3u8"
}
}
}Response fields
downloadandplayback.hlsAesare relative paths against your vdoX playback host, not full URLs.playback.manifestUrlis the same manifest, resolved to an absolute URL, but without a playback token: it isn't directly playable. Mint one viaPOST /videos/:id/playback-token(below), whose response already returns a ready-to-play, tokenizedmanifestUrl.playback.posterandplayback.thumbnailsVttare already absolute CDN URLs, usable as-is.downloadandplaybackarenulluntilstatusis"ready".
Errors: not_found. See Authentication for what each code means and its HTTP status.
Complete an upload
POST /api/v1/videos/:id/complete. Verifies the source file landed in storage and queues transcoding. Call this once, right after the presigned upload finishes.
POST /api/v1/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/complete
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e200 OK
{
"ok": true
}Errors: not_found, unsupported_file, upstream_error. See Authentication for what each code means and its HTTP status.
Mint a playback token
POST /api/v1/videos/:id/playback-token. No request body. Mints a short-lived token for one viewer to watch one video. Call this from your backend, not the browser, see Embed.
POST /api/v1/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/playback-token
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e
Content-Type: application/json
{
"viewer": { "label": "student@example.com" }
}200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2aWRlb0lkIjoiMDFBUlozTkRFS1RTVjRSUkZGUTY5RzVGQVYi...",
"expiresAt": "2026-07-13T09:05:00.000Z",
"manifestUrl": "https://api.vdox.example.com/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/hls/master.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"sessionId": "01JAVX7C2H8Q9M4N6P0R3T5W7Y"
}expiresAt is an ISO timestamp, 5 minutes (300 seconds) after minting by default. manifestUrl is an absolute HLS master URL with this token already appended (?token=...): hand it directly to hls.js or a <video> tag, no further assembly needed.
The request body is optional. Pass { "viewer": { "label": "..." } } (up to 80 characters) to identify the viewer: the label is stamped into the moving watermark and recorded on the playback session for the workspace admin. The response sessionId is the id of the playback session this token opened. Pass it as &session=<sessionId> on the embed URL so the player can heartbeat: when the workspace exceeds its concurrent-stream limit, or a session is revoked, that heartbeat stops playback. Per-plan stream limits are enforced server-side.
Without viewer.label, every caller collapses to a single "anon" identity and counts as ONE viewer against the plan's concurrent-stream limit, no matter how many different end-users are actually watching. Pass a distinct viewer.label per end-user (their id or email works well) if you rely on per-viewer stream limits.
Errors: not_found, not_ready, workspace_suspended, quota_plays, upstream_error. See Authentication for what each code means and its HTTP status.
Report a security event
POST /api/v1/security-events. Records a client-observed capture attempt (screen recording or screenshot) that the server cannot see on its own. Call it from your native app's backend, or the app itself if it holds a key, when iOS UIScreen.isCaptured turns true, an OS screenshot notification fires, or an Android FLAG_SECURE bypass is detected. See Mobile security.
POST /api/v1/security-events
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e
Content-Type: application/json
{
"type": "capture_reported",
"videoId": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"sessionId": "01JAVX7C2H8Q9M4N6P0R3T5W7Y",
"detail": { "signal": "UIScreen.isCaptured" }
}201 Created
{
"ok": true
}type is capture_reported or screenshot_reported. videoId, sessionId, and detail (a small JSON object, up to 2 KB serialized) are optional. The event is always recorded against your own workspace and surfaces on the admin security page.
Errors: invalid_request. See Authentication for what each code means and its HTTP status.
Cancel a video
POST /api/v1/videos/:id/cancel. Stops a video that's still uploading, uploaded, or processing. Use this to free an abandoned upload that would otherwise count against your video quota forever, with no other way to clear it.
POST /api/v1/videos/01ARZ3NDEKTSV4RRFFQ69G5FAV/cancel
Authorization: Bearer vdx_4f2b9e1a7c6d3f805e2b91ac4d7e0f3b1a2c9d8e200 OK
{
"ok": true
}Cancelling a video that's already ready, failed, or cancelled returns 409 conflict instead of an ok response.
Errors: not_found, conflict, upstream_error. See Authentication for what each code means and its HTTP status.