Skip to main content

Number Porting

Number porting lets you transfer existing phone numbers from another carrier to DialStack. This guide walks through the full port-in workflow: checking eligibility, creating a port order, approving the order, and tracking it through completion.

Overview​

Porting follows a draft-based workflow:

  1. Check eligibility — verify numbers can be ported
  2. Create a draft — provide subscriber details and your requested date and time
  3. Approve — customer approves the port order with their electronic signature
  4. Submit — send the order to the carrier for processing
  5. Track status — monitor until the port completes
Text
draft → approved → submitted → foc (scheduled) → complete
→ exception → (fix + resubmit) → submitted

Prerequisites​

  • DialStack API key (sk_live_*)
  • Phone numbers currently active with another US carrier
  • Subscriber details matching the losing carrier's records (name, service address, account number, and PIN as listed on the current carrier's records)

Step 1: Check Eligibility​

Before creating a port order, verify that the numbers can be ported. This endpoint is platform-scoped — no DialStack-Account header is needed.

Bash
curl -X POST https://api.dialstack.ai/v1/port-in-eligibility \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_numbers": ["+12025551234", "+12025555678"]
}'

Response:

JSON
{
"portable_numbers": [
{
"phone_number": "+12025551234",
"losing_carrier_name": "Verizon Wireless",
"losing_carrier_spid": "6006",
"is_wireless": true,
"account_number_required": true
}
],
"non_portable_numbers": [
{
"phone_number": "+12025555678",
"reason": "unsupported_rate_center",
"rate_center": "WASHINGT DC",
"city": "Washington",
"state": "DC"
}
]
}

Each entry in non_portable_numbers has a reason: unsupported_rate_center when there is no porting coverage in the number's rate center, unsupported_carrier when its current carrier is one we cannot port from, or unsupported_number when the number itself was refused with no rate center or carrier given (check that it is correct and still in service). losing_carrier_name names the current carrier whenever it is known. New reasons may be added, so treat an unrecognized one as not portable.

Only numbers listed in portable_numbers can be included in a port order. Check account_number_required — when true, include the subscriber's account_number in the port order. Wireless numbers (is_wireless: true) usually also need the account's port-out pin.

Step 2: Create a Draft Port Order​

Create a draft with the subscriber details, phone numbers, and your requested porting date and time. You can control exactly when your numbers transfer by setting requested_foc_date and optionally requested_foc_time. The date must be at least 5 business days and no more than 30 calendar days from today.

The date and time are read in requested_foc_timezone, an IANA timezone such as America/Los_Angeles. It is optional and defaults to Eastern (America/New_York). The carrier only accepts port times between 08:00 and 20:00 Eastern, so the time must fall inside that window once converted: 05:00 to 17:00 in America/Los_Angeles, for example. Responses and webhooks include requested_foc_timezone only when it was set on the order. When it is absent, the date and time are Eastern.

Once the carrier confirms the order, the port will complete at the date and time you requested — so you can plan your cutover with confidence.

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"phone_numbers": ["+12025551234"],
"subscriber": {
"btn": "+12025551234",
"business_name": "Smith Consulting",
"approver_name": "Jane Smith",
"account_number": "123456789",
"pin": "1234",
"address": {
"house_number": "123",
"street_name": "Main St",
"city": "Washington",
"state": "DC",
"zip": "20001"
}
},
"requested_foc_date": "2026-03-15",
"requested_foc_time": "10:00"
}'

Response:

JSON
{
"id": "port_01jkx...",
"status": "draft",
"details": {
"phone_numbers": ["+12025551234"],
"subscriber": {
"btn": "+12025551234",
"business_name": "Smith Consulting",
"approver_name": "Jane Smith",
"account_number": "123456789",
"pin": "1234",
"address": {
"house_number": "123",
"street_name": "Main St",
"city": "Washington",
"state": "DC",
"zip": "20001"
}
},
"requested_foc_date": "2026-03-15",
"requested_foc_time": "10:00"
},
"submitted_at": null,
"created_at": "2026-02-11T14:00:00Z",
"updated_at": "2026-02-11T14:00:00Z"
}
Subscriber Details

All subscriber fields must match the records on file with the losing carrier. Mismatches are the most common reason for port rejections.

  • BTN (Billing Telephone Number) — the main number on the account
  • Approver name — the name of the person authorized to approve the port
  • Service address — split into house_number and street_name to match the carrier's format
  • Account number — required when the eligibility response has account_number_required: true
  • PIN — the losing carrier account's port-out PIN, usually needed for wireless numbers

Step 3: Approve the Port Order​

Have the customer approve the port order by providing their electronic signature and IP address. This authorizes the transfer of their phone numbers.

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx.../approve \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"signature": "Jane Smith",
"ip": "203.0.113.42"
}'

Response:

JSON
{
"id": "port_01jkx...",
"status": "approved",
"details": {
"phone_numbers": ["+12025551234"],
"subscriber": { "...": "..." },
"approval": {
"signature": "Jane Smith",
"ip": "203.0.113.42",
"timestamp": "2026-02-11T14:02:00Z"
},
"requested_foc_date": "2026-03-15",
"requested_foc_time": "10:00"
},
"submitted_at": null,
"created_at": "2026-02-11T14:00:00Z",
"updated_at": "2026-02-11T14:02:00Z"
}

The status transitions from draft to approved. If the numbers or subscriber details are updated after approval, the approval is cleared and the status reverts to draft — the customer must approve again. Changing only the requested port date or time keeps the approval.

