Phone, SMS & voice
Submit the identity details a regulated country requires, hand the form to your customer with a branded link, and fix a declined number.
Many countries regulate who can hold a phone number. Before a number in those countries is ordered, the registrant (you, or your customer) proves who they are and where they are. Pozy collects the details, files them with the carrier and tracks the review. Numbers usually activate 1 to 3 business days after you submit.
You hit this flow when GET /v1/phone-numbers/countries shows needsKyc: true, or when a purchase answers 202 with status: "kyc_required".
#Two ways to collect the details
| Hosted link | API | |
|---|---|---|
| Who fills it in | Your customer, on a page with your logo and colour | Your own UI |
| Build effort | One call | A form per country, document upload, address checks |
| Best for | Platforms whose customers hold the legal details | Teams that already collect business documents |
#Hand the form to your customer
Hosted KYC links are not available yet: POST /v1/phone-numbers/kyc/share answers 403 with code: "feature_not_available". Collect the details in your own UI and submit them through the API until it opens.
POST /v1/phone-numbers/kyc/share returns a single-use page, valid for 7 days, that walks your customer through the country's requirements. It needs no Pozy login.
curl -X POST https://api.pozy.ai/v1/phone-numbers/kyc/share \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileId": "6650c1f0a2b3c4d5e6f70812",
"country": "GB",
"language": "en",
"branding": { "companyName": "Acme", "logoUrl": "https://acme.example.com/logo.png", "brandColor": "#1a73e8" },
"redirect_url": "https://acme.example.com/numbers/done"
}'const res = await fetch("https://api.pozy.ai/v1/phone-numbers/kyc/share", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
profileId: "6650c1f0a2b3c4d5e6f70812",
country: "GB",
language: "en",
branding: { companyName: "Acme", logoUrl: "https://acme.example.com/logo.png", brandColor: "#1a73e8" },
redirect_url: "https://acme.example.com/numbers/done",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.url, data.expiresAt);import os, requests
resp = requests.post(
"https://api.pozy.ai/v1/phone-numbers/kyc/share",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"profileId": "6650c1f0a2b3c4d5e6f70812",
"country": "GB",
"branding": {"companyName": "Acme", "logoUrl": "https://acme.example.com/logo.png"},
"redirect_url": "https://acme.example.com/numbers/done",
},
timeout=30,
)
print(resp.json()["url"])brandingputs your company name, logo and colour on the page.languagesets the page language:en,esorpt-BR. Without it, the page follows the browser and falls back to English.redirect_urlsends the customer back to your app afterwards, withkyc=submittedandcountry=<code>added to the query string.areaCodeholds the order to one area.
When your customer submits, the number enters review under your workspace and whatsapp.number.kyc_submitted fires.
#Submit through the API
#Check stock and the address rule
GET /v1/phone-numbers/availability?country=GB says whether numbers are in stock and which address the registrant needs (addressConstraint: geo, country or none). Checking first avoids a filing that passes review but can never get a number because the address is in the wrong area.
#Get the form for the country
GET /v1/phone-numbers/kyc?country=GB returns the fields the country requires. Add numberType if you want a type other than the default, and send the same value when you submit.
{
"country": "GB",
"numberType": "local",
"fields": [
{ "requirementId": "d7e8f9a0-b1c2-4d3e-9f4a-5b6c7d8e9f0a", "label": "Business name", "kind": "text", "example": "Acme Ltd" },
{ "requirementId": "e8f9a0b1-c2d3-4e4f-8a5b-6c7d8e9f0a1b", "label": "Proof of identity", "kind": "file" },
{ "requirementId": "f9a0b1c2-d3e4-4f5a-9b6c-7d8e9f0a1b2c", "label": "Business address", "kind": "address", "localTo": "GB" }
],
"reusable": null,
"pendingReview": false
}kind |
What to send |
|---|---|
text |
A string in values, keyed by requirementId |
date |
An ISO date in values |
address |
The address object |
file |
An entry in documents, either inline base64 or a documentId from POST /v1/phone-numbers/kyc/upload-document (about 4 MB per document). Base64 makes a file about a third larger and the whole request can carry about 4 MB, so keep inline files to about 3 MB in total per submission |
action |
An identity check the end user completes after the order, through a link. Send endUserFirstName and endUserLastName with the submit. |
#Check the packet (optional)
POST /v1/phone-numbers/kyc/validate-address checks an address before you upload anything. POST /v1/phone-numbers/kyc/review-packet reviews the whole packet for likely decline reasons.
#Submit
curl -X POST https://api.pozy.ai/v1/phone-numbers/kyc \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileId": "6650c1f0a2b3c4d5e6f70812",
"country": "GB",
"submissionId": "acme-gb-0001",
"values": { "d7e8f9a0-b1c2-4d3e-9f4a-5b6c7d8e9f0a": "Acme Ltd" },
"documents": [
{ "requirementId": "e8f9a0b1-c2d3-4e4f-8a5b-6c7d8e9f0a1b", "filename": "passport-both-sides.pdf", "base64": "JVBERi0xLjQK..." }
],
"address": {
"requirementId": "f9a0b1c2-d3e4-4f5a-9b6c-7d8e9f0a1b2c",
"country_code": "GB",
"street_address": "221 Baker Street",
"locality": "London",
"administrative_area": "London",
"postal_code": "NW1 6XE"
}
}'The response has status: "kyc_submitted" and the new number in pending_regulatory.
Carriers reject one-sided ID cards often. Combine front and back into one file before you upload it.
| Submit field | What it does |
|---|---|
submissionId |
Your key for this attempt. A retry with the same value returns the same number instead of ordering another. |
quantity |
1 to 5 numbers in the same country from one verification (a pre-order is always 1) |
wantsSms |
Order from SMS-capable stock and turn SMS on at activation |
reuse, reuseOptionId |
Reuse an approved verification you already hold for this country. The form response lists it in reusable. No documents needed, and the order is placed at once. |
areaCode, preOrder |
Order in one area, and accept a sourced number when it is out of stock |
Document bytes go straight to the carrier with the request. Pozy does not keep a copy.
#Out of stock: pre-order
When available is false but preOrderable is true, submit the same form with preOrder: true. Pozy takes the next number that comes back in stock, or one the carrier sources for this registration.
- Sourcing usually takes 2 to 4 weeks and is not guaranteed.
- The number stays
pending_regulatoryuntil sourced. Nothing is billed until it is active. - If the carrier cannot source it, the number is declined and nothing is charged.
- To cancel, release the pending number with
DELETE /v1/phone-numbers/{id}.
In geo countries the area comes from the address. If that area has no stock, the first submit returns 409 with code: "area_pre_order_available" and details (areaCode, areaName, estimatedWeeks, billedWhenActive). The error text is ready to show your customer. Once they agree to wait, resend the same body with preOrder: true.
#Forward the identity-check link
Some countries (Australian mobiles, for example) need the end user to pass a live identity check after the order is placed. When the form had an action field:
- Poll
GET /v1/phone-numbers/{id}after submitting. The check link appears on the number inonfidoVerificationUrlonce the order exists. - Or listen for
whatsapp.number.verification_required, which carries the same link inverificationUrl. - Send the link to the person whose ID is on file. The review waits until they finish.
#Fix a declined number
A declined number stays in your list with status: "regulatory_declined" and a regulatoryDeclineReason. You do not start over.
GET /v1/phone-numbers/{id}/remediatereturns only the requirements the reviewer flagged.POST /v1/phone-numbers/{id}/remediatewith correctedvalues,documentsoraddress.
The number goes back to pending_regulatory on the same record and is not billed again.
#Answer the reviewer
Sometimes the reviewer asks a question rather than flagging a field ("is this a personal or a business line?"). POST /v1/phone-numbers/{id}/remediate/respond sends a message, plus any corrected documents or address and up to 5 attachments (PDF, JPG, PNG or WebP, sent as base64, about 3 MB in total per request). The response status is replied for a message alone and resubmitted when corrections went with it.
curl -X POST https://api.pozy.ai/v1/phone-numbers/6650d2e1b3c4d5e6f7081923/remediate/respond \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "This is a business line for Acme Ltd. The utility bill is in the company name." }'#Statuses and events
| What happened | Number status | Event |
|---|---|---|
| Customer submitted a hosted link | pending_regulatory |
whatsapp.number.kyc_submitted |
| Reviewer needs more information | stays pending_regulatory |
whatsapp.number.action_required (with reason and per-requirement requirements) |
| End user must pass an ID check | stays pending_regulatory |
whatsapp.number.verification_required |
| Approved and live | active |
whatsapp.number.activated |
| Declined | regulatory_declined |
whatsapp.number.declined |
These events carry a whatsapp. prefix for historical reasons and fire for every number. No event fires when you submit through the API, because you already know.
verification.approved and verification.failed are not KYC events. They belong to the one-time code API.
#Limits
| Limit | Value |
|---|---|
| Review time | Usually 1 to 3 business days |
| Numbers per verification | 1 to 5, same country |
| Hosted link | Single use, valid 7 days |
| Reviewer attachments | Up to 5 files, about 3 MB in total per request (they travel as base64) |
#Errors
| Status | Code | Cause | Fix |
|---|---|---|---|
400 |
A value failed validation, often an address outside the country or an oversized file | Run validate-address first |
|
409 |
area_pre_order_available |
The address area is out of stock but can be pre-ordered | Resend with preOrder: true once the customer agrees |
409 |
area_code_unavailable |
The area has no stock and cannot be pre-ordered | Pick another area, or create a stock watch |
409 |
country_out_of_stock |
Nothing deliverable in the country and type | Create a stock watch |
409 |
reuse: true with no approved verification on file |
Submit the full form |
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/phone-numbers/kyc |
Get KYC form spec |
POST /v1/phone-numbers/kyc |
Submit KYC |
POST /v1/phone-numbers/kyc/upload-document |
Upload a KYC document |
GET /v1/phone-numbers/kyc/document/{documentId} |
View a KYC document on file |
POST /v1/phone-numbers/kyc/validate-address |
Pre-validate KYC address |
POST /v1/phone-numbers/kyc/review-packet |
Pre-review a KYC packet |
POST /v1/phone-numbers/kyc/share |
Create a hosted KYC link (not available yet) |
GET /v1/phone-numbers/{id}/remediate |
Get declined requirements |
POST /v1/phone-numbers/{id}/remediate |
Resubmit a declined number |
POST /v1/phone-numbers/{id}/remediate/reply |
Reply to the regulatory reviewer |
POST /v1/phone-numbers/{id}/remediate/respond |
Respond to the regulatory reviewer (message + corrections) |
#Webhook events
| Event | When it fires |
|---|---|
whatsapp.number.activated |
WhatsApp number activated. |
whatsapp.number.declined |
WhatsApp number declined. |
whatsapp.number.action_required |
WhatsApp number action required. |
whatsapp.number.verification_required |
WhatsApp number verification-required. |
whatsapp.number.kyc_submitted |
WhatsApp number KYC submitted. |