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

# Desk phones: zero-touch provisioning

> Register Polycom, Yealink, Cisco, and Grandstream desk phones by MAC address, point the vendor redirect server at the provisioning URL, pair each phone with a SIP credential, and bulk-import fleets by CSV — with full API parity.

# Desk phones: zero-touch provisioning

Zero-touch provisioning (ZTP) attaches a physical desk phone to your
workspace without typing SIP settings into the handset. You register the
phone's MAC address once, hand its vendor's redirect server a provisioning
URL, and the phone pulls a rendered, vendor-shaped config on its next boot
— SIP server, credential pair, label — and registers. Everything else runs
server-side.

Supported vendors: **Polycom**, **Yealink**, **Cisco**, and
**Grandstream**.

All SIP traffic the phone receives exits through the Devotel wholesale
softswitch. The provisioning URL hands the phone the Devotel-routed SIP
edge only — no provisioned phone ever registers directly at Telnyx or
DIDWW. You do not configure any of this; it is the platform's routing
contract, stated here so a network team auditing traffic knows what to
expect.

New to this area? Start with
[Extensions and desk phones](/guides/extensions-and-desk-phones) for the
create-the-extension walkthrough; use this page for the provisioning
lifecycle in depth and for the API.

## Where it lives

Open **Voice → Desk phones** in the dashboard
(`/voice/devices` under your locale). The surface is gated to **owner**
and **admin** roles — other roles see neither the page nor the write API.

The page lists every registered phone with its MAC, vendor, model,
paired SIP credential, claim state, and last provisioning fetch.

## Register a phone

1. Find the phone's **MAC address** — on the label, the box, or the
   phone's **Status → Network** screen.
2. Click **Register desk phone** and enter:
   * **MAC** — colons, dashes, dots, or whitespace are accepted and
     normalized for you.
   * **Vendor** — `polycom`, `yealink`, `cisco`, or `grandstream`.
   * **Model** (optional) — e.g. `VVX 450`, `T46U`.
   * **Label** (optional) — e.g. `Reception`. Helpful when the fleet
     grows past a handful.
   * **SIP credential** (optional) — pair now, or pair later from the
     device row.
3. Save. The response returns the **provisioning URL** — copy it from the
   confirmation dialog or from the device row at any time.

Each registration starts a **30-day claim window**. If the phone never
fetches its config in that window, the row flips to **Expired** and the
URL stops working — rotate it (below) when the phone finally arrives.

## Point the phone at the URL

The provisioning URL goes into the vendor's redirect service — the phone
already knows how to reach its vendor on first boot, and the vendor hands
it your URL:

| Vendor      | Redirect service |
| ----------- | ---------------- |
| Polycom     | Polycom ZTP      |
| Yealink     | Yealink RPS      |
| Cisco       | Cisco EDOS       |
| Grandstream | Grandstream GDMS |

Paste the provisioning URL against the phone's MAC in that portal
(supported vendors are also registered automatically on create when the
vendor cloud accepts it — the dashboard response and audit log say which
happened). Then **factory-reset the phone** so it boots through the
redirect.

<Warning>
  The provisioning URL carries a signed, single-use token bound to the
  MAC. Treat it like a password — anyone holding a live URL can provision
  the phone as your extension. Once the phone fetches its config the URL
  is spent and returns a generic refusal to everyone, including you.
</Warning>

If the URL leaked or was used on the wrong phone, run **Rotate
provisioning URL** from the device row: it mints a fresh token, unlocks
the fetch, and re-opens the claim window without touching the paired
credential.

## Pair with a SIP credential

A registered phone is inert until it is paired with a SIP credential —
the extension it registers and calls as. Pair from the device row, or
create the credential first under **Voice → Extensions** and pick it
during registration. On the next boot after pairing, the phone pulls its
config, authenticates, and registers against that extension. Re-pairing
to a different credential takes effect the same way: rotate the
provisioning URL and reset the phone.

## Bulk import (CSV)

For a fleet rollout, click **Bulk import** on **Voice → Desk phones** and
upload a CSV with a header row. `mac` and `vendor` are required; `label`
and `model` are optional. Column order and case do not matter, and
common header aliases are accepted.

