Skip to main content

Getting Started

In about 15 minutes you will talk into your microphone and watch your words appear in your terminal as text, labelled with your name. ODIN Cortex does the transcription. You don't need an existing game or app: this guide brings a small script and a one-file web page, and you only need a terminal, Node.js and a browser.

How it works​

Cortex doesn't produce audio. It listens to ODIN Voice rooms, the voice chat rooms your players or users talk in. When you ask Cortex to transcribe a room, it sends a bot into that room as one more participant. The bot hears every speaker separately and turns what they say into text.

A few words you'll meet along the way:

TermMeaning
RoomAn ODIN Voice room. It is just a name you choose; everyone who joins the same name can hear each other. This guide uses cortex-quickstart.
SessionOne transcription of one room. While a session is active, the bot is in the room.
BotThe Cortex participant that listens. It appears in the room as transcription-bot.
Room tokenA short-lived ticket that lets one person join one room. Your browser needs one to join.
MessageOne piece of transcribed speech, from when someone starts talking until they pause, with who said it.

Before you start​

You need:

  • A 4Players account and a project. Sign in at console.4players.io and create a project if you don't have one.
  • Cortex activated on that project. Open the project, click Cortex in the left sidebar, then click Activate ODIN Cortex. Cortex needs ODIN Voice on the same project; if Voice isn't active yet, the page offers to activate it first.
  • Your project ID. In the project, open Manage, then Overview. The ID has a copy button next to it.
  • Node.js 18 or newer (check with node --version) and a browser with a microphone.
Just activated Cortex?

It can take a few minutes until Cortex knows about a newly activated project. If you get Project settings not configured in Step 4, wait a moment and try again.

Step 1 — Create an API key​

Your script proves who it is with an API key. API keys belong to your account, not to one project, so you'll find them in your account settings rather than inside the project.

  1. Open console.4players.io/settings/api-keys. You can also get there from your user menu via Settings, then API Keys (Cortex). (The Tokens (Fleet) entry next to it is for a different product.)
  2. Click Create API Key.
  3. Enter a name, for example quickstart. Under Project Scope, select your project. Under Scopes, tick Sessions and Messages; that's all this guide needs.
  4. Click Create Key and copy the key right away. It starts with ots_live_ and is shown only once.

Create the key while you're logged in as the account that owns the project. A key created by a different team member's login won't work for it.

Keep the key secret

Anyone with this key can read every transcript of your project. Use it only on your own computer or server, never in a web page, mobile app or game build. The web page in this guide gets a short-lived room token instead.

Step 2 — Set up a folder​

Open a terminal and create a folder with the Cortex SDK. The second line tells Node.js to treat the script as a modern JavaScript module, which lets it use await at the top level.

mkdir cortex-quickstart && cd cortex-quickstart
npm init -y && npm pkg set type=module
npm install @4players/odin-cortex tsx

Then put your key and project ID into environment variables, so they stay out of the code:

export CORTEX_API_KEY=ots_live_...        # your key from Step 1
export CORTEX_PROJECT_ID=your-project-id # your project ID

Environment variables only live as long as the terminal window. If you open a new one, set them again.

Step 3 — Write the script​

Create a file called quickstart.ts in the folder and paste this in:

quickstart.ts
import { CortexClient } from "@4players/odin-cortex";

// 1. Connect to Cortex with your API key and pick your project.
const client = new CortexClient({
baseUrl: "https://cortex.odin.4players.io",
apiKey: process.env.CORTEX_API_KEY,
});
const project = client.project(process.env.CORTEX_PROJECT_ID!);

const ROOM = "cortex-quickstart";

// 2. Start a session. The bot joins the room right away.
const session = await project.sessions.create({
title: "My first session",
externalRoomId: ROOM,
idleTimeout: 120, // end automatically after 2 minutes with nobody in the room
});
console.log("Session", session.id, "is", session.status);

// 3. Get a room token so you can join the same room from your browser.
const { token } = await project.generateToken({ roomId: ROOM, userId: "alice" });
console.log("\nPaste this token into join.html:\n\n" + token + "\n");

