Phone, SMS & voice
Search live inventory, buy a number in one request, watch sold-out countries and release numbers you no longer need.
Buying a number takes two calls: search the inventory, then purchase. In countries that do not regulate numbers (the US among them) the number is active when the purchase returns. Regulated countries answer with a KYC step instead.
#Before you start
- A payment method on file. Telephony is billed on your monthly invoice.
- A profile id. Each number belongs to one profile. See Profiles.
#Search available numbers
GET /v1/phone-numbers/available queries the carrier's live inventory for a country (default US). Every result lists its features, and voice is always included.
curl "https://api.pozy.ai/v1/phone-numbers/available?country=US&areaCode=415&sms=true&limit=10" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/phone-numbers/available?country=US&areaCode=415&sms=true&limit=10", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
for (const n of data.numbers) console.log(n.phoneNumber, n.locality, n.features);import os, requests
resp = requests.get(
"https://api.pozy.ai/v1/phone-numbers/available",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={"country": "US", "areaCode": "415", "sms": "true", "limit": 10},
timeout=30,
)
for n in resp.json()["numbers"]:
print(n["phoneNumber"], n.get("locality"), n["features"]){
"country": "US",
"numberType": "local",
"requireSms": true,
"numbers": [
{ "phoneNumber": "+14155550142", "features": ["voice", "sms", "mms"], "locality": "San Francisco" },
{ "phoneNumber": "+14155550143", "features": ["voice", "sms", "mms"], "locality": "San Francisco", "bestEffort": true }
]
}| Query parameter | What it does |
|---|---|
country |
ISO 3166 alpha-2 code. Must be a country from GET /v1/phone-numbers/countries. |
numberType |
local, mobile, national or toll_free. Defaults to the country's default type. |
areaCode |
An area code (415) or a city name. A city covers every area code it has. |
locality |
A city name, matched against the national numbering plan. Accents and common aliases work. |
contains |
Digits the number must contain. |
sms |
true returns SMS-capable numbers only. |
limit |
Results to return. Default 20, maximum 100. |
When too few numbers match, nearby numbers fill the list and carry bestEffort: true. Drop them if the exact area matters. An unknown query parameter is a 400 that lists the accepted ones.
#Purchase
POST /v1/phone-numbers/purchase takes a profileId and a country. Leave out phoneNumber and Pozy assigns one. Pass a phoneNumber from your search to buy that exact number.
curl -X POST https://api.pozy.ai/v1/phone-numbers/purchase \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileId": "6650c1f0a2b3c4d5e6f70812",
"country": "US",
"phoneNumber": "+14155550142",
"wantsSms": true,
"connectWhatsapp": false,
"purchaseIntentId": "order-8841-line-1"
}'const res = await fetch("https://api.pozy.ai/v1/phone-numbers/purchase", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
profileId: "6650c1f0a2b3c4d5e6f70812",
country: "US",
phoneNumber: "+14155550142",
wantsSms: true,
connectWhatsapp: false,
purchaseIntentId: "order-8841-line-1",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.phoneNumber?.id, data.phoneNumber?.status);resp = requests.post(
"https://api.pozy.ai/v1/phone-numbers/purchase",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"profileId": "6650c1f0a2b3c4d5e6f70812",
"country": "US",
"phoneNumber": "+14155550142",
"wantsSms": True,
"connectWhatsapp": False,
"purchaseIntentId": "order-8841-line-1",
},
timeout=60,
)
print(resp.status_code, resp.json()){
"message": "Phone number provisioned",
"phoneNumber": {
"id": "6650d2e1b3c4d5e6f7081923",
"phoneNumber": "+14155550142",
"status": "active",
"country": "US",
"provisionedAt": "2026-10-30T10:12:00Z",
"profileId": "6650c1f0a2b3c4d5e6f70812"
}
}Keep phoneNumber.id. It is the {id} for every feature endpoint (/voice, /sms, /sip-trunk).
| Body field | Default | What it does |
|---|---|---|
profileId |
required | The profile that owns the number |
country |
US |
Country to buy in |
numberType |
country default | local, mobile, national or toll_free |
phoneNumber |
none | Buy this exact E.164 number. Only works where numbers activate instantly. |
areaCode |
none | Hold an auto-assigned number to this area. The purchase fails rather than give you another area. |
wantsSms |
false |
Draw from SMS-capable stock and flag the number for SMS |
connectWhatsapp |
true |
Start the WhatsApp connection on the new number. Send false for a calls or SMS line. |
wantsWhatsapp |
false |
With connectWhatsapp: false, still pick a number WhatsApp will accept |
purchaseIntentId |
none | Your own key for this purchase, up to 100 characters. A retry with the same key returns already_purchased instead of buying twice. |
allowMultiple |
false |
Confirm that a second purchase within 10 minutes is intentional |
Always send a purchaseIntentId, one per number you mean to buy. A retry with the same value returns { "status": "already_purchased", "numberId", "phoneNumber", "profileId" } and does not charge again. The key survives the round trip through adding a payment method.
#When the profile already has a number
A profile holds one number. If the profile you pass already has one, Pozy puts the new number on the next free profile (creating one if needed) and returns it in profileId. Read profileId from the response rather than assuming.
#Regulated countries
A country with needsKyc: true answers 202 instead of provisioning:
{
"status": "kyc_required",
"country": "GB",
"numberType": "local",
"kycUrl": "https://pozy.ai/kyc/..."
}Send your customer to kycUrl, or collect the details yourself with the KYC endpoints. Passing phoneNumber for a regulated country or type is a 400: the number is assigned after review.
#Check one country
GET /v1/phone-numbers/availability?country=BR&numberType=local answers for a single country: whether stock exists, which area codes are in stock (areaOptions), and the address rule for KYC (addressConstraint).
addressConstraint |
What the registrant needs |
|---|---|
geo |
An address inside the area of the number |
country |
Any address in the country |
none |
No address |
areaAvailability sorts every area of the country into inStock, preOrder and outOfStock. Pass an ndc from inStock as areaCode on the purchase to hold the order to that area.
#Watch a sold-out country
When a country or area has no stock, you have two options:
- Pre-order it. If the type is
preOrderable, submit the KYC form withpreOrder: true. The number is sourced for you, usually within 2 to 4 weeks, and nothing is billed until it is active. - Watch it.
POST /v1/phone-numbers/stock-watcheswith acountry(and optionallynumberTypeandareaCode) puts you on the list.
curl -X POST https://api.pozy.ai/v1/phone-numbers/stock-watches \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "country": "IE", "numberType": "local" }'Stock is checked every few hours. When numbers come back, Pozy sends the phone_number.stock_available webhook, so subscribe to it to hear about it. A watch fires once and is then used up, so create it again if you miss the stock.
| Rule | Value |
|---|---|
| Watches per workspace | 20 |
| Watches per country and type | 1 (a repeat request returns the existing watch) |
| Watch on a country that is in stock | 409: buy instead |
#Manage numbers
GET /v1/phone-numberslists your numbers. Filter bystatusorprofileId. Released numbers are left out unless you ask forstatus=released.GET /v1/phone-numbers/{id}returns one number with its features and voice settings.DELETE /v1/phone-numbers/{id}releases a number.
A released number goes back to the carrier pool and stops billing. You cannot get it back. A number attached to a SIP trunk must be detached first, or the release returns 409 invalid_resource_state.
#Limits
| Limit | Value |
|---|---|
| Numbers per profile | 1 |
| Numbers per workspace | Your profile limit (no cap where profiles are unlimited) |
| Purchase velocity | A second purchase within 10 minutes needs allowMultiple: true |
| Search results | 20 by default, 100 at most |
#Errors
| Status | Code | Cause | Fix |
|---|---|---|---|
400 |
The country is not offered, or phoneNumber was sent for a regulated country |
Pick a country from the countries list, or drop phoneNumber and run KYC |
|
400 |
numberType: "toll_free" with connectWhatsapp: true |
Send connectWhatsapp: false |
|
402 |
payment_required |
No payment method on file | Add a card, then retry with the same purchaseIntentId |
409 |
PHONE_NUMBER_UNAVAILABLE |
The exact number you picked is gone | Search again |
409 |
AREA_CODE_UNAVAILABLE |
No deliverable stock in that areaCode |
Pick another area, or leave it out |
409 |
NO_WHATSAPP_ELIGIBLE_NUMBER |
Every number offered is one WhatsApp refuses | Pass an areaCode, or retry |
409 |
PURCHASE_VELOCITY |
A second purchase within 10 minutes | Send allowMultiple: true |
All errors use the standard error shape.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/phone-numbers/countries |
List offerable number countries |
GET /v1/phone-numbers/availability |
Check country availability |
GET /v1/phone-numbers/available |
Search available numbers |
POST /v1/phone-numbers/purchase |
Purchase phone number |
GET /v1/phone-numbers/claims/{claimId} |
Resolve a number claim |
GET /v1/phone-numbers |
List phone numbers |
GET /v1/phone-numbers/{id} |
Get phone number |
DELETE /v1/phone-numbers/{id} |
Release phone number |
GET /v1/phone-numbers/stock-watches |
List stock watches |
POST /v1/phone-numbers/stock-watches |
Watch an out-of-stock country |
DELETE /v1/phone-numbers/stock-watches/{id} |
Stop watching a country |
#Webhook events
| Event | When it fires |
|---|---|
whatsapp.number.activated |
WhatsApp number activated. |
whatsapp.number.suspended |
WhatsApp number suspended. |
whatsapp.number.reactivated |
WhatsApp number reactivated. |
whatsapp.number.released |
WhatsApp number released. |
phone_number.stock_available |
Phone-number stock available. |