Livqeno Docs

Generate a token

Your backend decides who a user is and what they may do, then mints a short-lived token.

A client never asks Livqeno for its own token. Your backend does, from its own authenticated session, and hands the result to the client.

That ordering is the whole security model: a client that could name its own identity could impersonate any other user.

Mint one

import { Raven } from '@ravenkash/server';
 
const raven = new Raven({
  apiKey: process.env.RAVEN_API_KEY!,
  baseUrl: process.env.RAVEN_API_URL!, // https://api.ravenstack.online
});
 
app.post('/join-room', async (req, res) => {
  const room = await raven.rooms.create({ name: 'demo-room' });
 
  const credentials = await raven.tokens.create({
    room: room.id,
    identity: req.user.id,
    permissions: { join: true, subscribe: true, publish: true },
    expiresIn: 600,
  });
 
  res.json(credentials);
});

What comes back

{
  "id": "2b45e0e5-97f2-466d-b783-a09fed7f6505",
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "endpoint": "wss://api.your-raven-deployment.example/v1/rtc",
  "roomId": "8d86361a-7c01-4969-98cb-d0748360b803",
  "roomName": "demo-room",
  "participantIdentity": "user-42",
  "permissions": { "join": true, "subscribe": true, "publish": true, "publishAudio": true, "publishVideo": true, "publishData": false },
  "iceServers": [{ "urls": "stun:..." }, { "urls": "turn:...", "username": "...", "credential": "..." }],
  "telemetryUrl": "https://api.your-raven-deployment.example",
  "expiresAt": "2026-09-08T21:10:00.000Z",
  "createdAt": "2026-09-08T21:00:00.000Z"
}

Forward the whole object to your client. endpoint, iceServers and telemetryUrl are not decoration — they are how the SDK knows where to connect and which relays to use. Never hand-build any of them, and never substitute your own STUN or TURN servers: the TURN credentials in iceServers are minted fresh for this token and expire with it.

Permissions

Six flags, all denied unless granted:

FlagGrants
joinEntering the room at all
subscribeReceiving other participants' tracks
publishSending any track
publishAudioNarrows publish to the microphone
publishVideoNarrows publish to the camera
publishDataData-channel messages

publish: true with neither sub-flag set means both are allowed — "let them publish, I don't much care what" is the common case, and the sub-flags exist to narrow it. A sub-flag without publish grants nothing.

A viewer gets { join: true, subscribe: true } and cannot publish — not by policy, by construction. There is no request the client can make that widens what was signed.

Lifetime

expiresIn (ttlSeconds over HTTP) is 30–21600 seconds. There is no way to request a non-expiring token.

Short is the point. A token can be revoked early (DELETE /v1/rooms/{roomId}/rtc-tokens/{tokenId}), but that only blocks new connections and cannot end a call already running — so the lifetime is what actually bounds a leaked token. Mint on demand, one per participant per join. See RTC authentication → Revocation.

Chat tokens are separate

const chat = await raven.chat.createToken({
  userId: 'alice',
  conversations: [conversation.publicId],
  scopes: ['chat:read', 'chat:send'],
  expiresIn: 3600,
});

Neither token works on the other plane. See Chat authentication.

Next steps