REST over HTTPS. JSON in, JSON out. One endpoint to send SMS to 149 countries. Authenticate with a Bearer API key, POST your message, get a delivery receipt webhook seconds later. Pricing starts at $0.004 per SMS, billed from your crypto-funded balance. No SDK required — curl is enough to ship.

Quickstart — send your first SMS

  1. Sign up at smsroute.cc (email only, 30 seconds, no KYC)
  2. Top up $5 with crypto (USDT TRC-20 confirms in ~1 minute)
  3. Generate an API key at /dashboard/api-keys
  4. Run this curl — your message lands on the handset in ~2 seconds:
curl -X POST https://api.smsroute.cc/v1/messages \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567",
    "from": "smsroute",
    "body": "Hello from smsroute"
  }'

Response:

{
  "id": "msg_01HZX9W3K4PQTR8N5B2M7F6Y0E",
  "status": "accepted",
  "to": "+15551234567",
  "from": "smsroute",
  "body": "Hello from smsroute",
  "segments": 1,
  "price_usd": "0.015",
  "currency": "USD",
  "created_at": "2026-04-21T19:45:12.308Z"
}

Authentication

All API requests use Bearer token authentication. Generate your API key from the dashboard. Pass it in the Authorization header on every request:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx

API keys are scoped by environment:

Keys are shown exactly once on creation — copy them immediately. Leaked production keys should be revoked at /dashboard/api-keys — any outstanding in-flight requests using the revoked key are rejected within 2 seconds.

Endpoints

POST /v1/messages — send a single SMS

FieldTypeRequiredDescription
tostringyesE.164-formatted destination (e.g. +5491123456789)
fromstringyesSender ID (alphanumeric 3-11 chars) or long code
bodystringyesMessage text. Auto-detects GSM-7 vs UCS-2 encoding
status_callbackstringnoHTTPS webhook URL for delivery receipt
validity_periodintegernoSeconds before carrier stops retrying (default 172800 / 48 hrs)
referencestringnoYour internal reference string, returned on webhooks

GET /v1/messages/{id} — retrieve a message

Fetch current status + delivery metadata for a specific message ID. Returns the same shape as the send response, with updated status and (if delivered) delivered_at.

POST /v1/messages/bulk — send up to 1,000 messages in one call

For batch sends (marketing blasts, notification broadcasts, OTP waves). Request body is a JSON array of up to 1000 message objects. Response returns an array of individual message results with per-recipient status. More efficient than looping POST /v1/messages — a single auth + TLS handshake for the whole batch.

GET /v1/balance — query account balance

Returns current balance in USD and an estimated message count at average rate:

{
  "balance_usd": "42.17",
  "currency": "USD",
  "estimated_messages_remaining": 3514,
  "last_topup_at": "2026-04-18T14:22:08Z"
}

GET /v1/pricing — fetch per-country pricing grid

Returns the full 149-country pricing grid. Filter with ?country={ISO2} to get a single country:

GET /v1/pricing?country=AR

{
  "country": "Argentina",
  "iso2": "AR",
  "price_usd": "0.008",
  "currency": "USD",
  "updated_at": "2026-04-21T00:00:00Z"
}

Or GET /v1/pricing/lookup?to=+5491123456789 to resolve the exact price for a specific number (useful before sending to expensive numbers).

Webhooks — delivery receipts

Pass a status_callback URL on send and smsroute POSTs status updates to it. Each callback is a JSON body signed with an HMAC-SHA256 header you can verify:

POST https://your-webhook.example.com/smsroute/dr
Content-Type: application/json
X-SmsRoute-Signature: t=1713724912,v1=abc123...

{
  "message_id": "msg_01HZX9W3K4PQTR8N5B2M7F6Y0E",
  "reference": "order_12345",
  "status": "delivered",
  "to": "+15551234567",
  "from": "smsroute",
  "delivered_at": "2026-04-21T19:45:14.221Z",
  "carrier": "Verizon Wireless",
  "error_code": null,
  "error_message": null,
  "segments": 1,
  "price_usd": "0.015"
}

Verify the signature in your handler (Python example):

import hmac, hashlib, time

SECRET = os.environ["SMSROUTE_WEBHOOK_SECRET"]  # from /dashboard/webhooks

