API Reference · v1.0.0

Android Texter API

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.

Base URLhttps://androidtexter.comSwagger UI openapi.json

Quick start

  1. Install the Android Texter app on an Android phone and link it to your account.
  2. Create an API key on the API Keys page. Copy it then - it is only shown once.
  3. Send your first message:
cURL
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.

Authentication

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>.

  • Create a key on the API Keys page in your dashboard. The full key is shown once, at creation, and is stored only as a hash: it cannot be recovered later, so save it somewhere safe.
  • The dashboard lists only the first 10 characters of a key so you can tell them apart. That prefix is a label, not a credential, and will be rejected if you send it as a key.
  • Endpoints marked 'API Key or JWT' accept either. Endpoints marked 'JWT only' are dashboard session endpoints and are not usable with an API key.
header
X-API-Key: atx_your_key_here

Authentication

Identify 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.

GET/api/me

Returns the authenticated user's profile and subscription info. Useful as a credential check.

JWT only
cURL
curl -X GET "https://androidtexter.com/api/me" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/me"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/me", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "user": { "id": "...", "name": "Phil", "email": "[email protected]" },
  "subscription": { "plan": "free", "status": "active" }
}

Messages

Send and read SMS through your connected Android phones.

POST/api/messages/send

Send 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.

API Key or JWT

Parameters

NameInRequiredExample
phonebodyyes+14155551234
messagebodyyesHello from the API!
device_idbodynodevice UUID
scheduled_atbodyno2026-03-12T09:00:00Z
countrybodynoGB
cURL
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!"
}'
Python
import requests

url = "https://androidtexter.com/api/messages/send"
headers = {"X-API-Key": "YOUR_API_KEY"}
payload = {
  "phone": "+14155551234",
  "message": "Hello from the API!"
}

resp = requests.post(url, json=payload, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages/send", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "phone": "+14155551234",
  "message": "Hello from the API!"
})
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "status": 200,
  "message": "Message queued for sending on Pixel 7",
  "message_id": "uuid",
  "device_id": "uuid",
  "device_name": "Pixel 7"
}
GET/api/messages/scheduled

List pending scheduled messages that have not been sent yet.

API Key or JWT
cURL
curl -X GET "https://androidtexter.com/api/messages/scheduled" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/messages/scheduled"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages/scheduled", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "scheduled_messages": [
    {
      "id": "uuid",
      "phone": "+14155551234",
      "body": "Reminder: appointment tomorrow!",
      "scheduled_at": "2026-03-12T09:00:00Z",
      "scheduler_status": "pending",
      "device_id": "uuid"
    }
  ]
}
Paste an API key at the top of the page to send a live request.
DELETE/api/messages/scheduled/:id

Cancel a pending scheduled message before it is sent.

API Key or JWT
cURL
curl -X DELETE "https://androidtexter.com/api/messages/scheduled/:id" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/messages/scheduled/:id"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.delete(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages/scheduled/:id", {
  method: "DELETE",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "message": "Scheduled message cancelled" }
GET/api/messages

List all messages, inbound and outbound, newest first.

API Key or JWT

Parameters

NameInRequiredExample
pagequeryno1
limitqueryno50
cURL
curl -X GET "https://androidtexter.com/api/messages?page=1&limit=50" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/messages"
headers = {"X-API-Key": "YOUR_API_KEY"}
params = {
  "page": "1",
  "limit": "50"
}

resp = requests.get(url, params=params, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages?page=1&limit=50", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "messages": [
    { "id": "...", "phone": "+1...", "body": "Hello", "direction": "inbound", "created_at": "..." }
  ]
}
Paste an API key at the top of the page to send a live request.
GET/api/messages/sent

List outbound messages only.

API Key or JWT

Parameters

NameInRequiredExample
pagequeryno1
limitqueryno50
cURL
curl -X GET "https://androidtexter.com/api/messages/sent?page=1&limit=50" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/messages/sent"
headers = {"X-API-Key": "YOUR_API_KEY"}
params = {
  "page": "1",
  "limit": "50"
}

