Getting Started
Install videos-sdk and go from an empty project to playable video in a few steps.
Videos SDK is one small, honest API over multiple video providers. You write against a single interface; the adapter decides which backend runs underneath. Swap the adapter and every call site stays the same — and the compiler stops you from calling something a provider can't do.
Install
bun add videos-sdknpm install videos-sdkpnpm add videos-sdkyarn add videos-sdkThe core package has no provider dependencies. Mux ships its native client as an optional
peer dependency (@mux/mux-node); the other adapters use fetch.
Create a client
Pick an adapter and pass its config. This example uses Rehelios, the reference adapter:
import { createVideos } from 'videos-sdk';
import { rehelios } from 'videos-sdk/rehelios';
const videos = createVideos({
adapter: rehelios({ apiKey: process.env.REHELIOS_API_KEY! }),
});videos is fully typed for that adapter — including which optional capabilities exist.
Upload a video
upload takes a key and a body (Blob, ReadableStream, Uint8Array, ArrayBuffer, or
string) and returns a normalized Asset:
const asset = await videos.upload('intro.mp4', file);
asset.id; // provider id, normalized to a string
asset.status; // "waiting_upload" | "uploading" | "processing" | "ready" | "errored"
asset.duration; // seconds, when knownFor large files, prefer a resumable signed upload URL and upload straight from the browser — no bytes through your server:
const ticket = await videos.signedUploadUrl();
// { url, id, method: "TUS" | "PUT" | "POST" } — hand `url` to the clientWait until it's ready
Encoding is async on every provider. Poll get (or use webhooks)
and read the normalized status:
let current = await videos.get(asset.id);
while (current.status === 'processing') {
await new Promise((r) => setTimeout(r, 2000));
current = await videos.get(asset.id);
}Play it back
playback returns manifest URLs and a poster; thumbnail builds a still at any timestamp:
const { hls, dash, poster } = await videos.playback(asset.id);
const still = videos.thumbnail(asset.id, { time: 3 });
// short-lived signed playback for private content
const url = await videos.signedPlayback(asset.id, { expiresInSeconds: 3600 });dash only exists on the type for providers that support it (Rehelios, Cloudflare). On
Mux or Bunny, reading .dash is a compile error — see Capability-safe by
types.
Switch providers
The only thing that changes is the adapter import and its config. Every call site —
upload, get, list, delete, playback, thumbnail, signedPlayback — is identical:
import { mux } from 'videos-sdk/mux';
const videos = createVideos({ adapter: mux({ tokenId, tokenSecret }) });See the adapters for each provider's config and capabilities.
Handling errors
Every failure throws a typed VideoError with a discriminated code, so error handling
is the same across providers:
import { VideoError } from 'videos-sdk';
try {
await videos.get(id);
} catch (error) {
if (error instanceof VideoError && error.code === 'not_found') {
// ...
}
}More in Errors.