Skip to main content

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

FieldTypeRequiredDescription
cloudflare_video_idstringYesCloudflare Stream video ID
titlestringYesVOD title
descriptionstringNoVOD description
visibilitystringNopublic, followers, or private
group_idUUIDNoPost to a group
content_warningstringNoContent warning text
is_nsfwbooleanNoNSFW 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 parameterTypeDescription
statusstringNew status value

Valid status transitions:

FromTo
processingready
processingfailed
readydeleted
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

FieldTypeDefaultDescription
modelstringgpt-4oAI model to use
temperaturefloat0.3Model temperature
max_tokensinteger2000Max 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

ParameterTypeDescription
sourcestringFilter by manual or ai
searchstringKeyword search in title and description
min_confidencefloatMinimum 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.

ParameterTypeDescription
timestampintegerTimestamp 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

FieldTypeDefaultDescription
timestampsfloat[]Custom timestamps in seconds. If omitted, auto-generates at 20%, 40%, 60%, 80%, 100% of duration
resolutionsstring[]["small","medium","large"]Resolutions to generate
set_defaultinteger2Index of thumbnail to set as default (0-based)

Resolution sizes:

NameDimensionsUse case
small320×180Video cards
medium640×360List views
large1280×720Detail pages
xlarge1920×1080Fullscreen

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

FieldTypeDefaultDescription
qualitiesstring[]["360p","720p","1080p"]Quality levels to generate
formatstringhlsOutput format (only HLS supported)
prioritystringnormalProcessing priority: normal or high

Quality specifications:

QualityResolutionBitrateUse case
360p640×360800 kbpsMobile / slow connections
720p1280×7202500 kbpsStandard HD
1080p1920×10805000 kbpsFull 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 parameterTypeDescription
job_idstringJob 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:

StatusDescription
queuedWaiting to start
processingCurrently transcoding
completedAll qualities done
failedTranscoding 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.