Skip to main content
All content API endpoints require an API key with full, upload, or admin scope. Pass it as a Bearer token:
curl -H "Authorization: Bearer bk_your_api_key" \
  http://localhost:4100/api/v1/content

Register content

POST /api/v1/content
Creates a new content entry or upserts if id already exists. Request body:
{
  "name": "My Video",
  "id": "my-video-001",
  "metadata": { "duration": 120 }
}
FieldTypeRequiredDescription
namestringYesDisplay name
idstringNoContent ID (auto-generated UUID if omitted). Must match [a-zA-Z0-9_-]{1,256}.
sizeBytesnumberNoTotal size in bytes
metadataobjectNoArbitrary JSON metadata
Response (201):
{
  "content": {
    "id": "my-video-001",
    "name": "My Video",
    "status": "active",
    "sizeBytes": null,
    "manifestKey": null,
    "storageKey": "my-video-001/",
    "metadata": { "duration": 120 },
    "createdAt": "2026-03-02T12:00:00.000Z",
    "updatedAt": "2026-03-02T12:00:00.000Z",
    "presign_endpoint": "/api/v1/content/my-video-001/presign",
    "key_endpoint": "/keys/my-video-001"
  }
}

List content

GET /api/v1/content?limit=20&offset=0&status=active
ParameterTypeDefaultDescription
limitnumber50Page size
offsetnumber0Offset for pagination
statusstringFilter by active or disabled
Response (200):
{
  "items": [{ "id": "my-video-001", "name": "My Video", "..." : "..." }],
  "total": 1
}

Get content

GET /api/v1/content/:id
Response (200):
{
  "content": { "id": "my-video-001", "name": "My Video", "..." : "..." }
}
Returns 404 if the content does not exist.

Update content

PATCH /api/v1/content/:id
Request body (all fields optional):
{
  "name": "Updated Name",
  "sizeBytes": 1048576,
  "manifestKey": "manifest.m3u8",
  "metadata": { "duration": 180 }
}
Response (200): Updated content object.

Delete content

DELETE /api/v1/content/:id
Soft-deletes the content (sets status to disabled). Disabled content returns 404 from the key server — viewers can no longer fetch keys for it. Response (200): Content object with "status": "disabled".

Presigned upload URLs

POST /api/v1/content/:id/presign
Generates presigned S3 PUT URLs for uploading encrypted segments. Keys are automatically prefixed with the content ID. Request body:
{
  "keys": [
    "manifest.m3u8",
    { "key": "seg-0.ts", "contentType": "video/MP2T" },
    { "key": "seg-1.ts", "contentType": "video/MP2T" }
  ]
}
FieldTypeDescription
keysarrayList of S3 keys (strings or { key, contentType } objects)
Each key can be a plain string (defaults to video/MP2T) or an object with explicit contentType. Allowed content types: video/MP2T, video/mp4, application/vnd.apple.mpegurl Response (200):
{
  "urls": [
    { "key": "manifest.m3u8", "url": "https://s3.../my-video-001/manifest.m3u8?X-Amz-..." },
    { "key": "seg-0.ts", "url": "https://s3.../my-video-001/seg-0.ts?X-Amz-..." },
    { "key": "seg-1.ts", "url": "https://s3.../my-video-001/seg-1.ts?X-Amz-..." }
  ]
}
When a manifest file (.m3u8 or .mpd) is included in the presign request, the server automatically updates the content’s manifestKey field.

Error responses

All error responses follow the format:
{ "error": "ERROR_CODE" }
CodeHTTP StatusDescription
NOT_FOUND404Content does not exist
VALIDATION_ERROR400Invalid input (bad ID format, missing fields)
CONFLICT409ID conflict on create
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403Insufficient API key scope
INTERNAL_ERROR500Server error

Next steps