Port out safely
Port-outs are irreversible at carrier level after the FOC date lands, and the attack that matters for the operator side is unauthorized — a hijacked credential submits a port to a carrier the attacker controls before anyone notices the number moved. This guide covers the runbook your team runs to move numbers off Orbit on purpose, and the PIN control you switch on beforehand so a credential alone is not enough. Reads (list, get) need the numbers:read scope; writes (submit, refresh, cancel, PIN set/remove) need numbers:write and an owner, admin, or developer role. The API reference is at Numbers and the port-IN counterpart at Port a number end-to-end.
1. Treat port-outs as gated work
Two failure classes drive the whole design:- Port hijack — a stolen API key or session submits a port-out to a carrier the attacker controls. Because the losing carrier treats your submission as authorized, the number is live elsewhere before your ops team reacts. Orbit’s defense is the per-number port-out PIN (below): without the PIN, credentials alone cannot dispatch.
- Cancel-window misses — after the winning (gaining) carrier issues a Firm Order Commitment (FOC) date, the losing side may still honor cancellations, but only until the cutover. Watch the
number.port_out.requestedwebhook and the FOC date so the “do we actually want to cancel this” decision happens inside the window, not after it.
2. Pre-flight: portable status, then PIN protection
Two gates, in order. First, the number must be in a portable state — port-outs from a number that is already releasing, suspended, or mid-port return409 and nothing dispatches. Second, put a PIN on the number before any port-out is authorized. Even when your team expects to move the number tomorrow, set the PIN today: it is the control that keeps a credential compromise on an unrelated surface from draining your inventory.
Check the status first with GET /numbers/:id — a number in released, suspended, parked, or port_out_pending rejects POST /numbers/:id/port-out with 409 CONFLICT.
Enable PIN protection
GET returns the same shape:
DELETE to disable protection outright.
401 PORT_OUT_PIN_MISMATCH before the carrier dispatch fires — so the carrier portal never sees the unauthorized attempt.
3. Submit a single port-out
With pre-flight clean and the PIN set, submit the port-out. The body names the losing-carrier account (proof of ownership the current carrier validates), the authorized signer, and the winning carrier’s OCN:focDate is frequently null on first submission — the carrier schedules it asynchronously and you pick it up on refresh (below) or on the webhook. The number’s status flips to port_out_pending so the dashboard shows a distinct badge and a second submission is refused.
Error cases you will see:
401 PORT_OUT_PIN_MISMATCH— PIN protection is enabled and the PIN was missing or wrong. The carrier is not contacted. Fix the PIN and retry.409 CONFLICT— the number is in a non-portable status (port_out_pending,released,suspended,parked) or another port-out for the same number is already in flight. The lock is an atomic claim — a second submission races to409, and even the lock auto-expires (30s TTL) so a dead worker cannot strand the number.422 PORT_OUT_NOT_SUPPORTED— this number’s carrier has no self-serve port-out adapter (see availability caveat below). Dispatch manually in the carrier portal.502 CARRIER_PORT_OUT_FAILED— the carrier rejected or errored. The number’s local status is rolled back to its prior state so nothing is stranded in a phantom pending.
4. Batch flow: submit, list, fetch, cancel
For a multi-number move (an estate leaving, or a subset), submit up to 50 E.164 numbers in one call. All numbers must be hosted on the same carrier — mixed-carrier batches reject422.
port_out_pin is required for the whole batch and must match every protected number — a mismatched PIN rejects the entire request before any wireless dispatch.
Track the batch with the list and per-id fetch:
DELETE no longer applies. Treat the cancel window as real: a cancel decision delayed past FOC expiry means the number is gone.
5. Operate on webhooks
Subscribe to the port-out events rather than polling lists. Register your endpoint once (see Webhooks and Webhook Security for signature verification) and push these two:
The full catalog is at Webhook Events.
6. Carrier availability caveat
Self-serve port-out (POST /numbers/:id/port-out and the bulk path) dispatches through the carrier’s own API. Not every carrier your numbers ride on exposes a port-out adapter — when none exists, POST /numbers/:id/port-out returns 422 PORT_OUT_NOT_SUPPORTED and nothing is dispatched. In that case:
- Go to the carrier’s own portal and submit the port-out there directly.
- Record the carrier-issued order id in your own migration ledger — the carrier portal is authoritative for those numbers.
- Mark the number in Orbit as leaving via your ops process so dashboard state matches reality (the admin port-out surface handles the platform-side bookkeeping).
422 with the carrier name in the message, so you can detect and route around it per-number.
7. Locking behavior: one port-out per number, atomically
The port-out submit is serialized per number. A second submission while one is in flight — whether from a retry, a race between two operators, or an attacker’s parallel attempt — returns409 and never reaches the carrier:
- The claim is atomic at the database layer: only one request flips the number from its portable status to
port_out_pending; the loser sees zero rows and aborts before any carrier call. - A Redis lock serializes per-number submissions across API replicas; the TTL auto-expires after 30 seconds so a crashed worker cannot hold the number hostage.
- The carrier layer deduplicates via an idempotency key derived from the DID id, so even a retry that somehow re-entered after a release does not open a second carrier order.
409 only after checking the list endpoint — a 409 almost always means “someone already submitted this,” not “submit again louder.”
See Also
- Number Porting — the symmetric port-IN runbook.
- Number Lifecycle — release, auto-renew, and scheduled-release for numbers staying on Orbit.
- Number Porting endpoints — the shorter per-endpoint reference.
- Numbers API Reference — full request/response schemas.
- Webhook Events — the complete event catalog.