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

# Choosing a calling path

> The one decision that picks your path: does an AI need to be on a call with a real person, and where should your staff be, in the browser or on a phone?

RevDesk connects people to live calls in a few different ways. They share one security baseline and
differ in **where the audio lives**, **whether the destination is locked by construction**, and
**whether an AI can be on the call**. This page helps you pick.

## Start here: what are you building?

<CardGroup cols={2}>
  <Card title="Talk to an AI" icon="robot">
    A person talks to an **AI voice agent in the browser**. **No phone number, no carrier, nobody's
    phone rings**. The browser opens a WebRTC audio line straight to the AI. This is how the live demo
    on our site works, and how an embedded "talk to our AI" widget works.

    **This is not one of the three paths below.** It isn't a phone call at all. It's always available.
  </Card>

  <Card title="Place or connect a real phone call" icon="phone">
    You're putting a person on a **live phone call to a real number**: outbound dialing, connecting
    staff to customers, masked caller ID. This is what the **three paths** (A / B / C) are for. Pick
    one with the decision tree below.
  </Card>
</CardGroup>

<Note>
  **WebRTC is just the transport, not the deciding factor.** WebRTC can carry a call to a carrier
  (path A) *or* a line straight to an AI (talk-to-AI, above). So "an AI can only join when RevDesk
  places the call", below, is specifically about adding an AI to a call that **also has a person on
  a phone**. It does not mean a browser can't reach an AI.
</Note>

<Note>
  **AI that *answers* your inbound calls** (an agent that picks up when someone dials your
  number) is a separate capability, not one of the outbound paths on this page.
</Note>

## Placing a real call: pick a path

One question does most of the work: **does an AI need to be on the call, or do you need the
destination locked so the client can't change it?** If yes, RevDesk places the call (B or C). If no,
the browser can dial directly (A). Then: is your staff member in the browser, or on their own phone?

```mermaid theme={null}
flowchart TD
  Q1{"Need an AI on the call,<br/>or a hard destination lock?"}
  Q1 -->|No| A["<b>Path A</b><br/>Browser dials out"]
  Q1 -->|Yes| Q2{"Where is your<br/>staff member?"}
  Q2 -->|In the browser| B["<b>Path B</b><br/>RevDesk places it,<br/>browser joins"]
  Q2 -->|On their own phone| C["<b>Path C</b><br/>RevDesk bridges<br/>both phones"]
```

### At a glance

| | **A — Browser** | **B — Browser, RevDesk-placed** | **C — Bridge (your phone)** |
| - | - | - | - |
| **Status** | Available today | Beta | Available today |
| **Who places the call** | The browser | RevDesk | RevDesk |
| **Your staff talk from** | Browser + mic | Browser + mic | Their own phone |
| **Phones that ring** | One (the destination) | One (the destination) | Two (yours + the destination) |
| **Destination lock** | Scoped & bounded | Hard (server-set) | Hard (server-set) |
| **AI can join the call** | No | Yes (roadmap) | Yes (roadmap) |
| **Needs mic + good network** | Yes | Yes | No, it's a normal phone call |
| **Connect latency** | Instant | Short | Short |
| **In-app controls (mute, transfer)** | Yes | Yes | Limited |
| **Best for** | Simplest internal dialing | In-browser, but with a hard lock or AI | Resilient / regulated / cellular |

## The three paths

<Steps>
  <Step title="Path A — Call from your browser (today)">
    Staff click **Call** and talk through their browser; no phone line needed. The browser gets a
    scoped, single-use token for that one call and dials the carrier directly.

    Quickest to launch and proven-stable. The destination is **declared, attributed, and
    guardrail-bounded**, but because the browser is what dials, this path doesn't *lock* the
    destination the way B and C do, and there's no room for an AI to join.

    **Use it for** simple internal dialing by trusted staff.
  </Step>

  <Step title="Path B — Browser call, placed by RevDesk (beta)">
    Same in-browser experience, but the browser **never dials**. RevDesk places the call and the
    browser simply *joins* it, using a room-join token from `POST /v1/room-token` and the RevDesk
    browser SDK.

    Because the destination is set server-side, it **can't be changed by the client or replayed to
    reach a different number**, and because the call lives in a room, you can **add an AI voice**
    (mid-call assist, AI voicemail, AI follow-ups; on the roadmap). Only the destination's phone
    rings; your staff join from the browser.

    **Use it for** an in-browser experience that still needs a hard destination lock or AI on the call.
  </Step>

  <Step title="Path C — Bridge: connect on your own phone (today)">
    The path for a regular phone, with no browser or SDK. Staff click **Call**, *their own phone rings*,
    and when they answer the other party is already on the line. RevDesk places both legs, so the
    destination is locked server-side and the other party only ever sees your RevDesk number, never
    the staff member's personal number.

    Two phones ring (the staff member's and the destination's). Most resilient, since it falls back to the
    cellular network and needs no mic or browser permissions.

    **Use it for** regulated / privacy-sensitive calling, or anyone who'd rather answer on a phone.
  </Step>
