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

# Port-out PIN protection lifecycle

> The tenant-owned port-out PIN: how to set, rotate, and remove it per number, why verification is opt-in, how it interacts with pending port orders, and what a mismatch does.

# Port-out PIN protection lifecycle

Port-out PIN protection is the tenant-owned control that makes a stolen
API credential insufficient to move a number off your account. It is a
per-number secret you set yourself; Devotel Orbit verifies it before any
port-out reaches the carrier. This page models the PIN's lifecycle — set,
rotate, remove — and how it slots into the wider port-out flow. The
port-out state machine it protects is [Port-out
lifecycle](/concepts/number-port-out-model); the runbook procedure is
[Port numbers out safely](/guides/port-out-numbers).

## Why port-out needs its own control

Every other number lifecycle exit — release, suspend, park — returns the
number to inventory or freezes it. Port-out is different: it transfers
ownership to another carrier. That makes it the one operation where a
caller holding your API key can permanently take a number away, the
port-hijack attack class. Reversing a mistaken release means re-buying a
DID that may still be in stock; reversing a completed port-out means
negotiating a port-back with a carrier you do not control. The PIN closes
that gap with a second factor only the organization that owns the number
can set — the same "account PIN" model carriers surface under the FCC
port-out-protection rule (47 CFR § 64.6100).

## The self-serve PIN lifecycle

Three operations, all under the number's own path, all restricted to the
organization that owns the DID — sibling organizations in a shared
workspace cannot read, set, or strip each other's PINs (a miss surfaces
as a `404`, never a `403`, so existence cannot be probed across
siblings):

* **Set** — `POST /numbers/:id/port-out-protection` with
  `port_out_pin` in the body. The PIN is scrypt-hashed with a per-number
  salt the moment it arrives. The response confirms only
  `{ enabled: true, set_at }`.
* **Rotate** — the same `POST` replaces the existing PIN with a fresh
  hash and salt. Rotation is also the recovery path: because the PIN is
  one-way hashed and never readable, a lost PIN is replaced, not
  recovered.
* **Remove** — `DELETE /numbers/:id/port-out-protection` clears the
  hash and salt entirely, returning the number to opt-out.

A PIN is 4–32 characters of alphanumerics or common punctuation; a
rejected format returns `422 VALIDATION_ERROR` before anything is
written. Readable state is deliberately minimal:
`GET /numbers/:id/port-out-protection` returns
`{ enabled, set_at }` and nothing else — the plaintext value is
unobservable by design.

## When the PIN is required: opt-in, per number

Protection is opt-in on a per-DID basis, and that choice shapes the whole
verification model:

* **No PIN set → verification auto-passes.** A port-out on an
  unprotected number proceeds without a `port_out_pin` in the body. The
  absence of protection is not a rejection — you opt in on exactly the
  numbers whose loss would hurt.
* **PIN set → verification is mandatory.** Every port-out submission
  (`POST /numbers/:id/port-out`, and the batch `POST /numbers/porting/out`)
  must carry `port_out_pin`, and for a batch it must match every
  protected number in the request, or the whole batch is refused before
  any carrier call.

Because the check is per-DID, an operator can protect the revenue-bearing
DIDs while leaving test or staging numbers unprotected, and can adopt the
control incrementally without a migration across the inventory.

## Concurrency: pending orders and PIN changes

A number in `port_out_pending` already has a live carrier order behind
it, so two interactions matter:

* **New port-outs are blocked.** Any further port-out submission — PIN
  correct or not — returns `409 CONFLICT` while the order is in flight.
  The PIN gate and the pending-order gate stack; both must pass.
* **Rotate and remove stay available.** The PIN surface itself is not
  frozen by a pending order. That is deliberate: if a PIN was exposed
  mid-port (a leaked credential or a departed operator), you must be able
  to rotate it without cancelling the order first. Rotation while
  `port_out_pending` does not affect the in-flight order — the verify
  happened at submission time, and downstream status updates run on the
  order id, not the PIN.

## Two directions, one object

The unified [number portability model](/concepts/number-portability-model)
treats port-in and port-out as directions of one portability object. The
PIN lives squarely on the outbound direction: it is an ownership
assertion made by the organization that currently holds the DID, checked
on the losing side of the transfer. Port-in carries no PIN at all — its
proof of ownership is the LOA and account evidence on the winning side,
and a PIN on an external number is meaningless to Orbit. When you read
the unified model, the PIN is the outbound-side answer to "prove this is
my number leaving," just as the signed LOA is the inbound-side answer to
"prove this number may arrive."

## Failure modes

| Failure                                                     | Response                    | Behavior                                                                                          |
| ----------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------- |
| PIN missing or wrong on a protected number                  | `401 PORT_OUT_PIN_MISMATCH` | Rejected before the carrier is contacted; the request never burns an upstream submission attempt. |
| Malformed PIN on set/rotate                                 | `422 VALIDATION_ERROR`      | Nothing is written; the previous protection state (or absence) is unchanged.                      |
| Port-out against a pending/released/suspended/parked number | `409 CONFLICT`              | PIN is not consulted; the status gate rejects first.                                              |
| Sibling organization touches the PIN surface                | `404`                       | Ownership is checked before any read or write; misses deliberately look like "no such number."    |

Verification compares hashes with a timing-safe equality check, so the
response time does not leak whether a guess was closer to the stored PIN.
Each set, rotate, and remove is written to your organization's audit log
with the lifecycle event only — the plaintext never appears in logs,
responses, or audit details.

## Cross-links

* [Port-out lifecycle](/concepts/number-port-out-model) — the
  `port_out_pending` state machine, status gates, and events this PIN
  gates entry into.
* [Number portability model](/concepts/number-portability-model) — the
  one-object, two-direction portability vocabulary.
* [Port numbers out safely](/guides/port-out-numbers) — the operational
  runbook with endpoint examples.
* [Number lifecycle](/concepts/number-lifecycle) — the status enum
  `port_out_pending` belongs to.
* [Webhook events](/webhooks/events) — the `number.port_out.*` payloads.