// 4. Print every new message as soon as Cortex has transcribed it.
const sub = session.watchMessages();
sub.onSnapshot((_messages, changes) => {
for (const change of changes) {
if (change.type === "added") {
console.log(`${change.data.senderName}: ${change.data.content}`);
}
}
});
sub.onError((err) => console.error(err));

// 5. When you press Ctrl+C, stop the session. The bot leaves the room.
process.on("SIGINT", async () => {
sub.unsubscribe();
await session.stop();
console.log("Session stopped.");
process.exit(0);
});

What each part does:

  1. Connect. Every call goes through a client that carries your API key, scoped to your project.
  2. Start a session. externalRoomId is the name of the room to transcribe. The room doesn't have to exist yet: the bot joins and waits. idleTimeout is optional and ends the session when the room has been empty for that many seconds.
  3. Get a room token. Cortex creates a token for the same room. userId is the name you'll appear under in the transcript; alice is just an example.
  4. Watch the transcript. watchMessages() keeps a live connection open and calls you for every new message, so you don't have to keep asking.
  5. Clean up. Stopping the session makes the bot leave and ends the transcription.

Step 4 — Run the script​

npx tsx quickstart.ts

You should see something like this, and then the script keeps running and waits:

Session 73ad570e-eb30-44b4-ac1e-00eafbeab7a6 is active

Paste this token into join.html:

eyJhbGciOiJFZERTQSIsImtpZCI6...

Leave this terminal open. Your transcript will appear here.

Step 5 — Join the room and talk​

Now you join the room from your browser. Save this page as join.html in the same folder:

join.html
<!doctype html>
<html>
<body>
<h1>Cortex quick start</h1>
<p>Paste the token from your terminal, click Join room, then start talking.</p>
<textarea id="token" rows="4" cols="80"></textarea><br />
<button id="join">Join room</button>
<pre id="log"></pre>

<script type="module">
import * as ODIN from "https://cdn.jsdelivr.net/npm/@4players/odin@1.10.1/+esm";

const log = (line) => (document.getElementById("log").textContent += line + "\n");

document.getElementById("join").onclick = async () => {
try {
const token = document.getElementById("token").value.trim();

// Browsers only allow audio after a click, so all of this runs in the click handler.
await ODIN.setOutputDevice({});
const room = new ODIN.Room();
room.onPeerJoined = ({ peer }) => log("in the room: " + peer.userId);
room.onPeerLeft = ({ peer }) => log("left the room: " + peer.userId);

await room.join(token, { gateway: "https://gateway.odin.4players.io" });
log("Joined. Start talking!");

// Send your microphone into the room.
const mic = await ODIN.DeviceManager.createAudioInput();
await room.addAudioInput(mic);
} catch (err) {
log("Error: " + (err?.message ?? err));
}
};
</script>
</body>
</html>

Browsers are strict about microphone access for files opened straight from disk, so serve the page from a small local web server. Open a second terminal in the same folder and run:

npx serve .

If npx asks whether it may install serve, answer y.

Then:

  1. Open http://localhost:3000/join.html.
  2. Copy the long token from your first terminal and paste it into the box.
  3. Click Join room and allow microphone access when the browser asks.

The page should list transcription-bot and alice as being in the room, and you'll hear the bot say "This conversation is now being recorded and transcribed." That means the bot is listening.

Now say a few sentences, with a short pause after each. Every time you pause, a new line appears in your first terminal a few seconds later:

[System]: A participant joined the room
alice: Hello, this is my first Cortex transcript.
alice: It even knows it was me who said this.

The first line is a notice from Cortex that someone joined. Everything after it is you, labelled with the userId from the token.

The token expires after five minutes

If you take longer than that between starting the script and clicking Join room, the browser can't join. Press Ctrl+C in the first terminal and run the script again for a fresh token.

Step 6 — Stop​

Press Ctrl+C in the first terminal. The script stops the session, the bot says goodbye and leaves the room, and you'll see Session stopped. You can close the browser tab and stop the local server with Ctrl+C as well.