Step 4: Submit the Order​

Once the order is approved, submit it to begin the porting process:

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx.../submit \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"

Response:

JSON
{
"id": "port_01jkx...",
"status": "submitted",
"details": { "...": "..." },
"submitted_at": "2026-02-11T14:05:00Z",
"created_at": "2026-02-11T14:00:00Z",
"updated_at": "2026-02-11T14:05:00Z"
}

The status transitions from approved to submitted. The necessary authorization documents are generated and forwarded to the carrier automatically.

Step 5: Track the Order​

To be notified as the order progresses, subscribe a webhook endpoint to port_order.updated. It fires on every status change and whenever the port date moves, usually within seconds of the carrier acting. See port_order.updated for the payload.

You can also fetch the order at any time. Non-terminal orders are refreshed from the carrier when read:

Bash
curl https://api.dialstack.ai/v1/port-orders/port_01jkx... \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"

You can also list all port orders, optionally filtering by status:

Bash
curl "https://api.dialstack.ai/v1/port-orders?status=submitted" \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"

Status Reference​

StatusDescription
draftOrder created, not yet approved
approvedCustomer has approved the order, ready to submit
submittedSent to the carrier, awaiting processing
exceptionRejected by the carrier — can be corrected and resubmitted
focScheduled — the carrier has confirmed a completion date and time (in telecom, this confirmation is called a Firm Order Commitment, abbreviated FOC). Your numbers will port at the confirmed date and time.
completeNumbers successfully ported and active in DialStack
cancelledOrder was cancelled

Viewing the Audit Trail​

Every status change is recorded. View the full history:

Bash
curl https://api.dialstack.ai/v1/port-orders/port_01jkx.../events \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"

Handling Rejections​

If the carrier rejects the port (status becomes exception), the rejection details appear in the order:

JSON
{
"status": "exception",
"details": {
"rejection": {
"code": "address_mismatch",
"message": "Service address mismatch"
}
}
}

An update to a rejected order only saves the correction — the order stays in exception and nothing reaches the carrier until you send "resubmit": true. How many requests that takes depends on what you correct.

The port date only. The requested date and time are not on the signed authorization, so the approval survives and you can save and resubmit in one request:

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx... \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"requested_foc_date": "2026-09-02",
"resubmit": true
}'

Anything else — the numbers or any subscriber field — changes what the customer signed, so the correction clears the approval and needs a fresh signature before it can go back. That takes three requests. First, save the correction:

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx... \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"subscriber": {
"btn": "+12025551234",
"business_name": "Smith Consulting",
"approver_name": "Jane Smith",
"address": {
"house_number": "456",
"street_name": "Oak Ave",
"city": "Washington",
"state": "DC",
"zip": "20002"
}
}
}'

Then have the customer approve again with POST /v1/port-orders/{id}/approve (see Approve the Port Order), and resubmit:

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx... \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{ "resubmit": true }'

Sending "resubmit": true together with a correction that changes the authorization returns 422 with code port_order_reapproval_required. The correction is still saved; approve and resubmit to finish.

A subscriber you send replaces the stored one, except account_number and pin: leave either out to keep what is stored, or send it as null to remove it. On a rejected order, removing the pin asks the carrier to remove it too when you resubmit; that is the fix when the carrier rejects a PIN the losing carrier never required. A stored account_number can be corrected on a rejected order but not removed, and removing one returns 409 with code port_order_edit_not_supported.

Changing the Port Date​

For an order in submitted or foc, send only the new port date and time. They are read in the order's requested_foc_timezone, or in Eastern if it has none. You can send requested_foc_timezone too, which moves the cutover even when the date and time stay the same:

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx... \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"requested_foc_date": "2026-09-02",
"requested_foc_time": "08:00"
}'

The order keeps its current status. Other fields cannot be changed at this stage and result in a 409 response with code: port_order_invalid_state.

Cancelling an Order​

Cancel a port order that hasn't reached a terminal state:

Bash
curl -X POST https://api.dialstack.ai/v1/port-orders/port_01jkx.../cancel \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"

Orders in complete or cancelled status cannot be cancelled.

Common Rejection Reasons​

CodeReasonHow to Fix
address_mismatchService address mismatchUpdate the address to match the losing carrier's records
account_number_invalidAccount number requiredContact the losing carrier for your account number
name_mismatchApprover name mismatchUse the exact name on file with the losing carrier
business_name_mismatchBusiness name mismatchUse the business name from the carrier's Customer Service Record (CSR), which may differ from the name on the bill
btn_mismatchBTN mismatchVerify the billing telephone number with the losing carrier
pin_requiredPIN requiredContact the losing carrier to get the PIN or passcode for the account, then include it in the pin field
pin_invalidPIN invalidThe PIN provided doesn't match the carrier's records — contact the losing carrier to verify the correct PIN and update the order

Error Handling​

StatusErrorDescription
400Validation errorsInvalid phone numbers, bad date format, etc.
404Port order not foundOrder doesn't exist or belongs to a different account
409Invalid stateOperation not allowed in the order's current status
409Number limit reachedThe port would take the account past its phone number limit — see below
422Approval requiredMust approve the order before submitting

Phone number limit​

A port order holds up to 100 numbers. Porting also counts against the account's cap on how many numbers it can hold, including numbers reserved by other port orders in flight: an order that would exceed it returns 409 with "code": "phone_number_limit_exceeded". See New Numbers for how the cap works.