Phone Numbers API
The Phone Numbers API lets you find and buy phone numbers — one by one or in bulk — route their calls and SMS to your systems, send and receive SMS, and complete KYC. All phone numbers are in E.164 format, for example +442071234567.
[!TIP]
Try it here: enter your Client ID and Client Secret in the Authentication box, click Authorize, then use Test Request on any endpoint.
What you can do
| Task |
Endpoint |
Description |
| See what is on sale |
GET /coverage |
Countries, number types, billing periods and lowest prices |
| Search numbers |
GET /available |
Numbers you can buy, with the price of every billing period |
| Get a quote |
POST /preview |
Exact price for the numbers you picked |
| Buy numbers |
POST / |
Buy the numbers you picked |
| Bulk order |
POST /bulk-orders |
Buy up to 100 numbers of one country and type; we pick them |
| Follow orders |
GET /orders |
Your orders and the numbers in them |
| Your numbers |
GET / |
Numbers your organization owns |
| Route calls and SMS |
PATCH /{id} |
Assign voice and SMS endpoints to a number |
| Release a number |
DELETE /{id} |
Give a number back |
| Routing endpoints |
/endpoints |
Your SIP (voice) and HTTPS (SMS) destinations |
| Send SMS |
POST /sms/messages |
Send an SMS from a number you own |
| SMS history |
GET /sms/messages |
Inbound and outbound messages |
| SMS prices |
GET /sms/pricing |
Inbound and outbound SMS rates |
| KYC |
/kyc/… |
What is required, document upload and submissions |
| Health |
GET /health |
Service availability (no token needed) |
Quick start
- Enable the API. Phone numbers must be enabled for your organization in OmaxTelecom Console.
- Get a token — see Getting started.
- Check KYC.
GET /kyc/requirements?country_iso=GB&number_type=local tells you whether you need to submit KYC before buying.
- Buy a number in three calls:
| Step |
Endpoint |
What happens |
| 1. Search |
GET /available?country_iso=GB&number_type=local |
Numbers on sale, each with terms: the price of every billing period |
| 2. Quote |
POST /preview with number_ids and billing_period |
The exact total and a quote_token, valid for 10 minutes |
| 3. Buy |
POST / with the same number_ids and billing_period, the quote_token and expected_total_cost_minor |
The numbers are charged to your wallet and activated |
- Route the number: create an endpoint with
POST /endpoints and assign it with PATCH /{phoneNumberId}.
With the Node.js SDK (installation), the same purchase:
import { Omax } from "@omaxtelecom/sdk";
const omax = new Omax(); // reads OMAX_CLIENT_ID and OMAX_CLIENT_SECRET
const { numbers } = await omax.phoneNumbers.searchAvailable({ country_iso: "GB", number_type: "local" });
const number_ids = [numbers[0].id];
const { quote } = await omax.phoneNumbers.preview({ number_ids, billing_period: "monthly" });
const purchase = await omax.phoneNumbers.purchase({
number_ids,
billing_period: "monthly",
quote_token: quote.quote_token,
expected_total_cost_minor: quote.upfront_total.amount_minor,
});
The SDK sends the required Idempotency-Key and keeps it when it retries, so a retry never buys twice.
Authentication
Send an access token on every request except GET /health:
Authorization: Bearer <access_token>
Get the token from POST https://api.omaxtelecom.com/auth/token with your Console client_id and client_secret. Getting started shows how, with code samples.
Machine-to-machine clients whose token has no organization claim also send:
X-Organization-Id: <organization_id>
The organization must exist with status active, and phone numbers must be enabled for it.
Prices and billing periods
A number is bought for one billing period: monthly (1 month), quarterly (3), six_monthly (6) or annually (12). Which periods are available, and their prices, depend on the country and number type — terms in the search result lists them, and billing_periods in GET /coverage shows them per country. Some countries are sold on one period only; quote and buy only on a period that is listed.
For the period you choose you pay:
- a one-off connection fee (
connection_fee) per number, when you buy;
- the period price (
recurring_fee) per number, when you buy and again on every renewal (next_billing_date).
recurring_fee is the price of the whole period, not per month; monthly_equivalent is for comparison only. The price is fixed when you buy, for the life of the number.
Amounts are objects — use amount_minor (cents) for calculations:
{ "amount": "8.00", "amount_minor": 800, "currency": "EUR" }
Buying numbers
Search
curl "https://api.omaxtelecom.com/numbers/available?country_iso=GB&number_type=local&limit=20" \
-H "Authorization: Bearer $TOKEN"
Each number has a catalog id and its terms. Add billing_period to only see numbers sold on that period, or pattern to look for digits (*123*). Catalog IDs are temporary — use them within a few minutes, and keep all IDs of one purchase from the same search.
Quote
curl -X POST "https://api.omaxtelecom.com/numbers/preview" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"number_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "billing_period": "annually"}'
{
"success": true,
"data": {
"numbers": [ "…" ],
"quote": {
"billing_period": "annually",
"months": 12,
"quantity": 1,
"connection_fee": { "amount": "0.00", "amount_minor": 0, "currency": "EUR" },
"recurring_fee": { "amount": "28.00", "amount_minor": 2800, "currency": "EUR" },
"upfront_total": { "amount": "28.00", "amount_minor": 2800, "currency": "EUR" },
"quote_token": "QT_3f9a1c",
"expires_at": "2026-10-16T10:10:00Z",
"first_billing_date": "2026-10-16",
"next_billing_date": "2027-10-16"
}
}
}
Buy
Send the quote back. Idempotency-Key is required, so a retry after a network error never buys twice.
curl -X POST "https://api.omaxtelecom.com/numbers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-10-16-001" \
-d '{
"number_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
"billing_period": "annually",
"quote_token": "QT_3f9a1c",
"expected_total_cost_minor": 2800
}'
The answer is 202 Accepted with the order and the numbers; the Location header points to GET /orders/{orderId}.
{
"success": true,
"data": {
"order": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": "completed" },
"phone_numbers": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"e164": "+442071234567",
"national_format": "020 7123 4567",
"country_iso": "GB",
"number_type": "local",
"recurring_fee": { "amount": "28.00", "amount_minor": 2800, "currency": "EUR" },
"status": "active",
"kyc_status": "approved",
"traffic_direction": "inbound_and_outbound",
"capabilities": { "voice": true, "sms": true },
"voice_endpoint_id": null,
"sms_endpoint_id": null,
"next_billing_date": "2027-10-16",
"billing_period": "annually"
}
]
}
}
Your wallet is charged before the numbers are bought from the provider. If some numbers can't be delivered, that part is refunded automatically. If the price changed since your quote, the purchase fails with PRICE_CHANGED — get a new quote.
Bulk orders
To buy many numbers of one country and type without picking them:
GET /coverage — what is on sale, and bulk.max_quantity (100).
POST /bulk-orders/preview with country_iso, number_type, billing_period and quantity (optionally pattern). You get the price, a single-use quote_token (10 minutes) and availability — whether enough numbers are in stock right now.
POST /bulk-orders with the same fields plus quote_token and expected_total_cost_minor (quote.upfront_total.amount_minor). Idempotency-Key is required.
curl -X POST "https://api.omaxtelecom.com/numbers/bulk-orders" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bulk-2026-10-16-001" \
-d '{
"country_iso": "GB",
"number_type": "local",
"billing_period": "annually",
"quantity": 25,
"quote_token": "QT_8b21d4",
"expected_total_cost_minor": 70000
}'
The order runs in the background, in batches of 10. Each batch is charged before it is bought, and anything that can't be delivered is refunded. Poll GET /orders/{orderId} until status is final:
| Status |
Meaning |
pending, processing |
Still running |
completed |
Every number was bought |
partially_completed |
Stock or balance ran out — you keep and pay only what was bought; failure says why |
failed |
Nothing was bought; failure says why |
quantity_fulfilled shows how many numbers were bought so far. GET /orders lists all your orders, newest first.
Routing calls and SMS
Create an endpoint once, then assign it to any number.
curl -X POST "https://api.omaxtelecom.com/numbers/endpoints" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Office PBX", "type": "voice", "destination_uri": "203.0.113.10:5060"}'
- Voice (
type: voice) — a SIP destination: an IP (203.0.113.10), IP and port (203.0.113.10:5060), a domain (pbx.example.com) or a full sip: URI. The called number goes where you put %e164, %e164noplus, %national or %raw; without a placeholder %e164 is used. The response shows the normalized value, for example sip:%e164@203.0.113.10:5060.
- SMS (
type: sms) — mailto: with up to 5 comma-separated email addresses; every inbound SMS is emailed to all of them. To receive inbound SMS in your system, use webhooks (phone_numbers.sms.received).
The endpoint is registered with the carrier right away. If the carrier refuses it (422) or is unavailable (503), nothing is created and you can retry.
Assign endpoints to a number — fields you leave out stay unchanged, null removes the assignment:
curl -X PATCH "https://api.omaxtelecom.com/numbers/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"voice_endpoint_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "sms_endpoint_id": "c3d4e5f6-a7b8-9012-cdef-123456789012"}'
An endpoint that is still assigned to a number can't be deleted (409 ENDPOINT_IN_USE).
SMS
Send
curl -X POST "https://api.omaxtelecom.com/numbers/sms/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sms-order-991" \
-d '{"from": "+442071234567", "to": "+447911123456", "body": "Your code is 123456", "client_reference": "order-991"}'
from must be an active number you own. The answer is 202 with the message (status: pending) and its price; the delivery status updates later. GET /sms/messages/{messageId} shows it, and GET /sms/messages lists inbound and outbound messages, newest first, with direction, phone_number_id, status, since and until filters and cursor paging.
Receive
SMS sent to your numbers reach you in two ways, and you can use both:
- By webhook: subscribe to
phone_numbers.sms.received in the Console under Webhooks. Every inbound SMS is sent as a signed JSON POST to your HTTPS endpoint, and you can limit an endpoint to chosen numbers. The payload, signature check and retries are described in the Webhooks guide.
- By email: assign an SMS endpoint with a
mailto: destination to the number.
GET /sms/messages?direction=inbound lists received messages, and POST /sms/messages/{messageId}/retry forwards an inbound message to its SMS endpoint again.
Status changes of the SMS you send, numbers bought, renewed or released, orders, KYC reviews and porting are also available as webhooks.
KYC
Some countries and number types require KYC (know your customer) before you can buy.
GET /kyc/requirements?country_iso=GB&number_type=local — the fields and document_types you need. Empty lists mean nothing is required.
POST /kyc/documents — upload each document (multipart/form-data: file as PDF, JPG or PNG up to 10 MB, and its type). You get a document id.
- Submit
data (the field values) and document_ids:
POST /kyc/organization — when your organization uses organization-level KYC (once, before buying);
POST /{phoneNumberId}/kyc — when your organization uses per-number KYC.
GET /kyc/organization and GET /{phoneNumberId}/kyc show the status of a submission. Buying without the required KYC fails with 403 KYC_REQUIRED.
Responses
Successful responses wrap the result in data:
{ "success": true, "data": { ... } }
Errors return success: false with a code you can branch on:
{
"success": false,
"error": { "code": "ERROR_CODE", "message": "Human-readable message" }
}
Idempotency and rate limits
POST /, POST /bulk-orders and POST /sms/messages require an Idempotency-Key header; creating endpoints and KYC submissions accept one. The same key with the same body within 24 hours returns the first response with Idempotent-Replayed: true; the same key with a different body returns 422 IDEMPOTENCY_KEY_REUSED.
- Requests are rate limited per organization. Every response has
RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; when you go over, you get 429 RATE_LIMITED with Retry-After.
Errors
| HTTP status |
Error code |
What to do |
401 |
UNAUTHORIZED |
Get a new access token |
402 |
INSUFFICIENT_BALANCE |
Top up your wallet in Console |
403 |
PHONE_NUMBERS_DISABLED |
Ask support to enable phone numbers for your organization |
403 |
BILLING_NOT_CONFIGURED |
Billing is not set up for your organization — contact support |
403 |
KYC_REQUIRED |
Complete KYC first — see GET /kyc/requirements |
403 |
TRAFFIC_RESTRICTED |
This number can't send SMS |
403 |
SENDER_NOT_OWNED, SENDER_NOT_ACTIVE |
Send from an active number you own |
403 |
RATE_NOT_CONFIGURED |
No SMS rate for this destination — contact support |
404 |
NOT_FOUND |
The resource doesn't exist or isn't yours |
404 |
OFFERING_NOT_FOUND |
No numbers are sold for that country, type or billing period. For a billing period the message lists the ones that are, e.g. Available: quarterly. — use one of them |
409 |
NUMBER_UNAVAILABLE |
The number is no longer available — search again |
409 |
PRICE_CHANGED |
The price changed since your quote — get a new quote |
409 |
QUOTE_EXPIRED, QUOTE_MISMATCH |
Get a new quote for exactly the same request |
409 |
ENDPOINT_IN_USE |
Unassign the endpoint from its numbers first |
409 |
IDEMPOTENCY_IN_PROGRESS |
The same key is still being processed — retry shortly |
422 |
VALIDATION_FAILED, VALIDATION_ERROR |
Fix the request; the response lists the fields |
422 |
IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_REUSED |
Send a new, unique Idempotency-Key |
422 |
PURCHASE_NOT_AVAILABLE |
The number provider refused to quote these numbers (POST /preview). Don't retry the same request — search again, and contact support with the X-Request-Id if it keeps happening |
429 |
RATE_LIMITED |
Wait for Retry-After seconds |
503 |
PHONE_NUMBERS_UNAVAILABLE, BILLING_UNAVAILABLE |
Temporary problem — retry later with back-off |
Branch on error.code and the HTTP status, never on message. Every response has an X-Request-Id header (send your own X-Request-Id to set it); quote it when you contact support so we can find the exact request.
Need help?
Email support@omaxtelecom.com. Tell us which endpoint you called, when, and what the response was — including its request_id if it has one. Never send your Client Secret or access token.