def verify(request_body_raw: bytes, header: str) -> bool:
    parts = dict(p.split("=") for p in header.split(","))
    t = int(parts["t"])
    if abs(time.time() - t) > 300:  # reject if > 5 min skew
        return False
    expected = hmac.new(
        SECRET.encode(),
        f"{t}.".encode() + request_body_raw,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Status values

StatusMeaning
acceptedMessage accepted by smsroute, queued for carrier delivery
sentHanded off to the destination carrier
deliveredConfirmed delivered to the handset (billed)
undeliveredCarrier retry window expired (not billed, balance refunded)
failedCarrier rejected the message (not billed)

Error codes

HTTPCodeMeaning + fix
400invalid_destinationNumber failed E.164 validation. Re-format to +[country][number].
400invalid_sender_idSender ID violates the destination country's rules (too long, disallowed chars, or not registered where registration is required).
400body_too_longBody exceeds segmentation limit (160 GSM-7 chars × 8 segments = 1,280 max; UCS-2 halves these).
401unauthorizedMissing or invalid API key. Check the Authorization: Bearer header.
402insufficient_balanceAccount balance below the destination's price. Top up with crypto.
403forbidden_destinationDestination country is unsupported or temporarily suspended (regulatory freeze).
429rate_limitedExceeded 100 req/s. Check the Retry-After header and implement exponential backoff.
500internal_errorOur problem. Safe to retry with idempotency key within 60s.
503carrier_unreachableDestination carrier is not accepting traffic (usually brief). Safe to retry with backoff.

Client libraries

Pythonpip install smsroute
Node.jsnpm install smsroute
Gogo get github.com/smsroute/smsroute-go
PHPcomposer require smsroute/smsroute
Rubygem install smsroute
Rustcargo add smsroute

Python example

from smsroute import Client

sms = Client(api_key="sk_live_YOUR_API_KEY")
result = sms.messages.create(
    to="+5491123456789",
    from_="smsroute",
    body="Your verification code is 384921",
    status_callback="https://api.example.com/smsroute/dr",
)
print(result.id, result.status)   # msg_01HZ... accepted

Node.js example

import { SmsRoute } from "smsroute";

const sms = new SmsRoute({ apiKey: process.env.SMSROUTE_API_KEY });
const result = await sms.messages.create({
  to: "+5491123456789",
  from: "smsroute",
  body: "Your verification code is 384921",
  statusCallback: "https://api.example.com/smsroute/dr",
});
console.log(result.id, result.status);

Rate limits + scaling

Default limit is 100 requests per second per API key, with a 200-req burst tolerance. Response header X-RateLimit-Remaining tells you how much budget you have in the current second. Hit the limit and you get HTTP 429 with Retry-After: 1.

Higher throughput — 500, 1,000, or 5,000 req/s — is available on request. Email support@smsroute.cc with your projected peak TPS + typical hourly volume, and we provision a lifted account.

For batch sends of more than a few hundred messages, prefer POST /v1/messages/bulk over looping POST /v1/messages — one auth + TLS handshake covers the whole batch and returns per-recipient results in order.

Sandbox mode

Every account gets a sandbox API key (sk_test_*). Sandbox responses are synthetic: they do not consume balance and no SMS is actually delivered. Sandbox is controlled by the destination number pattern:

Idempotency

Pass an Idempotency-Key header (any unique string, typically a UUIDv4) on POST /v1/messages and /v1/messages/bulk. smsroute caches the response by key for 24 hours and returns the cached response on retry:

curl -X POST https://api.smsroute.cc/v1/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: 8f1e0b82-7c59-4a8c-b5e9-2c4f3d9a1e2f" \
  -H "Content-Type: application/json" \
  -d '{"to":"+15551234567","from":"smsroute","body":"Hello"}'

Lets you safely retry on transient network errors without risk of duplicate sends. Idempotency keys are scoped per API key.

API status + changelog

Live API status at status.smsroute.cc. Breaking changes to the API are announced at least 90 days ahead; backward-compatible additions happen quarterly. Current version is v1 — stable, no planned migrations.

Related pages

How do I authenticate requests to the smsroute API?

All requests use Bearer token authentication. Generate an API key in your dashboard at smsroute.cc/dashboard/api-keys and pass it in the Authorization header: Authorization: Bearer YOUR_API_KEY. API keys are per-environment (sandbox and production are separate). Sandbox keys do not consume balance and do not actually deliver SMS — responses are synthetic success/failure payloads for integration testing.

What's the rate limit on the smsroute API?

Default rate limit is 100 requests per second per API key, with burst tolerance of 200 requests in a 1-second window. If you exceed the limit, the API returns HTTP 429 with a Retry-After header. Higher limits are available on request — email support@smsroute.cc with your use case and expected throughput. For batch sends of more than a few hundred messages, use POST /v1/messages/bulk rather than looping POST /v1/messages.

Does smsroute support webhooks for delivery receipts?

Yes. Pass a status_callback field in your send request (HTTPS URL required). smsroute POSTs a JSON body to that URL whenever the message status changes: accepted, sent, delivered, failed, or undelivered. Each callback includes the message ID, status, timestamp, and a carrier-level reason code on failure. Retries follow exponential backoff for 24 hours if your endpoint returns a 5xx; we stop retrying on 4xx responses.

How do I handle idempotency with smsroute?

Pass an Idempotency-Key header (any unique string, typically a UUID) on POST /v1/messages and POST /v1/messages/bulk. If the same key is used within 24 hours, smsroute returns the original response without sending again. This lets you safely retry on network errors without risk of duplicate sends. Idempotency keys are scoped per API key.

Can I get per-country pricing programmatically?

Yes. GET /v1/pricing returns the complete per-country pricing grid as JSON, updated in real time. You can filter by country code (GET /v1/pricing?country=AR) or query a specific number to get the delivery route + price before sending (GET /v1/pricing/lookup?to=+5491123456789). The public pricing page at smsroute.cc/prices shows the same grid in human-readable form.

What happens when a message fails?

If the carrier refuses the message, the webhook callback delivers a failed status with a reason code (e.g. 'handset_blocked', 'invalid_destination', 'spam_filtered', 'carrier_unreachable'). Your balance is not charged for carrier-rejected messages. If delivery times out at the handset (typically 48 hours of operator retry), status becomes undelivered — also refunded. You're only charged for delivered messages.

Does smsroute have client libraries?

Yes — official libraries for Python (smsroute-python on PyPI), Node.js (npm install smsroute), Go (github.com/smsroute/smsroute-go), PHP (Composer: smsroute/smsroute), Ruby (gem install smsroute), and Rust (cargo add smsroute). All are MIT-licensed open source with test suites and GitHub Actions CI. The raw REST API works identically across them — use curl first to prototype, switch to a library when you want idiomatic error types.

How do I migrate from Twilio to smsroute?

The request shape is similar. Twilio uses POST https://api.twilio.com/2010-04-01/Accounts/{SID}/Messages.json with form-encoded fields To, From, Body. smsroute uses POST https://api.smsroute.cc/v1/messages with JSON fields to, from, body. Webhook payload is close to compatible. We maintain a drop-in migration helper at github.com/smsroute/twilio-compat and a written migration guide at smsroute.cc/integrate/twilio-migration covering API mapping, feature parity, and cost comparison.

How do I authenticate requests to the smsroute API?

All requests use Bearer token authentication. Generate an API key in your dashboard at smsroute.cc/dashboard/api-keys and pass it in the Authorization header: Authorization: Bearer YOUR_API_KEY. API keys are per-environment (sandbox and production are separate). Sandbox keys do not consume balance and do not actually deliver SMS — responses are synthetic success/failure payloads for integration testing.

What's the rate limit on the smsroute API?

Default rate limit is 100 requests per second per API key, with burst tolerance of 200 requests in a 1-second window. If you exceed the limit, the API returns HTTP 429 with a Retry-After header. Higher limits are available on request — email support@smsroute.cc with your use case and expected throughput. For batch sends of more than a few hundred messages, use POST /v1/messages/bulk rather than looping POST /v1/messages.

Does smsroute support webhooks for delivery receipts?

Yes. Pass a status_callback field in your send request (HTTPS URL required). smsroute POSTs a JSON body to that URL whenever the message status changes: accepted, sent, delivered, failed, or undelivered. Each callback includes the message ID, status, timestamp, and a carrier-level reason code on failure. Retries follow exponential backoff for 24 hours if your endpoint returns a 5xx; we stop retrying on 4xx responses.

How do I handle idempotency with smsroute?

Pass an Idempotency-Key header (any unique string, typically a UUID) on POST /v1/messages and POST /v1/messages/bulk. If the same key is used within 24 hours, smsroute returns the original response without sending again. This lets you safely retry on network errors without risk of duplicate sends. Idempotency keys are scoped per API key.

Can I get per-country pricing programmatically?

Yes. GET /v1/pricing returns the complete per-country pricing grid as JSON, updated in real time. You can filter by country code (GET /v1/pricing?country=AR) or query a specific number to get the delivery route + price before sending (GET /v1/pricing/lookup?to=+5491123456789). The public pricing page at smsroute.cc/prices shows the same grid in human-readable form.

What happens when a message fails?

If the carrier refuses the message, the webhook callback delivers a failed status with a reason code (e.g. 'handset_blocked', 'invalid_destination', 'spam_filtered', 'carrier_unreachable'). Your balance is not charged for carrier-rejected messages. If delivery times out at the handset (typically 48 hours of operator retry), status becomes undelivered — also refunded. You're only charged for delivered messages.

Does smsroute have client libraries?

Yes — official libraries for Python (smsroute-python on PyPI), Node.js (npm install smsroute), Go (github.com/smsroute/smsroute-go), PHP (Composer: smsroute/smsroute), Ruby (gem install smsroute), and Rust (cargo add smsroute). All are MIT-licensed open source with test suites and GitHub Actions CI. The raw REST API works identically across them — use curl first to prototype, switch to a library when you want idiomatic error types.

How do I migrate from Twilio to smsroute?

The request shape is similar. Twilio uses POST https://api.twilio.com/2010-04-01/Accounts/{SID}/Messages.json with form-encoded fields To, From, Body. smsroute uses POST https://api.smsroute.cc/v1/messages with JSON fields to, from, body. Webhook payload is close to compatible. We maintain a drop-in migration helper at github.com/smsroute/twilio-compat and a written migration guide at smsroute.cc/integrate/twilio-migration covering API mapping, feature parity, and cost comparison.

How do I authenticate requests to the smsroute API?

All requests use Bearer token authentication. Generate an API key in your dashboard at smsroute.cc/dashboard/api-keys and pass it in the Authorization header: Authorization: Bearer YOUR_API_KEY. API keys are per-environment (sandbox and production are separate). Sandbox keys do not consume balance and do not actually deliver SMS — responses are synthetic success/failure payloads for integration testing.

What's the rate limit on the smsroute API?

Default rate limit is 100 requests per second per API key, with burst tolerance of 200 requests in a 1-second window. If you exceed the limit, the API returns HTTP 429 with a Retry-After header. Higher limits are available on request — email support@smsroute.cc with your use case and expected throughput. For batch sends of more than a few hundred messages, use POST /v1/messages/bulk rather than looping POST /v1/messages.

Does smsroute support webhooks for delivery receipts?

Yes. Pass a status_callback field in your send request (HTTPS URL required). smsroute POSTs a JSON body to that URL whenever the message status changes: accepted, sent, delivered, failed, or undelivered. Each callback includes the message ID, status, timestamp, and a carrier-level reason code on failure. Retries follow exponential backoff for 24 hours if your endpoint returns a 5xx; we stop retrying on 4xx responses.

How do I handle idempotency with smsroute?

Pass an Idempotency-Key header (any unique string, typically a UUID) on POST /v1/messages and POST /v1/messages/bulk. If the same key is used within 24 hours, smsroute returns the original response without sending again. This lets you safely retry on network errors without risk of duplicate sends. Idempotency keys are scoped per API key.

Can I get per-country pricing programmatically?

Yes. GET /v1/pricing returns the complete per-country pricing grid as JSON, updated in real time. You can filter by country code (GET /v1/pricing?country=AR) or query a specific number to get the delivery route + price before sending (GET /v1/pricing/lookup?to=+5491123456789). The public pricing page at smsroute.cc/prices shows the same grid in human-readable form.

What happens when a message fails?

If the carrier refuses the message, the webhook callback delivers a failed status with a reason code (e.g. 'handset_blocked', 'invalid_destination', 'spam_filtered', 'carrier_unreachable'). Your balance is not charged for carrier-rejected messages. If delivery times out at the handset (typically 48 hours of operator retry), status becomes undelivered — also refunded. You're only charged for delivered messages.

Does smsroute have client libraries?

Yes — official libraries for Python (smsroute-python on PyPI), Node.js (npm install smsroute), Go (github.com/smsroute/smsroute-go), PHP (Composer: smsroute/smsroute), Ruby (gem install smsroute), and Rust (cargo add smsroute). All are MIT-licensed open source with test suites and GitHub Actions CI. The raw REST API works identically across them — use curl first to prototype, switch to a library when you want idiomatic error types.

How do I migrate from Twilio to smsroute?

The request shape is similar. Twilio uses POST https://api.twilio.com/2010-04-01/Accounts/{SID}/Messages.json with form-encoded fields To, From, Body. smsroute uses POST https://api.smsroute.cc/v1/messages with JSON fields to, from, body. Webhook payload is close to compatible. We maintain a drop-in migration helper at github.com/smsroute/twilio-compat and a written migration guide at smsroute.cc/integrate/twilio-migration covering API mapping, feature parity, and cost comparison.

How do I authenticate requests to the smsroute API?

All requests use Bearer token authentication. Generate an API key in your dashboard at smsroute.cc/dashboard/api-keys and pass it in the Authorization header: Authorization: Bearer YOUR_API_KEY. API keys are per-environment (sandbox and production are separate). Sandbox keys do not consume balance and do not actually deliver SMS — responses are synthetic success/failure payloads for integration testing.

What's the rate limit on the smsroute API?

Default rate limit is 100 requests per second per API key, with burst tolerance of 200 requests in a 1-second window. If you exceed the limit, the API returns HTTP 429 with a Retry-After header. Higher limits are available on request — email support@smsroute.cc with your use case and expected throughput. For batch sends of more than a few hundred messages, use POST /v1/messages/bulk rather than looping POST /v1/messages.

Does smsroute support webhooks for delivery receipts?

Yes. Pass a status_callback field in your send request (HTTPS URL required). smsroute POSTs a JSON body to that URL whenever the message status changes: accepted, sent, delivered, failed, or undelivered. Each callback includes the message ID, status, timestamp, and a carrier-level reason code on failure. Retries follow exponential backoff for 24 hours if your endpoint returns a 5xx; we stop retrying on 4xx responses.

How do I handle idempotency with smsroute?

Pass an Idempotency-Key header (any unique string, typically a UUID) on POST /v1/messages and POST /v1/messages/bulk. If the same key is used within 24 hours, smsroute returns the original response without sending again. This lets you safely retry on network errors without risk of duplicate sends. Idempotency keys are scoped per API key.

Can I get per-country pricing programmatically?

Yes. GET /v1/pricing returns the complete per-country pricing grid as JSON, updated in real time. You can filter by country code (GET /v1/pricing?country=AR) or query a specific number to get the delivery route + price before sending (GET /v1/pricing/lookup?to=+5491123456789). The public pricing page at smsroute.cc/prices shows the same grid in human-readable form.

What happens when a message fails?

If the carrier refuses the message, the webhook callback delivers a failed status with a reason code (e.g. 'handset_blocked', 'invalid_destination', 'spam_filtered', 'carrier_unreachable'). Your balance is not charged for carrier-rejected messages. If delivery times out at the handset (typically 48 hours of operator retry), status becomes undelivered — also refunded. You're only charged for delivered messages.

Does smsroute have client libraries?

Yes — official libraries for Python (smsroute-python on PyPI), Node.js (npm install smsroute), Go (github.com/smsroute/smsroute-go), PHP (Composer: smsroute/smsroute), Ruby (gem install smsroute), and Rust (cargo add smsroute). All are MIT-licensed open source with test suites and GitHub Actions CI. The raw REST API works identically across them — use curl first to prototype, switch to a library when you want idiomatic error types.

How do I migrate from Twilio to smsroute?

The request shape is similar. Twilio uses POST https://api.twilio.com/2010-04-01/Accounts/{SID}/Messages.json with form-encoded fields To, From, Body. smsroute uses POST https://api.smsroute.cc/v1/messages with JSON fields to, from, body. Webhook payload is close to compatible. We maintain a drop-in migration helper at github.com/smsroute/twilio-compat and a written migration guide at smsroute.cc/integrate/twilio-migration covering API mapping, feature parity, and cost comparison.