Phone, SMS & voice
Move a number you already own at another carrier onto Pozy. Check portability, upload the authorization documents, submit and track the order.
Porting moves a number you already have at another carrier onto Pozy, so it keeps ringing for your customers while you gain the API. The number arrives ready for calls, and ready for SMS where the order supports messaging. Porting itself is free. The number bills its normal monthly price once it is active.
#What you need
- The number or numbers, in E.164.
- A signed Letter of Authorization (LOA) from the account holder.
- A recent invoice from the current carrier.
- The account details on that carrier: account holder name, authorized person, account number, service address, and a transfer PIN for US and Canadian mobiles.
#Where porting works
| Country | Notes |
|---|---|
| United States, Canada | A few business days. Some numbers qualify for same-day porting (fastPortable: true). |
| United Kingdom | Mobiles port with a PAC code. |
| Spain, Germany, France, Netherlands | Landline and national numbers. Mobiles cannot be ported yet. |
| Australia, Brazil | Landline and national numbers. |
International ports can take several weeks while the carrier and the local regulator review the documents.
#Check portability
POST /v1/phone-numbers/port-in/check is read-only and creates nothing. Run it before you ask your customer for any documents.
curl -X POST https://api.pozy.ai/v1/phone-numbers/port-in/check \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "phoneNumbers": ["+34911234567", "+14155550188"] }'const res = await fetch("https://api.pozy.ai/v1/phone-numbers/port-in/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ phoneNumbers: ["+34911234567", "+14155550188"] }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
for (const r of data.results) console.log(r.phoneNumber, r.portable, r.notPortableReason);import os, requests
resp = requests.post(
"https://api.pozy.ai/v1/phone-numbers/port-in/check",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"phoneNumbers": ["+34911234567", "+14155550188"]},
timeout=30,
)
for r in resp.json()["results"]:
print(r["phoneNumber"], r["portable"], r["notPortableReason"])Each result has portable, fastPortable, lineType, countryCode and phoneNumberType. Keep countryCode and phoneNumberType: they are the country and numberType for the next step. The answer is per number because coverage differs by number type inside a country.
#Get the country requirements
Skip this for US and Canadian ports. For every other country, GET /v1/phone-numbers/port-in/requirements?country=ES&numberType=local returns the extra fields the regulator wants, each with a requirementId and a kind.
kind |
How to fill it |
|---|---|
text |
A string, such as a Spanish CIF or a UK PAC code |
date |
An ISO date |
file |
Upload the document in the next step and use its documentId as the value |
address |
Nothing to send. The service address from endUser is used. |
action |
Cannot be done through the API (supported: false). Contact support@pozy.ai to port this type. |
#Upload the documents
POST /v1/phone-numbers/port-in/documents takes multipart form data: a file part (PDF, JPEG or PNG, up to about 4 MB, the most one API request can carry) and a kind part (loa, invoice, or a short slug such as id-copy for requirement documents). Images for requirements are converted to PDF for you.
curl -X POST https://api.pozy.ai/v1/phone-numbers/port-in/documents \
-H "Authorization: Bearer $POZY_API_KEY" \
-F file=@loa.pdf \
-F kind=loaEach upload returns a documentId. Upload right before you submit: documents that are not attached to an order are deleted after 30 minutes.
#Submit the port
POST /v1/phone-numbers/port-in with the numbers, both document ids, the current carrier account in endUser and, for international ports, the requirements.
curl -X POST https://api.pozy.ai/v1/phone-numbers/port-in \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phoneNumbers": ["+34911234567"],
"loaDocumentId": "3f2a9c1e-7b4d-4e8a-9c6f-1d2e3f4a5b6c",
"invoiceDocumentId": "8a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"endUser": {
"entityName": "Acme SL",
"authPersonName": "Lucia Martin",
"accountNumber": "A-778812",
"streetAddress": "Calle de Alcala 42",
"locality": "Madrid",
"postalCode": "28014",
"countryCode": "ES",
"taxIdentifier": "B12345674"
},
"requirements": [
{ "requirementTypeId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "fieldValue": "B12345674" }
],
"customerReference": "acme-port-01"
}'#Submit options
| Field | What it does |
|---|---|
endUser.pinPasscode |
Transfer PIN for US and Canadian mobiles. Forwarded to the carrier, not stored. |
endUser.administrativeArea |
State or province. Required (and checked) for US and Canada. |
endUser.taxIdentifier, businessIdentifier |
For EU business ports |
endUser.countryCode |
Service-address country. One of US, CA, GB, ES, DE, FR, NL, AU, BR. |
focDatetimeRequested |
The cutover date you want. US and Canadian orders default to one week out, moved off weekends. International orders go into the carrier's next porting window on or after this date. |
portType |
full (default) when the old account ports every number it holds, partial when it keeps some |
customerReference |
Your own reference, up to 100 characters |
Put numbers from different countries in separate requests whenever any of them is outside the US and Canada.
#Read the response
The carrier may split one request into several orders (by country, number type or current carrier). orders[] holds the result of each.
{
"id": "67d0e1f2a3b4c5d6e7f8a9b0",
"status": "pending",
"phoneNumbers": ["+34911234567"],
"orders": [
{ "id": "67d0e1f2a3b4c5d6e7f8a9b0", "status": "pending", "phoneNumbers": ["+34911234567"] }
]
}A partial failure still returns 201. The failed orders carry an error and stay as drafts you can fix and resubmit. GET /v1/phone-numbers/port-in/{id}/requirements lists what an order is still missing at any time.
#Track the order
GET /v1/phone-numbers/port-in lists your orders, newest first.
status |
Meaning |
|---|---|
draft |
Not yet accepted by the carrier. Fix what error names and resubmit, or cancel. Drafts left alone for 14 days are cancelled. |
pending |
Submitted. The current carrier is processing the transfer. |
foc_confirmed |
The carrier confirmed the cutover date in focDatetimeActual. The number moves on that date. |
ported |
Done. The number is in your list, ready for calls. |
exception |
The current carrier flagged a problem in declineReason. Not final: correct the details and the order goes back to pending. |
cancelled |
Stopped. It will not proceed. |
Cancel any order that has not reached ported with DELETE /v1/phone-numbers/port-in/{id}.
Keep the old line active until the order reaches ported. Cancelling service at the current carrier before the cutover can release the number and break the port.
#After the port
The ported number shows up in GET /v1/phone-numbers like a purchased one. From there:
- Route its calls.
- Enable SMS, and register it if it is a US number.
- Connect WhatsApp.
#Errors
| Status | Cause | Fix |
|---|---|---|
422 |
A number is not portable, numbers span several non-US/CA countries, or every split order failed | Check each number and submit one country per request |
409 |
A number is already on Pozy or already in an open port | Remove it from the request |
400 |
A field failed validation, named in param |
Fix the field |
#Endpoints
| Endpoint | What it does |
|---|---|
POST /v1/phone-numbers/port-in/check |
Check portability |
GET /v1/phone-numbers/port-in/requirements |
Country porting requirements |
POST /v1/phone-numbers/port-in/documents |
Upload a porting document |
POST /v1/phone-numbers/port-in |
Port numbers in |
GET /v1/phone-numbers/port-in |
List port-in orders |
GET /v1/phone-numbers/port-in/{id}/requirements |
A port-in order's pending requirements |
GET /v1/phone-numbers/port-in/claims/{claimId} |
Resolve a port claim |
DELETE /v1/phone-numbers/port-in/{id} |
Cancel a port-in |