Get Started
How it works Features Pricing
SMS API
Cheapest SMS API Cheap SMS gateway Free SMS API Long SMS API Compare Integrations Blog FAQ Google Play Sign In Get Started

SMS API Documentation

MySMSGate turns an Android phone into your own SMS gateway. Messages are sent over the REST API below and delivered by the SIM card in your phone, so there is no per-country carrier pricing and no per-message charge at all — you pay $10.00/month for the first phone and $5.00/month for each additional one, whatever you send. A multi-part message up to 7 segments (1071 GSM-7 characters / 469 Unicode characters) counts as one message.

Examples below use YOUR_API_KEY as a placeholder. Create a free account to get a real key — the same page then shows these examples with your key already filled in.
Authentication

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_KEY

POST /api/v1/send

Send an SMS message

Request
curl -X POST https://mysmsgate.net/api/v1/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to": "+79001234567", "message": "Hello!", "slot": 0}'
import requests resp = requests.post( "https://mysmsgate.net/api/v1/send", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={ "to": "+79001234567", "message": "Hello!", "device_id": "abc-123-...", # optional "slot": 0 # optional } ) print(resp.json()) # {"success": true, "sms_id": 258, "status": "pending"}
const resp = await fetch("https://mysmsgate.net/api/v1/send", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ to: "+79001234567", message: "Hello!", device_id: "abc-123-...", // optional slot: 0 // optional }) }); const data = await resp.json(); // {success: true, sms_id: 258, status: "pending"}
$ch = curl_init("https://mysmsgate.net/api/v1/send"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer YOUR_API_KEY", "Content-Type: application/json" ], CURLOPT_POSTFIELDS => json_encode([ "to" => "+79001234567", "message" => "Hello!", "device_id" => "abc-123-...", // optional "slot" => 0 // optional ]) ]); $response = json_decode(curl_exec($ch), true); // ["success" => true, "sms_id" => 258, "status" => "pending"]
payload := map[string]interface{}{ "to": "+79001234567", "message": "Hello!", "slot": 0, // optional } body, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", "https://mysmsgate.net/api/v1/send", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) // HTTP 202: {"success": true, "sms_id": 258, "status": "pending"}
Parameters
FieldTypeRequiredDescription
tostringYesPhone number (e.g. +79001234567)
messagestringYesSMS text
device_idstringNoDevice UUID. If not specified, uses first device.
slotintNoSIM slot: 0 = SIM 1, 1 = SIM 2. If not specified, uses device default.
Response (HTTP 202 Accepted)
{ "success": true, "sms_id": 258, "status": "pending" }

SMS is queued and delivered to the phone in batches according to the device SMS/min rate limit. If the phone is offline, a push notification is sent to wake it up.

GET /api/v1/devices

List your registered devices

curl https://mysmsgate.net/api/v1/devices \ -H "Authorization: Bearer YOUR_API_KEY"
resp = requests.get( "https://mysmsgate.net/api/v1/devices", headers={"Authorization": "Bearer YOUR_API_KEY"} ) print(resp.json())
const resp = await fetch("https://mysmsgate.net/api/v1/devices", { headers: { "Authorization": "Bearer YOUR_API_KEY" } }); const data = await resp.json();
$ch = curl_init("https://mysmsgate.net/api/v1/devices"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY"] ]); $response = json_decode(curl_exec($ch), true);
req, _ := http.NewRequest("GET", "https://mysmsgate.net/api/v1/devices", nil) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req)
Response
{ "devices": [ { "id": "abc-123-...", "name": "My Phone", "status": "standby", "battery_level": 85, "default_sim_slot": 0 } ] }

GET /api/v1/status

Check phone connection status (all devices)

curl https://mysmsgate.net/api/v1/status \ -H "Authorization: Bearer YOUR_API_KEY"
resp = requests.get( "https://mysmsgate.net/api/v1/status", headers={"Authorization": "Bearer YOUR_API_KEY"} ) print(resp.json())
const resp = await fetch("https://mysmsgate.net/api/v1/status", { headers: { "Authorization": "Bearer YOUR_API_KEY" } }); const data = await resp.json();
$ch = curl_init("https://mysmsgate.net/api/v1/status"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY"] ]); $response = json_decode(curl_exec($ch), true);
req, _ := http.NewRequest("GET", "https://mysmsgate.net/api/v1/status", nil) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req)
Response
{ "connected": true, "devices": [ { "id": "abc-123-...", "name": "My Phone", "status": "online", "battery": 85 } ] }

GET /api/v1/balance

Delivery counters, plus any prepaid SMS left on the account (0 on a subscription)

curl https://mysmsgate.net/api/v1/balance \ -H "Authorization: Bearer YOUR_API_KEY"
resp = requests.get( "https://mysmsgate.net/api/v1/balance", headers={"Authorization": "Bearer YOUR_API_KEY"} ) print(resp.json()) # {"balance": 95, "sent": 5, "failed": 0}
const resp = await fetch("https://mysmsgate.net/api/v1/balance", { headers: { "Authorization": "Bearer YOUR_API_KEY" } }); const data = await resp.json(); // {balance: 95, sent: 5, failed: 0}
$ch = curl_init("https://mysmsgate.net/api/v1/balance"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY"] ]); $response = json_decode(curl_exec($ch), true); // ["balance" => 95, "sent" => 5, "failed" => 0]
req, _ := http.NewRequest("GET", "https://mysmsgate.net/api/v1/balance", nil) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req) // {"balance": 95, "sent": 5, "failed": 0}
Response
{ "balance": 95, "sent": 5, "failed": 0 }

GET /api/v1/sms?id={sms_id}

Check the status of a specific SMS

curl "https://mysmsgate.net/api/v1/sms?id=258" \ -H "Authorization: Bearer YOUR_API_KEY"
resp = requests.get( "https://mysmsgate.net/api/v1/sms", params={"id": 258}, headers={"Authorization": "Bearer YOUR_API_KEY"} ) print(resp.json())
const resp = await fetch("https://mysmsgate.net/api/v1/sms?id=258", { headers: { "Authorization": "Bearer YOUR_API_KEY" } }); const data = await resp.json();
$ch = curl_init("https://mysmsgate.net/api/v1/sms?id=258"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY"] ]); $response = json_decode(curl_exec($ch), true);
req, _ := http.NewRequest("GET", "https://mysmsgate.net/api/v1/sms?id=258", nil) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req)
Response
{ "sms_id": 258, "to": "+79001234567", "status": "sent", "created_at": "2026-03-03T11:05:23Z", "sent_at": "2026-03-03T11:10:55Z" }
SMS Statuses
StatusDescription
pendingQueued, waiting for phone to pick up
sendingClaimed by phone, sending in progress
sentSuccessfully delivered
failedFailed to send (error field contains details)

GET /api/v1/history

Get SMS sending history

curl "https://mysmsgate.net/api/v1/history?limit=10&offset=0" \ -H "Authorization: Bearer YOUR_API_KEY"
resp = requests.get( "https://mysmsgate.net/api/v1/history", params={"limit": 10, "offset": 0}, headers={"Authorization": "Bearer YOUR_API_KEY"} ) print(resp.json())
const resp = await fetch("https://mysmsgate.net/api/v1/history?limit=10&offset=0", { headers: { "Authorization": "Bearer YOUR_API_KEY" } }); const data = await resp.json();
$ch = curl_init("https://mysmsgate.net/api/v1/history?limit=10&offset=0"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY"] ]); $response = json_decode(curl_exec($ch), true);
req, _ := http.NewRequest("GET", "https://mysmsgate.net/api/v1/history?limit=10&offset=0", nil) req.Header.Set("Authorization", "Bearer YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req)
Parameters
FieldTypeDefaultDescription
limitint50Number of records (max 100)
offsetint0Offset for pagination
directionstringin for received messages, out for sent ones. Omit for both. Any other value returns 400.
statusstringpending, sending, sent or failed. Any other value returns 400.
since_idintReturn only messages newer than this id, oldest first. Use it to poll for new messages instead of paging with offset.
Polling for new messages

To watch for incoming SMS, poll with direction=in and the since_id you got back last time. Start with since_id=0 to read from the beginning. Messages come oldest first, and the response carries next_since_id — store it and pass it on the next call. An empty page returns the same next_since_id you sent, so a quiet minute never rewinds your cursor.

curl "https://mysmsgate.net/api/v1/history?direction=in&since_id=0&limit=50" \ -H "Authorization: Bearer YOUR_API_KEY" # -> {"history": [...], "next_since_id": 312, ...} # next call: ?direction=in&since_id=312

Do not page through new messages with offset: new rows land at the top of the default ordering, so the window shifts under you and you would both skip and repeat messages. since_id is a cursor and does not move.

Response
{ "total": 42, "limit": 10, "offset": 0, "history": [ { "id": 258, "phone_from": "+79371802075", "phone_to": "+79001234567", "message": "Hello!", "status": "sent", "created_at": "2026-03-03T11:05:23Z", "sent_at": "2026-03-03T11:10:55Z" } ] }

Webhooks

Get an HTTP POST the moment something happens, instead of polling /api/v1/history.

Events
EventFires when
message.receivedOne of your phones receives an SMS
message.statusAn outgoing message reaches a final state: sent or failed

Importing a phone's existing messages (/api/v1/inbox/sync) does not fire message.received — otherwise connecting a phone for the first time would replay years of old texts into your endpoint.

Register an endpoint
curl -X POST https://mysmsgate.net/api/v1/webhooks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/sms-hook","events":["message.received","message.status"]}'
{ "webhook": { "id": "0f3c...", "url": "https://example.com/sms-hook", "secret": "whsec_...", "events": ["message.received", "message.status"], "format": "json", "is_active": true, "created_at": "2026-09-08T16:20:00Z" }, "note": "Store the secret now: it is shown only in this response." }

The signing secret appears in this response and never again — listings omit it. Lost it? Delete the webhook and register it again. GET /api/v1/webhooks lists your endpoints, DELETE /api/v1/webhooks?id=... removes one. Up to 5 per account.

What we send
POST /sms-hook Content-Type: application/json X-Mysmsgate-Event: message.received X-Mysmsgate-Delivery: 4711 X-Mysmsgate-Signature: t=1788884443,v1=9807973513a1... { "event": "message.received", "sms_id": 258, "device_id": "1a83c5e6-...", "direction": "in", "phone_from": "+14155551234", "phone_to": "", "message": "Hello!", "sim_slot": 0, "created_at": "2026-09-08T16:20:38Z" }

phone_to is usually empty on received messages, and that is not a fault: Android cannot read the SIM's own number on most carriers, so the device — device_id — is the addressable endpoint here, not a number. A message.status event carries status and, when it failed, error.

Verifying the signature

HMAC-SHA256 over <timestamp>.<raw body> with your webhook secret. The timestamp is inside the signed string, so a captured request cannot be replayed later — reject anything older than five minutes. HTTP header names are case-insensitive: read the header through your framework’s accessor (request.headers[...]) rather than a raw dictionary lookup, and it will not matter how the casing reaches you.

import hmac, hashlib, time def verify(secret, header, raw_body, tolerance=300): parts = dict(p.split("=", 1) for p in header.split(",")) ts, sig = parts["t"], parts["v1"] if abs(time.time() - int(ts)) > tolerance: return False expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, sig)

Sign the raw body, before any JSON parsing — re-serialising it changes the bytes and the signature will not match.

Delivery, retries and failure
  • Any 2xx counts as delivered. Answer quickly and do the work afterwards — we wait 15 seconds.
  • Anything else is retried 5 times over about two and a half hours (30s, 2m, 10m, 30m, 2h).
  • 410 Gone is taken at face value: no retries, we treat the endpoint as retired.
  • After 5 deliveries fail their whole retry schedule in a row, the webhook is switched off and we email you. Events are not replayed afterwards — catch up with GET /api/v1/history?direction=in&since_id=....
  • Redirects are not followed, and the endpoint must be reachable on the public internet: private, loopback and link-local addresses are refused, both when you register and on every delivery.
  • The same event is delivered once. A message that fails and is then accepted late produces two message.status events — the correction is not a duplicate.

Already using Twilio? Change one line

We answer Twilio’s own REST shape at /2010-04-01/Accounts/{AccountSid}/Messages, with Twilio’s parameter names, response fields and error bodies. Point the official SDK at us and the rest of your code is untouched.

from twilio.rest import Client client = Client(ACCOUNT_SID, AUTH_TOKEN) client.api.base_url = "https://mysmsgate.net" # the only line you add msg = client.messages.create(to="+14155551234", body="Hello") print(msg.sid, msg.status)
Your credentials
Twilio fieldWhat to put there
Account SIDACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Auth Tokenyour API key, above — with or without the dashes
Fromthe id of the phone that should send, from GET /api/v1/devices. Omit it if the account has one phone. The SIM’s own number works too when the phone could read it — most cannot.
curl
curl -X POST https://mysmsgate.net/2010-04-01/Accounts/ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Messages.json \ -u "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:YOUR_API_KEY" \ --data-urlencode "To=+14155551234" \ --data-urlencode "Body=Hello"
What is different, and we would rather say it plainly
  • There is no delivered status. Twilio learns delivery from carrier receipts; your messages leave your own SIM and nobody reports back. You will see queued, then sent (the phone handed it to the network) or failed. We will not print delivered just to look familiar.
  • From is a phone, not a number. Replies come back to that handset and appear in your account, so the sender your recipient sees is your own SIM — not a number we rent you.
  • price is always null. There is no per-message price to report: you pay per connected phone.
  • Unsupported parameters are rejected, not ignored. MediaUrl (no MMS) and StatusCallback (use the account-level webhook above instead) return a 400 that says so. Silently dropping them would look like a working migration and behave like a broken one.
Endpoints
MethodPathDoes
POST/2010-04-01/Accounts/{AccountSid}/Messages.jsonsend — 201 with the message resource
GET/2010-04-01/Accounts/{AccountSid}/Messages.jsonlist — To, From, PageSize, Page
GET/2010-04-01/Accounts/{AccountSid}/Messages/{MessageSid}.jsonfetch one

Errors come back in Twilio’s shape (code, message, more_info, status), so your existing TwilioRestException handling keeps working.

Error Codes

CodeDescription
200Success
202SMS accepted and queued for delivery
400Bad request (missing fields, invalid JSON, no device)
401Unauthorized (invalid or missing API key)
402Insufficient SMS balance
404Resource not found (e.g. SMS ID not found)
429Rate limit exceeded (max 10 req/s per IP)
500Internal server error

Start sending in three minutes

Install the Android app, scan the pairing code, send your first SMS from the API. No card required.

Get your API key
📲

Wait — try it free for 14 days

Turn the Android phone in your pocket into an SMS gateway. Full access for 14 days; your card is only charged when the trial ends.

Start the free trial