livqenoDocs

SDKsTypeScript

TypeScript SDK

@livqeno/server — make tokens and manage rooms, chat and streams from your TypeScript backend.

@livqeno/server is for your TypeScript backend (plain JavaScript works too). It holds your secret API key and talks to the Livqeno REST API for you.

Its most important job is making tokens. Your app (web, React, React Native, or Flutter) can't join anything without one, and only a backend with the API key can make one. So in a typical app, this SDK sits behind an endpoint like POST /join-room:

  1. Your app calls your endpoint.
  2. Your endpoint checks who the user is (from your own login session).
  3. Your endpoint calls livqeno.tokens.create(...) and returns the result.
  4. Your app passes that result to its client SDK and joins.

It also manages rooms, chat conversations, and live streams, and reads metrics. It works the same way as the Python, Go, Java and PHP SDKs, so pick whichever matches your backend's language.

You need: a project and an API key. See API credentials.

Install

pnpm add @livqeno/server

Set up the client

On your backend, create one Livqeno instance when your server starts, and reuse it for every request:

import { Livqeno } from '@livqeno/server';
 
const livqeno = new Livqeno({
  apiKey: process.env.LIVQENO_API_KEY,
  baseUrl: process.env.LIVQENO_API_URL, // https://api.livqeno.com
});

Pass both values yourself. The SDK never reads process.env on its own. This is on purpose: if it picked up variables silently, a production key could end up in a development process without anyone noticing.

OptionRequiredWhat it's for
apiKeyYesYour project API key. The SDK throws right away without it.
baseUrlYes in productionhttps://api.livqeno.com. Unset, it falls back to http://localhost:4100, and refuses to start when NODE_ENV=production.
timeoutNoTime limit per request, in milliseconds. Default 10000.
maxRetriesNoHow many times a temporary failure is retried. Default 2.

What you can do

Here's the whole surface at a glance. Each call runs on your backend, usually inside one of your own HTTP endpoints. When you'd use each group:

GroupYou'd use it when…
tokensA user is about to join a call. Make one token per user, per join.
roomsYou need a room for a meeting, a support ticket, or a game lobby.
connections, errors, metrics, diagnosticsYou're building an admin page or debugging why calls fail.
chatA user opens a chat. You create the conversation, add them, and make a chat token.
liveStreamsYou run webinars, classes, or broadcasts with one host and many viewers.
transcriptionsYou need live captions or a transcript of a room. See Live Transcription & Captions.
agentsYou want an AI voice agent to join a room. See AI Voice Agents.
// Projects
await livqeno.projects.get(); // the one project this key belongs to
 
// RTC tokens — the core of this SDK
const token = await livqeno.tokens.create({
  room: roomId,
  identity: 'user-42',
  permissions: { join: true, publish: true, subscribe: true },
  expiresIn: 3600,
});
 
// Rooms
await livqeno.rooms.create({ name: 'demo-room', getOrCreate: true });
await livqeno.rooms.create({ name: 'board-meeting', e2ee: true }); // end-to-end encrypted media
await livqeno.rooms.list();
await livqeno.rooms.get(roomId);
await livqeno.rooms.participants.list(roomId); // null means the SFU couldn't be reached — distinct from a genuinely empty room ([])
 
// Observability
await livqeno.connections.list();
await livqeno.errors.list();
await livqeno.metrics.get('1h'); // '15m' | '1h' | '24h' | '7d'
await livqeno.diagnostics.get();
 
// Chat — see Chat Overview and the Python SDK page for the full surface
const conv = await livqeno.chat.createConversation({ name: 'support-room-42', getOrCreate: true });
await livqeno.chat.addMember(conv.publicId, { userId: 'alice' }); // a token only reaches conversations its user belongs to
await livqeno.chat.createToken({ userId: 'alice', conversations: [conv.publicId] });
 
// Live Streaming — see below
await livqeno.liveStreams.create({ title: 'Friday Q&A', hostIdentity: 'user-123' });

Note: rooms.participants.list() returns null if the media server can't be reached, and [] if the room is really empty. Treat null as "unknown", not "nobody is here", or you might wrongly close a room that still has people in it.

getOrCreate: true matters when two users join at the same moment. Both call your endpoint, both try to create demo-room, and without it the second one gets RAVEN_CONFLICT (409). With it, the second one simply gets the room that already exists.

e2ee: true creates an end-to-end encrypted room: the SDKs encrypt media with keys Livqeno never sees, and server-side recording, HLS and restreaming are refused for it. See End-to-end encryption.

Moderating a live call

These act on someone who is connected right now (otherwise RAVEN_PARTICIPANT_NOT_CONNECTED, 404). roomId takes the room's id or its name.

await livqeno.rooms.muteParticipant(roomId, 'alice', { kind: 'audio', muted: true }); // or { trackId, muted }
await livqeno.rooms.updateParticipantPermissions(roomId, 'alice', { publish: false }); // returns the result
await livqeno.rooms.removeParticipant(roomId, 'alice'); // doesn't revoke their token
 
