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

# RevDesk SDK & CLI

> Official typed TypeScript packages and command-line tooling for the RevDesk API and browser calling.

RevDesk ships two official packages so you don't have to hand-roll HTTP calls or wire up browser
audio:

<CardGroup cols={2}>
  <Card title="@revdesk/sdk" icon="code" href="https://www.npmjs.com/package/@revdesk/sdk">
    Typed REST client for the v1 API: calls, SMS, phone numbers, caller IDs, caller trust, usage, and
    sub-entities. Types are generated from the OpenAPI spec, so they never drift from the API.
  </Card>

  <Card title="@revdesk/webrtc" icon="phone" href="https://www.npmjs.com/package/@revdesk/webrtc">
    Browser-calling client. Place and receive calls in the browser through the RevDesk voice network, with one
    branded surface for both connection paths.
  </Card>
</CardGroup>

The REST SDK is open source on
[GitHub](https://github.com/cell-labs-inc/revdesk-sdk). Its generated types come from RevDesk's
[public OpenAPI 3.1 schema](https://www.revdesk.com/openapi.json).

## @revdesk/sdk

### Install

```bash theme={null}
npm install @revdesk/sdk
# or: bun add @revdesk/sdk
```

### Command line

`@revdesk/sdk` includes the official `revdesk` CLI for scripts and one-off API requests:

```bash theme={null}
export REVDESK_API_KEY="rv_…"
npx @revdesk/sdk request GET /v1/me
npx @revdesk/sdk request GET '/v1/calls?limit=10'
```

Run `npx @revdesk/sdk --help` for all options. The CLI requires `/v1/*` paths and sends the key only
through the `Authorization` header.

### Quickstart

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

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

// List phone numbers
const { data: numbers } = await revdesk.phoneNumbers.list({ limit: 25 });

// Place an outbound call
await revdesk.calls.dial({ from_number: "+15551230000", to_number: "+15554560000" });

// Send an SMS
await revdesk.sms.send({ from_number: "+15551230000", to_number: "+15554560000", body: "Hi!" });
```

### AI agents

Every number you buy gets a RevDesk AI agent wired up to answer it automatically, through the
dashboard or the API, the same provisioning path, no extra setup. The `agents` resource lets you
discover, configure, assign, and call those agents from code.

```ts theme={null}
// List the agents in your workspace (and which number each answers)
const { data: agents } = await revdesk.agents.list();

// Configure one: greeting, instructions, voice, language, on/off
await revdesk.agents.update(agents[0].id, {
  greeting: "Thanks for calling Acme — how can I help?",
  system_prompt: "You are Acme's assistant. Book appointments and answer questions.",
  voice_id: "aoede",
  language: "en",
});

// Reassign which agent answers an inbound number
await revdesk.phoneNumbers.update(phoneId, { agent_id: agents[0].id });

// Point an outbound call at a specific agent
await revdesk.calls.create({ phoneNumber: "+15554560000", assistantId: agents[0].id });

// Talk to an agent straight from the browser, with no phone number needed
const { data: call } = await revdesk.agents.webCall(agents[0].id);
// → { token, room_url, … } hand to @revdesk/webrtc's RevDeskRoom to join
```

<Note>
  Reading agents needs the `agents:read` scope; configuring, assigning, and `webCall` need `agents:write`.
  Configuring an agent requires a user-scoped key.
</Note>

### Bounded client tokens

Mint a short-lived, number-restricted token on your **server**, then hand it to an untrusted client
(mobile app, browser) in place of your API key. The client can only call within the from/to numbers
you choose. See [Bounded client tokens](/api-reference/client-tokens) for the full model.

```ts theme={null}
// On your server (holds the API key):
const { data } = await revdesk.clientTokens.create({
  from_numbers: ["+15551234567"], // caller IDs you own
  to_numbers: ["+15557654321"], // optional destination allowlist
  ttl_seconds: 900,
});
// Hand data.token (an rdc_… string) to your client.

// In the client, construct the SDK with the minted token instead of the API key:
const client = new RevDesk({ apiKey: data.token });
await client.webrtc.getToken({ from_number: "+15551234567", to_number: "+15557654321" });
```

<Note>Minting requires the `tokens:mint` scope. A minted token cannot mint further tokens.</Note>

### Pagination

Cursor-paginated endpoints expose `listAll()`, an async iterator that walks every page for you:

```ts theme={null}
for await (const number of revdesk.phoneNumbers.listAll()) {
  console.log(number.phone_number);
}
```

`listAll()` is available on `phoneNumbers`, `agents`, `subEntities`, and `callerTrust.reputation.numbers`.

### Idempotency

Mutating calls accept an `idempotencyKey` so a retried request is processed once:

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

await revdesk.sms.send(
  { from_number: "+15551230000", to_number: "+15554560000", body: "Hi!" },
  { idempotencyKey: generateIdempotencyKey() }
);
```

### Errors

Every non-2xx response throws a typed `RevDeskError`:

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

try {
  await revdesk.calls.dial({ from_number: "+1555…", to_number: "+1555…" });
} catch (err) {
  if (err instanceof RevDeskError) {
    console.error(err.code, err.status, err.message);
    if (err.isRetryable) {
      /* rate-limited or 5xx, safe to retry */
    }
  }
}
```

See [Errors](/api-reference/errors) for the full `RevDeskError` shape.

## @revdesk/webrtc

### Install

```bash theme={null}
npm install @revdesk/webrtc
# or: bun add @revdesk/webrtc
```

Issue the call token server-side with `@revdesk/sdk`, then hand it to the browser client. Both
clients emit the same `callUpdate` / `error` / `ended` events, so your UI doesn't change when you
switch paths. See [Choosing a calling path](/api-reference/calling-paths) for the differences.

### Browser-direct — `RevDeskRTC`

```ts theme={null}
import { RevDeskRTC } from "@revdesk/webrtc";

// token from revdesk.webrtc.getToken({ from_number, to_number })
const call = new RevDeskRTC({ token });
call.on("callUpdate", ({ state }) => console.log(state));
await call.connect();
call.dial({ to: "+15554560000", from: "+15551230000" });
call.sendDTMF("1"); // out-of-band IVR input; not mixed into microphone audio
```

Handle `reconnecting` as an in-progress call: the client is restoring media after a network or
native call-audio interruption. Do not tear the call down; it returns to `active` when audio is ready.

During initial setup, recoverable signaling interruptions also emit `reconnecting`. Keep waiting
for `connect()` before dialing; concurrent calls to `connect()` share the same pending connection.
The original `connectTimeoutMs` deadline still applies (15 seconds by default), and authentication
failures reject immediately. Timeout stops the underlying connection.

Calling `disconnect()` during setup rejects the pending promise with `connection_canceled`, returns
the state to `idle`, and prevents a later timeout error. This cancellation does not emit a call-failure
event. Close unused server-side tickets separately; see [WebRTC security](/api-reference/webrtc-security).

### Relay — `RevDeskRoom`

```ts theme={null}
import { RevDeskRoom } from "@revdesk/webrtc";

// from revdesk.webrtc.getRoomToken({ from_number, to_number })
const { token, room_url, call_id } = await revdesk.webrtc.getRoomToken({ from_number, to_number });
const call = new RevDeskRoom({ token, roomUrl: room_url, callId: call_id });
call.on("callUpdate", ({ state }) => console.log(state));
await call.join();
```

For relay calls, `active` means the destination answered. Stop local ringback on that state.
Audio playback permission is separate: subscribe to `audioPlaybackChanged` before `join()` and
show an "Enable call audio" button when it reports `false`. Call `call.startAudio()` directly
from the button's click/tap handler, catching rejection if playback remains blocked. A playback
block does not end the call. Read `call.canPlaybackAudio` for the current permission state.

<Note>
  Calls fail through the `error` event with a typed `RevDeskCallError` (`code` is one of `connection_failed`,
  `connection_timeout`, `call_rejected`, …).
</Note>


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