SMS Forwarder

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

  1. Install the app on the phone that receives the texts and tap Get started.
  2. Allow SMS access. For codes inside app notifications, also turn on Also forward codes from app notifications in Settings.
  3. Copy the API key from the app's home screen (Connect your apps and AI). It starts with sk_.
  4. 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.

CallWhat it does
GET /v1/keysList keys (prefix, name, last used)
POST /v1/keysCreate another key. Body: {"name":"CI"}. The full key is shown once
DELETE /v1/keys/:idRevoke a key
GET /v1/mePlan, 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
fromService 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
containsText the message must contain
sinceISO time or unix milliseconds. Only messages after it count. Default: 2 minutes ago
timeoutSeconds to wait, 0–120. Default 60
deviceOnly messages from this phone id
sourcesms 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/webhookBody {"url":"https://…"}. Returns the signing secret
POST /v1/webhook/testSend a sample event
DELETE /v1/webhookStop 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:

ServiceURL looks like
Slackhttps://hooks.slack.com/services/…
Discordhttps://discord.com/api/webhooks/…
Telegramhttps://api.telegram.org/bot<token>/sendMessage?chat_id=<chat id>
Feishu / Larkhttps://open.feishu.cn/open-apis/bot/v2/hook/…
DingTalkhttps://oapi.dingtalk.com/robot/send?access_token=…
WeComhttps://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/devicesConnected phones with lastSeenAt
DELETE /v1/devices/:idDisconnect a phone
POST /v1/pairing-codesA 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":"…"}}.

StatusCodeMeaning
401missing_api_key, invalid_api_keyNo key, a wrong key or a revoked key
402device_limitThe plan's phone limit is reached
404not_foundUnknown 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.