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

# Manage outbound caller IDs

> Register a personal or partner phone number as a verified outbound caller ID, confirm it with a one-time code, and control the number presented on outbound calls.

# Outbound caller IDs

A caller ID is the number presented to the person you call. Outbound
presentation works two ways on Orbit:

* **Orbit-hosted numbers** — numbers you purchased or ported into Orbit. An
  active org-hosted number attests at level **A** on US outbound traffic and
  always presents without verification steps.
* **Verified caller IDs (register-and-confirm)** — a personal or partner phone
  number you control outside Orbit. You register it here and prove control
  with a one-time code so Orbit can present it as your caller ID.

Use this page when an agent's callback number, a partner desk line, or a
legacy number outside Orbit needs to appear on outbound calls without porting
it in. For branded name display on top of the number, see
[CNAM & caller ID](/numbers/cnam).

**Base path:** `/api/v1/voice/caller-ids`

**Authentication:** Clerk session (`Authorization: Bearer <token>`) or API key (`X-API-Key`). Reads accept any `voice` scope; register, confirm, resend, and revoke require `voice:write`.

***

## 1. Register a caller ID

`POST /api/v1/voice/caller-ids/verify` registers the number and sends a
one-time code to its handset — by voice call (TTS readback) or SMS. You choose
the channel.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/caller-ids/verify" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155550100",
    "friendly_name": "Partner desk line",
    "channel": "sms"
  }'
```

**Fields:**

* **`phone_number`** — the E.164 number to verify (8–20 characters, digits
  with optional leading `+`).
* **`friendly_name`** — optional operator label (≤120 characters) shown in the
  dashboard table. It is a label for you, not a CNAM string — it never
  displays on the recipient handset.
* **`channel`** — `voice` or `sms`. Only the two PSTN-backed channels are
  accepted; a verified caller ID proves control of the number, so an
  off-network channel such as WhatsApp or email is rejected with `422`.

The response carries the row id and the verification id you confirm against:

```json theme={null}
{
  "data": {
    "id": "cid_01J...",
    "verification_id": "vf_01J...",
    "status": "pending",
    "channel": "sms",
    "expires_at": "2026-08-31T14:15:00.000Z"
  }
}
```

Registering an already-`verified` number for a different friendly name returns
`409`; revoke the existing row first. A previously-`revoked` row for the same
number is re-claimed by the new registration instead of creating a duplicate.

<Note>
  The one-time code is dispatched through Orbit's outbound voice/SMS path —
  the same Orbit-hosted carrier path every other outbound call uses. Nothing
  in the verification flow touches a third-party carrier account you would
  need to configure.
</Note>

## 2. Confirm the code

`POST /api/v1/voice/caller-ids/confirm` submits the code the handset
received. A correct code moves the row to `verified` and opens the number for
outbound presentation.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/caller-ids/confirm" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "verification_id": "vf_01J...",
    "code": "4821"
  }'
```

```json theme={null}
{
  "data": {
    "id": "cid_01J...",
    "phone_number": "+14155550100",
    "status": "verified",
    "verified_at": "2026-08-31T14:04:11.000Z"
  }
}
```

A wrong code returns `422` and decrements the challenge's attempt budget —
enough wrong attempts or an elapsed `expires_at` flips the row to `expired`,
at which point only a resend or fresh registration moves it forward. A
`verified` row ignores further codes (`409`).

## 3. Verification statuses

Every row moves through one lifecycle you can watch in the dashboard
(**Voice → Caller IDs**) or read over `GET /api/v1/voice/caller-ids`:

| Status      | Meaning                                                                     | Next step                     |
| ----------- | --------------------------------------------------------------------------- | ----------------------------- |
| `pending`   | Registered; no code dispatched yet or a resend cleared the prior challenge. | Resend or wait, then confirm. |
| `verifying` | Code dispatched; awaiting confirm.                                          | Submit the code.              |
| `verified`  | Confirmed; presentable on outbound calls.                                   | Use it as caller ID.          |
| `expired`   | Code TTL elapsed or attempts exhausted.                                     | Resend the challenge.         |
| `revoked`   | Removed from the presentation pool.                                         | Re-register to re-claim.      |

Read the list with:

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/caller-ids?limit=100" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Each row reports `challenge_channel`, `last_challenge_at`, and
`challenge_attempts` alongside the status, so a stuck registration shows
whether the code went out and how many wrong attempts burned the budget.
`verification_id` is projected on `pending`/`verifying` rows only — that is
what lets the dashboard deep-link the confirm step without a fresh dispatch.

## 4. Resend and revoke

