Developers

VoxioTelecom REST API

One JSON API for your wallet balance, A2P SMS delivery, OTP verification numbers and crypto top-up invoices. Scoped bearer keys, per-key spend caps, idempotent billing calls and predictable error codes.

https://voxiotelecom.com/api/public/v1 Create an API key

Start here

Quickstart

  1. Sign in to the portal and open API keys.
  2. Create a key, pick only the scopes your integration needs, and copy the secret — it is shown once.
  3. Send it as a bearer token on every request. All responses are JSON.
bash
curl -s https://voxiotelecom.com/api/public/v1/balance \
  -H "Authorization: Bearer vk_1a2b3c4d.<secret>"
json
{
  "balance": 412.8391,
  "currency": "USD",
  "account": { "email": "you@example.com", "status": "active" }
}

An unauthenticated GET https://voxiotelecom.com/api/public/v1 returns a machine-readable index of every endpoint — handy for autogenerated clients and AI agents.

Security

Authentication

Keys look like vk_<prefix>.<secret>. The prefix identifies the key, the secret is 256 bits of entropy and is stored only as a SHA-256 hash — nobody, including our staff, can read it back. Lost a key? Revoke it and issue a new one.

FieldTypeDescription
Authorizationheader, requiredBearer vk_<prefix>.<secret>
Content-Typeheader, required on POSTapplication/json (max 200 KB body)
Idempotency-Keyheader, optionalSafe retries on billing endpoints (see below)

Never ship a key in browser JavaScript, a mobile app bundle or a public repository. Calls are billed to your wallet. If a key leaks, revoke it in the portal — revocation takes effect immediately.

Least privilege

Scopes & key limits

Every key carries an explicit scope list. A call without the required scope fails with 403 insufficient_scope — it is never silently allowed.

FieldTypeDescription
balance:readreadWallet balance and account status
sms:readreadPricing quotes and your message log
sms:sendspendSend SMS — debits your wallet
otp:readreadOTP catalogue and activation status
otp:writespendRent and cancel OTP numbers — debits your wallet
funds:readreadCrypto deposit history
funds:topupbillingCreate a crypto invoice (cannot credit balance)

Per key you can also set:

  • Expiry — the key stops working automatically on that date.
  • IP allowlist — requests from any other address are rejected.
  • Daily spend limit — a hard USD ceiling per 24h, enforced before every charge.

Maximum 10 active keys per account. Every authenticated call is logged with IP, status and amount charged, visible in the portal.

Fair use

Rate limits

Limits are per key and enforced both in-process and in the database, so they hold across our whole edge fleet.

FieldTypeDescription
Read endpoints60–120 / minutebalance, quotes, message log, OTP status
SMS send30 / minuteup to 50 recipients per call
OTP request20 / minuterent verification numbers
Funds top-up10 / hourinvoice creation

Exceeding a limit returns 429 rate_limited. Back off and retry — no charge is applied.

Reliability

Idempotency

Send an Idempotency-Key header (any unique string, e.g. a UUID) on SMS send, OTP request/cancel and funds top-up. If the same key hits the same endpoint again, we replay the original response instead of charging twice — ideal for timeouts and network retries.

bash
curl -X POST https://voxiotelecom.com/api/public/v1/sms/send \
  -H "Authorization: Bearer vk_1a2b3c4d.<secret>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f1c2d54-7b2f-4d1a-9f0e-2c1b7d9a4e11" \
  -d '{"to":["12025550123"],"message":"Your code is 1234"}'

Contract

Errors

Every failure returns the same envelope, with a stable machine-readable code. We never leak provider names, SQL or stack traces.

json
{ "error": { "code": "insufficient_funds", "message": "Insufficient balance: $2.10 available, $5.00 required." } }
FieldTypeDescription
400 invalid_requestclientBody or query failed validation
401 unauthorizedclientMissing, malformed, revoked or invalid key
401 key_expiredclientKey passed its expiry date
402 insufficient_fundsclientWallet balance too low for the call
402 billing_failedclientCharge failed; the purchase was rolled back
403 insufficient_scopeclientKey lacks the scope for this endpoint
403 ip_not_allowedclientCaller IP is outside the key allowlist
403 spend_limit_exceededclientDaily spend cap for this key reached
404 not_foundclientResource does not exist or is not yours
409 not_cancellableclientActivation is past the cancellable state
413 payload_too_largeclientBody exceeds 200 KB
415 unsupported_media_typeclientContent-Type is not application/json
429 rate_limitedclientSlow down and retry
503 unavailableserverUpstream provider temporarily unavailable

Endpoints

Balance

GET/balance
scope: balance:read60 / minute

Live wallet balance straight from the billing platform — the same figure your SIP trunk and SMS traffic draw from.

bash
curl -s https://voxiotelecom.com/api/public/v1/balance -H "Authorization: Bearer $VOXIO_KEY"
json
{ "balance": 412.8391, "currency": "USD", "account": { "email": "you@example.com", "status": "active" } }

Endpoints

SMS

GET/sms/quote?number=12025550123
scope: sms:read60 / minute

