VOD & Recordings
Streams are automatically recorded as VODs (Video on Demand) when they end. You can also upload standalone VOD files. This page covers VOD access, chapter markers, stream scheduling, and thumbnail and transcoding APIs.
VOD upload
POST /api/v1/vod/upload
Upload a VOD recording and automatically create a linked community post. The video must first be uploaded to Cloudflare Stream — this endpoint links the Cloudflare video to the platform and creates the post.
Auth required: Yes
The authenticated user must belong to a tenant (organization).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
cloudflare_video_id | string | Yes | Cloudflare Stream video ID |
title | string | Yes | VOD title |
description | string | No | VOD description |
visibility | string | No | public, followers, or private |
group_id | UUID | No | Post to a group |
content_warning | string | No | Content warning text |
is_nsfw | boolean | No | NSFW flag |
Response
{
"vod": {
"id": "vod-uuid",
"status": "processing",
"title": "My coding session",
"cloudflare_video_id": "abc123",
"playback_url": "https://...",
"created_at": "2026-04-25T11:00:00Z"
},
"post": {
"id": "post-uuid",
"type": "VIDEO",
"content": "My coding session",
"visibility": "public",
"created_at": "2026-04-25T11:00:00Z"
}
}
Get VOD details
GET /api/v1/vod/{vod_id}
Get full details for a VOD including the linked community post.
Auth required: Yes
Returns 404 if the VOD is not found within the user's tenant.
Update VOD status
PATCH /api/v1/vod/{vod_id}/status
Update the processing status of a VOD. Used by the processing pipeline to mark a VOD as ready or failed.
Auth required: Yes
| Query parameter | Type | Description |
|---|---|---|
status | string | New status value |
Valid status transitions:
| From | To |
|---|---|
processing | ready |
processing | failed |
ready | deleted |
PATCH /api/v1/vod/vod-uuid/status?status=ready
Chapter markers
Chapters divide a video into named segments for navigation. Chapters can be created manually or generated automatically using AI analysis of the video transcript.
Generate AI chapters
POST /api/v1/videos/{video_id}/chapters/generate
Automatically generate chapter markers from a video's transcript. The AI analyzes content structure and timestamps to produce named segments.
Auth required: Yes (video owner only)
The video must have a transcript stored in its metadata. If no transcript exists, the request returns 400.
Existing AI-generated chapters are replaced on each call. Manually created chapters are preserved.
Request body
| Field | Type | Default | Description |
|---|---|---|---|
model | string | gpt-4o | AI model to use |
temperature | float | 0.3 | Model temperature |
max_tokens | integer | 2000 | Max response tokens |
Response
{
"chapters": [
{
"id": "chapter-uuid",
"video_id": "video-uuid",
"title": "Introduction",
"description": "Overview of the session goals",
"start_time": 0,
"end_time": 120,
"order": 0,
"source": "ai",
"confidence_score": 0.92,
"thumbnail_url": null
}
],
"count": 8,
"model_used": "gpt-4o",
"processing_time_seconds": 4.2
}
List chapters
GET /api/v1/videos/{video_id}/chapters
List all chapters for a video. Chapters are returned in order.
Auth required: No
| Parameter | Type | Description |
|---|---|---|
source | string | Filter by manual or ai |
search | string | Keyword search in title and description |
min_confidence | float | Minimum AI confidence score (0.0–1.0) |
Get chapter
GET /api/v1/videos/{video_id}/chapters/{chapter_id}
Get a specific chapter by ID.
Create manual chapter
POST /api/v1/videos/{video_id}/chapters
Create a chapter manually. Validates that the time range is within the video duration and does not overlap with existing chapters.
Auth required: Yes (video owner only)
{
"title": "Database setup",
"description": "Configuring PostgreSQL and running migrations",
"start_time": 540,
"end_time": 1080,
"order": 3,
"thumbnail_url": "https://..."
}
start_time and end_time are in seconds. Overlapping chapters return 400.
Update chapter
PUT /api/v1/videos/{video_id}/chapters/{chapter_id}
Update an existing chapter. Time range updates are validated for overlaps.
Auth required: Yes (video owner only)
Updatable fields: title, description, start_time, end_time, order, thumbnail_url.
Delete chapter
DELETE /api/v1/videos/{video_id}/chapters/{chapter_id}
Delete a chapter. Returns 204 No Content.
Auth required: Yes (video owner only)
Chapter navigation
Find chapter at timestamp
GET /api/v1/videos/{video_id}/chapters/at-time
Find the chapter that contains a specific timestamp.
| Parameter | Type | Description |
|---|---|---|
timestamp | integer | Timestamp in seconds |
Returns 404 if no chapter covers the given timestamp.
Get next chapter
GET /api/v1/videos/{video_id}/chapters/{chapter_id}/next
Get the next chapter in sequence. Returns 404 if there is no next chapter.
Get previous chapter
GET /api/v1/videos/{video_id}/chapters/{chapter_id}/previous
Get the previous chapter in sequence. Returns 404 if at the beginning.
Thumbnail generation
Thumbnails are generated asynchronously via Celery. The endpoint returns immediately with a task ID.
Generate thumbnails
POST /api/v1/projects/{project_id}/database/videos/{video_id}/thumbnails
Generate thumbnails for a video.
Auth required: Yes
The video must have status ready.
Request body
| Field | Type | Default | Description |
|---|---|---|---|
timestamps | float[] | — | Custom timestamps in seconds. If omitted, auto-generates at 20%, 40%, 60%, 80%, 100% of duration |
resolutions | string[] | ["small","medium","large"] | Resolutions to generate |
set_default | integer | 2 | Index of thumbnail to set as default (0-based) |
Resolution sizes:
| Name | Dimensions | Use case |
|---|---|---|
small | 320×180 | Video cards |
medium | 640×360 | List views |
large | 1280×720 | Detail pages |
xlarge | 1920×1080 | Fullscreen |
Response
{
"video_id": "video-uuid",
"thumbnails": [],
"task_id": "celery-task-id",
"default_thumbnail_url": null,
"processing_time_ms": null
}
The thumbnails array is empty until processing completes. Use the task ID to track progress or wait for webhook delivery.
List thumbnails
GET /api/v1/projects/{project_id}/database/videos/{video_id}/thumbnails
Get all generated thumbnails for a video.
Auth required: Yes
[
{
"thumbnail_id": "thumb-uuid",
"url": "https://cdn.ainative.studio/thumbs/abc.jpg",
"webp_url": "https://cdn.ainative.studio/thumbs/abc.webp",
"timestamp": 120.0,
"resolution": "640x360",
"size_bytes": 45231,
"is_default": true
}
]
Update default thumbnail
PUT /api/v1/projects/{project_id}/database/videos/{video_id}/default-thumbnail
Set a specific thumbnail as the default. The default thumbnail is shown in video cards, lists, and previews.
Auth required: Yes
{
"thumbnail_url": "https://cdn.ainative.studio/thumbs/abc.jpg"
}
Delete thumbnail
DELETE /api/v1/projects/{project_id}/database/videos/{video_id}/thumbnails/{thumbnail_id}
Delete a specific thumbnail. Cannot delete the default thumbnail — set another as default first.
Auth required: Yes
Video transcoding
Transcode videos to HLS format with multiple quality levels for adaptive bitrate streaming. Processing runs asynchronously via Celery.
Start transcoding
POST /api/v1/projects/{project_id}/database/videos/{video_id}/transcode
Queue a transcoding job. Returns 202 Accepted immediately.
Auth required: Yes
The video must have status ready.
Request body
| Field | Type | Default | Description |
|---|---|---|---|
qualities | string[] | ["360p","720p","1080p"] | Quality levels to generate |
format | string | hls | Output format (only HLS supported) |
priority | string | normal | Processing priority: normal or high |
Quality specifications:
| Quality | Resolution | Bitrate | Use case |
|---|---|---|---|
360p | 640×360 | 800 kbps | Mobile / slow connections |
720p | 1280×720 | 2500 kbps | Standard HD |
1080p | 1920×1080 | 5000 kbps | Full HD |
Estimated processing times:
- 10-minute video: 5–8 minutes for all three qualities
- 1-hour video: 30–45 minutes for all three qualities
Response
{
"job_id": "transcode_video-uuid_1745568000",
"video_id": "video-uuid",
"status": "queued",
"progress": 0,
"estimated_time": "8m 30s",
"qualities_requested": ["360p", "720p", "1080p"]
}
Get transcoding status
GET /api/v1/projects/{project_id}/database/videos/{video_id}/transcode/status
Poll for transcoding progress. Poll every 5–10 seconds during processing. Stop when status is completed or failed.
Auth required: Yes
| Query parameter | Type | Description |
|---|---|---|
job_id | string | Job ID from the start response. If omitted, returns status of the most recent job. |
Response
{
"job_id": "transcode_video-uuid_1745568000",
"status": "processing",
"progress": 62,
"current_quality": "720p",
"completed_qualities": ["360p"],
"failed_qualities": [],
"estimated_time_remaining": "3m 15s",
"outputs": [
{
"quality": "360p",
"url": "https://cdn.ainative.studio/hls/video-uuid/360p/playlist.m3u8",
"bitrate": "800k",
"resolution": "640x360",
"segment_count": 42,
"size_bytes": 24300000,
"status": "completed"
}
],
"master_playlist_url": null,
"error": null
}
Status values:
| Status | Description |
|---|---|
queued | Waiting to start |
processing | Currently transcoding |
completed | All qualities done |
failed | Transcoding failed — check error field |
When status is completed, master_playlist_url contains the adaptive bitrate master playlist URL.
Cancel transcoding
DELETE /api/v1/projects/{project_id}/database/videos/{video_id}/transcode/{job_id}
Cancel an in-progress transcoding job. Partial results may be preserved. Cannot cancel already-completed jobs.
Auth required: Yes
Retry failed quality
POST /api/v1/projects/{project_id}/database/videos/{video_id}/transcode/retry/{quality}
Retry transcoding for a single quality level that failed. Useful when only one quality fails and others succeed.
Auth required: Yes
quality must be one of: 360p, 720p, 1080p.