Build phone verification into your own product.
Order numbers, receive codes, and run your own storefront on top of CODASMS. Pre-funded balance, 700+ services, 190+ countries — one API key. New here? Start with the API overview →
Order numbers, receive codes, and run your own storefront on top of CODASMS. Pre-funded balance, 700+ services, 190+ countries — one API key. New here? Start with the API overview →
The Reseller API is not self-serve. Every reseller is reviewed and approved by us before an API key is issued — this keeps supply predictable for the resellers who are already trading on it.
To apply, fill in the application form — it takes about two minutes. If it's a fit, we'll set up your reseller account, fund it with your first deposit, and send you an API key.
Already approved? Your API key was sent to you directly. It is shown once and stored only as a hash — we cannot look it up or recover it. If you lose it, ask us to revoke it and issue a new one.
Every request carries your API key as a bearer token. Keys look like coda_live_….
Authorization: Bearer coda_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Your key identifies your reseller account. Orders are billed to that account's balance — there is no account or user field in any request body, so a key can only ever spend its own balance and read its own orders.
Treat your key like a password. Anyone holding it can spend your balance. Keep it server-side — never ship it in a browser, mobile app, or public repository. If it leaks, contact us and we'll revoke it immediately; revocation takes effect on the next request.
https://api.codasms.com
All endpoints are under /v1. Requests and responses are JSON. All amounts are integer US cents — never floats. A response always carries an ok boolean; check it before reading anything else.
60 requests per minute, per key. Exceeding it returns 429 with reason: "rate_limited" and a retryAfterSeconds field. Back off and retry — the window is rolling, not fixed.
If you need a higher limit for a specific integration, ask us. It's a per-key setting.
Your current balance. Check it before a batch of orders — an order against an empty balance fails with 402 and costs you a round trip.
# request
curl https://api.codasms.com/v1/balance \
-H "Authorization: Bearer coda_live_..."
# response
{
"ok": true,
"balance_cents": 15000,
"balance_usd": 150.00,
"currency": "USD"
}
balance_cents is authoritative. balance_usd is a convenience float for display — never use it for arithmetic.
The live catalog: what's buyable, where, at what price, with current stock.
Unfiltered, this returns the entire catalog — around 500KB. If you want one cell, ask for one cell.
| Param | Example | Returns |
|---|---|---|
service | ?service=tg | Telegram, every country that has it |
country | ?country=6 | Everything available in Indonesia |
| both | ?service=tg&country=6 | That one cell |
| none | — | The full catalog |
# request
curl "https://api.codasms.com/v1/services?service=tg&country=6" \
-H "Authorization: Bearer coda_live_..."
# response
{
"ok": true,
"currency": "USD",
"filters": { "service": "tg", "country": "6" },
"counts": { "services": 1, "countries": 1 },
"services": [ { "code": "tg", "name": "Telegram", "from_cents": 61, "stock": 2349045 } ],
"countries": [ { "code": "6", "name": "Indonesia", "iso": "ID", "flag": "🇮🇩" } ],
"matrix": {
"6": { "tg": { "standard_cents": 61, "priority_cents": 92, "count": 2349045 } }
},
"generated_at": "2026-07-17T07:20:36.529Z",
"stale": false
}
The matrix is the part that matters. It's keyed matrix[country][service] and holds the real price for that exact combination. The from_cents on a service is its cheapest price anywhere — a headline figure, not a quote.
The catalog refreshes about every 5 minutes and responses carry Cache-Control: max-age=300. stale: true means you're seeing a slightly old snapshot; the prices are still what we'd charge, so it's safe to use.
Prices here are indicative, not a locked quote. Stock and supply move. Every order is priced server-side at the moment you place it, and the response tells you exactly what you were charged.
A lightweight list of the countries you can buy numbers in — just enough to build a country picker without pulling the full catalog. Each country has the same shape it has in Services, so if you already parse countries there, this needs no new code.
| Query | Required | Notes |
|---|---|---|
service | no | Limit to countries where that service is buyable, e.g. ?service=tg |
Use ?service= to drive a second dropdown: a customer picks a service, and you show only the countries where it actually works — not all of them, most of which would fail at checkout.
# request
curl https://api.codasms.com/v1/countries?service=tg \
-H "Authorization: Bearer coda_live_..."
# response
{
"ok": true,
"filters": { "service": "tg" },
"count": 188,
"countries": [
{ "code": "6", "name": "Indonesia", "iso": "ID", "flag": "\ud83c\uddee\ud83c\udde9" }
],
"generated_at": "2026-07-18T09:38:07Z",
"stale": false
}
A country appears only if it has live, buyable stock. Countries that are sold out, or only available as a manual quote, are left out — so the list never shows something an order would reject.
Buys a number and debits your balance. This is the one endpoint that moves money.
| Field | Required | Notes |
|---|---|---|
service | yes | Service code from the catalog, e.g. tg |
country | yes | Country code from the catalog, e.g. 6 |
band | no | standard (default) or priority |
The Idempotency-Key header is required. Send a unique value — a UUID is ideal — with every order you place. If a request times out or you lose the connection, retry with the same key: you'll get back the original order, not a second number. This is the difference between a dropped response and a double charge, so your client must set it.
Priority costs more and buys from higher rungs of supply. It's worth it for services where delivery is hard; for most, standard is fine.
# request
curl -X POST https://api.codasms.com/v1/orders \
-H "Authorization: Bearer coda_live_..." \
-H "Idempotency-Key: 3f2a9c14-8b6e-4d17-9a2f-1e5c7b0d4a88" \
-H "Content-Type: application/json" \
-d '{"service":"tg","country":"6","band":"standard"}'
# response
{
"ok": true,
"order_id": 542,
"number": "628xxxxxxxxx",
"service": "tg",
"country": "6",
"band": "standard",
"price_cents": 67,
"status": "active",
"expires_in_seconds": 1200
}
Use number immediately — request the verification code from whatever service you're verifying, then poll order status for the code.
You are never charged for nothing. If we can't issue a number, you're not debited. If we issue one and no code arrives within 20 minutes, the order expires and your balance is refunded automatically — even if you never poll and never cancel.
Reuse the same Idempotency-Key to retry a request you're unsure completed. A retry after the order succeeded returns the original response unchanged — same order_id, same number, no second charge. A retry while the first is still processing returns 409 with reason: "in_progress"; wait a few seconds and try again. Reusing a key for a different order (a different service or country) returns 422 with reason: "bad_request" — a key belongs to one order, so use a fresh key each time.
Poll for the code, or cancel for an early refund.
| Field | Required | Notes |
|---|---|---|
order_id | yes | From the create response |
action | no | poll (default) or cancel |
# poll
curl -X POST https://api.codasms.com/v1/orders/status \
-H "Authorization: Bearer coda_live_..." \
-H "Content-Type: application/json" \
-d '{"order_id":542}'
# waiting
{ "ok": true, "order_id": 542, "status": "active", "waiting": true }
# code arrived
{ "ok": true, "order_id": 542, "status": "delivered", "code": "123456" }
# cancel early — refunds immediately
curl -X POST https://api.codasms.com/v1/orders/status \
-H "Authorization: Bearer coda_live_..." \
-H "Content-Type: application/json" \
-d '{"order_id":542,"action":"cancel"}'
{ "ok": true, "order_id": 542, "status": "refunded" }
Poll every 3–5 seconds. Faster wastes your rate limit; codes typically take ~20 seconds. Polling a delivered order is safe and idempotent — it returns the same code.
Cancelling an order that has already delivered will not refund it: you received what you paid for, and the response returns the code instead.
| Status | Means |
|---|---|
active | Number issued, waiting for a code. Keep polling. |
delivered | Code arrived. Terminal — you were charged. |
refunded | Cancelled or expired. Terminal — balance returned. |
Every order reaches a terminal state on its own. A number that receives no code within 20 minutes is expired and refunded automatically by our system — you don't need to poll or cancel for that to happen.
A minimal integration:
POST /v1/orders → take number and order_idPOST /v1/orders/status every 3–5s until delivered or refundedaction: "cancel" to get your money back soonerErrors return ok: false and a stable reason string. Branch on reason, not on the HTTP status or on any message text — reason strings are the contract; wording is not.
Some responses also carry detail (human-readable, may change) and refunded: true where money was returned.
| Status | reason | What to do |
|---|---|---|
| 400 | bad_request | Malformed JSON or a missing field. See detail. |
| 401 | missing_or_malformed_key | No bearer token, or it isn't a coda_live_ key. |
| 401 | invalid_key | Unknown or revoked key. Contact us. |
| 402 | insufficient_balance | Top up. Nothing was charged. |
| 409 | in_progress | An order with this Idempotency-Key is still processing. Retry the same key in a few seconds. |
| 403 | reseller_not_active | Your account is suspended. Contact us. |
| 403 | forbidden | That order isn't yours. |
| 404 | not_found | No such order. |
| 405 | method_not_allowed | Wrong HTTP verb for that endpoint. |
| 429 | rate_limited | Over 60/min. Back off; see retryAfterSeconds. |
| Status | reason | What to do |
|---|---|---|
| 409 | unavailable | No live supply for that combination. Try another. |
| 409 | out_of_stock | Pool is empty right now. Try later. |
| 409 | concierge_only | Priced above the self-serve range — not sold via the API. |
| 409 | no_provider_ref | Order never finished provisioning. Contact us. |
| Status | reason | What to do |
|---|---|---|
| 409 | no_numbers | Supply vanished mid-purchase. Retry. |
| 409 | price_moved | Price shifted mid-purchase. Retry. |
Both carry refunded: true. Your balance is already back.
| Status | reason | What to do |
|---|---|---|
| 500 | internal_error | Our bug. Retry with backoff; tell us if it persists. |
| 502 | provider_error | Upstream problem. Retry with backoff. |
| 503 | catalog_unavailable | Catalog warming. Retry shortly. |
Retry safely. 500, 502 and 503 are transient — use exponential backoff, not a tight loop. If an order failed after we charged you, the response says refunded: true; if it doesn't, nothing was charged.
Integration questions, a higher rate limit, a lost key, or an order that looks wrong — contact us. Include your order_id and the key prefix (the first ~18 characters, e.g. coda_live_qKcyrcU2) so we can find it. Never send us your full key.
Not a reseller yet? Apply for access.