> ## 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` | **Required, at least one.** The token may only dial these destinations, and they must fall within your key's ceiling. There is no "unrestricted" form: a browser token that could dial anywhere is a toll-fraud liability, so an empty list is rejected. |
| `ttl_seconds` | Optional, `60`–`3600`, default `900`. Revocation is by expiry, so mint short-lived tokens and re-mint as needed. |
| `scopes` | Optional, and the only accepted value is `["voice:webrtc"]` (also the default). A client token can never carry a durable-write scope, mint further tokens, or read recordings or transcripts, no matter what the minting key holds. Anything beyond browser calling has to go through your own server with the real API key. |

## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.