Number portability model
Local number portability (LNP) is the one lifecycle operation that moves a DID between carriers without the digits leaving service. Orbit models both directions as the same object — a porting request against a set of numbers — driven from opposite sides of the carrier boundary. This page defines that one model: who owns the number at each stage, the shared vocabulary, and how a port relates to the number status map. The endpoint-by-endpoint reference stays in Number porting; this page owns the model only.The two carrier directions
Every port has a winning carrier (the one gaining the number) and a losing carrier (the one giving it up). Which direction you are driving determines what Orbit can do for you:- Winning-carrier flow (port-in). Another carrier hosts the DID today;
you claim it into Orbit with
POST /numbers/porting. Orbit is the winning carrier: it files the LOA with the losing carrier, tracks the FOC date, and provisions the number onto your account when the port completes. The losing carrier is a third party; Orbit polls and reacts to its decisions. - Losing-carrier flow (port-out). Orbit hosts the DID today; the
winning carrier (your next provider) comes asking for it. You initiate
the move tenant-side with
POST /numbers/:id/port-out; Orbit validates, dispatches the order upstream, and holds the number atport_out_pendinguntil the winning carrier completes the port.
The unified state vocabulary
A port in either direction moves through the same milestones:- submitted — the request exists and has been validated. Port-in
stores the request even before dispatch if you omit
country; port-out dispatches immediately after its pre-carrier guards pass. - carrier review — the losing carrier holds the request and verifies the account data (account number, authorized signer, service address). A mismatch produces a supplement request or a rejection, not a silent stall.
- LOA lifecycle — the Letter of Authorization proving you may move the
number:
draftafter upload,signedafter the in-platform signature,submittedonce forwarded to the carrier. One-directional — a signed or submitted LOA refuses replacement; cancel and resubmit instead. - FOC scheduled — the losing carrier has assigned a Firm Order Commitment date. From here the port completes on that date unless one side cancels or the carrier rejects.
- completed — the winning carrier provisions the number. Port-in ends
with the DID landing on your Orbit account as
active; port-out ends with the DID leaving Orbit’s inventory entirely.
port_out_pending → cancelled/failed → active — the
number returns to exactly the state it left (Port-out lifecycle and
ownership model). On the port-in side, a
cancelled request simply never provisions anything.
The ownership boundary: a port both is and is not a release
A completed port-out removes the number from your account, but it is NOT a release, and the distinction matters:- Release hands the digits back to the carrier’s inventory. The number enters the aging window in Number reuse and aging before anyone can re-buy it — carrier inventory logic.
- Port-out directs the digits to a specific new carrier of your choosing. The number never enters reuse or aging; it stops existing in Orbit’s inventory logic the moment the port completes.
port_out_pending is a synthetic status in the same closed
enum as active, released, and the rest of the number status
map. The six-state page lists the states a
number settles into; port_out_pending is the seventh member that only
exists mid-transition, so the list endpoint, the dashboard badge, and
every “is this number still mine to use” guard can treat a claimed DID
uniformly. It has two exit edges, both reversible in the ordinary case:
completion (the number is gone) or cancel/failure (the number returns to
active). Port-in mirrors this from the outside: until the port
completes, no Orbit number row exists at all.
Preflight: ask before you file
A port filed against a non-portable number wastes carrier review time and restarts the FOC clock if the losing carrier kicks it back. Three read endpoints let you gate a submission before you pay that cost (full request/response shapes in Number porting):POST /numbers/porting/check— single-number portability verdict.POST /numbers/porting/check/bulk— up to 1000 distinct numbers per call, chunked upstream to the carrier so one bad number never blocks the batch.POST /numbers/porting/pre-validate— the combined readiness gate: portability plus CSR/address checks, so the data mismatch classes that cause supplement loops surface before the LOA goes out.
pre-validate and check/bulk are rate-limited to 5 requests per minute
per auth context. Run preflight on the estate first, then file one
port-in request per account with the winning carrier.
Events: one family, one fan-out
Ports emit webhook events so you subscribe instead of polling a timeline:
Port milestones also fan out to the in-product Notification Center under
the
messaging bell category — the same category sender-ID review and
port status changes use — so ops users see a port move without wiring a
webhook at all (Notification model).
Payload shapes and subscription setup live in Webhook
events.
Port-side ownership guards
The losing-carrier direction carries the heavier controls because a caller with your API credentials could otherwise remove a number permanently:- PIN protection — an optional per-DID PIN checked before any carrier
dispatch, mirroring the carrier account-PIN model. Set, read back
(
enabled/set_atonly), rotate, or disable underPOST|GET|DELETE /numbers/:id/port-out-protection; submissions against a protected DID without the PIN are rejected before upstream. - Concurrency guards — an atomic status claim, a short-lived
distributed lock, and carrier-derived idempotency keys guarantee one
carrier order per number no matter how requests overlap. A
409on retry means “already in progress,” never two orders.
Bulk CSV migration
For an estate of 50–500 DIDs, the one-number-at-a-time form doesn’t fit: upload a CSV (POST /numbers/port-in/bulk-csv) where each valid row
becomes its own porting request at submitted status, stored for manual
carrier dispatch. Attach LOA and country data per-row afterwards through
the normal flow — the import deliberately avoids firing hundreds of
sequential carrier calls inside one request. Caps: 2MB file, 1000 rows,
30-second budget; rate-limited to 2 requests per minute per organization.
Worked example: a US port-in, and its mirror on the losing side
A US port from preflight to completion, wire by wire:- Preflight.
POST /numbers/porting/pre-validatewith["+14155551234"], country: "US"confirms the number is portable and the CSR/address data will survive carrier review. - Submit.
POST /numbers/portingfiles the request with the account number, authorized signer, and service address. The request enterssubmitted. - LOA. Upload the artefact, capture the in-platform signature
(
loaSignatureStatus: draft → signed), then submit it to the losing carrier. Each step emitsporting.request.*events. - Carrier review → FOC. The losing carrier verifies the data and
assigns a FOC date.
GET /numbers/porting/:id/timelineturns that into a per-stage view (submitted → validating_loa → carrier_review → foc_assigned → foc_scheduled → completed), andporting.request.status_changedfires on each carrier move. - Completed. On the FOC date the number lands on your account as
active;number.portedfires.
POST /numbers/:id/port-out (with port_out_pin if
protection is enabled) claims the row atomically to port_out_pending,
dispatches the order, and fires number.port_out.requested. The number
sits claimed-but-yours until the winning carrier completes the port; a
cancel returns it to active and fires number.port_out.cancelled.
Either way, the number never touches the release/aging machinery.
Cross-links
- Number porting — the endpoint-by-endpoint port-in flow and preflight reference.
- Port-out lifecycle and ownership model — the losing-carrier side: PIN, concurrency, carrier adapters.
- Number status map — the closed enum
port_out_pendingjoins as a synthetic seventh status. - Number reuse and aging — the release path a port explicitly avoids.
- Number lifecycle — the operations (release, reclaim, suspend) the status map rides on.
- Notification model — the
messagingbell category port milestones fan out into. - Webhook events — full payload reference for the
porting.request.*andnumber.port_out.*families.