A stream is the record for one live event, like "Launch Day, Friday 6pm". This page covers everything your backend does with it: create, start, read, update, and end.
Where this fits: all of these calls run on your backend with your API key (or in your terminal with the CLI). Your app never calls them. It only joins a stream with the credentials from Hosts and Viewers.
Create a stream
Create one stream per event. A common pattern: create it when the host
schedules the event in your app, save stream.id next to your own event
record, and start it later.
const stream = await livqeno.liveStreams.create({ title: 'Launch Day', hostIdentity: 'alice' });stream = livqeno.live_streams.create(CreateLiveStreamParams(title="Launch Day", host_identity="alice"))stream, err := client.LiveStreams.Create(ctx, livqeno.CreateLiveStreamParams{Title: "Launch Day", HostIdentity: "alice"})LiveStream stream = livqeno.liveStreams().create(CreateLiveStreamParams.builder().title("Launch Day").hostIdentity("alice").build());$stream = $client->liveStreams->create(['title' => 'Launch Day', 'hostIdentity' => 'alice']);livqeno streams create "Launch Day" --host alicecurl -X POST "$LIVQENO_API_URL/v1/live-streams" \
-H "Authorization: Bearer $LIVQENO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Launch Day", "hostIdentity": "alice"}'This one call does three things:
- Creates the RTC room, where the video will happen.
- Creates the chat conversation. See Live chat.
- Records
hostIdentity(herealice) as the stream'sHOST.
It does not give Alice credentials to join yet. Call
addHost for that, when she's ready to go on.
The new stream's status is CREATED, and your backend gets the
live_stream.created webhook.
Lifecycle
CREATED → STARTING → LIVE → ENDING → ENDED
Only two calls change the status:
| Call | Moves | When to call it | What else happens |
|---|---|---|---|
start | CREATED → LIVE (STARTING is skipped) | The host clicks Go live. | Sends live_stream.started. Checks the "1 live stream at a time" limit. Starts the 240-minute clock. |
end | LIVE → ENDED (ENDING is skipped) | The host clicks End, or your event is over. | Closes the room and disconnects everyone. Sends live_stream.ended. |
Hosts and viewers can already join while the stream is CREATED. That's
useful for a "backstage" check: the host joins, tests the camera, and only
then you call start. The status tells your app whether to show the stream
as live. It doesn't open or close the room by itself.
If a stream stays LIVE for 240 minutes, Livqeno ends it for you.
await livqeno.liveStreams.start(streamId);
await livqeno.liveStreams.end(streamId);livqeno.live_streams.start(stream_id)
livqeno.live_streams.end(stream_id)stream, err := client.LiveStreams.Start(ctx, streamID)
stream, err = client.LiveStreams.End(ctx, streamID)livqeno.liveStreams().start(streamId);
livqeno.liveStreams().end(streamId);$client->liveStreams->start($streamId);
$client->liveStreams->end($streamId);livqeno streams end <streamId>There's no livqeno streams start. A stream usually goes live from your app,
when the host starts broadcasting.
curl -X POST "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/start" \
-H "Authorization: Bearer $LIVQENO_API_KEY"
curl -X POST "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/end" \
-H "Authorization: Bearer $LIVQENO_API_KEY"Read a stream
Read a stream to show it in your app, for example a "Live now" list or an event page with the current viewer count:
const stream = await livqeno.liveStreams.get(streamId);
const all = await livqeno.liveStreams.list({ status: 'LIVE' }); // no live viewer counts — one GET per stream for thatstream = livqeno.live_streams.get(stream_id)
live_streams = livqeno.live_streams.list(status="LIVE")stream, err := client.LiveStreams.Get(ctx, streamID) // ViewerCount is nil if the SFU was unreachable
live, err := client.LiveStreams.List(ctx, &livqeno.ListLiveStreamsParams{Status: livqeno.LiveStreamLive})LiveStream stream = livqeno.liveStreams().get(streamId);
List<LiveStream> live = livqeno.liveStreams().list(ListLiveStreamsParams.withStatus(LiveStreamStatus.LIVE));$stream = $client->liveStreams->get($streamId);
$live = $client->liveStreams->list(['status' => 'LIVE']);livqeno streams list --status LIVE
livqeno streams inspect <streamId>curl "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID" \
-H "Authorization: Bearer $LIVQENO_API_KEY"{
"id": "stream_jRoD1T3EXh0PMJRGG4zYzQ",
"status": "LIVE",
"conversationId": "conv_0iojWXJCtXZUUkDR7THBmQ",
"chatRootMessageId": "msg_Efm2zArYJTSRUr88BV5bZg",
"hosts": [{ "identity": "alice", "role": "HOST" }],
"viewerCount": 12,
"peakViewerCount": 47,
"startedAt": "2026-08-19T08:21:13.792Z",
"endedAt": null
}viewerCountis how many viewers are connected right now. It's worked out live each time you callget(), so it isn't stored. See Analytics.hostslists who can broadcast, with their role.conversationIdis the stream's chat. You rarely need it directly, becausestream.chatin your app is already connected to it.
Update a stream
Update a stream to fix its details, for example when the host renames the event. You can change: title, description, category, tags, language, visibility, and metadata.
You can't change status this way. Use start() and end().
await livqeno.liveStreams.update(streamId, { title: 'Launch Day (Part 2)' });from livqeno import UpdateLiveStreamParams
livqeno.live_streams.update(stream_id, UpdateLiveStreamParams(title="Launch Day (Part 2)"))stream, err := client.LiveStreams.Update(ctx, streamID, livqeno.UpdateLiveStreamParams{Title: livqeno.String("Launch Day (Part 2)")})livqeno.liveStreams().update(streamId, UpdateLiveStreamParams.builder().title("Launch Day (Part 2)").build());$client->liveStreams->update($streamId, ['title' => 'Launch Day (Part 2)']);livqeno streams update <streamId> --title "Launch Day (Part 2)"Once a stream has ENDED, updates fail with RAVEN_STREAM_INVALID_STATE. An
ended stream can't be changed.
Common errors
| Error | Cause | Fix |
|---|---|---|
RAVEN_STREAM_NOT_FOUND | Wrong id, or the stream is in another project or environment. | Use an id from create() or list(). |
RAVEN_STREAM_INVALID_STATE | A wrong lifecycle move, or updating an ENDED stream. | Check status first. Status only moves forward. |
Production tips
- Don't poll
get()too often. Each call asks the media server who is connected. Poll only as often as your UI needs, for example every 10–30 seconds for a viewer count. list()doesn't include live viewer counts (viewerCount: null), so a "Live now" list stays fast. Useget()on a single stream if you need its count.- Use webhooks instead of polling for status.
live_stream.startedandlive_stream.endedtell your backend the moment a stream changes. See Stream events.
Next steps
You have a stream. Now give people a way in:
- Hosts & co-hosts: make credentials for the people who broadcast.
- Viewers: make credentials for the audience.