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

# Admin: Voice Destinations dial-code deck

> Manage the prefix-to-operator directory that voice pricing uses to resolve a destination number to a country and operator before a call is charged.

# Admin: Voice Destinations dial-code deck

The **Voice Destinations** console (`/settings/billing/voice-destinations`) is the admin surface for the dial-code deck: the prefix → operator/country directory that voice pricing longest-prefix-matches against. It is the counterpart to the SMS **MCCMNC Overrides** console, but for voice: it tells Orbit which operator owns a number starting with a given E.164 prefix, so the rate chain can key on the operator instead of falling back to the country-level rate.

<Note>The console is super-admin only. It does not appear for admin or operator roles.</Note>

***

## 1. What the deck is and why it matters

Voice pricing resolves in three layers:

1. **Destination number → prefix.** The resolver takes the called number, strips non-digits, and tests the longest matching prefix in `public.dial_codes`.
2. **Prefix → operator/country.** A match returns an MCC/MNC, operator name, and country name.
3. **Operator/country → rate.** The resolver keys on the MCC/MNC first, then falls back to the country-level rate if the prefix is missing or the operator has no published rate.

The country fallback is safe, but it is built from each country's most expensive operator so it never bills below cost. That means traffic to cheaper operators is overcharged when the prefix is absent. Keeping the deck accurate is what lets voice calls price on the correct operator rate.

The resolver uses longest-prefix matching and returns the most specific entry it can find. A shorter country-level prefix only wins when no longer, more specific prefix matches.

***

## 2. Open the console

Open **Settings → Billing → Voice Destinations** (or `/settings/billing/voice-destinations`). The page shows a paginated table of every prefix in the deck with these columns:

* **Code** — the E.164 prefix, digits only, no leading `+` (for example `98912`).
* **MCCMNC** — the mobile country code + mobile network code, MNC zero-padded to 3 digits (for example `432011`).
* **Operator** — the operator name the resolver reports for that prefix.
* **Country** — the country name.
* **Updated at** — the last time the row was written.

Use the search box and the **Country** / **Operator** / **MCCMNC** filters to narrow the list. The country filter is populated from `GET /api/v1/admin/voice-destinations/countries`.

***

## 3. CRUD operations

The console maps to four API routes under `/api/v1/admin/voice-destinations`:

| Action | API route | Console control |
| - | - | - |
| List / search | `GET /api/v1/admin/voice-destinations` | The main table |
| Country filter values | `GET /api/v1/admin/voice-destinations/countries` | The **Country** dropdown |
| Upsert one prefix | `PUT /api/v1/admin/voice-destinations` | **Add prefix** or edit a row |
| Delete one prefix | `DELETE /api/v1/admin/voice-destinations/:code` | The delete action on a row |

All four routes require super-admin access. Non-super-admin callers receive a `403`.

### 3.1 Upsert a prefix

Choose **Add prefix** or click the edit action on an existing row. The form accepts:

* **Code** — 1 to 11 digits, no `+`. The longest match wins, so enter the most specific prefix you have.
* **MCCMNC** — 5 to 6 digits: a 3-digit MCC followed by a 2 or 3-digit MNC. The MNC is internally zero-padded to 3 digits, so `2621` becomes `26201` and `26201` stays `26201`.
* **Operator name** — up to 200 characters.
* **Country name** — up to 120 characters.

The primary key is `code`, so re-submitting an existing prefix updates the operator, country, and MCCMNC in place.

Example request body:

```json theme={null}
{
  "code": "989123",
  "mccmnc": "432011",
  "operatorName": "Example MVNO",
  "countryName": "Iran"
}
```

### 3.2 Delete a prefix

Click the delete action and confirm. Deletion removes only the prefix row; it does **not** cascade to rate cards, overrides, or historical charges. Calls to numbers that matched the deleted prefix fall back to country-level pricing on the next send.

***

## 4. Worked example: add an MVNO on its own prefix

Suppose a new MVNO in Iran starts using the range `989123`. Without a deck entry, calls to numbers beginning with `989123` longest-match the shorter `98912` entry (if present) or fall back to the Iran country rate.

1. Open **Settings → Billing → Voice Destinations**.
2. Choose **Add prefix**.
3. Enter:
   * Code: `989123`
   * MCCMNC: `432011`
   * Operator name: `Iran Example MVNO`
   * Country name: `Iran`
4. Save.

From the next call onward, a number like `+98 912 345 6789` resolves to operator `432011` instead of whatever the shorter prefix or country fallback returned. If a per-operator voice rate exists for `432011`, that rate applies; otherwise the country fallback still applies, but the operator name on the call record is now correct.

***

## 5. Bulk import vs single-row upsert vs the loader script

There are three ways to update the deck. Use the right one for the job:

| Method | When to use |
| - | - |
| **Single-row upsert in the console** | One-off corrections, a missing MVNO range, or testing a prefix before a wider import. |
| **CSV bulk import** | A curated list of corrections from a carrier or a partner. The console accepts a CSV with the same shape the loader uses. |
| **`apps/api/src/scripts/load-dial-codes.ts`** | Seeding or refreshing the entire deck from the GSMA source CSV. This is the one-off loader, not a recurring admin operation. |

The loader script expects a CSV with this header:

```csv theme={null}
Code,MCCMNC,Mobile operator name,Mobile operator country
```

It is idempotent: re-running it upserts on `code` and updates rows in place. It deduplicates by `code` before writing, so duplicate rows in the source do not fail the batch. Run it with:

```bash theme={null}
node --import tsx apps/api/src/scripts/load-dial-codes.ts /path/to/ALLPRX_MCCMNC_codes.csv
```

Do not use the loader for small corrections — the console is faster and leaves a clearer audit trail. Do not use the console for a full GSMA refresh — the loader is built for that volume.

***

## 6. Correction loop: when a fix takes effect

A change to the deck affects pricing from the **next call** after the write commits. It does not rewrite charges for calls that already priced. The resolver reads the deck at pricing time, so:

* Adding a missing prefix moves future calls from the country fallback to the operator rate.
* Updating an operator or MCCMNC changes the key future calls price against.
* Deleting a prefix reverts future calls to the country fallback.

Existing call records and ledger rows remain unchanged — existing charges are immutable. If you need to credit calls that were mispriced before the correction, handle that through your normal billing-adjustment process.

***

## 7. Guardrails and safety

* **Super-admin only.** Every route checks `request.ctx.isSuperAdmin`. Any other role gets `403 FORBIDDEN`.
* **Destructive delete is isolated.** `DELETE /api/v1/admin/voice-destinations/:code` removes only the prefix row. Rates, overrides, and historical charges are untouched.
* **Validation rejects malformed prefixes.** The API rejects codes with non-digit characters, codes longer than 11 digits, and MCCMNC values outside the 5–6 digit range.
* **Fail-soft resolver.** If a prefix is missing or the lookup fails, pricing falls back to the country rate. A missing prefix never blocks a call.

***

## 8. API examples

### 8.1 List prefixes

```bash theme={null}
curl -X GET "https://api.orbit.devotel.io/api/v1/admin/voice-destinations?country=Iran&limit=3" \
  -H "Authorization: Bearer $ORBIT_ADMIN_TOKEN"
```

Example response:

```json theme={null}
{
  "data": [
    {
      "code": "98912",
      "mccmnc": "432011",
      "operatorName": "Mobile Communications Company of Iran",
      "countryName": "Iran",
      "updatedAt": "2026-09-09T10:23:00.000Z"
    },
    {
      "code": "989123",
      "mccmnc": "432011",
      "operatorName": "Iran Example MVNO",
      "countryName": "Iran",
      "updatedAt": "2026-10-10T14:02:00.000Z"
    }
  ],
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-10-10T14:02:00.000Z",
    "pagination": { "cursor": "989123", "has_more": true }
  }
}
```

### 8.2 Upsert a prefix

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/admin/voice-destinations" \
  -H "Authorization: Bearer $ORBIT_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "989123",
    "mccmnc": "432011",
    "operatorName": "Iran Example MVNO",
    "countryName": "Iran"
  }'
```

### 8.3 Worked mismatch: call priced at country level

You review a call to `+98 912 345 6789` and see it priced under the Iran country rate instead of the operator rate. The cause is that the prefix `989123` is missing from the deck, so the longest match was a shorter prefix that maps to the country fallback.

After you add the `989123` entry as shown above, the next call to a number in that range resolves to operator `432011` and prices against the operator rate if one exists.

***

## 9. Troubleshooting

* **A prefix still prices at country level after you added it.** Check that the code is the longest match for the destination number. A longer prefix always beats a shorter one. Also verify the MCCMNC has a published voice rate; without one, the resolver falls back to the country rate even when the operator is known.
* **The console shows an empty list.** The deck may not be seeded, or your role is not super-admin. Only super-admin callers can read this surface.
* **CSV import rejects rows.** The loader rejects rows where the code contains non-digits or the MCCMNC is not 5–6 digits. Fix the source row and re-import.
* **Delete returns `404`.** The prefix was already removed, or the code in the URL does not match an existing row.

***

## Related pages

* [Pricing and rate resolution](/concepts/pricing-rate-resolution) — how operator and country rates resolve into a charge.
* [MCCMNC overrides](/guides/billing-mccmnc-overrides) — the SMS counterpart: per-operator rate overrides.
* [Voice country coverage](/guides/voice-country-coverage) — where voice termination is available.


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