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:
- Check eligibility — verify numbers can be ported
- Create a draft — provide subscriber details and your requested date and time
- Approve — customer approves the port order with their electronic signature
- Submit — send the order to the carrier for processing
- Track status — monitor until the port completes
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.
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:
{
"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.
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:
{
"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"
}
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_numberandstreet_nameto 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.
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:
{
"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:
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:
{
"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:
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:
curl "https://api.dialstack.ai/v1/port-orders?status=submitted" \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"
Status Reference
| Status | Description |
|---|---|
draft | Order created, not yet approved |
approved | Customer has approved the order, ready to submit |
submitted | Sent to the carrier, awaiting processing |
exception | Rejected by the carrier — can be corrected and resubmitted |
foc | Scheduled — 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. |
complete | Numbers successfully ported and active in DialStack |
cancelled | Order was cancelled |
Viewing the Audit Trail
Every status change is recorded. View the full history:
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:
{
"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:
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:
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:
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:
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:
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
| Code | Reason | How to Fix |
|---|---|---|
address_mismatch | Service address mismatch | Update the address to match the losing carrier's records |
account_number_invalid | Account number required | Contact the losing carrier for your account number |
name_mismatch | Approver name mismatch | Use the exact name on file with the losing carrier |
business_name_mismatch | Business name mismatch | Use the business name from the carrier's Customer Service Record (CSR), which may differ from the name on the bill |
btn_mismatch | BTN mismatch | Verify the billing telephone number with the losing carrier |
pin_required | PIN required | Contact the losing carrier to get the PIN or passcode for the account, then include it in the pin field |
pin_invalid | PIN invalid | The PIN provided doesn't match the carrier's records — contact the losing carrier to verify the correct PIN and update the order |
Error Handling
| Status | Error | Description |
|---|---|---|
| 400 | Validation errors | Invalid phone numbers, bad date format, etc. |
| 404 | Port order not found | Order doesn't exist or belongs to a different account |
| 409 | Invalid state | Operation not allowed in the order's current status |
| 409 | Number limit reached | The port would take the account past its phone number limit — see below |
| 422 | Approval required | Must 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.
Related Resources
- API Reference — Number Porting — Complete endpoint documentation
- Phone Numbers — Managing active phone numbers