Docs
SMS Forwarder sends the texts your Android phone receives, and the verification codes apps show as notifications, to an API you can call from scripts and AI agents. Base URL: https://api.smsforwarder.app.
Getting started
- Install the app on the phone that receives the texts and tap Get started.
- Allow SMS access. For codes inside app notifications, also turn on Also forward codes from app notifications in Settings.
- Copy the API key from the app's home screen (Connect your apps and AI). It starts with
sk_. - Tap Send a test message in the app, then run:
curl -H "Authorization: Bearer sk_your_key" https://api.smsforwarder.app/v1/messages?limit=1
API keys
Send the key as Authorization: Bearer sk_…. x-api-key: sk_… and a ?key= query parameter also work, for tools that can't set headers.
| Call | What it does |
|---|---|
GET /v1/keys | List keys (prefix, name, last used) |
POST /v1/keys | Create another key. Body: {"name":"CI"}. The full key is shown once |
DELETE /v1/keys/:id | Revoke a key |
GET /v1/me | Plan, limits and messages used this month |
Wait for a code
GET /v1/codes/wait keeps the request open until a message with a code arrives, then returns it. Call it right after you trigger the code.
| Parameter | |
|---|---|
from | Service or sender to match, e.g. Google, 淘宝, or part of the number. Matches the sender, the app name and the message text. Pass it whenever you know the service |
contains | Text the message must contain |
since | ISO time or unix milliseconds. Only messages after it count. Default: 2 minutes ago |
timeout | Seconds to wait, 0–120. Default 60 |
device | Only messages from this phone id |
source | sms or notification |
curl -G https://api.smsforwarder.app/v1/codes/wait \ -H "Authorization: Bearer sk_your_key" \ --data-urlencode "from=GitHub" -d timeout=60
200 returns {"code":"482913","message":{…}}. 404 returns {"code":null} when nothing arrived in time.
GET /v1/codes/latest takes the same parameters and answers immediately with the newest matching code.
List messages
GET /v1/messages returns recent messages, newest first. Parameters: from, contains, since, source, device, codes_only=true, limit (1–100, default 20).
curl -G https://api.smsforwarder.app/v1/messages \ -H "Authorization: Bearer sk_your_key" -d codes_only=true -d limit=5
Message fields
{
"id": "msg_…",
"source": "sms", // or "notification"
"service": "GitHub", // app name, the 【brand】 in a Chinese SMS, or the sender
"from": "+1 650-555-0100", // sender number, or notification title
"app": null, // app name for notifications
"appPackage": null,
"body": "Your GitHub verification code is 482913.",
"code": "482913", // null if no code was found
"device": { "id": "dev_…", "name": "Pixel 8" },
"simSlot": 0,
"receivedAt": "2026-10-13T09:41:00.000Z"
}
MCP
The MCP server is at https://api.smsforwarder.app/mcp (Streamable HTTP). Tools: wait_for_code, list_messages, list_devices.
Claude Code
claude mcp add --transport http sms-forwarder https://api.smsforwarder.app/mcp \ --header "Authorization: Bearer sk_your_key"
Cursor, Claude Desktop and other clients
{
"mcpServers": {
"sms-forwarder": {
"type": "http",
"url": "https://api.smsforwarder.app/mcp",
"headers": { "Authorization": "Bearer sk_your_key" }
}
}
}
Clients that only take a URL can use https://api.smsforwarder.app/mcp?key=sk_your_key.
Agent Skill
For Claude Code and other agents that load skills, save skill.md as SKILL.md in a sms-forwarder skills folder and set SMSFORWARDER_API_KEY:
mkdir -p ~/.claude/skills/sms-forwarder curl -o ~/.claude/skills/sms-forwarder/SKILL.md https://smsforwarder.app/skill.md export SMSFORWARDER_API_KEY=sk_your_key
CLI
Needs Node.js 18 or newer.
npm install -g smsforwarder smsforwarder login sk_your_key # or set SMSFORWARDER_API_KEY smsforwarder code --from Google # prints just the code; exit 2 on timeout smsforwarder code --json # the whole message smsforwarder messages --codes --limit 5 smsforwarder devices smsforwarder webhook set https://example.com/sms smsforwarder mcp # prints MCP config
Webhooks
Every new message is POSTed to your URL as {"event":"message.received","message":{…}}. Failed deliveries are retried twice.
PUT /v1/webhook | Body {"url":"https://…"}. Returns the signing secret |
POST /v1/webhook/test | Send a sample event |
DELETE /v1/webhook | Stop sending |
Chat apps and push
In the app, open Settings, tap Forward to a group chat or URL and paste one of these. Or set it with the API. Messages are posted as chat text (code and service on the first line) instead of JSON:
| Service | URL looks like |
|---|---|
| Slack | https://hooks.slack.com/services/… |
| Discord | https://discord.com/api/webhooks/… |
| Telegram | https://api.telegram.org/bot<token>/sendMessage?chat_id=<chat id> |
| Feishu / Lark | https://open.feishu.cn/open-apis/bot/v2/hook/… |
| DingTalk | https://oapi.dingtalk.com/robot/send?access_token=… |
| WeCom | https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=… |
| Bark (iPhone push) | https://api.day.app/<device key>. Tapping the push copies the code |
Use POST /v1/webhook/test to check: it returns {"ok":true}, or what the service answered if it refused the message.
Verifying signatures
Each request has x-signature: t=<unix seconds>,v1=<hex>, where hex is HMAC-SHA256 of <t>.<raw body> with your secret:
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
Phones
GET /v1/devices | Connected phones with lastSeenAt |
DELETE /v1/devices/:id | Disconnect a phone |
POST /v1/pairing-codes | A 6-digit code, valid 10 minutes, to connect another phone to this account |
A phone that hasn't been seen for hours is usually being put to sleep by the system. In the app, tap Keep running in the background; on Xiaomi, Huawei, OPPO and vivo also allow autostart.
Errors and limits
Errors look like {"error":{"code":"invalid_api_key","message":"…"}}.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_api_key, invalid_api_key | No key, a wrong key or a revoked key |
| 402 | device_limit | The plan's phone limit is reached |
| 404 | not_found | Unknown endpoint, or no code arrived in time |
Free plan: 1 phone, 100 messages a month, messages kept 7 days. Texts over the monthly limit stay on the phone and aren't forwarded.