# Client HTTP & WebSocket API

> Lower-level P2P token minting and the signaling WebSocket protocol, message by message.

Use `@radist-tech/client` (/docs/client/js.md) to skip this and work at a higher level. Call _creation_ needs a secret key — see /docs/server/api.md.

All endpoints live under `/api/v1` and accept a `rad_pk_...` public key.

## `POST /api/v1/p2p/calls/:callId/token`

Mints a participant token for a **public** call.

```ts
const response = await fetch(`https://radist.tech/api/v1/p2p/calls/${callId}/token`, {
	method: 'POST',
	headers: { Authorization: 'Bearer <your public key>' }
});

const { callToken } = await response.json();
```

| Status | Response                                                     |
| ------ | ------------------------------------------------------------ |
| `200`  | `{ "callToken": "ct_..." }`                                  |
| `401`  | `{ "error": "Missing bearer token." }` or invalid public key |
| `404`  | `{ "error": "Call not found." }`                             |
| `409`  | `{ "error": "All peers are already connected." }`            |
| `410`  | `{ "error": "Call has been terminated." }`                   |

## P2P signaling WebSocket

Initial connection:

```ts
const ws = new WebSocket(
	'wss://radist.tech/api/v1/p2p/signaling/ws?publicKey=rad_pk_...&callToken=ct_...'
);
```

Reconnect with the identity from `call_joined`:

```ts
const ws = new WebSocket(
	'wss://radist.tech/api/v1/p2p/signaling/ws' +
		'?publicKey=rad_pk_...&callId=<uuid>&peerId=<peer id>&reconnectToken=<token>'
);
```

| Close code                        | Meaning                                                   |
| --------------------------------- | --------------------------------------------------------- |
| `4400 invalid_connection_request` | Provide either `callToken` or the full reconnect payload. |
| `4401 unauthorized`               | Invalid public key.                                       |
| `4401 invalid_token`              | Invalid or already consumed call token.                   |

### Client → server messages

| Type     | Fields                               | Description                                                             |
| -------- | ------------------------------------ | ----------------------------------------------------------------------- |
| `signal` | `targetPeerId, signalType, payload?` | Forward SDP offers, SDP answers, or ICE candidates to a connected peer. |
| `leave`  | `requestId?`                         | Gracefully leave the current call.                                      |

### Server → client messages

| Type                                             | Fields                                                | Description                                                |
| ------------------------------------------------ | ----------------------------------------------------- | ---------------------------------------------------------- |
| `call_joined`                                    | `callId, peerId, reconnectToken, call`                | The socket joined a call with a new peer identity.         |
| `call_reconnected`                               | `callId, peerId, call`                                | The socket reconnected with a previous peer identity.      |
| `peer_joined` / `peer_reconnected` / `peer_left` | `callId, peerId, reason?, call`                       | Lifecycle updates for the other peer.                      |
| `call_left`                                      | `callId, peerId, reason, call`                        | Confirmation that this socket left or disconnected.        |
| `signal`                                         | `callId, fromPeerId, signalType, payload, occurredAt` | A WebRTC signal forwarded from the other peer.             |
| `error`                                          | `code, message, requestId?`                           | A protocol error for the connection or a specific request. |

Protocol error codes: `unsupported_message`, `socket_not_registered`, `already_connected`, `invalid_reconnect_request`, `call_not_found`, `peer_not_found`, `invalid_reconnect_token`, `peer_exists`, `call_full`, `not_connected`, `invalid_signal_request`, `target_not_found`, `target_unavailable`.

### Call snapshot

Messages carrying a `call` field include:

```json
{
	"callId": "550e8400-e29b-41d4-a716-446655440000",
	"createdAt": "2026-05-05T10:30:00.000Z",
	"updatedAt": "2026-05-05T10:31:00.000Z",
	"peers": [
		{ "peerId": "peer_a1b2c3", "status": "connected", "updatedAt": "..." },
		{ "peerId": "peer_d4e5f6", "status": "disconnected", "updatedAt": "..." }
	]
}
```

The first peer to appear in `peers` is the host; that ordering is what `connection.role` reports.

## Room signaling WebSocket

```ts
const ws = new WebSocket(
	'wss://radist.tech/api/v1/sfu/signaling/ws?publicKey=rad_pk_...&roomToken=ct_...'
);
```

### Client → server messages

| Type                  | Fields                                      | Description                                                                |
| --------------------- | ------------------------------------------- | -------------------------------------------------------------------------- |
| `connect-transport`   | `transportId, dtlsParameters`               | Complete the DTLS handshake for a send or recv transport.                  |
| `produce`             | `kind, rtpParameters, transportId?`         | Publish an audio or video track. Server replies with `produced`.           |
| `consume`             | `producerId, rtpCapabilities, transportId?` | Subscribe to a remote producer. Server replies with `consumer-parameters`. |
| `set-preferred-layer` | `consumerId, spatialLayer, temporalLayer?`  | Tune simulcast layer preferences.                                          |
| `leave-room`          | —                                           | Leave the room. Closes producers/consumers and ends billing.               |
| `e2ee-announce`       | `publicKey, keyId`                          | Broadcast your X25519 public key and current sender key id.                |
| `e2ee-key`            | `targetPeerId, keyId, wrappedKey, wrapIv`   | Deliver an ECDH-wrapped sender key to a peer.                              |
| `e2ee-request-key`    | `targetPeerId`                              | Ask a peer to resend their latest wrapped sender key.                      |

### Server → client messages

| Type                  | Fields                                                        | Description                                                     |
| --------------------- | ------------------------------------------------------------- | --------------------------------------------------------------- |
| `room-joined`         | `roomId, peerId, sendTransport, recvTransport, existingPeers` | Initial snapshot with transport parameters and connected peers. |
| `transport-connected` | `transportId`                                                 | Acknowledges `connect-transport`.                               |
| `produced`            | `producerId, kind`                                            | Acknowledges `produce` and assigns a producer id.               |
| `new-producer`        | `peerId, producerId, kind`                                    | Another peer started producing. Call `consume` to subscribe.    |
| `consumer-parameters` | `peerId, consumerId, producerId, kind, rtpParameters`         | Reply to `consume`, with the parameters mediasoup-client needs. |
| `peer-left`           | `peerId`                                                      | A peer disconnected or left the room.                           |
| `error`               | `code?, message`                                              | A protocol error for the connection or last request.            |
| `e2ee-peer-announced` | `fromPeerId, publicKey, keyId`                                | A peer broadcast their X25519 public key and sender key id.     |
| `e2ee-peer-key`       | `fromPeerId, keyId, wrappedKey, wrapIv`                       | A peer delivered their wrapped sender key.                      |
| `e2ee-key-requested`  | `fromPeerId`                                                  | A peer is asking you to resend your wrapped sender key.         |
