LiveRelay Documentation

A Rust-powered WebRTC SFU as-a-service. Stream live video in 5 minutes.


Quick Start

Go from zero to live streaming in five steps. All you need is a running LiveRelay server and a browser.

1Get your API key

When you start the LiveRelay server, a bootstrap API key is logged to stdout:

terminal
INFO LiveRelay SFU listening on http://0.0.0.0:8080
INFO Bootstrap API key: lr_aebfc064dd0c8fa2...

Or create one via the Dashboard.

2Create a room
bash
curl -X POST https://your-server.com/v1/rooms \
  -H "Authorization: Bearer lr_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"room_type": "broadcast"}'

Response:

json
{
  "id": "8271274b-fc62-4b73-98c5-40e28b7a1b81",
  "type": "broadcast",
  "tokens": {
    "publish": "eyJ...",
    "subscribe": "eyJ..."
  }
}
3Add the SDK

Include via a script tag:

html
<script src="https://your-server.com/static/liverelay.js"></script>

Or via npm:

bash
npm install @liverelay/sdk
4Publish a stream
javascript
const lr = new LiveRelay('https://your-server.com');

const session = await lr.publish({
  token: 'eyJ...publish_token',
  video: true,
  audio: true
});

console.log(session.state); // 'connected'
5Subscribe to a stream
javascript
const session = await lr.subscribe({
  token: 'eyJ...subscribe_token',
  element: document.getElementById('video')
});
That's it! You're streaming live video.

Authentication

LiveRelay uses a two-level authentication system that separates backend management from user-facing connections.

LevelWhoHowUsed for
API Key Your backend Authorization: Bearer lr_... REST API (create rooms, tokens)
JWT Token Your users Authorization: Bearer eyJ... SFU endpoints (publish, subscribe, call)

Authentication Flow

Your Backend LiveRelay Your Users ──────────────── ───────── ────────── Has API key (lr_...) ──POST /v1/rooms──► ◄──{tokens}──────── Gives token to user ─────────────────────────► Has JWT token ◄──POST /sfu/publish── Uses SDK ──SDP answer──────────►

API Key format

lr_ + 32 hex characters, e.g. lr_aebfc064dd0c8fa2cb0cbc01998e3f9b

JWT Token claims

json
{
  "sub": "peer-uuid",
  "room_id": "room-uuid",
  "role": "publish|subscribe|call",
  "key_id": "lr_aebfc",
  "exp": 1770498859,
  "iat": 1770412459
}

Tokens expire after 24 hours by default.


REST API Reference

All REST endpoints require an API key unless noted otherwise. Send it via the Authorization: Bearer lr_... header.

POST /v1/rooms Create a room

Creates a new broadcast or call room and returns join tokens.

bash
curl -X POST https://your-server.com/v1/rooms \
  -H "Authorization: Bearer lr_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"room_type": "broadcast"}'

Request body

FieldTypeDescription
room_typestring"broadcast" (1 publisher, N subscribers) or "call" (2 peers, bidirectional)

Response — broadcast

json
{
  "id": "uuid",
  "type": "broadcast",
  "tokens": {
    "publish": "jwt...",
    "subscribe": "jwt..."
  }
}

Response — call

json
{
  "id": "uuid",
  "type": "call",
  "tokens": {
    "caller": "jwt...",
    "callee": "jwt..."
  }
}

Errors

StatusCodeCause
401auth_header_missingMissing Authorization header
401api_key_invalidInvalid API key
GET /v1/rooms List rooms
bash
curl https://your-server.com/v1/rooms \
  -H "Authorization: Bearer lr_your_api_key"

Response

json
[
  {
    "room_id": "uuid",
    "room_type": "broadcast",
    "publisher_count": 1,
    "subscriber_count": 42,
    "created_at_secs": 120
  }
]
GET /v1/rooms/:room_id Get room info

Returns the same object as a single list item.

bash
curl https://your-server.com/v1/rooms/8271274b-fc62-4b73-98c5-40e28b7a1b81 \
  -H "Authorization: Bearer lr_your_api_key"

Errors

