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.
Search Available Numbers
Response
The matching numbers are nested underdata.numbers — data is an object, not an array. Prices are decimal strings: monthly_cost is the recurring fee and setup_cost the one-time activation fee, both denominated in the currency field (currently USD). setup_cost is always present — it is "0.00" when there is no setup charge.
Fields prefixed with an underscore (e.g.
_provider) are internal and may change without notice — do not depend on them.
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
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.
Returns
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, messaging_mps_cap,
monthly_spend_cap_cents, 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. Set messaging_mps_cap to an integer between 1 and 1000 to cap
this number’s outbound messages-per-second rate, or null to clear the cap
(uncapped). Set monthly_spend_cap_cents to an integer between 1 and
100,000,000 cents (up to $1,000,000) to cap this number’s spend per
calendar month, or null to clear the cap (uncapped).
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, ring_group, shared_line, dispatch_to_voicemail_box, or
webhook; each type has its own config shape. The last three are:
shared_line— fork-rings every member device of a shared line so any member can answer.configis{ sharedLineId, ringTimeoutSec? }, wheresharedLineIdreferences a shared line created via/voice/shared-linesandringTimeoutSecis5–120(optional).dispatch_to_voicemail_box— drops the caller into a shared department mailbox.configis{ boxId, greetingUrl?, maxDurationSec? }, whereboxIdreferences an existing voicemail box in this organization andmaxDurationSecis5–900(optional).webhook— fetches call control verbs from your own HTTPS endpoint on every inbound call.configtakes exactly one of{ url }(an HTTPS URL) or{ staticVerbs }(an inline sequence of 1–50 verbs served without a fetch), plus optionalmethod(POSTdefault, orGET),timeoutSec(1–10, default5), andfallbackBehavior(safe-defaultdefault,voicemail, ordecline).
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
Pass the
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.
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.
There is no flat porting SLA — completion time depends on the country and losing carrier (carrier-published ranges run from roughly 5 business days to 15+ business days, with most non-US geographies in the 4–10 business-day band). Use
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:
3. Submit the port-in request
4. Track the port
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
A successful submission returns
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.
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.
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.