v0.1.0 — early preview →

Upload once.
Stream anywhere.

One type-safe API for video across Rehelios, Mux, Bunny Stream, and Cloudflare Stream. Swap the adapter, keep every call site — and let the compiler catch what a provider can't do.

Read the docs →

Live snippet

The exact same code. Any provider.

Switch the adapter, keep every call site. Here is the same upload, playback, and thumbnail sequence across four providers.

import { createVideos } from "videos-sdk";import { rehelios } from "videos-sdk/rehelios";const videos = createVideos({  adapter: rehelios({ apiKey: process.env.REHELIOS_API_KEY! }),});await videos.upload("intro.mp4", file);const { hls } = await videos.playback(id);const poster = videos.thumbnail(id, { time: 3 });

Capabilities

Everything you need for video.

A complete set of operations behind one interface, capability-safe by types, with a normalized asset lifecycle and playback that works the same on every provider.

Every operation, one interface

Read the docs →

create, upload, get, list, delete — the same calls on every adapter, returning one normalized Asset. No provider types leak into your code.

await videos.upload("intro.mp4", file);const asset = await videos.get(id);const page = await videos.list({ limit: 20 });await videos.delete(id);
intro.mp448 MBready
webinar.mp41.2 GBprocessing
promo.mov210 MBready
raw-take.mkv3.4 GBwaiting_upload

Unsupported is a compile error

Read the docs →

Each adapter declares its capabilities as literal types. The API narrows to match, so a provider that can't do something never lets you call it.

const mux = createVideos({ adapter: mux(cfg) });(await mux.playback(id)).dash;//                       ❌ Mux has no DASHconst cf = createVideos({ adapter: cloudflare(cfg) });(await cf.playback(id)).dash; // ✅ string
await mux.playback(id).dash
Property 'dash' does not exist on type 'Playback'.
await cf.playback(id).dash✓ string

One normalized lifecycle

Read the docs →

Every provider's status codes collapse to five canonical states, so your app reads the same asset lifecycle no matter what's behind the adapter.

const { status } = await videos.get(id);// "waiting_upload" | "uploading"// "processing" | "ready" | "errored"
1waiting_upload
2processing
3ready

Playback & signed URLs

Read the docs →

HLS and DASH manifests, poster thumbnails at any timestamp, and short-lived signed playback URLs — one shape across every backend.

const { hls, dash } = await videos.playback(id);const poster = videos.thumbnail(id, { time: 3 });const url = await videos.signedPlayback(id, {  expiresInSeconds: 3600,});
signed
HLSDASH2:14

Honest by design

One interface, honest about the differences.

Where a provider can't do something, the SDK says so — in the types and in this table.

CapabilityReheliosMuxBunnyCloudflare
Resumable upload
Ingest from URL
HLS playback
DASH playback
Signed playback
Thumbnail at time
Captions
Webhooks

Ship the video layer once.

Open source, MIT licensed, built around web standards. Drop in an adapter and forget the difference.