• 228 destinations
  • No KYC, ever
  • Bitcoin, Monero, USDT and 4 more

API reference

Six endpoints.
Every field documented.

Version 1 of the REST API. JSON in, JSON out, bearer keys, idempotent writes. The OpenAPI description of this page is downloadable on the SDKs page.

  • Base URL https://api.smsmeteor.com/v1
  • 50 requests per second per key
  • OpenAPI 3.1 available

Conventions

Requests accept application/json or form-encoded bodies; responses are always JSON. Timestamps are ISO 8601 in UTC. Money is a decimal string in USD with four places. Phone numbers are E.164 with the leading plus. Identifiers are prefixed: msg_, bat_, evt_, cmp_. Every response carries X-Request-Id; quote it when you write to support.

Authentication

Header
Authorization: Bearer sk_live_9f2c...a71e

Scopes: send (create messages), read (everything GET). A key may carry an IP allowlist; requests from other addresses receive 401 unauthorized. Test keys (sk_test_) behave identically but never send and never bill.

Idempotency

Any POST accepts Idempotency-Key, a string of up to 128 characters chosen by you. Same key and same payload within 24 hours: the original response is returned, with the header Idempotent-Replayed: true. Same key, different payload: 409 idempotency_conflict.

Create a message

POST/v1/messages

ParameterTypeDescription
torequiredstringDestination in E.164, for example +14155550142. Unsupported prefixes are rejected before sending.
textrequiredstringMessage body. GSM-7 or Unicode is detected automatically; up to 6 parts per message.
fromstringSender: alphanumeric (3 to 11 characters, A-Z, 0-9, space) or a rented number. Omit to let the platform pick per destination. Replaced automatically where the route requires it.
send_atdatetimeSchedule for a fixed moment in UTC. Mutually exclusive with window.
windowobject{ local_start, local_end, quiet_hours }. Delivers inside the recipient's local window; quiet_hours is default, off or bypass for transactional traffic.
validityintegerMinutes before an undelivered message expires. Default 2880 (48 hours), maximum 4320.
campaignstringFree tag, up to 64 characters, to group messages in reports and webhooks.
referencestringYour own identifier, up to 128 characters, echoed in responses and webhooks.
webhook_urlstringOverride the account endpoints for this message only. Must be HTTPS.
POST /v1/messages
Authorization: Bearer sk_live_...
Idempotency-Key: order-4821-ship
Content-Type: application/json

{
  "to": "+14155550142",
  "from": "METEOR",
  "text": "Your order #4821 has shipped. Track it: smsmtr.io/t/8fK2",
  "campaign": "shipping",
  "reference": "order-4821"
}
{
  "id": "msg_9Kd2fQ",
  "status": "queued",
  "to": "+14155550142",
  "from": "METEOR",
  "text": "Your order #4821 has shipped. Track it: smsmtr.io/t/8fK2",
  "segments": 1,
  "encoding": "gsm7",
  "price": "0.0084",
  "currency": "USD",
  "country": "us",
  "campaign": "shipping",
  "reference": "order-4821",
  "created_at": "2026-09-21T11:42:07Z",
  "sent_at": null,
  "delivered_at": null,
  "carrier_code": null
}

Create a batch

POST/v1/messages/batch

Up to 1,000 messages per API request, each with its own to, text and optional from, reference. Top-level from, campaign, send_at, window and pace apply to every message that does not override them. The whole batch is accepted or rejected atomically on syntax; individual messages can still be rejected for destination or content and are listed with their reason.

ParameterTypeDescription
messagesrequiredarrayList of message objects, 1 to 1,000 items.
from, campaign, send_at, windowmixedDefaults applied to each message; see Create a message.
paceobject{ per_minute } or { over_hours }: drip the batch at a fixed rate or spread it over N hours. Combines with window.
Response 201
{
  "batch_id": "bat_3Hq9Zr",
  "accepted": 2, "rejected": 1,
  "total_price": "0.0214", "currency": "USD",
  "messages": [
    { "id": "msg_9Kd2fQ", "to": "+14155550142", "status": "queued", "price": "0.0084" },
    { "id": "msg_9Kd2fR", "to": "+447700900123", "status": "queued", "price": "0.0130" },
    { "to": "+99912345", "status": "rejected", "error": { "code": "unsupported_destination" } }
  ]
}

Scheduling parameters

