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

# Number portability: one model for port-in and port-out

> Local number portability as a single two-direction model — winning-carrier (port-in) vs losing-carrier (port-out), the unified submitted → review → LOA → FOC → completed vocabulary, how a port differs from a release, preflight reads, the porting.request.* event family, and the port-side ownership guards.

# 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](/concepts/number-lifecycle).
The endpoint-by-endpoint reference stays in [Number
porting](/numbers/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 at
  `port_out_pending` until the winning carrier completes the port.

These are one model, not two unrelated features. Both directions operate
on the same nouns — a porting request, an LOA, an FOC date, a completion —
and both converge on the same ownership handoff. The difference is which
side of the handoff you sit on, and therefore which of the two sides Orbit
can automate for you. The port-out internals (PIN gate, concurrency
guards, carrier adapters) are covered in [Port-out lifecycle and ownership
model](/concepts/number-port-out-model); the step-by-step port-in flow is
[Number porting](/numbers/porting).

## The unified state vocabulary

A port in either direction moves through the same milestones:

```
submitted → carrier review → LOA (draft → signed → submitted) → FOC scheduled → completed
```

* **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: `draft` after upload, `signed` after the in-platform signature,
  `submitted` once 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.

The cancelling side is symmetric with the six-state lifecycle: on the
port-out side, `port_out_pending → cancelled/failed → active` — the
number returns to exactly the state it left ([Port-out lifecycle and
ownership model](/concepts/number-port-out-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](/concepts/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.

While a port-out is pending, the DID is neither releasable nor usable
inventory: `port_out_pending` is a synthetic status in the same closed
enum as `active`, `released`, and the rest of the [number status
map](/concepts/number-lifecycle). 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](/numbers/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:

| Event                                                     | Fires when                                                                                                       |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `porting.request.submitted`                               | A port-in request was created and validated.                                                                     |
| `porting.request.loa_signed`                              | The in-platform LOA signature was captured.                                                                      |
| `porting.request.supplement_submitted`                    | You answered a carrier-requested data correction.                                                                |
| `porting.request.manual_review_required`                  | The port fell back to operator review.                                                                           |
| `porting.request.cancelled`                               | You or an admin cancelled the request.                                                                           |
| `porting.request.status_changed`                          | The carrier-side port status moved (e.g. review → FOC).                                                          |
| `number.ported`                                           | The port-in completed; the number is live on your account.                                                       |
| `number.port_out.requested` / `number.port_out.cancelled` | The losing-carrier side: your port-out was accepted, or a pending port-out returned the DID to its prior status. |

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](/concepts/notification-model)).
Payload shapes and subscription setup live in [Webhook
events](/webhooks/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_at` only), rotate, or disable under
  `POST|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 `409` on
  retry means "already in progress," never two orders.

Both are detailed in [Port-out lifecycle and ownership
model](/concepts/number-port-out-model).

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

1. **Preflight.** `POST /numbers/porting/pre-validate` with
   `["+14155551234"], country: "US"` confirms the number is portable and
   the CSR/address data will survive carrier review.
2. **Submit.** `POST /numbers/porting` files the request with the account
   number, authorized signer, and service address. The request enters
   `submitted`.
3. **LOA.** Upload the artefact, capture the in-platform signature
   (`loaSignatureStatus: draft → signed`), then submit it to the losing
   carrier. Each step emits `porting.request.*` events.
4. **Carrier review → FOC.** The losing carrier verifies the data and
   assigns a FOC date. `GET /numbers/porting/:id/timeline` turns that
   into a per-stage view (`submitted → validating_loa → carrier_review →
   foc_assigned → foc_scheduled → completed`), and
   `porting.request.status_changed` fires on each carrier move.
5. **Completed.** On the FOC date the number lands on your account as
   `active`; `number.ported` fires.

The matching trace on the losing side, if you were porting the same
number OUT instead: `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](/numbers/porting) — the endpoint-by-endpoint port-in
  flow and preflight reference.
* [Port-out lifecycle and ownership model](/concepts/number-port-out-model) —
  the losing-carrier side: PIN, concurrency, carrier adapters.
* [Number status map](/concepts/number-lifecycle) — the closed enum
  `port_out_pending` joins as a synthetic seventh status.
* [Number reuse and aging](/concepts/number-reuse-and-aging) — the
  release path a port explicitly avoids.
* [Number lifecycle](/numbers/lifecycle) — the operations (release,
  reclaim, suspend) the status map rides on.
* [Notification model](/concepts/notification-model) — the `messaging`
  bell category port milestones fan out into.
* [Webhook events](/webhooks/events) — full payload reference for the
  `porting.request.*` and `number.port_out.*` families.