// Metadata and attributes: the whole room gets participantMetadataChanged
await livqeno.rooms.updateParticipant(roomId, 'alice', { metadata: '{"role":"host"}', attributes: { hand: null } });
 
// A data message from the server (no sender participant on the receiving side)
await livqeno.rooms.sendData(roomId, 'meeting ends in 5 min', { topic: 'system' });

tokens.create() also takes the participant's starting metadata and attributes, and permissions.updateOwnMetadata lets the app change its own (Metadata and attributes). sendData() is never retried, so a retry can't deliver a message twice (Data messages).

Webhooks

import { verifyWebhook } from '@livqeno/server';
 
const event = verifyWebhook(rawBody, req.header('swalvo-signature'), process.env.LIVQENO_WEBHOOK_SECRET!);

Pass the raw body (a string or bytes, before any JSON parsing). It returns the parsed event, or throws a LivqenoError with RAVEN_WEBHOOK_INVALID_SIGNATURE, RAVEN_WEBHOOK_TIMESTAMP_OUT_OF_TOLERANCE (older than toleranceSeconds, 300 by default) or RAVEN_WEBHOOK_INVALID_PAYLOAD. See Webhooks.

Live streaming

A live stream has one main host, optional co-hosts, and many viewers who only watch (think webinar or a Friday Q&A). On your backend, the flow is: create the stream, start it, then give each person the right credential.

const stream = await livqeno.liveStreams.create({
  title: 'Friday Q&A',
  hostIdentity: 'user-123', // registered as this stream's HOST
});
 
await livqeno.liveStreams.start(stream.id);
 
// Registering a co-host mints full-publish RTC + moderator chat credentials
// in one call. Hand the result to the client SDK unchanged.
const hostCredential = await livqeno.liveStreams.addHost(stream.id, { identity: 'user-456' });
 
// A viewer token is always subscribe-only — there is no field here that
// can request publish access.
const viewerCredential = await livqeno.liveStreams.createViewerToken(stream.id, 'user-789');
 
await livqeno.liveStreams.removeHost(stream.id, 'user-456');
await livqeno.liveStreams.end(stream.id); // LIVE → ENDED, terminal
MethodWhat it does
get() / list()Read a stream's details and hosts. get() also returns the live viewer count.
update()Changes the stream's title, description, or other details. To change its status, use start() or end() instead.
addHost()Makes someone a co-host. Gives them publish + moderator chat access.
createViewerToken()Makes a watch-only token. It can never publish.
leave()Tells Livqeno a viewer left, so the live_stream.viewer_left webhook fires.
end()Ends the stream. An ended stream can't restart.

liveStreams can also do three more things, each explained on its own page:

  • liveStreams.stage.invite(), .promote(), and .demote() bring a viewer up as a co-host. See Stage.
  • liveStreams.restreams pushes the stream to other destinations, like YouTube or Twitch. See Restream.
  • liveStreams.ingress lets a host go live from OBS or another encoder instead of a browser. See Ingest.

More: Live Streaming Overview and SDK Support Matrix.

Errors

Every failed call throws a LivqenoError. Log its requestId: if you contact support, that id lets them find the exact request.

import { LivqenoError } from '@livqeno/server';
 
try {
  await livqeno.rooms.get('missing-room');
} catch (error) {
  if (error instanceof LivqenoError) {
    console.log(error.code, error.statusCode, error.requestId);
  }
}

Errors never contain server stack traces, database errors, or RTC/TURN credentials. All codes: Error Codes.

Retries, timeouts, and lists

  • Retries: only temporary failures are retried (network errors, timeouts, 429, 502, 503, 504), with exponential backoff, up to maxRetries times (default 2). Other 4xx errors are not retried, because sending the same bad request again would fail the same way.
  • Timeouts: timeout limits every request (default 10 seconds), so a slow network never hangs your endpoint forever.
  • Lists: chat history uses cursor pages. Every other list returns a plain array. You can cap it with limit.

Security

The API key is stored in a private field (#apiKey). It never appears in JSON.stringify(), logs, or errors. See Authentication.

Works with any framework

Express, Fastify, Next.js Route Handlers, serverless functions: any TypeScript or JavaScript server on a Node.js 20+ runtime. It's a plain HTTP client.

Common mistakes

MistakeWhat you'll seeFix
Using this package in the browserYour API key is exposed to every user.Use it only on the server. In the browser, use @livqeno/rtc.
Taking identity from the request bodyAny user can join as anyone else.Take the user id from your own login session.
Creating a room without getOrCreateRAVEN_CONFLICT (409) for the second person who joins.Pass getOrCreate: true.
Changing the token response before sending itCalls work on Wi-Fi but fail on mobile data.Send the whole object to your app as-is.
Forgetting baseUrlRequests go to localhost:4100, or the SDK won't start in production.Set LIVQENO_API_URL and pass it.

Next steps

  1. Build the full token endpoint and a working call: Quickstart.
  2. Learn what you can put in a token: Access tokens.
  3. Set up the app side: Next.js, React, React Native, or Flutter.
  4. Calling the REST API directly too? Retry it safely with Idempotency.