That's it: you've transcribed a voice room with Cortex. In your own product, your backend does what the script did, and your game or app does what join.html did.

Other ways to read the transcript​

watchMessages() is the easiest way to follow a conversation live. Two alternatives:

Everything at once, for example after a session has ended:

const messages = await session.getMessages();
for (const message of messages) {
console.log(`${message.senderName}: ${message.content}`);
}

Checking for new messages every few seconds, useful where a live connection isn't practical, such as in a serverless function. Each message has a running number (seq). You ask only for messages after the last one you've seen. If there's nothing new, the call returns null, and the check costs Cortex almost nothing:

let after = 0;
setInterval(async () => {
const page = await session.listMessages({ after }, { ifNoneMatch: `"${after}"` });
if (!page) return; // nothing new since last time
for (const m of page.messages) console.log(`${m.senderName}: ${m.content}`);
after = page.latestSeq;
}, 2000);
Starting the bot later

Sessions start the bot as soon as they're created. To create a session first and send the bot in later, pass autoStart: false when creating it, then call await session.start() when you're ready.

Prefer raw HTTP?​

The SDK is a thin layer over the REST API. The same steps with curl:

# Start a session (the bot joins right away)
curl -X POST https://cortex.odin.4players.io/api/projects/{projectId}/sessions \
-H "X-API-Key: ots_live_..." \
-H "Content-Type: application/json" \
-d '{ "title": "My first session", "externalRoomId": "cortex-quickstart", "idleTimeout": 120 }'

# Get a room token for the browser
curl -X POST https://cortex.odin.4players.io/api/projects/{projectId}/token \
-H "X-API-Key: ots_live_..." \
-H "Content-Type: application/json" \
-d '{ "roomId": "cortex-quickstart", "userId": "alice" }'

# Read messages after number 0
curl "https://cortex.odin.4players.io/api/projects/{projectId}/sessions/{sessionId}/messages?after=0&limit=50" \
-H "X-API-Key: ots_live_..."

# Stop the session
curl -X POST https://cortex.odin.4players.io/api/projects/{projectId}/sessions/{sessionId}/stop \
-H "X-API-Key: ots_live_..."

Every endpoint is described in the REST API reference.

Troubleshooting​

"Top-level await is currently not supported" or ERR_REQUIRE_ASYNC_MODULE The folder isn't set up as a module. Run npm pkg set type=module in the folder and try again.

403 "This API key cannot access project … The project must be configured in the ODIN console by the account the key belongs to." The key was created by a different account than the one that owns the project. Log in to the console as the project owner, create a new key, and use that one. If you are the owner, log out and back in, then create a new key.

403 "API key missing required scopes: …" The key is missing a permission. The message names it. Create a new key with that scope ticked; you can't add scopes to an existing key.

400 "Project settings not configured" Cortex doesn't know about the project yet. This happens right after activating Cortex; wait a few minutes and try again.

The browser can't join, or you never hear the bot. Make sure the script is still running and you pasted the token from its latest run; tokens expire after five minutes. If the room was empty for longer than idleTimeout, the session has ended: restart the script.

The browser doesn't ask for the microphone, or no sound gets through. Open the page through http://localhost:3000, not by double-clicking the file. Check that the browser is allowed to use the microphone for localhost.

You hear the bot, but no text appears. Pause after speaking; Cortex transcribes when you stop talking. If still nothing appears and you're joining from your own app instead of join.html: the bot has to use the same ODIN SDK generation as your app. Check Manage → Bot Settings → ODIN SDK Version in the console. The default, ODIN 1.x, matches the page in this guide.

429 Too Many Requests You're asking too often. Wait for the time given in the Retry-After header, check less often, or use watchMessages().

Next steps​

Core concepts

Sessions and gatherings, participants and authentication: the ideas behind the API.

Read concepts
Real-time updates

Follow transcripts and sessions live with watch(), including reconnects.

Moderation

Detect toxic speech in the transcript and enforce mutes and bans in your game.

Serverless functions

Run your own code when something happens, for example email a summary when a session ends.