resp = requests.get(url, params=params, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages/sent?page=1&limit=50", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "messages": [] }
Paste an API key at the top of the page to send a live request.
GET/api/messages/received

List inbound messages only.

API Key or JWT

Parameters

NameInRequiredExample
pagequeryno1
limitqueryno50
cURL
curl -X GET "https://androidtexter.com/api/messages/received?page=1&limit=50" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/messages/received"
headers = {"X-API-Key": "YOUR_API_KEY"}
params = {
  "page": "1",
  "limit": "50"
}

resp = requests.get(url, params=params, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages/received?page=1&limit=50", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "messages": [] }
Paste an API key at the top of the page to send a live request.
GET/api/v1/messages/history/:phone

Fetch paginated historical messages scoped to a single contact phone number.

API Key or JWT

Parameters

NameInRequiredExample
pagequeryno1
limitqueryno50
cURL
curl -X GET "https://androidtexter.com/api/v1/messages/history/:phone?page=1&limit=50" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/v1/messages/history/:phone"
headers = {"X-API-Key": "YOUR_API_KEY"}
params = {
  "page": "1",
  "limit": "50"
}

resp = requests.get(url, params=params, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/v1/messages/history/:phone?page=1&limit=50", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "messages": [
    { "id": "...", "phone": "+1...", "body": "Hello", "direction": "inbound", "created_at": "..." }
  ],
  "phone": "+1...",
  "page": 1,
  "limit": 50
}
GET/api/v1/messages/status/:id

Look up the delivery state of one message by its id, as returned by Send Message.

API Key or JWT
cURL
curl -X GET "https://androidtexter.com/api/v1/messages/status/:id" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/v1/messages/status/:id"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/v1/messages/status/:id", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "id": "uuid", "status": "sent", "sent_at": "..." }
GET/api/messages/conversations

A grouped list of conversations with the latest message from each phone number.

JWT only

Parameters

NameInRequiredExample
limitqueryno50
cURL
curl -X GET "https://androidtexter.com/api/messages/conversations?limit=50" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/messages/conversations"
headers = {"X-API-Key": "YOUR_API_KEY"}
params = {
  "limit": "50"
}

resp = requests.get(url, params=params, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/messages/conversations?limit=50", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "conversations": [
    { "phone": "+1...", "last_body": "Hey!", "last_at": "...", "unread": 2 }
  ]
}

Devices

The Android phones linked to your account. A phone must be online to send.

GET/api/devices

Returns every device linked to your account with its online status. Use a device id to pin a send to one phone.

API Key or JWT
cURL
curl -X GET "https://androidtexter.com/api/devices" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/devices"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/devices", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "devices": [
    {
      "id": "uuid",
      "name": "Pixel 7",
      "phone_number": "+14155551234",
      "online": true,
      "last_seen": "2026-03-11T20:00:00Z"
    }
  ]
}
Paste an API key at the top of the page to send a live request.
GET/api/devices/:id

Details for one device by id.

API Key or JWT
cURL
curl -X GET "https://androidtexter.com/api/devices/:id" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/devices/:id"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/devices/:id", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "device": { "id": "...", "name": "...", "online": true } }

Webhooks

Get an HTTP callback the moment a reply arrives, instead of polling.

GET/api/webhooks

Returns all configured webhook endpoints.

JWT only
cURL
curl -X GET "https://androidtexter.com/api/webhooks" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/webhooks"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/webhooks", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "webhooks": [{ "id": "...", "url": "https://...", "events": ["message.received"], "active": true }] }
POST/api/v1/webhooks

Register a webhook URL programmatically. Returns a signing secret used to verify payloads; it is shown once.

API Key or JWT

Parameters

NameInRequiredExample
urlbodyyeshttps://your-server.com/webhook
eventsbodyyes["message.received"]
cURL
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\"]"
}'
Python
import requests

url = "https://androidtexter.com/api/v1/webhooks"
headers = {"X-API-Key": "YOUR_API_KEY"}
payload = {
  "url": "https://your-server.com/webhook",
  "events": "[\"message.received\"]"
}

resp = requests.post(url, json=payload, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/v1/webhooks", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "url": "https://your-server.com/webhook",
  "events": "[\"message.received\"]"
})
});
const data = await resp.json();
console.log(data);