Re-deliver the one-time code for a `pending`/`verifying` row when the handset
did not receive it:

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/caller-ids/cid_01J.../resend" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Resend carries a per-recipient rate cap (5 dispatches per hour by default);
crossing it returns `429` with `details.window_seconds` you can render as a
cooldown. Resend on a `verified` or `revoked` row returns `409` — the
challenge is settled.

Remove a number from the presentation pool with:

```bash cURL theme={null}
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/caller-ids/cid_01J..." \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Revoke returns `204`. The row goes to `revoked` and stops being eligible for
outbound presentation; registering the same number again re-claims it through
a fresh verification.

## 5. How presentation resolves on a call

A verified caller ID is one pool your outbound calls can draw from; your
Orbit-hosted numbers are the other. Presentation follows the same precedence
on every outbound call — API-initiated calls, the browser softphone, dialer
campaigns:

1. **Owned Orbit-hosted number** — when your org owns an active number in the
   destination's presence pool, an owned number wins and the call attests at
   level **A**.
2. **Verified external caller ID** — a presented `cid_*` number you verified
   here resolves against your verified list; a delegate certificate you
   registered raises that number from C to **B** attestation (never A — A is
   ownership-only by design).
3. **Default fallback** — with neither match, the platform default fallback
   number applies and the call attests at **C**.

Set a per-agent default on the softphone itself: when the agent registers,
`callerIdNumber` in `POST /api/v1/voice/softphone/register` is the number
their browser calls present from, chosen from your owned pool or verified
caller IDs. A queue- or agent-level override is the softphone/dialer field
that picks one row over another at dial time — there is no separate global
default endpoint to set.

<Warning>
  Attestation ceiling: registering a verified caller ID never produces
  A-level attestation. If answer rate on US traffic matters, run the traffic
  from numbers your org owns through Orbit (A) rather than a verified external
  ID (B). The difference is read in the
  [attestation posture snapshot](/channels/voice/stir-shaken).
</Warning>

## 6. CNAM and STIR/SHAKEN interaction

Caller ID verification and [CNAM](/numbers/cnam) are separate controls that
compose on the recipient handset:

* The **verified caller ID** decides *which number* is presented and feeds
  STIR/SHAKEN attestation resolution (owned → A, verified-with-certificate →
  B, fallback → C).
* The **CNAM registration** decides *what name* carriers display alongside
  that number, gated on carrier propagation of the LIDB write.

Both are per-org: a verified caller ID from one org never shows on another
org's caller-ID list, and a CNAM registration on an owned number belongs to
that org alone.

For US outbound traffic on [SIP trunks](/guides/sip-trunk-setup), the attested
caller ID is the one resolved by the precedence above — a verified caller ID
you present there counts toward the same attestation resolution, capped at B
without ownership.

## 7. Troubleshoot presentation-blocked calls

When outbound calls present the wrong number — or the recipient sees the
fallback instead of your caller ID — walk the list in order:

| Symptom                                                          | Likely cause                                                                        | Fix                                                                                                  |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Call presents the platform default, not your number              | The caller ID isn't verified, or it isn't in the presence pool for the destination. | Confirm the row is `verified`; check the destination has a matching owned/verified entry.            |
| Attestation stuck at C despite a registered delegate certificate | The certificate or number coverage lapsed.                                          | Re-check certificate validity; fall back to ownership for A.                                         |
| CNAM shows the old name                                          | Carrier LIDB propagation still in flight.                                           | Allow days, not minutes; re-dispatch only after the current cycle settles.                           |
| Confirm returns `409` on a row you thought was pending           | Status already advanced past the code step.                                         | Read the row's current status before resubmitting; `verified` and `revoked` rows don't accept codes. |
| Resend returns `429`                                             | Per-recipient rate cap (5/hour default).                                            | Wait out `details.window_seconds` shown in the error and retry.                                      |
| Dashboard row shows `expired`                                    | Code TTL elapsed, or wrong-attempt budget consumed.                                 | Resend the challenge from the same row.                                                              |

<Note>
  Every control on this page is tenant-owned. Orbit registers the caller ID
  you prove control of, applies the attestation level your ownership earns,
  and enforces your per-DID inbound floor — it does not impose a presentation
  posture on your traffic. Regulatory compliance for how you present caller ID
  on outbound calls stays your responsibility.
</Note>

## Deep dives

* [CNAM & caller ID](/numbers/cnam) — register the branded name carriers display
* [STIR/SHAKEN attestation](/channels/voice/stir-shaken) — how attestation levels resolve and what you control
* [Voice queues](/guides/voice-queues) — route inbound callers to staffed queues
* [SIP trunk setup](/guides/sip-trunk-setup) — bring-your-own-carrier trunks with attestation on US outbound
* [Number masking](/numbers/masking) — present a masked number on two-party sessions
