Raven Docs

Live Streaming Quickstart

Create a stream, mint host and viewer credentials, and join as host or viewer — on every supported SDK.

This walks through the shortest real path: create a stream from your backend, join as host, join as viewer, chat, and end the stream.

Prerequisites: a Raven project and a project API key (see API Keys) — streams are created and host/viewer credentials are minted with it, server-side, and never in a browser or app.

1. Create a stream

Your backend calls the Control API with your project API key — never exposed to the browser. Creating a stream also creates its attached chat conversation and the host's row in one call:

import { Raven } from '@corvidhq/server';
const raven = new Raven({ apiKey: process.env.RAVEN_API_KEY });
 
const stream = await raven.liveStreams.create({ title: 'Launch Day', hostIdentity: 'alice' });
// { id: 'stream_jRoD1T3EXh0PMJRGG4zYzQ', status: 'CREATED',
//   conversationId: 'conv_0iojWXJCtXZUUkDR7THBmQ',
//   chatRootMessageId: 'msg_Efm2zArYJTSRUr88BV5bZg',
//   hosts: [{ identity: 'alice', role: 'HOST' }], ... }

2. Mint the host's credentials

const hostCredential = await raven.liveStreams.addHost(stream.id, { identity: 'alice' });
// { identity: 'alice', role: 'HOST', rtc: {...}, chat: {...} }

This returns an RTC token with full publish permissions and a chat token with ADMIN scope, bundled together — forward this response unchanged to your frontend as the join credentials.

3. Join as host

import { LiveStream } from '@corvidhq/client';
 
const stream = await LiveStream.join(credentials); // exactly what addHost() returned, reshaped as { streamId, role, rtc, chat, chatRootMessageId }
 
await stream.room.enableCamera();
await stream.room.enableMicrophone();

stream.room is a real @corvidhq/rtc Room and stream.chat is a real @corvidhq/chat client — LiveStream composes them, it doesn't wrap or hide them.

Then flip the stream live:

await raven.liveStreams.start(stream.id);

4. Mint a viewer token and join

const viewerCredential = await raven.liveStreams.createViewerToken(stream.id, 'carol');

This is always subscribe-only — there's no field on this call that grants publish permission, by design.

const stream = await LiveStream.join(credentials); // role: 'VIEWER'
 
stream.room.on('trackSubscribed', (track, participant) => {
  if (track.kind === 'camera') track.attach(videoEl);
});

5. Chat and react

await stream.chat.sendMessage({ text: 'hey!' });
stream.chat.on('message', (m) => console.log(m.senderId, m.text));
 
await stream.react('❤️');
stream.chat.on('reactionAdded', (e) => console.log(e.userId, e.emoji));

6. End the stream

await raven.liveStreams.end(stream.id);

This closes the underlying room (disconnecting any remaining participants) and fires live_stream.ended. Calling start or end again on the same stream is rejected — the lifecycle only moves forward.

What Raven handles vs. what you handle

Raven handles: the RTC room, the attached chat conversation, and enforcing that a viewer's credential can never publish — regardless of which SDK or endpoint mints it.

You handle: deciding who's allowed to host (your own authorization), and the UI around joining/leaving/reacting.

Common errors

ErrorWhyFix
RAVEN_STREAM_NOT_FOUNDstreamId wrong, or the stream belongs to a different project/environment.Confirm you're using the id create() returned, not a guess.
RAVEN_STREAM_INVALID_STATECalling start() on a non-CREATED stream, or end() on a non-LIVE one.Check stream.status first — the lifecycle only moves forward.
Viewer's room.enableCamera() throws PERMISSION_DENIEDExpected — a viewer token always has publish: false.There's no client-side workaround; mint a host/co-host credential instead.

Production notes

  • Never call raven.liveStreams/raven.live_streams methods from a browser or app — they need your project API key.
  • addHost()/createViewerToken() are the security-critical calls: the role a client ends up with is determined entirely by which one your backend calls, never by anything the client sends.
  • A stream's chatRootMessageId is required for stream.react() to work — always forward it as part of the join credentials.

Full working example

examples/live-streaming-demo in the Raven repo is a complete two-tab host/viewer demo — a FastAPI backend minting credentials and a plain HTML/JS frontend using exactly the calls above, with no bundler.