Phone Numbers
Orbit provides virtual phone numbers across a broad, provider-driven country footprint for SMS, voice, and multi-channel messaging. Search available numbers, purchase instantly, and configure routing — all through the API or dashboard. Per-country and per-type availability is provider-driven and changes over time, so it is not hardcoded here. Query the live source of truth instead:GET /api/v1/numbers/available/countries— countries that currently have searchable inventory.GET /api/v1/numbers/country-capabilities— per-countryline_type × capabilitycounts (local/mobile/toll-free, SMS/voice), including the authoritativetwo_way_smscount. Note that toll-free SMS is send-only (toll-free is never two-way), which is why toll-free SMS is surfaced separately fromtwo_way_sms.
| Page | What it covers |
|---|---|
| Inventory & Export | List numbers, bulk CSV/JSON export, low-stock alerts |
| Country Capabilities | Per-country inventory summary before you search |
| Regulatory Preview | Document/field requirements before you buy |
| Number Lifecycle | Auto-renew, scheduled release, reassign, reclaim, trial pool |
| Emergency Address (E911) | Register and validate dispatchable addresses |
| CNAM & Caller ID | Branded caller name, CNAM dip, spam-label remediation |
| Number Health & Reputation | Warming, health, reputation, usage & branded-calling analytics |
| Number Lookup | Carrier / portability / SIM-swap / roaming intelligence |
Search Available Numbers
Response
The matching numbers are nested underdata.numbers — data is an object, not an array. Each item’s recurring fee is monthlyRate, a numeric dollar amount (there is no separate currency field).
| Field | Type | Description |
|---|---|---|
id | string | Provider-scoped identifier for the available number. |
phoneNumber | string | The number in E.164 format. |
capabilities | string[] | Supported channels (e.g. sms, voice, mms). |
sms_two_way | boolean | true when the number can both send and receive SMS. |
monthlyRate | number | Recurring monthly fee in dollars. |
setupRate | number? | One-time setup/activation fee in dollars. Omitted when there is no setup charge. |
provider | string | Carrier that supplies the number. |
expectedActivation | object? | When the carrier is expected to activate the number after purchase. |
Purchase a Number
Bulk Purchase
Buy a known list of numbers in a single call withPOST /api/v1/numbers/buy-bulk. Use this when you already have the exact E.164 numbers (for example from GET /numbers/available) and want to provision up to 50 of them at once.
The wallet is checked up-front against the sum of the per-number costs. Per-row carrier failures are reported in failed[] and never cause partial-batch debits — either every charged number is debited, or none is. The endpoint is rate-limited at 2 requests/minute per tenant because each call can fan out to 50 upstream provider calls.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
items | array | yes | 1–50 numbers to purchase. |
items[].phone_number | string | yes | Number in E.164 format. |
items[].country_code | string | no | ISO 3166-1 alpha-2 code. Defaults to US. |
items[].provider | string | no | One of devotel, telnyx, didww. Provider hint. |
items[].id | string | no | Provider-issued inventory row id, when supplied by GET /numbers/available. |
compliance_profile_id | string | no | Compliance profile applied to every item in the batch. |
Response
Returns200 OK even on partial success — inspect succeeded[] and failed[] to render per-row results without parsing 4xx bodies. (A batch where every item fails is still 200; check succeeded.length to decide UI state.)
total_cost_cents is the summed cost of all requested items; debited_cents is the amount actually charged for the items in succeeded[].
Bulk Reserve
When you need to provision a contiguous block of DIDs but don’t yet have the specific numbers, use the bulk-reservation lifecycle. You reserve up to 1000 DIDs that match a country / area-code / region filter, the platform holds them for 15 minutes, then you finalize (purchase + bill + create inbound routes) or cancel (release the hold). This closes the gap wherebuy-bulk requires a pre-built list of E.164s.
1. Reserve
POST /api/v1/numbers/bulk-reserve searches provider inventory (DIDWW + Telnyx, picking the cheapest N across the union unless provider is pinned) and places a 15-minute hold. Rate-limited at 1 request/minute per tenant.
| Field | Type | Required | Description |
|---|---|---|---|
country | string | yes | ISO 3166-1 alpha-2 code. |
quantity | number | yes | Number of DIDs to reserve, 1–1000. |
number_type | string | no | One of local, mobile, toll_free. Defaults to local. |
area_code | string | no | 2–5 digit area/region code filter. |
name_pattern | string | no | Digits-only pattern (max 20) the number must match. |
pattern_type | string | no | contains (default), starts_with, or ends_with. |
capabilities | string | no | Comma-separated filter of sms, mms, voice, fax. |
provider | string | no | Pin to devotel, telnyx, or didww. Otherwise both DIDWW + Telnyx are searched. |
compliance_profile_id | string | no | Compliance profile reused across every item at finalize time. |
201 Created. status is ready when the full requested quantity was found, or partial when fewer matched. The reservation expires at expires_at (creation + 15 minutes).
2. Inspect
GET /api/v1/numbers/bulk-reserve/:id returns the current reservation state and items. The row is inline-expired on read once expires_at has passed, so a held-but-never-finalized reservation reports as expired without a separate sweep.
3. Finalize
POST /api/v1/numbers/bulk-reserve/:id/finalize purchases, bills, and creates inbound routes for every reserved item. It runs the same wallet preflight, per-item compliance gate, and per-item audit as buy-bulk. Per-item failures are reported in the response counts; no partial-batch debit occurs.
4. Cancel
POST /api/v1/numbers/bulk-reserve/:id/cancel releases every reserved item back to the carrier inventory pool. No upstream carrier call is made — a reservation never placed an order. Returns 409 if the reservation has already been finalized, cancelled, or expired.
Configure Routing
Update the number record
Update a number’s settings — including a generic inbound webhook and SMS forwarding — withPUT /api/v1/numbers/:id:
channel_config, status,
label, capabilities, forwarding_number, webhook_url,
sms_forwarding, compliance_profile_id, and tags. Unknown fields are
rejected with a VALIDATION_ERROR (422). Use webhook_url for a single
generic inbound webhook and sms_forwarding (E.164) to forward inbound SMS
to another number.
Route inbound calls or SMS to an agent or queue
To send inbound calls or messages to an AI agent, IVR, queue, ring group, voicemail, or other destination, use the dedicated inbound-routing surface keyed by the number in E.164 form —agent_id is not a field on the
number record:
type is one of agent, softphone_user, softphone_register,
ivr, voicemail, queue, sip_forward, transfer, conference,
decline, or ring_group; each type has its own config shape. Inbound
SMS termination is configured independently via sms_route_type
(webhook, agent, auto_reply, or disabled) and sms_route_config.
Retrieve the current route with GET /api/v1/numbers/:phoneNumber/routing.
Number Types
| Type | API type value | Description |
|---|---|---|
| Local | local | Geographic number tied to a city or region |
| Toll-Free | toll_free | Free for callers, costs billed to you. SMS is send-only where supported (toll-free is never two-way) |
| Mobile | mobile | Mobile number for SMS and voice |
type value to the search endpoint, e.g. ?type=toll_free. Short codes are not available for search or purchase through the API.
Which countries and types are actually available is provider-driven — rather than a fixed matrix, query GET /api/v1/numbers/available/countries (countries with current inventory) and GET /api/v1/numbers/country-capabilities (per-country counts by line type and capability, including the authoritative two_way_sms count) for live availability.
Number Capabilities
Capabilities are expressed through two distinct enums — the search filter and the purchase field are not interchangeable, and passing a value from the wrong set returns422:
- Search filter — the
capabilitiesquery param onGET /api/v1/numbers/availableaccepts a comma-separated subset ofsms,mms,voice,fax.whatsapp/rcsare not valid here and are rejected with422. - Purchase field — the
capabilitiesarray onPOST /api/v1/numbers/purchaseacceptssms,voice,whatsapp,rcs. Use this enum (not the search one) to request over-the-top channels at provisioning time;mms/faxare not purchase-time toggles.
| Capability | Description | Search filter | Purchase field |
|---|---|---|---|
sms | Send and receive SMS | yes | yes |
voice | Make and receive calls | yes | yes |
mms | Send and receive MMS (images, media) | yes | — |
fax | Send and receive fax (T.38) | yes | — |
whatsapp | Register as WhatsApp Business number | — | yes |
rcs | Register for RCS Business Messaging | — | yes |
Number Porting
Bring your existing numbers to Orbit. Port-in is a four-step flow — check eligibility → attach a signed LoA → submit → track. Don’t submit blind: the pre-flight check tells you whether the number is portable and returns the carrier’s estimated completion date, so you don’t burn business days on a doomed order. You can run the whole flow from the dashboard under Numbers > Port a Number, or via the API as below.1. Check portability (pre-flight)
Run a read-only eligibility check before you submit. This calls the resolved carrier (Telnyx for US/CA numbers, DIDWW for everything else) and returns the verdict synchronously — no order is created and no credits are consumed. Rate-limited to 20 requests/minute per tenant.| Field | Type | Description |
|---|---|---|
eligible | boolean | Whether the number can be ported to Orbit. |
reasons | string[] | Human-readable blockers when eligible is false; empty when eligible. |
splittable | boolean | true when the carrier may port voice but leave SMS behind — include explicit SMS instructions in your LoA. |
estimated_foc_date | string? | Carrier’s estimated Firm Order Commitment (completion) date in ISO 8601. Present when the carrier can pre-compute it (typically the fast_portable path). |
fast_portable | boolean? | true when the carrier flags a fast port (often same- or next-business-day). |
check_status | string | checked (live carrier verdict), check-not-supported (carrier has no synchronous check — e.g. DIDWW v3; verify eligibility manually), or check-skipped (carrier unreachable; eligible is optimistic). |
provider | string | Carrier selected for the check, based on the number’s country. |
estimated_foc_date from this check and the live timeline endpoint for the authoritative estimate rather than a hardcoded figure. For countries where check_status is check-not-supported, the carrier confirms eligibility after submission (DIDWW does so within ~2 business days).
2. Attach a signed Letter of Authorization (LoA)
Carriers require a signed LoA to release the numbers. Upload the LoA PDF viaPOST /api/v1/files/upload and pass the returned GCS-signed URL as loaFileUrl when you submit (step 3). loaFileUrl must be a Devotel-issued storage.googleapis.com signed URL — a caller-supplied or arbitrary URL is rejected with LOA_URL_INVALID_ORIGIN (422).
Alternatively, submit first in manual mode (omit loaFileUrl), then attach and sign the LoA against the created request:
| Method | Path | Description |
|---|---|---|
POST | /api/v1/numbers/porting/:id/loa | Upload the LoA artefact (PDF / JPEG / PNG / HEIC, multipart file field). Stores it to GCS and sets loaSignatureStatus to draft. |
POST | /api/v1/numbers/porting/:id/loa/sign | Capture the signer’s name, email, and acknowledgement; transitions loaSignatureStatus to signed. |
3. Submit the port-in request
| Field | Required | Description |
|---|---|---|
numbers | yes | One E.164 string or an array of them. |
currentCarrier | yes | Name of the losing carrier (carrier is accepted as an alias). |
country | no | ISO 3166-1 alpha-2 of the numbers; drives provider selection (Telnyx US/CA, DIDWW elsewhere). Omit to store without dispatching for hand-processing. |
loaFileUrl | no | GCS-signed URL of the uploaded LoA (see step 2). Omitting it stores the request in manual mode. |
accountNumber | no | Your account / BTN with the losing carrier. |
accountPin | no | Account PIN (required by some carriers, e.g. US wireless). |
authorizedSigner | no | Name of the LoA signatory. |
serviceAddress | no | Service address (line1, line2, city, state, postalCode, countryCode) matching the losing carrier’s billing record. |
4. Track the port
| Method | Path | Description |
|---|---|---|
GET | /api/v1/numbers/porting | List your port-in requests and their status. |
GET | /api/v1/numbers/porting/:id/timeline | Structured per-stage timeline — submitted → validating_loa → carrier_review → foc_assigned → foc_scheduled → completed — with the elapsed-days counter, the FOC date once assigned, and (on rejection) a plain-English explanation of the carrier reject code plus a recommended action. |
POST | /api/v1/numbers/porting/:id/refresh | Re-poll the losing carrier for the latest status and FOC date. |
POST | /api/v1/numbers/porting/:id/supplement | Submit a correction (supplement/amend) for a rejected request within the 7-day amend window — avoids restarting the FOC clock. |
DELETE | /api/v1/numbers/porting/:id | Cancel a port-in request. |
Port Out
Migrate a number you own with Orbit away to another carrier. You can submit a port-out per DID or as a batch request, then track it through its lifecycle.Submit a port-out for a single number
| Field | Required | Description |
|---|---|---|
losing_carrier_account_number | yes | Your account number with the carrier currently hosting the number. Proof of ownership required before the port-out is accepted. |
authorized_person | yes | Name of the LoA signatory authorizing the release. Audit-logged and forwarded to the carrier. |
target_carrier_ocn | yes | Operating Company Number (OCN) of the winning carrier, used to schedule the FOC. |
port_out_pin | conditional | Required only when the number has Port-Out Protection enabled. A missing or wrong value returns PORT_OUT_PIN_MISMATCH (401) before any carrier dispatch. |
201 with the provider order details
(provider, provider_order_id, provider_status, foc_date).
Port-out request lifecycle
The/porting/out endpoints submit and track a batch port-out request
covering up to 50 numbers.
| Method | Path | Description |
|---|---|---|
POST | /api/v1/numbers/porting/out | Submit a new port-out request |
GET | /api/v1/numbers/porting/out | List port-out requests |
GET | /api/v1/numbers/porting/out/:id | Fetch a single port-out request |
POST | /api/v1/numbers/porting/out/:id/refresh | Re-poll the carrier for the latest status |
DELETE | /api/v1/numbers/porting/out/:id | Cancel a port-out request |
| Field | Required | Description |
|---|---|---|
numbers | yes | 1–50 E.164 numbers from your inventory to port away. All must be hosted on the same provider. |
destinationCarrier | yes | Name of the winning carrier. |
newOwnerInfo | no | Free-text details of the new account owner (max 2000 chars). |
port_out_pin | conditional | Required when any number in the batch has Port-Out Protection enabled. A missing or wrong value returns PORT_OUT_PIN_MISMATCH (401) before carrier dispatch; numbers without protection auto-pass. |
Port-Out Protection (PIN)
Port-Out Protection guards against port hijacking (a SIM-swap variant where an attacker with captured credentials triggers a port to a carrier they control). It mirrors FCC 47 CFR § 64.6100 and GSMA M.108: when enabled on a number, every port-out submission for that number must present the matching PIN or receivePORT_OUT_PIN_MISMATCH (401) before the carrier dispatch fires.
The PIN is one-way scrypt-hashed at rest with a per-number salt and is never
returned by any endpoint — only the { enabled, set_at } status is observable.
If you lose the PIN, rotate it with a fresh POST; there is no recovery flow.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/numbers/:id/port-out-protection | Read protection status ({ enabled, set_at }) |
POST | /api/v1/numbers/:id/port-out-protection | Set or rotate the PIN |
DELETE | /api/v1/numbers/:id/port-out-protection | Disable protection |
Enable protection
port_out_pin must be 4–32 characters. A successful set returns 201 with
{ enabled: true, set_at }. Disabling via DELETE returns { enabled: false }.
Once protection is enabled, include the same port_out_pin in any
port-out submission for the number. A missing or mismatched PIN is
rejected with PORT_OUT_PIN_MISMATCH (401) before the carrier is contacted.