---
name: radist
description: Use when adding real-time audio, video, or data features with Radist (radist.tech) — creating P2P calls or SFU rooms from a backend with @radist-tech/server (or the Go/Python SDKs), connecting browsers with @radist-tech/client, building persistent spaces, or debugging Radist tokens, roles, and signaling.
---

# Building with Radist

Radist runs WebRTC signaling, TURN, and SFU media forwarding. You keep the UI and the auth decisions.

## The one rule

The **secret key** (`rad_sk_...`) creates calls and mints tokens, and lives only in your backend. The **public key** (`rad_pk_...`) plus a **participant token** (`ct_...`) is all the browser ever gets. If you are about to put a `rad_sk_` value in browser code, framework client-side env (`VITE_`, `NEXT_PUBLIC_`, `PUBLIC_`), or a repo, stop — that is the wrong half of the API.

## Shape of an integration

1. Backend authenticates the user with _your_ auth system.
2. Backend calls `createP2PConnection()` (2 people) or `createRoom()` (more than 2).
3. Backend hands exactly one token to each approved browser session.
4. Browser calls `radist.call(token).connect({ channels })`.
5. Media and data flow peer to peer (P2P) or via the SFU (rooms).

## Backend

```ts
import { RadistServerClient } from '@radist-tech/server';

const radist = new RadistServerClient(); // RADIST_PROJECT_ID + RADIST_KEY from env

const { callId, callTokens } = await radist.createP2PConnection();
// callTokens[0] -> first participant, callTokens[1] -> second
```

Go: `radist.NewClient(secret, radist.WithProjectID(id))` then `client.CreateP2PConnection(ctx, nil)`.
Python: `RadistServerClient(secret, project_id=id)` then `client.create_p2p_connection()`.

Other methods: `createRoom()`, `mintConnectionToken({ callId } | { roomId })`, `createSpace()`, `listSpaces()`, `getSpace()`, `updateSpace()`, `deleteSpace()`, `rotateSpaceHostToken()`.

## Browser

```ts
import { RadistClient } from '@radist-tech/client';

const radist = new RadistClient({ publicKey: 'rad_pk_...' });
const connection = await radist.call(token).connect({ channels: ['audio', 'data'] });

connection.on('remotestream', ({ stream }) => (audioEl.srcObject = stream));
connection.on('datachannel', ({ channel }) => {
	channel.addEventListener('message', (event) => handle(event.data));
});
```

Channels: `audio`, `video`, `data`. Events: `statechange`, `localstream`, `remotestream`, `datachannel`, `error`. Rooms use `radist.room(roomToken).connect()`, spaces use `radist.space(slug).connect({ name, password? })`.

## Things that bite

- **Tokens are single-use.** One token admits one participant. Never hand the same token to two browsers or reuse one after a refresh — mint a fresh one with `mintConnectionToken({ callId })`. A consumed token closes the socket with `4401 invalid_token`.
- **`role` is arrival order, not authorship.** `connection.role` is `'host'` for the first peer to join, `'guest'` for the second. It is not "whoever created the call" — if you show an invite link before the creator has connected, a fast invitee can take the host slot. Connect first, then share.
- **`connect()` resolves early.** For the first peer it resolves at `waiting-for-peer`, before anyone else is there. Read `connection.role` and `connection.state` from the object after it resolves; do not assume your `statechange` listener has already fired.
- **Gate app logic on the data channel, not on `state`.** If moves or messages travel over `data`, wait for the channel's `open` event and check `channel.readyState === 'open'`. That is the thing that actually has to be up.
- **React StrictMode double-mounts effects.** In dev, an unguarded `useEffect` runs `connect()` twice and burns the single-use token. Guard with a `useRef(false)` latch.
- **Muting is a track flag.** `connection.localStream.getAudioTracks()` then `track.enabled = false`. Do not renegotiate.
- **HTTPS is required off localhost.** Browsers only grant camera/microphone on a secure origin.
- **`accessControl`.** Calls are private by default: only server-minted tokens work. `{ accessControl: 'public' }` lets anyone with the public key mint a token — demos only.

## Full documentation

Every docs page is available as Markdown by appending `.md` to its URL.

- Index for agents: https://radist.tech/llms.txt
- Everything in one file: https://radist.tech/llms-full.txt
- Quickstart: https://radist.tech/docs/quickstart.md
- Browser SDK: https://radist.tech/docs/client/js.md
- Backend SDK: https://radist.tech/docs/server/js.md
- Keys and auth: https://radist.tech/docs/configuration.md
- Raw HTTP and WebSocket protocol: https://radist.tech/docs/server/api.md and https://radist.tech/docs/client/api.md
