# Port a number in

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.

```bash cURL
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"] }'
```
```ts Node
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);
```
```python Python
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.

```bash
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=loa
```

Each 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`.

```bash
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.

```json
{
  "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}`.

> **Tip:**
> 
> 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](https://pozy.ai/docs/telephony/voice-calls#route-inbound-calls).
- [Enable SMS](https://pozy.ai/docs/telephony/sms), and [register it](https://pozy.ai/docs/telephony/10dlc) if it is a US number.
- [Connect WhatsApp](https://pozy.ai/docs/whatsapp/phone-numbers).

## 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`](https://pozy.ai/docs/api/checkPhoneNumberPortability) | Check portability |
| [`GET /v1/phone-numbers/port-in/requirements`](https://pozy.ai/docs/api/getPhoneNumberPortInRequirements) | Country porting requirements |
| [`POST /v1/phone-numbers/port-in/documents`](https://pozy.ai/docs/api/uploadPhoneNumberPortInDocument) | Upload a porting document |
| [`POST /v1/phone-numbers/port-in`](https://pozy.ai/docs/api/createPhoneNumberPortIn) | Port numbers in |
| [`GET /v1/phone-numbers/port-in`](https://pozy.ai/docs/api/listPhoneNumberPortIns) | List port-in orders |
| [`GET /v1/phone-numbers/port-in/{id}/requirements`](https://pozy.ai/docs/api/getPhoneNumberPortInOrderRequirements) | A port-in order's pending requirements |
| [`GET /v1/phone-numbers/port-in/claims/{claimId}`](https://pozy.ai/docs/api/getPhoneNumberPortClaim) | Resolve a port claim |
| [`DELETE /v1/phone-numbers/port-in/{id}`](https://pozy.ai/docs/api/cancelPhoneNumberPortIn) | Cancel a port-in |
