Bolrach Stream

Upload, encode and deliver video. Broadcast live. Run interactive rooms. Base URL https://api.bolrach.com — every endpoint lives under /v1/stream.

Authentication

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>"

Uploading

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`, {});
Part size is decided by the session, not by you. Use partSizeBytes exactly. Slicing the file your own way produces a part count that does not match what was reserved, and complete will refuse it.

Playback

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
The token is bound to the hostname you pass. A token minted for one host is refused on another. That is what stops one custom-domain tenant replaying another's token — so mint per viewer and never share one between them.

Live

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
The RTMP port is 1936, not 1935. Use the URL exactly as returned. Pointing an encoder at the default port reaches a different service entirely, and the failure looks like "live is broken" rather than "wrong port".

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.

Interactive rooms

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 and limits

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.

API reference

Machine-readable: OpenAPI 3.1 · endpoint-by-endpoint: API reference · console: stream.bolrach.com/console.

Health

GET/v1/stream/docsHuman-readable documentation
GET/v1/stream/health/liveLiveness probe
GET/v1/stream/healthService health
GET/v1/stream/openapi.jsonThis OpenAPI document

Uploads

DELETE/v1/stream/uploads/{uploadId}Abort an upload and release its quota
GET/v1/stream/uploads/{uploadId}Upload status and progress
GET/v1/stream/watermarksList tenant watermark images
POST/v1/stream/uploads/{uploadId}/completeFinish the upload
POST/v1/stream/uploads/{uploadId}/parts/signGet signed URLs for parts
POST/v1/stream/uploads/{uploadId}/pausePause an upload
POST/v1/stream/uploads/{uploadId}/resumeResume a paused upload
POST/v1/stream/uploadsStart a resumable upload
POST/v1/stream/watermarksRegister a watermark image

Analytics

OPTIONS/v1/stream/collectTelemetry CORS preflight
POST/v1/stream/collectPlayer QoE beacon

Assets

GET/v1/stream/assets/{assetId}/deletionRead durable deletion progress
GET/v1/stream/assets/{assetId}/policyRead the current asset policy generation
GET/v1/stream/assets/{assetId}Read one asset
GET/v1/stream/assetsList your assets
PATCH/v1/stream/assets/{assetId}Change who can watch an asset
POST/v1/stream/assets/{assetId}/captionsRequest captions for a published asset
POST/v1/stream/assets/{assetId}/deletion/cancelCancel deletion before physical cleanup starts
POST/v1/stream/assets/{assetId}/deletionRequest durable primary storage deletion

Playback

DELETE/v1/stream/assets/playback-sessions/{sessionId}Revoke a playback session immediately
POST/v1/stream/assets/{assetId}/playback-sessionsMint a viewer playback token

Live

GET/v1/stream/live/channels/{channelId}Channel status
GET/v1/stream/live/channelsList live channels
POST/v1/stream/live/channels/{channelId}/keys/rotateRotate a stream key
POST/v1/stream/live/channels/{channelId}/playback-sessionsMint a viewer token for a live channel
POST/v1/stream/live/channelsCreate a live channel

Rooms

DELETE/v1/stream/rooms/{roomId}End a room and disconnect everyone
GET/v1/stream/rooms/{roomId}/participantsWho is in the room
GET/v1/stream/roomsList rooms
POST/v1/stream/rooms/{roomId}/broadcastBroadcast a room to a live channel
POST/v1/stream/rooms/{roomId}/tokensIssue a participant join token
POST/v1/stream/roomsCreate an interactive room

Custom domains

DELETE/v1/stream/domains/{domainId}Remove a custom domain
GET/v1/stream/domains/{domainId}Domain status
GET/v1/stream/domainsList custom domains
POST/v1/stream/domains/{domainId}/verifyCheck verification and issue a certificate
POST/v1/stream/domainsAdd a custom playback domain