KeepMyNumber
Home Sign in

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 (status expired), 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.