```csv theme={null}
mac,vendor,label,model
00:04:f2:aa:bb:cc,polycom,Reception,VVX 450
80-5E-C0-11-22-33,yealink,Desk 12,T46U
```

The importer validates the header first — a missing `mac` or `vendor`
column rejects the file before anything is written — then registers rows
sequentially. The result panel reports registered / failed / skipped
counts with per-line reasons: an invalid MAC or unknown vendor affects
only that row; a duplicate MAC lands in the failed list with its line
number. Fix the rejected lines and re-upload — already-registered rows
report as duplicates, not errors. Bulk registration is rate-limited to
50 phones per hour per workspace, sized for exactly this rollout flow.

## Remove a device

Delete a device from its row to revoke it: the SIP-edge association is
soft-deleted, the provisioning URL dies, the vendor-cloud redirect entry
is removed on a best-effort basis, and the action lands in the audit
log. The phone keeps its last rendered config until it is reset — wiping
and factory-resetting the handset is still on you. Re-registering the
same MAC afterwards revives the row as a fresh device (new token, new
claim window), so a torn-down phone costs nothing to bring back.
Removing a device never touches the paired SIP credential.

## API parity

Everything the dashboard does is available on the devices API under
`/api/v1/voice/devices`. Base URL `https://api.orbit.devotel.io`;
authenticate with a session bearer token or an API key.
List and get accept `owner`, `admin`, `developer`, and `viewer` roles;
create, update, rotate, and delete require `owner` or `admin`.

**Create** — `POST /api/v1/voice/devices` returns the device with its
`provisioningUrl`:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/devices" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "macAddress": "00:04:f2:aa:bb:cc",
    "vendor": "polycom",
    "model": "VVX 450",
    "label": "Reception",
    "sipCredentialId": "sipcred_01HZ…"
  }'
```

`macAddress` accepts any common separator; `claimExpiresAt` (ISO 8601)
overrides the default 30-day window, and `null` removes expiry for
long-tail rollouts. A duplicate live MAC returns **409** — a MAC already
in your workspace, or one registered to another workspace (contact
support to transfer it).

**List** — `GET /api/v1/voice/devices?limit=50&cursor=<cursor>` paginates
by keyset cursor (`limit` 1–200, default 50; follow `next_cursor`).
**Get** — `GET /api/v1/voice/devices/:id` returns one device.

**Update** — `PATCH /api/v1/voice/devices/:id` changes `label`, `model`,
`sipCredentialId` (set to `null` to unpair), `notes`, `disabled`, or
`claimExpiresAt`. Disabling a device kills its provisioning URL without
deleting the row.

**Rotate the provisioning URL** — `POST
/api/v1/voice/devices/:id/rotate-provisioning` mints a fresh token and
unlocks the fetch after the first boot locked it. Returns **409** when
there is nothing to rotate (the device has not been fetched yet).

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/devices/sipdev_01HZ…/rotate-provisioning" \
  -H "Authorization: Bearer <token>"
```

**Delete** — `DELETE /api/v1/voice/devices/:id` soft-deletes the device
and revokes the SIP-edge association.

**Phone-side fetch** — the phone itself calls
`GET /api/v1/voice/provisioning/<mac>.xml?token=<token>` (or `.cfg`),
which is what the provisioning URL points at. It is unauthenticated by
design — the booting phone owns nothing yet; the signed token in the
URL is the credential. Every failure path returns the same generic 403,
so the URL cannot be probed for valid MACs.

Every device row also carries a `directoryUrl`: the remote
corporate-directory (phonebook) feed for that phone. Yealink and
Grandstream pick it up from their rendered config when the remote
phonebook feature is on; for Cisco and Polycom, paste it into the
vendor's directory config by hand.

## Screenshots

1. **Voice → Desk phones — device list** with claim states.
2. **Register desk phone** dialog (MAC, vendor, model, label, pairing).
3. **Provisioning URL confirmation** after a successful registration.
4. **Bulk import result panel** with per-line verdicts.

## More

* [Extensions and desk phones](/guides/extensions-and-desk-phones) —
  create the SIP credential the phone pairs with.
* [Desk-phone SIP extensions: reference](/voice/extensions) — the full
  credential field set and lifecycle.
* [SIP trunk troubleshooting](/troubleshooting/sip-trunk) — when a
  registered phone loops on REGISTER.