</Steps>

<Tip>
  **For a hard destination lock, use B or C.** Because RevDesk places the call, the destination can't
  be changed by the client and the token can't be replayed to reach a different number. Path A scopes
  and bounds the destination but doesn't lock it by construction.
</Tip>

## What every path shares (the baseline)

No matter which path you choose:

* **Scoped, single-use tokens.** Each token is issued for exactly one call and invalidated when that call ends, so it can't be reused.
* **You can only call *as* a number you own.** Caller ID is validated at issuance against your verified numbers.
* **Per-call attribution.** Every call is logged to your org, the issuing user, and the from/to. Reconcile via `GET /v1/calls`.
* **Account guardrails.** A destination allowlist, a daily spend cap, a concurrency cap, and a max per-minute rate bound the blast radius even if a token leaked.
* **A call-length limit.** Every call has a maximum duration and is ended automatically if it reaches it, so a stuck or looping call can never run unbounded. Default is **60 minutes**; it can be raised per number or per workspace (contact us or set it in workspace settings), up to a 3-hour ceiling.

## Caller-ID selection and health warnings

`POST /v1/calls/dial` accepts either an exact `from_number` or a `number_set_id`:

```json theme={null}
{
  "number_set_id": "YOUR_NUMBER_SET_ID",
  "to_number": "+12025550123",
  "connect_number": "+12025550124",
  "record": false
}
```

If both are supplied, **exact `from_number` wins**. Existing requests using `from_number` continue
to work. Number Set selection is available on `/v1/calls/dial`; it is not an additional input to
the WebRTC or room-token endpoints.

The **202** response includes the resolved `from_number`, `selection_source`
(`exact_from_number` or `number_set_sticky_health`), `evidence_state`, `health_warning`, and
`recommended_action`. A high-risk exact caller ID is dialed with a warning, not silently switched
or rejected for reputation alone. An inactive or technically unroutable exact number still fails
normally, without fallback. Billing, wallet, concurrency, and contractual protections remain.

Number Sets prefer the healthiest eligible members with recipient-sticky selection. Even when
all eligible members are flagged, a call can proceed with a warning; no eligible member is an
error. A preview does not reserve a caller ID: health and membership can change before dialing.
See [Number Health](/api-reference/number-health) for set configuration, evidence states, and
resumable handset testing.

## Ending a call

A call ends when any of these happens (whichever comes first):

* **The AI decides the conversation is over** and hangs up on its own.
* **A person on the call hangs up.**
* **You end it on demand:** `POST /v1/calls/{id}/hangup` stops an in-progress call immediately (ending an already-ended call just returns success).
* **It hits the call-length limit** above.

<Tip>
  Testing your agents by calling your own AI numbers (AI-to-AI) is fully supported. Those calls run
  like any other and are bounded by the same call-length limit, so they can't get stuck. Use
  `POST /v1/calls/{id}/hangup` to end a test call the moment you're done.
  Synthetic tests are connectivity QA only: they never count as human readiness evidence or as a
  reputation-building campaign. See [Number Health test modes](/api-reference/number-health#1-create-the-session).
</Tip>

## Same surface, swap the path

Paths B and C expose the **same client surface**. Issue a token, attach a `callUpdate` handler, and
switching paths is close to a one-line change:

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

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

// Path B: browser joins a RevDesk-placed call
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", render);
await call.join();

// Path C: bridge to a real phone. RevDesk rings connect_number; the destination
// still only ever sees from_number.
await revdesk.calls.dial({ from_number, to_number, connect_number });
```

See [WebRTC security](/api-reference/webrtc-security) for the token model, caller-ID handling, the
guardrails, and concern-by-concern answers.


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