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/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} | 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 |
DELETE | /v1/stream/assets/playback-sessions/{sessionId} | Revoke a playback session immediately |
POST | /v1/stream/assets/{assetId}/playback-sessions | Mint a viewer playback token |
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-sessions | Mint a viewer token for a live channel |
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 |