Example response

json
{
  "webhook": { "id": "uuid", "url": "https://...", "active": true },
  "secret": "whsec_..."
}
DELETE/api/webhooks/:id

Remove a webhook endpoint.

JWT only
cURL
curl -X DELETE "https://androidtexter.com/api/webhooks/:id" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/webhooks/:id"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.delete(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/webhooks/:id", {
  method: "DELETE",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "message": "Webhook deleted" }

Quick Replies

Saved message shortcuts, shared between the dashboard and the phone app.

GET/api/quick-replies

Returns all quick reply shortcuts for the authenticated user.

API Key or JWT
cURL
curl -X GET "https://androidtexter.com/api/quick-replies" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/quick-replies"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.get(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/quick-replies", {
  method: "GET",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "quick_replies": [{ "id": "...", "shortcut": "thanks", "body": "Thank you!", "usage_count": 5 }] }
Paste an API key at the top of the page to send a live request.
POST/api/quick-replies

Create a new quick reply shortcut.

API Key or JWT

Parameters

NameInRequiredExample
shortcutbodyyesthanks
titlebodyyesthanks
bodybodyyesThank you for your message! We'll get back to you shortly.
cURL
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."
}'
Python
import requests

url = "https://androidtexter.com/api/quick-replies"
headers = {"X-API-Key": "YOUR_API_KEY"}
payload = {
  "shortcut": "thanks",
  "title": "thanks",
  "body": "Thank you for your message! We'll get back to you shortly."
}

resp = requests.post(url, json=payload, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/quick-replies", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "shortcut": "thanks",
  "title": "thanks",
  "body": "Thank you for your message! We'll get back to you shortly."
})
});
const data = await resp.json();
console.log(data);

Example response

json
{ "quick_reply": { "id": "uuid", "shortcut": "thanks", "body": "..." } }
PUT/api/quick-replies/:id

Update an existing quick reply.

API Key or JWT

Parameters

NameInRequiredExample
shortcutbodyyesthanks
titlebodyyesthanks
bodybodyyesUpdated message body
cURL
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"
}'
Python
import requests

url = "https://androidtexter.com/api/quick-replies/:id"
headers = {"X-API-Key": "YOUR_API_KEY"}
payload = {
  "shortcut": "thanks",
  "title": "thanks",
  "body": "Updated message body"
}

resp = requests.put(url, json=payload, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/quick-replies/:id", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "shortcut": "thanks",
  "title": "thanks",
  "body": "Updated message body"
})
});
const data = await resp.json();
console.log(data);

Example response

json
{ "quick_reply": { "id": "...", "shortcut": "thanks", "body": "Updated message body" } }
DELETE/api/quick-replies/:id

Remove a quick reply shortcut.

API Key or JWT
cURL
curl -X DELETE "https://androidtexter.com/api/quick-replies/:id" \
  -H "X-API-Key: YOUR_API_KEY"
Python
import requests

url = "https://androidtexter.com/api/quick-replies/:id"
headers = {"X-API-Key": "YOUR_API_KEY"}

resp = requests.delete(url, headers=headers)
print(resp.json())
Node.js
const resp = await fetch("https://androidtexter.com/api/quick-replies/:id", {
  method: "DELETE",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json"
  }
});
const data = await resp.json();
console.log(data);

Example response

json
{ "message": "Quick reply deleted" }

Webhook payloads

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.received

Fired when an SMS is received on any connected device.

json
{
  "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"
  }
}

Verifying signatures

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.

python
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

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.

ScopeSustainedBurstNotes
POST /api/messages/send and POST /api/messages/broadcast10 requests/second20Counted 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)60The 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 verify3 requests/second5Counted 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

Errors are returned as JSON with an error field and a matching HTTP status.

StatusBodyMeaning
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.

Other ways to use this API

  • MCP server - give Claude, Cursor or any MCP client the ability to send, schedule and read SMS directly.
  • openapi.json - the machine-readable Swagger 2.0 spec, for Zapier, Make, n8n or code generation.
  • Integrations - Zapier and no-code connectors that wrap these endpoints.
  • The same reference ships inside the Android app, under Menu, API Documentation.