---
title: "Videos"
description: "Videos are an automatic specialization of files stored in Cloudflare Stream, keyed 1:1 by file id."
section: "General"
group: "Concepts"
---

# Videos

Videos are an automatic specialization of [files](/concepts/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: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`
- `transcodingStatus` — `queued`,
  `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.
