Port-outs
A port-out happens when another carrier — on behalf of the end user — requests one of your numbers. Regulations require these to be honored unless there is a legitimate reason to reject, and decisions have deadlines.
How it reaches you
When a provider notifies us of a port-out request, we validate it against your
numbers registry and emit a port_out.created webhook. What happens next
depends on your account's port-out policy:
| Policy | Behavior |
|---|---|
auto_approve (default) |
If the number has a port_out_pin set, the requester's PIN must match; otherwise the request is approved automatically. Wrong PIN → auto-rejected with reason pin_mismatch. |
manual |
The request is held in action_required and you must decide via the API within the deadline (24h by default). |
Switch policy at any time:
curl -X PATCH https://api.example.com/v2/account/settings \
-H "Authorization: Bearer pk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"settings": {"port_out_policy": "manual"}}'
Protecting numbers with a PIN
Let the platform generate the PIN — recommended. We store it, verify it against incoming port-out requests, and sync it with the hosting carrier when that carrier enforces PINs on its own side. You never need to know which provider hosts the number:
curl -X POST https://api.example.com/v2/numbers/{number_id}/port_out_pin \
-H "Authorization: Bearer pk_live_YOUR_KEY"
{
"number_id": "66a7fc25-576f-4787-8ff1-92f79c14faa8",
"phone_number": "+14155552671",
"port_out_pin": "482913"
}
The PIN is returned only in this response — it can never be read back from the numbers API, so show or store it for your end user right away. Calling the endpoint again rotates the PIN and invalidates the previous one (useful when the user loses it).
Share the PIN with the legitimate end user; a gaining carrier must present it for auto-approval.
Prefer to bring your own PIN? Set it directly:
curl -X PATCH https://api.example.com/v2/numbers/{number_id} \
-H "Authorization: Bearer pk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"port_out_pin": "4821"}'
Deciding manually
curl https://api.example.com/v2/port_out_requests?status=action_required \
-H "Authorization: Bearer pk_live_YOUR_KEY"
# one request in full
curl https://api.example.com/v2/port_out_requests/{id} \
-H "Authorization: Bearer pk_live_YOUR_KEY"
curl -X POST https://api.example.com/v2/port_out_requests/{id}/approve \
-H "Authorization: Bearer pk_live_YOUR_KEY"
curl -X POST https://api.example.com/v2/port_out_requests/{id}/reject \
-H "Authorization: Bearer pk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "account holder did not authorize this transfer"}'
Rules:
- Rejections require a reason — it is kept as a regulatory audit trail.
- Undecided requests expire at
deadline_at(statusexpired), which in practice usually lets the port proceed on the provider side. Do not sit on requests. - Requests for numbers we don't manage are auto-rejected and never reach you.
Lifecycle
pending → action_required (manual only) → approved | rejected | expired,
then completed when the number actually leaves. On completion the number's
registry status becomes ported_out, its routing is torn down, and you receive
number.ported_out.
