Videos

Videos are an automatic specialization of files stored in Cloudflare Stream, keyed 1:1 by file id.

Videos are an automatic specialization of files. Every video is backed by a regular file row — there is no separate video id. When you upload a file with a video/* MIME type, an event handler copies the bytes into Cloudflare Stream and inserts a 1

side-table row keyed by the underlying file id. Cloudflare handles ingest, adaptive bitrate transcoding (HLS), automatic thumbnails, and a global CDN-backed player — you never touch raw video bytes after upload.

Storage model

Videos live in Cloudflare Stream, not in the workspace R2 bucket. The video row records:

  • fileId — the primary key, matches the file row id
  • cfStreamUid — the Cloudflare Stream UID returned from the copy-from-URL call
  • duration, width, height, size, metadata
  • transcodingStatusqueued, processing, ready, or error
  • workspaceId — the workspace the video belongs to

Creating a video

You don't create videos directly — just upload a file. On file.ready with a video/* MIME, the runtime emits video.provision_requested; a handler tells Cloudflare Stream to pull the bytes from a signed R2 URL, inserts the video side-table row (transcoding processing), and emits video.created. Transcoding then runs on Cloudflare's side asynchronously.

The API lazy-reconciles processing status against CF Stream on each GET /api/videos/:file_id call, so simply polling the video is enough — no separate status endpoint, no webhook to wire up. On the first terminal transition, the reconciler emits video.ready or video.error so downstream handlers can react.

Playback

Every video exposes two derived URLs at read time:

  • playbackUrl — an HLS manifest (.m3u8)
  • thumbnailUrl — an auto-generated JPG thumbnail

The platform renders video with @cloudflare/stream-react — a thin <Stream> component that wraps the upstream Cloudflare Stream player and handles adaptive bitrate, buffering, and picture-in-picture for you.

Operations

  • GET /api/videos — list videos, filter by workspace_id
  • GET /api/videos/:file_id — retrieve a single video by file id (lazy-reconciles processing status)
  • DELETE /api/videos/:file_id — delete the video side table row and cascade into the underlying file

All three operations are also available via the CLI ( aeontel video list/get/delete), the MCP server ( list_videos, get_video, delete_video), and the React hooks ( useListVideos, useRetrieveVideo, useDeleteVideo). There is no create_video — upload a file with a video/* MIME and the video row is created automatically.