> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revdesk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bounded client tokens

> Mint short-lived, number-restricted tokens on your server and ship them safely into untrusted client apps.

A **bounded client token** lets your backend hand a calling credential to an untrusted client (a
mobile app, a browser, a partner's device) **without shipping your API key** and without letting that
client call from any number to any number.

You mint a token on your server with your API key. The token is restricted to a set of caller-ID
(`from`) numbers and, optionally, a set of destination (`to`) numbers. The client uses the token in
place of your API key — on the same `Authorization: Bearer` header — and can only place calls within
those bounds. The token is a short-lived, signed `rdc_…` string; it carries no secret you'd mind a
client holding, and it expires on its own.

<Note>
  This is the delegation layer on top of the per-call security already described in the
  [WebRTC security model](/api-reference/webrtc-security). Each call placed with a bounded token is
  still scoped, attributed, and guardrail-bounded — the token simply guarantees the client can't step
  outside the from/to numbers you chose when you minted it.
</Note>

## How it works

```mermaid theme={null}
sequenceDiagram
  participant Backend as Your server (holds API key)
  participant API as RevDesk API
  participant Client as Your app (untrusted)

  Backend->>API: POST /v1/client-tokens { from_numbers, to_numbers, ttl_seconds }
  API->>API: verify from_numbers are active numbers you own
  API->>API: verify from/to within this key's allowlist ceiling
  API-->>Backend: rdc_… token (short-lived, signed)
  Backend-->>Client: hand off the rdc_… token
  Client->>API: POST /v1/webrtc-token (Authorization: Bearer rdc_…)
  API->>API: enforce request from/to ∈ token bounds
  API-->>Client: per-call token → place the call
```

## Mint a token (server side)

```ts theme={null}
import { RevDesk } from "@revdesk/sdk";

const revdesk = new RevDesk({ apiKey: process.env.REVDESK_API_KEY! });

const { data } = await revdesk.clientTokens.create({
  from_numbers: ["+15551234567"],      // caller IDs the client may present (numbers you own)
  to_numbers: ["+15557654321"],        // optional: destinations the client may dial
  ttl_seconds: 900,                    // optional: 60–3600, default 900
  // scopes: ["voice:webrtc"],         // optional: subset of this key's scopes
});

// data.token  -> "rdc_…"   hand this to your client
// data.expires_in, data.from_numbers, data.to_numbers, data.scopes
```

Requires the `tokens:mint` scope on your API key. A minted token **cannot** mint further tokens.

## Use the token (client side)

Construct the SDK (or the `@revdesk/webrtc` browser SDK) with the minted token in place of your API
key — nothing else changes:

```ts theme={null}
import { RevDesk } from "@revdesk/sdk";

const revdesk = new RevDesk({ apiKey: tokenFromYourServer }); // an rdc_… token

// Allowed: from/to are within the token's bounds
await revdesk.webrtc.getToken({ from_number: "+15551234567", to_number: "+15557654321" });

// Rejected with 403: destination outside the token's to-allowlist
await revdesk.webrtc.getToken({ from_number: "+15551234567", to_number: "+15550009999" });
```

## Binding rules

| Field          | Behavior                                                                                                                               |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `from_numbers` | Required, at least one. Each must be an **active number your organization owns**. The token may only present these caller IDs.         |
| `to_numbers`   | Optional. When set, the token may only dial these destinations. **Empty = any non-emergency destination** (within your key's ceiling). |
| `ttl_seconds`  | Optional, `60`–`3600`, default `900`. Revocation is by expiry — mint short-lived tokens and re-mint as needed.                         |
| `scopes`       | Optional. Must be a subset of the minting key's scopes. Defaults to `["voice:webrtc"]`.                                                |

## Key-level ceiling

You can also cap an API key itself with **allowed caller IDs** and **allowed destinations** (in the
dashboard under the key's settings, or via the key management API). When set, every token minted from
that key — and every call the key places directly — must fall within that ceiling. Leave the ceiling
blank to allow any number the workspace owns. This lets you, for example, issue one key per customer
that is permanently restricted to that customer's numbers.

## Where bounds are enforced

The from/to bounds are enforced on **every** call-placing endpoint, not just token minting:
`/v1/webrtc-token`, `/v1/room-token`, `/v1/calls`, `/v1/calls/dial`, and `/v1/sms/send`. A request
whose `from` or `to` falls outside the credential's bounds is rejected with `403` before any call is
placed.
