Upload, encode and deliver video. Broadcast live. Run interactive rooms.
Base URL https://api.bolrach.com — every endpoint lives under /v1/stream.
Send your API key as a bearer token on every request. Keys are per application, so revoking one does not affect the rest of your estate.
curl https://api.bolrach.com/v1/stream/live/channels \
-H "Authorization: Bearer <your api key>"
Your file never passes through this API. You ask for an upload session, get signed URLs, and send the bytes straight to storage. That is why upload size is bounded by your plan rather than by any request timeout, and why a dropped connection resumes instead of restarting.
// 1. start a session — bytes decides the part size
const s = await post('/v1/stream/uploads', {
bytes: file.size, filename: file.name, title: 'Lecture 1',
idempotencyKey: 'lecture-1-v1', // a retry returns THIS session, not a second one
});
// 2. send each part directly to storage
for (let n = 1; n <= s.partsExpected; n++) {
const { parts } = await post(`/v1/stream/uploads/${s.uploadId}/parts/sign`, { partNumbers: [n] });
const start = (n - 1) * s.partSizeBytes;
await fetch(parts[0].url, { method: 'PUT', body: file.slice(start, start + s.partSizeBytes) });
}
// 3. finish — this starts probing and encoding
await post(`/v1/stream/uploads/${s.uploadId}/complete`, {});
partSizeBytes exactly. Slicing the file your own way produces a part count that does
not match what was reserved, and complete will refuse it.Mint a short-lived token per viewer, then hand them the HLS or embed URL.
const p = await post(`/v1/stream/assets/${assetId}/playback-sessions`, {
host: 'media.bolrach.video',
expiresInSeconds: 3600,
viewerId: 'user_1042',
});
// p.embedUrl -> drop in an iframe
// p.hlsUrl -> feed your own player
const ch = await post('/v1/stream/live/channels', { name: 'Main Stage', record: true });
// ch.ingest.rtmp.url + ch.ingest.rtmp.streamKey -> point your encoder here
// ch.ingest.backup.streamKey -> failover
// ch.playback.hls -> viewers
Stream keys are shown once; only hashes are stored, so a lost key is rotated
rather than recovered. A channel already carrying a primary publisher refuses a second one — use
the backup key to take over, which makes failover deliberate instead of accidental. Set
record: true and each broadcast becomes an ordinary on-demand asset when it ends,
through the same processing every upload goes through.
Live is currently passthrough: viewers receive your source quality with no adaptive ladder.
Multi-party real-time sessions. Create a room, then issue one token per participant.
role is enforced rather than advisory — a viewer token cannot publish
media. A room can also be broadcast to one of your live channels, where it gets the same
recording, metering and playback as any other broadcast.
Errors are JSON with a stable error code and a human message.
401 means the key is missing or wrong, 403 means the key is valid but not
permitted, 429 means slow down. Plan limits (storage, concurrent encodes, live
channels, rooms, custom domains) return 400 with a code naming the limit and the
current usage, so you can show the user something true.
Machine-readable: OpenAPI 3.1 · endpoint-by-endpoint: API reference · console: stream.bolrach.com/console.
GET | /v1/stream/docs | Human-readable documentation |
GET | /v1/stream/health/live | Liveness probe |
GET | /v1/stream/health | Service health |
GET | /v1/stream/openapi.json | This OpenAPI document |
DELETE | /v1/stream/uploads/{uploadId} | Abort an upload and release its quota |
GET | /v1/stream/uploads/{uploadId} | Upload status and progress |
GET | /v1/stream/watermarks | List tenant watermark images |
POST | /v1/stream/uploads/{uploadId}/complete | Finish the upload |
POST | /v1/stream/uploads/{uploadId}/parts/sign | Get signed URLs for parts |
POST | /v1/stream/uploads/{uploadId}/pause | Pause an upload |
POST | /v1/stream/uploads/{uploadId}/resume | Resume a paused upload |
POST | /v1/stream/uploads | Start a resumable upload |
POST | /v1/stream/watermarks | Register a watermark image |
OPTIONS | /v1/stream/collect | Telemetry CORS preflight |
POST | /v1/stream/analytics/ttff | Time-to-first-frame beacon (player) |
POST | /v1/stream/collect | Player QoE beacon |
GET | /v1/stream/assets/{assetId}/deletion | Read durable deletion progress |
GET | /v1/stream/assets/{assetId}/policy | Read the current asset policy generation |
GET | /v1/stream/assets/{assetId}/source | Get a tenant-owned original for provenance or archival inspection |
GET | /v1/stream/assets/{assetId} | Read one asset |
GET | /v1/stream/assets | List your assets |
PATCH | /v1/stream/assets/{assetId} | Change who can watch an asset |
POST | /v1/stream/assets/{assetId}/captions | Request captions for a published asset |
POST | /v1/stream/assets/{assetId}/deletion/cancel | Cancel deletion before physical cleanup starts |
POST | /v1/stream/assets/{assetId}/deletion | Request durable primary storage deletion |
POST | /v1/stream/assets/{assetId}/protect | Make an asset play only through protected playback |
DELETE | /v1/stream/assets/playback-sessions/{sessionId} | Revoke a playback session immediately |
POST | /v1/stream/assets/{assetId}/playback-sessions | Mint a viewer playback token |
DELETE | /v1/stream/assets/{assetId}/ads | The video follows its library's ad breaks again |
GET | /v1/stream/ads/settings | Your library's ad breaks |
GET | /v1/stream/assets/{assetId}/ads | A video's ad breaks: library, its own, and what playback serves |
GET | /v1/stream/assets/{assetId}/poster | Poster of an asset, protected ones included (your server only) |
GET | /v1/stream/playback/jwks.json | Public keys that verify playback v2 tokens (RFC 8037 JWK set) |
GET | /v1/stream/playback/key/{keyId} | HLS AES-128 content key (protected) |
GET | /v1/stream/playback/public/{assetId} | Resolve a PUBLIC asset to its manifest (embeds) |
GET | /v1/stream/playback/session/{sid}/live/{file} | Per-session live playlists for native HLS players (protected live) |
GET | /v1/stream/playback/session/{sid}/{file} | Per-session HLS playlist (protected) |
POST | /v1/stream/assets/{assetId}/dmca/release | Release a DMCA hold (512(g) put-back, withdrawal, rejection) |
POST | /v1/stream/assets/{assetId}/dmca | Place a DMCA hold on an asset (17 U.S.C. 512(c)) |
POST | /v1/stream/playback/heartbeat | Playback heartbeat (protected) |
POST | /v1/stream/playback/session | Start playback (public: clean URL; protected: signed session) |
PUT | /v1/stream/ads/settings | Set your library's ad breaks (Earn with ads) |
PUT | /v1/stream/assets/{assetId}/ads | Set one video's ad breaks (overrides the library) |
GET | /v1/stream/live/channels/{channelId}/recordings | Recordings of a channel's broadcasts |
GET | /v1/stream/live/channels/{channelId} | Channel status |
GET | /v1/stream/live/channels | List live channels |
POST | /v1/stream/live/channels/{channelId}/keys/rotate | Rotate a stream key |
POST | /v1/stream/live/channels/{channelId}/playback-class | Make a channel's broadcasts public or protected (live v2) |
POST | /v1/stream/live/channels/{channelId}/playback-sessions | Mint a viewer token for a live channel |
POST | /v1/stream/live/channels/{channelId}/revoke | Stop and retire a channel |
POST | /v1/stream/live/channels/{channelId}/start | Open a channel for publishing |
POST | /v1/stream/live/channels/{channelId}/stop | Stop the broadcast |
POST | /v1/stream/live/channels | Create a live channel |
DELETE | /v1/stream/rooms/{roomId} | End a room and disconnect everyone |
GET | /v1/stream/rooms/{roomId}/participants | Who is in the room |
GET | /v1/stream/rooms | List rooms |
POST | /v1/stream/rooms/{roomId}/broadcast | Broadcast a room to a live channel |
POST | /v1/stream/rooms/{roomId}/tokens | Issue a participant join token |
POST | /v1/stream/rooms | Create an interactive room |
DELETE | /v1/stream/domains/{domainId} | Remove a custom domain |
GET | /v1/stream/domains/{domainId} | Domain status |
GET | /v1/stream/domains | List custom domains |
POST | /v1/stream/domains/{domainId}/verify | Check verification and issue a certificate |
POST | /v1/stream/domains | Add a custom playback domain |