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
- Sign up at smsroute.cc (email only, 30 seconds, no KYC)
- Top up $5 with crypto (USDT TRC-20 confirms in ~1 minute)
- Generate an API key at
/dashboard/api-keys - 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:
sk_live_*— production keys. Consume balance. Actually deliver SMS.sk_test_*— sandbox keys. Do not consume balance. Return synthetic success / failure / timeout responses for integration testing.
/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
| Field | Type | Required | Description |
|---|---|---|---|
to | string | yes | E.164-formatted destination (e.g. +5491123456789) |
from | string | yes | Sender ID (alphanumeric 3-11 chars) or long code |
body | string | yes | Message text. Auto-detects GSM-7 vs UCS-2 encoding |
status_callback | string | no | HTTPS webhook URL for delivery receipt |
validity_period | integer | no | Seconds before carrier stops retrying (default 172800 / 48 hrs) |
reference | string | no | Your 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
| Status | Meaning |
|---|---|
accepted | Message accepted by smsroute, queued for carrier delivery |
sent | Handed off to the destination carrier |
delivered | Confirmed delivered to the handset (billed) |
undelivered | Carrier retry window expired (not billed, balance refunded) |
failed | Carrier rejected the message (not billed) |
Error codes
| HTTP | Code | Meaning + fix |
|---|---|---|
| 400 | invalid_destination | Number failed E.164 validation. Re-format to +[country][number]. |
| 400 | invalid_sender_id | Sender ID violates the destination country's rules (too long, disallowed chars, or not registered where registration is required). |
| 400 | body_too_long | Body exceeds segmentation limit (160 GSM-7 chars × 8 segments = 1,280 max; UCS-2 halves these). |
| 401 | unauthorized | Missing or invalid API key. Check the Authorization: Bearer header. |
| 402 | insufficient_balance | Account balance below the destination's price. Top up with crypto. |
| 403 | forbidden_destination | Destination country is unsupported or temporarily suspended (regulatory freeze). |
| 429 | rate_limited | Exceeded 100 req/s. Check the Retry-After header and implement exponential backoff. |
| 500 | internal_error | Our problem. Safe to retry with idempotency key within 60s. |
| 503 | carrier_unreachable | Destination carrier is not accepting traffic (usually brief). Safe to retry with backoff. |
Client libraries
pip install smsroutenpm install smsroutego get github.com/smsroute/smsroute-gocomposer require smsroute/smsroutegem install smsroutecargo add smsroutePython 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:
+15005550001→ returnsaccepted, thendeliveredvia webhook 2s later+15005550002→ returnsaccepted, thenfailedwithinvalid_destination+15005550003→ returnsaccepted, thenundeliveredafter simulated 48-hour timeout (webhook fires at +30s in sandbox)+15005550004→ returns HTTP 429rate_limited+15005550005→ returns HTTP 500internal_error
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.