Three ways to time a send, on single messages and batches. They combine: a window with a pace is the usual setting for marketing.

ParameterTypeDescription
send_atdatetimeISO 8601 in UTC. Everyone receives at that instant regardless of local time.
window.local_starttimeHH:MM in the recipient's local time. The campaign opens in each zone when its clock reaches it.
window.local_endtimeHH:MM. Messages not sent by then wait for the next day's window.
window.quiet_hoursstringdefault applies the destination's quiet hours, off disables them, bypass marks the traffic transactional.
pace.per_minuteintegerMaximum messages per minute for the batch.
pace.over_hoursnumberSpread the batch evenly over this many hours instead.

Retrieve a message

GET/v1/messages/{id}

Returns the Message object with its current status, timestamps, price and carrier code when a failure has one.

List messages

GET/v1/messages

ParameterTypeDescription
statusstringOne of the six statuses.
countrystringISO 3166-1 alpha-2, lower case.
campaignstringCampaign tag.
from_date, to_datedatetimeCreation time bounds, inclusive, UTC.
limitinteger1 to 200, default 50.
cursorstringOpaque cursor from the previous page's next_cursor.
Response 200
{ "data": [ { "id": "msg_9Kd2fQ", "status": "delivered", "...": "..." } ],
  "next_cursor": "eyJpZCI6Im1zZ185S2QyZlEifQ", "has_more": true }

Rates

GET/v1/ratesandGET/v1/rates/{iso}

The public rate card, identical to the pricing page. Each entry has the destination, dial code, price per segment, sender-ID rule and quiet-hours window. Cacheable for an hour; rates change at most once a day. No key required.

Response 200GET /v1/rates/gb
{ "country": "gb", "name": "United Kingdom", "dial": "44",
  "price": "0.0130", "currency": "USD",
  "sender": "alphanumeric", "quiet_hours": { "start": "08:00", "end": "21:00" },
  "updated_at": "2026-09-21" }

Balance

GET/v1/balance

Response 200
{ "balance": "1240.60", "currency": "USD", "pending": "0.00",
  "low_threshold": "100.00", "updated_at": "2026-09-21T11:40:00Z" }

The Message object

ParameterTypeDescription
idstringUnique identifier, msg_ prefix.
statusstringqueued, sent, delivered, failed, expired, rejected, or test for test keys.
to, from, textstringAs sent. from reflects the sender actually used after any replacement.
segments, encodinginteger, stringSegment count and gsm7 or ucs2.
price, currencystringTotal price for all segments, USD.
countrystringDestination ISO code resolved from the number.
campaign, referencestringYour tags, echoed back.
created_at, sent_at, delivered_atdatetimeLifecycle timestamps; null until reached.
carrier_code, failure_reasonstringRaw carrier code and its plain-English reason on failures.

Statuses

  1. queuedAccepted and priced, waiting for a route slot.
  2. sentHanded to the carrier. Billing point.
  3. deliveredHandset confirmed receipt. Final.
  4. failedCarrier could not deliver; carrier_code set. Final.
  5. expiredValidity elapsed without delivery. Final.
  6. rejectedRefused before sending. Never billed. Final.

Errors

402 Payment Required
{ "error": { "code": "insufficient_balance", "message": "Balance 0.42 USD is below the message price 0.85 USD", "field": null } }
HTTPCodeMeaning
400invalid_requestMalformed JSON or missing required field; field names it.
400invalid_numberto is not a valid E.164 number.
400invalid_senderfrom is not a valid alphanumeric sender or a number you rent.
400text_too_longMore than 6 parts per message.
401unauthorizedMissing, revoked, wrongly scoped or IP-restricted key.
402insufficient_balanceThe message would take the balance below zero.
404not_foundUnknown message, batch or country.
409idempotency_conflictKey reused with a different payload.
422unsupported_destinationNo route for that prefix.
422sender_not_allowedAlphanumeric sender not permitted and replacement disabled.
422content_blockedText matches the acceptable use blocklist (phishing patterns, impersonation).
429rate_limitedOver 50 requests per second per key. Honour Retry-After.
5xxinternalOur side; nothing was sent. Retry with the same idempotency key.

Rate limits

50 requests per second per key, measured on a sliding window. Batch requests count as one request each. Responses include X-RateLimit-Limit, X-RateLimit-Remaining and, on 429, Retry-After in seconds.

Changelog