Price options for a destination before you commit. Pass an option_id to /sms/send as route_option_id to pin a route.

json
{
  "country": "United States",
  "options": [
    { "option_id": "us-direct", "label": "US direct — alpha sender not supported", "price_usd": 0.0091 }
  ]
}
POST/sms/send
scope: sms:send30 / minutesupports Idempotency-Key
FieldTypeDescription
tostring | string[]Up to 50 recipients in E.164 without '+'. Comma or newline separated strings are accepted.
messagestring (1–1600)Message body. Unicode is supported; long messages are billed per segment.
sender_idstring, optionalAlphanumeric sender where the destination allows it.
route_option_idstring, optionalPin a specific route from /sms/quote.
bash
curl -X POST https://voxiotelecom.com/api/public/v1/sms/send \
  -H "Authorization: Bearer $VOXIO_KEY" -H "Content-Type: application/json" \
  -d '{"to":["12025550123","447700900123"],"message":"Your code is 1234","sender_id":"Voxio"}'
json
{
  "sent": 2,
  "failed": 0,
  "charged_usd": 0.0284,
  "results": [
    { "recipient": "12025550123", "status": "sent", "charged_usd": 0.0091, "error": null }
  ]
}

Each recipient is charged individually; a failed recipient is not billed and is reported in results.

GET/sms/messages?page=1&page_size=20
scope: sms:read60 / minute

Paginated delivery log for your account: recipient, status, segments, cost and provider timestamps.

json
{ "messages": [ … ], "total": 184, "page": 1, "page_size": 20, "pages": 10 }

Endpoints

OTP verification numbers

GET/otp/services?service=wa
scope: otp:read60 / minute

Without service you get the full catalogue of services and countries. With it, you get live per-country offers and prices in USD.

json
{ "service": "wa", "offers": [ { "country": 0, "country_name": "Russia", "price_usd": 0.42, "availability": "good" } ] }
POST/otp/request
scope: otp:write20 / minutesupports Idempotency-Key
FieldTypeDescription
servicestringService code from /otp/services, e.g. 'wa', 'tg', 'go'.
countrynumberCountry id from the catalogue.
max_price_usdnumber, optionalAbort instead of renting if the live price is higher.
json
{
  "activation": {
    "id": "0f6f1c0e-4c8b-4b26-b0a2-3b0f1e0b7c11",
    "phone": "79991234567",
    "service_code": "wa",
    "status": "waiting",
    "charged_usd": 0.42,
    "expires_at": "2026-09-01T22:20:00.000Z"
  }
}

Returns 201. If billing fails the number is released instantly and you are not charged.

GET/otp/status?id=<activation uuid>
scope: otp:read120 / minute

Poll every 3–5 seconds until status becomes code_received. Omit id to list your recent activations.

json
{ "activation": { "id": "0f6f…", "status": "code_received", "sms_code": "418902", "sms_text": "418902 is your code" } }
POST/otp/cancel
scope: otp:write30 / minutesupports Idempotency-Key

Cancel a number that never received a code. Eligible activations are refunded to your wallet automatically.

json
{ "id": "0f6f…", "status": "cancelled", "refunded_usd": 0.42 }

Endpoints

Funds & deposits

POST/funds/topup
scope: funds:topup10 / hoursupports Idempotency-Key
FieldTypeDescription
amount_usdnumber (10–5000)Invoice amount in USD.
currencyenumBTC, ETH, USDT, USDT_TRX, USDT_BSC, LTC or TRX.
bash
curl -X POST https://voxiotelecom.com/api/public/v1/funds/topup \
  -H "Authorization: Bearer $VOXIO_KEY" -H "Content-Type: application/json" \
  -d '{"amount_usd":100,"currency":"USDT_TRX"}'
json
{
  "invoice_url": "https://plisio.net/invoice/…",
  "txn_id": "6603…",
  "amount": "100.00",
  "currency": "USDT_TRX",
  "amount_usd": 100,
  "note": "Balance is credited automatically once the payment is confirmed on-chain."
}

This endpoint can never credit a balance. It only asks the payment processor for an invoice; funds post exclusively through the signed payment callback once the blockchain confirms the transaction.

GET/funds/deposits
scope: funds:read60 / minute

Your last 50 crypto deposits with network, transaction hash, confirmations and credit status — use it to reconcile invoices.

json
{ "deposits": [ { "network": "tron", "tx_hash": "9c2…", "amount_usd": 100, "status": "credited", "credited_at": "2026-09-01T18:04:22Z" } ] }

Best practice

Security checklist

Keep keys server-side

Call the API from your backend only. Browser and mobile bundles are readable by anyone.

One key per integration

Separate keys make revocation surgical and the audit log meaningful.

Grant the minimum scopes

A reporting job needs balance:read — not sms:send.

Set a daily spend cap

Caps turn a compromised key into a small, bounded incident.

Pin your server IPs

With an allowlist, a stolen key is useless from anywhere else.

Rotate on staff changes

Create the new key, deploy, then revoke the old one — zero downtime.

Something unclear or missing? Our engineers answer developer tickets directly.

Talk to an engineer