StatusCode
404room_not_found
DELETE /v1/rooms/:room_id Delete a room
bash
curl -X DELETE https://your-server.com/v1/rooms/8271274b-fc62-4b73-98c5-40e28b7a1b81 \
  -H "Authorization: Bearer lr_your_api_key"

Returns 204 No Content on success.

Errors

StatusCode
404room_not_found
POST /v1/rooms/:room_id/token Generate a token

Generate an additional JWT token for an existing room.

bash
curl -X POST https://your-server.com/v1/rooms/uuid/token \
  -H "Authorization: Bearer lr_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"role": "subscribe"}'

Request body

FieldTypeDescription
rolestring"publish", "subscribe", or "call"

Response

json
{
  "token": "jwt..."
}

Errors

StatusCode
404room_not_found
400invalid_role
POST /v1/keys Create API key

Requires an existing API key. Creates a new API key for programmatic access.

bash
curl -X POST https://your-server.com/v1/keys \
  -H "Authorization: Bearer lr_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app"}'

Response

json
{
  "key": "lr_...",
  "name": "my-app"
}
GET /health Health check (no auth)
This endpoint does not require authentication.
bash
curl https://your-server.com/health

Response

json
{
  "status": "ok",
  "version": "0.2.0",
  "rooms_active": 5,
  "subscribers_active": 12
}

SFU Endpoints

These endpoints use JWT token authentication (not API keys). The token's role claim determines access.

POST /sfu/publish Start publishing

Requires a JWT with role "publish" or "call".

bash
curl -X POST https://your-server.com/sfu/publish \
  -H "Authorization: Bearer eyJ...jwt_token" \
  -H "Content-Type: application/json" \
  -d '{"sdp": "v=0...", "type": "offer"}'

Request body

FieldTypeDescription
sdpstringWebRTC SDP offer
typestringAlways "offer"

Response

json
{
  "sdp": "v=0...",
  "type": "answer"
}
POST /sfu/subscribe Start subscribing

Requires a JWT with role "subscribe" or "call".

bash
curl -X POST https://your-server.com/sfu/subscribe \
  -H "Authorization: Bearer eyJ...jwt_token" \
  -H "Content-Type: application/json" \
  -d '{"sdp": "v=0...", "type": "offer"}'

Response

json
{
  "sdp": "v=0...",
  "type": "answer"
}
POST /sfu/call Join a 1:1 call

Requires a JWT with role "call".

bash
curl -X POST https://your-server.com/sfu/call \
  -H "Authorization: Bearer eyJ...jwt_token" \
  -H "Content-Type: application/json" \
  -d '{"sdp": "v=0...", "type": "offer"}'

Response

json
{
  "sdp": "v=0...",
  "type": "answer"
}

SDK Reference

Installation

html
<script src="https://your-server.com/static/liverelay.js"></script>
<!-- Available as window.LiveRelay -->

Constructor

javascript
new LiveRelay(serverUrl)
new LiveRelay({ server: serverUrl })

LiveRelay.VERSION  // "0.2.0"

lr.publish(options) → Promise<PublishSession>

javascript
const session = await lr.publish({
  token: string,           // Required: JWT publish token
  stream: MediaStream,     // Option A: provide your own stream
  video: boolean,          // Option B: auto-capture (default: false)
  audio: boolean,          // Option B: auto-capture (default: false)
  onStateChange: function  // Optional: callback(state)
});

lr.subscribe(options) → Promise<SubscribeSession>

javascript
const session = await lr.subscribe({
  token: string,              // Required: JWT subscribe token
  element: HTMLVideoElement,  // Required: video element to render to
  onStateChange: function     // Optional: callback(state)
});

lr.call(options) → Promise<CallSession>

javascript
const session = await lr.call({
  token: string,              // Required: JWT call token
  stream: MediaStream,        // Option A: provide your own stream
  video: boolean,             // Option B: auto-capture
  audio: boolean,             // Option B: auto-capture
  element: HTMLVideoElement,  // Required: for remote video
  localElement: HTMLVideoElement, // Optional: local preview
  onStateChange: function     // Optional: callback(state)
});

Session properties & methods

Common to all session types:

javascript
session.state          // 'connecting' | 'connected' | 'disconnected' | 'failed'
session.close()        // Close the session

Mute controls (PublishSession & CallSession)

javascript
session.muteAudio()
session.unmuteAudio()
session.muteVideo()
session.unmuteVideo()
session.isAudioMuted    // boolean
session.isVideoMuted    // boolean

LiveRelayError

javascript
try {
  await lr.publish({ token, video: true });
} catch (e) {
  if (e instanceof LiveRelayError) {
    console.log(e.code);       // 'AUTH_ERROR', 'NETWORK_ERROR', etc.
    console.log(e.message);    // Human-readable message
    console.log(e.httpStatus); // HTTP status code (if from server)
  }
}

SDK Error codes

CodeDescription
AUTH_ERRORToken is invalid, expired, or has wrong role
NETWORK_ERRORFailed to connect to the server
MEDIA_ERRORCamera/microphone access denied or unavailable
TIMEOUTConnection attempt timed out
SERVER_ERRORServer returned an unexpected error
ROOM_NOT_FOUNDRoom does not exist or was deleted
ROOM_FULLBroadcast room already has a publisher

Examples

Live Streaming (Twitch-like)

A broadcast room with one publisher streaming to many subscribers.

Publisher (streamer page)

javascript
const lr = new LiveRelay('https://api.liverelay.dev');

const session = await lr.publish({
  token: publishToken,
  video: true,
  audio: true
});

// Toggle mute
document.getElementById('mute-btn').onclick = () => {
  session.isAudioMuted ? session.unmuteAudio() : session.muteAudio();
};

// Stop stream
document.getElementById('stop-btn').onclick = () => session.close();

Subscriber (viewer page)

javascript
const lr = new LiveRelay('https://api.liverelay.dev');

const session = await lr.subscribe({
  token: subscribeToken,
  element: document.getElementById('player')
});

Video Call (1:1)

javascript
const lr = new LiveRelay('https://api.liverelay.dev');

const session = await lr.call({
  token: callToken,
  video: true,
  audio: true,
  element: document.getElementById('remote-video'),
  localElement: document.getElementById('local-video')
});

Node.js Backend Integration

Example Express.js routes for creating rooms and distributing tokens to your users.

javascript
const LIVERELAY = 'https://api.liverelay.dev';
const API_KEY = process.env.LIVERELAY_API_KEY;

// Create a broadcast room
app.post('/api/go-live', async (req, res) => {
  const room = await fetch(`${LIVERELAY}/v1/rooms`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ room_type: 'broadcast' })
  }).then(r => r.json());

  // Store room in your database
  await db.streams.create({
    userId: req.user.id,
    roomId: room.id,
    tokens: room.tokens
  });

  res.json({ publishToken: room.tokens.publish });
});

// Get subscribe token for viewers
app.get('/api/watch/:streamId', async (req, res) => {
  const stream = await db.streams.findById(req.params.streamId);
  res.json({ subscribeToken: stream.tokens.subscribe });
});

Error Codes

Complete reference of all error codes returned by the LiveRelay REST API and SFU endpoints.

CodeStatusMessageCause
auth_header_missing 401 Authorization header is missing No Bearer token in request
api_key_invalid 401 API key is not valid Wrong or revoked API key
token_invalid 401 Token signature is invalid Corrupted or wrong JWT
token_expired 401 Token has expired JWT past expiration time
role_insufficient 403 Token role cannot perform this action e.g., subscribe token used on /sfu/publish
room_not_found 404 Room does not exist Wrong room_id or room was deleted
room_full 409 Room has reached publisher limit Broadcast room already has a publisher
no_publisher_available 404 Room has no active publisher Trying to subscribe before anyone publishes
invalid_role 400 Invalid role value Role must be publish, subscribe, or call
room_type_mismatch 400 Wrong room type for this endpoint e.g., using /sfu/call on a broadcast room
invalid_sdp 400 Malformed SDP offer Invalid WebRTC session description
peer_connection_failed 500 WebRTC connection setup failed Server-side WebRTC error
All error responses follow the format: {"error": "code", "message": "Human-readable message"}