Android Texter turns an Android phone into an SMS gateway. Messages you send through this API are delivered by the SIM in a phone you have linked to your account, from your own number, and replies are synced back and can be pushed to a webhook. There are no per-message carrier fees and no shared short codes.
curl -X POST "https://androidtexter.com/api/messages/send" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+14155551234",
"message": "Hello from the API!"
}'Optional. Pasting a key rewrites every sample on this page to use it and enables the live Try it buttons on read-only endpoints. It is kept in this tab only, is never sent anywhere except https://androidtexter.com, and is forgotten when you close the tab.
Every request is authenticated with an API key in the X-API-Key header. Dashboard-only endpoints instead take a JWT in Authorization: Bearer <token>.
X-API-Key: atx_your_key_hereIdentify the account a request belongs to. Send an API key in the X-API-Key header, or a JWT bearer token for dashboard session endpoints.
/api/meReturns the authenticated user's profile and subscription info. Useful as a credential check.
curl -X GET "https://androidtexter.com/api/me" \
-H "X-API-Key: YOUR_API_KEY"Example response
{
"user": { "id": "...", "name": "Phil", "email": "[email protected]" },
"subscription": { "plan": "free", "status": "active" }
}Send and read SMS through your connected Android phones.
/api/messages/sendSend an SMS to a phone number. Optionally choose which linked device sends it, and a future time to deliver it. A number without a country code is read in the country of the SIM sending it; pass country (ISO 3166-1 alpha-2) to read it in another country instead.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| phone | body | yes | +14155551234 |
| message | body | yes | Hello from the API! |
| device_id | body | no | device UUID |
| scheduled_at | body | no | 2026-03-12T09:00:00Z |
| country | body | no | GB |
curl -X POST "https://androidtexter.com/api/messages/send" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+14155551234",
"message": "Hello from the API!"
}'Example response
{
"status": 200,
"message": "Message queued for sending on Pixel 7",
"message_id": "uuid",
"device_id": "uuid",
"device_name": "Pixel 7"
}/api/messages/scheduledList pending scheduled messages that have not been sent yet.
curl -X GET "https://androidtexter.com/api/messages/scheduled" \
-H "X-API-Key: YOUR_API_KEY"Example response
{
"scheduled_messages": [
{
"id": "uuid",
"phone": "+14155551234",
"body": "Reminder: appointment tomorrow!",
"scheduled_at": "2026-03-12T09:00:00Z",
"scheduler_status": "pending",
"device_id": "uuid"
}
]
}/api/messages/scheduled/:idCancel a pending scheduled message before it is sent.
curl -X DELETE "https://androidtexter.com/api/messages/scheduled/:id" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "message": "Scheduled message cancelled" }/api/messagesList all messages, inbound and outbound, newest first.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| page | query | no | 1 |
| limit | query | no | 50 |
curl -X GET "https://androidtexter.com/api/messages?page=1&limit=50" \
-H "X-API-Key: YOUR_API_KEY"Example response
{
"messages": [
{ "id": "...", "phone": "+1...", "body": "Hello", "direction": "inbound", "created_at": "..." }
]
}/api/messages/sentList outbound messages only.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| page | query | no | 1 |
| limit | query | no | 50 |
curl -X GET "https://androidtexter.com/api/messages/sent?page=1&limit=50" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "messages": [] }/api/messages/receivedList inbound messages only.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| page | query | no | 1 |
| limit | query | no | 50 |
curl -X GET "https://androidtexter.com/api/messages/received?page=1&limit=50" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "messages": [] }/api/v1/messages/history/:phoneFetch paginated historical messages scoped to a single contact phone number.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| page | query | no | 1 |
| limit | query | no | 50 |
curl -X GET "https://androidtexter.com/api/v1/messages/history/:phone?page=1&limit=50" \
-H "X-API-Key: YOUR_API_KEY"Example response
{
"messages": [
{ "id": "...", "phone": "+1...", "body": "Hello", "direction": "inbound", "created_at": "..." }
],
"phone": "+1...",
"page": 1,
"limit": 50
}/api/v1/messages/status/:idLook up the delivery state of one message by its id, as returned by Send Message.
curl -X GET "https://androidtexter.com/api/v1/messages/status/:id" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "id": "uuid", "status": "sent", "sent_at": "..." }/api/messages/conversationsA grouped list of conversations with the latest message from each phone number.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| limit | query | no | 50 |
curl -X GET "https://androidtexter.com/api/messages/conversations?limit=50" \
-H "X-API-Key: YOUR_API_KEY"Example response
{
"conversations": [
{ "phone": "+1...", "last_body": "Hey!", "last_at": "...", "unread": 2 }
]
}The Android phones linked to your account. A phone must be online to send.
/api/devicesReturns every device linked to your account with its online status. Use a device id to pin a send to one phone.
curl -X GET "https://androidtexter.com/api/devices" \
-H "X-API-Key: YOUR_API_KEY"Example response
{
"devices": [
{
"id": "uuid",
"name": "Pixel 7",
"phone_number": "+14155551234",
"online": true,
"last_seen": "2026-03-11T20:00:00Z"
}
]
}/api/devices/:idDetails for one device by id.
curl -X GET "https://androidtexter.com/api/devices/:id" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "device": { "id": "...", "name": "...", "online": true } }Get an HTTP callback the moment a reply arrives, instead of polling.
/api/webhooksReturns all configured webhook endpoints.
curl -X GET "https://androidtexter.com/api/webhooks" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "webhooks": [{ "id": "...", "url": "https://...", "events": ["message.received"], "active": true }] }/api/v1/webhooksRegister a webhook URL programmatically. Returns a signing secret used to verify payloads; it is shown once.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| url | body | yes | https://your-server.com/webhook |
| events | body | yes | ["message.received"] |
curl -X POST "https://androidtexter.com/api/v1/webhooks" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": "[\"message.received\"]"
}'Example response
{
"webhook": { "id": "uuid", "url": "https://...", "active": true },
"secret": "whsec_..."
}/api/webhooks/:idRemove a webhook endpoint.
curl -X DELETE "https://androidtexter.com/api/webhooks/:id" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "message": "Webhook deleted" }Saved message shortcuts, shared between the dashboard and the phone app.
/api/quick-repliesReturns all quick reply shortcuts for the authenticated user.
curl -X GET "https://androidtexter.com/api/quick-replies" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "quick_replies": [{ "id": "...", "shortcut": "thanks", "body": "Thank you!", "usage_count": 5 }] }/api/quick-repliesCreate a new quick reply shortcut.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| shortcut | body | yes | thanks |
| title | body | yes | thanks |
| body | body | yes | Thank you for your message! We'll get back to you shortly. |
curl -X POST "https://androidtexter.com/api/quick-replies" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shortcut": "thanks",
"title": "thanks",
"body": "Thank you for your message! We'll get back to you shortly."
}'Example response
{ "quick_reply": { "id": "uuid", "shortcut": "thanks", "body": "..." } }/api/quick-replies/:idUpdate an existing quick reply.
Parameters
| Name | In | Required | Example |
|---|---|---|---|
| shortcut | body | yes | thanks |
| title | body | yes | thanks |
| body | body | yes | Updated message body |
curl -X PUT "https://androidtexter.com/api/quick-replies/:id" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shortcut": "thanks",
"title": "thanks",
"body": "Updated message body"
}'Example response
{ "quick_reply": { "id": "...", "shortcut": "thanks", "body": "Updated message body" } }/api/quick-replies/:idRemove a quick reply shortcut.
curl -X DELETE "https://androidtexter.com/api/quick-replies/:id" \
-H "X-API-Key: YOUR_API_KEY"Example response
{ "message": "Quick reply deleted" }When a message arrives on a connected device we POST a JSON payload to your configured webhook URL. Register one with Create Webhook above, or on the dashboard.
message.receivedFired when an SMS is received on any connected device.
{
"event": "message.received",
"timestamp": "2026-03-11T20:26:47Z",
"data": {
"id": "uuid",
"from": "+14155551234",
"body": "Hello!",
"device_id": "uuid",
"device_name": "Pixel 7",
"received_at": "2026-03-11T20:26:47Z"
}
}Each webhook is signed with your webhook secret. Verify the X-Webhook-Signature header using HMAC-SHA256 over the raw request body, and compare with a constant-time function.
import hmac, hashlib
def verify_webhook(payload_bytes, signature, secret):
expected = hmac.new(
secret.encode(),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)Rate limits are token buckets with a sustained rate and a burst allowance, counted per API key (or per user for JWT requests), not per device or per IP.
| Scope | Sustained | Burst | Notes |
|---|---|---|---|
| POST /api/messages/send and POST /api/messages/broadcast | 10 requests/second | 20 | Counted per API key, not per device. Sending from several devices on one key shares this bucket. |
| Everything under /api/v1/ | 5 requests/second (300/minute) | 60 | The large burst is deliberate: polling clients bunch their requests, and a tight burst was returning 429 to callers whose average rate was well under the limit. |
| POST /api/auth/login, register and MFA verify | 3 requests/second | 5 | Counted per IP address, since there is no authenticated identity yet. |
Exceeding a bucket returns 429 with { "error": "Rate limit exceeded. Try again later." }. Retry after a short back-off.
Errors are returned as JSON with an error field and a matching HTTP status.
| Status | Body | Meaning |
|---|---|---|
| 401 | { "error": "Invalid API key" } | The key is missing, malformed, revoked, or you sent the 10-character display prefix instead of the full key. |
| 400 | { "error": "..." } | The request body failed validation. The message names the field at fault. |
| 404 | { "error": "..." } | The addressed record does not exist, or does not belong to your account. |
| 429 | { "error": "Rate limit exceeded. Try again later." } | You exceeded a rate limit bucket. Back off and retry. |