Skip to main content

Getting Started

Rooms is built on ODIN Voice and can be extended with your own tools - e.g. bots for recording, transcription/AI, custom UIs, or external automations. Rooms uses ODIN access tokens. Tokens are created from an access key that is private to your Rooms project.

This minimal guide shows how to:

  1. Create a Rooms-compatible ODIN token via our hosted token service, and
  2. Join a Rooms room with the ODIN Web SDK.

Prerequisites

  • A Rooms instance (free option available): https://www.rooms.chat/pricing/
  • Your Rooms Project API Key (apiKey) for token creation (keep this private / server-side).
  • Your Rooms Project Secret (secret) for token creation (keep this private / server-side).

To get your Rooms instance, order a free instance here: https://www.rooms.chat/pricing/. After that, navigate to https://app.netplay-config.4players.de/rooms and select your instance from the list. Then copy the apiKey and secret from the settings page.

note

The secret is used to sign the token, so it must be kept private. If you don't trust your secret anymore, for example because you leaked it accidentally in a public repository, you can create a new secret and invalidate all existing tokens in our dashboard.

1) Create an ODIN token for a Rooms room (server-side)

Rooms provides a hosted service that returns everything your client/bot needs:

  • token (JWT)
  • domain (your Rooms domain)

Request

POST https://secure.4players.de/public/api/v1/projects/${apiKey}/create-token

Payload:

{
"roomId": "my-room",
"userId": "my-user-id",
"secret": "my-secret"
}

Response:

{
"data": {
"token": "eyJhbGciOiJFZERTQSIsIm...",
"domain": "example.rooms.chat"
},
"error": {
"code": 0,
"message": ""
}
}

This is a tiny Node.js endpoint example that you can host anywhere (i.e. as a Lambda function in AWS or on your own server). It uses the fetch API to call the Rooms token endpoint with your apiKey.

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/rooms/create-token", async (req, res) => {
const {roomId, userId, secret} = req.body;

// Keep this on the server only (env var / secret manager).
const apiKey = process.env.ROOMS_PROJECT_API_KEY;

const r = await fetch(
`https://secure.4players.de/public/api/v1/projects/${apiKey}/create-token`,
{
method: "POST",
headers: {"content-type": "application/json"},
body: JSON.stringify({roomId, userId, secret}),
}
);

if (!r.ok) {
return res.status(r.status).json({error: "Token request failed"});
}

const json = await r.json();
// Forward only what the client needs:
res.json(json.data);
});

app.listen(3000);
warning

For testing purposes you can directly call our token service, but do not call the token endpoint directly from the browser in production, otherwise your apiKey becomes public. Rooms api keys must not be embedded in client-side code in production. Our token server has a rate limit of 100 requests per minute.

2) Join the room with ODIN Web SDK (client-side)

This is the smallest practical flow:

  • Initialize the web audio plugin
  • Fetch token data from your backend
  • Hoin the room (with E2EE cipher)
  • Start audio output + microphone

Minimal example (ESM / TypeScript-style)

import {init, Room, DeviceManager} from "@4players/odin";
import {createPlugin} from "@4players/odin-plugin-web";

let audioPlugin;

async function initOdinPlugin() {
if (audioPlugin) return audioPlugin;

audioPlugin = createPlugin(async (sampleRate) => {
const audioContext = new AudioContext({sampleRate});
await audioContext.resume();
return audioContext;
});

init(audioPlugin);

// Required to hear other peers (audio output is plugin-global).
await audioPlugin.setOutputDevice({});

return audioPlugin;
}

async function joinRooms({roomId, displayName}) {
await initOdinPlugin();

// 1) Create token (via your backend)
const userId =
displayName.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase() +
"_" +
Math.floor(Math.random() * 10000);

const tokenResp = await fetch("/api/rooms/create-token", {
method: "POST",
headers: {"content-type": "application/json"},
body: JSON.stringify({roomId, userId}),
});

const {token} = await tokenResp.json();

// Let's use the Europe gateway for now in this example, all available gateways are listed here:
// https://docs.4players.io/voice/server/cloud/#gateway
const gateway = "https://gateway.odin.4players.io";

// 2) Prepare Rooms peer data (so Rooms UI can display name/avatar/mute states)
const roomsPeerData = {
userId,
name: displayName,
avatar: `https://app-server.odin.4players.io/v1/avatar/${userId}/initials`,
inputMuted: false,
outputMuted: false,
role: "Guest",
};

const userData = new TextEncoder().encode(JSON.stringify(roomsPeerData));

// 3) Join ODIN room
const room = new Room();

await room.join(token, {
gateway,
// Rooms is end-to-end encrypted by default.
// If you didn't change encryption settings in Rooms, the password is the roomId.
cipher: {type: "OdinCrypto", password: roomId},
userData,
});

// 4) Start microphone (AudioInput)
const audioInput = await DeviceManager.createAudioInput();
await room.addAudioInput(audioInput);

return {room, audioInput};
}

You can find more examples in the ODIN Web SDK documentation: https://docs.4players.io/odin/web/

Rooms displays a list of all peers in the room. Every peer in a room has an avatar icon and a name. The mute state is also displayed in the rooms peer list. To make sure that every bot, script or agent in the room looks good, a peer object of a specific structure must be set for each participant. This is done in the roomsPeerData variable in the example above.

note

It's important to understand to use the same gateway for all participants in the room. The gateway is used to establish a secure connection between the client and the Rooms server. There are three parts that need to match for each user that should be connected to the same room:

  • Rooms project API key
  • Room ID
  • Gateway

Rooms peer data (why it matters)

Rooms uses a JSON “peer data” object to correctly display participants (name, avatar, mute/output state, etc.). When building bots/scripts/apps that join Rooms, you should send this user data so your peer appears correctly in the Rooms UI.

The Rooms peer data object is a JSON string that has this format (ZOD definition):

export const userDataSchema = z.object({
userId: z.string().default(''),
name: z.string().default('Unknown'),
status: userStatusSchema.catch('online').default('online'),
avatar: z.string().default(defaultAvatar),
outputMuted: z.boolean().catch(false).default(false),
inputMuted: z.boolean().catch(false).default(false),
renderer: z.string().default('').optional(),
platform: z.string().default('').optional(),
revision: z.string().default('').optional(),
version: z.string().default('').optional(),
buildNo: z.number().default(0).optional(),
role: z.string().default('Guest').optional(),
emoji: z.string().optional(),
customStatus: z.string().optional(),
presentationStartedAt: z.number().default(0),
});

You can extend the object for your own needs. However, Rooms may update/override certain fields when settings change in the Rooms UI (this behavior may vary and should be tested).

If anything behaves unexpectedly, please reach out on Discord (or email support): https://docs.4players.io/rooms/

Avatar Service

The most important fields are userId, name and avatar. avataris a URL to an image that will be displayed in the Rooms UI. For testing purposes you can use our avatar service to generate a random avatar for your users: https://app-server.odin.4players.io/v1/avatar/{seed}/initials

The seed is the initials followed by a - and a random number encoding a color.

Room and User IDs

The userId field is used to identify the user in the Rooms UI. It must be unique for each participant in the room. The roomId is used to identify the room. It can be any string, but it must be the same for all participants in the room.

For more advanced features (events, device selection, React/Angular examples), continue here: https://docs.4players.io/voice/web/

Sample project

ODIN Communicator shows a complete ODIN Web SDK implementation for this flow: https://github.com/4Players/odin-communicator (currently private; will be published later)