Save 44% with yearly billing.View Plans
textbee Logotextbee.dev
Documentation

Agent quickstart

The two textbee API endpoints an AI agent needs: account-level send and message history with cursor polling, with copy-paste curl and SDK examples.

Updated

Everything an AI agent or automation needs from textbee is two endpoints: one to send, one to read. Both are account-level, so there is no device id to manage. Authenticate every request with the x-api-key header using a key from the dashboard.

If your agent speaks MCP, use the MCP server instead of calling these directly. This page is for HTTP-level integrations and for LLMs writing textbee code.

Send an SMS

POST https://api.textbee.dev/api/v1/gateway/send-sms
Shell
curl -X POST https://api.textbee.dev/api/v1/gateway/send-sms \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: YOUR_API_KEY' \
  -d '{
    "recipients": ["+12015550123"],
    "message": "Hello from textbee"
  }'
  • recipients (array, required): phone numbers in E.164 format with the country code.
  • message (string, required): the text to send.
  • deviceId (string, optional): omit it. textbee picks your default device, otherwise the enabled device with the most recent heartbeat. Pass it only to force a specific phone.
  • simSubscriptionId (number, optional): which SIM on a dual-SIM phone.
  • scheduledAt (ISO 8601, optional): send later instead of now.

The response includes an smsBatchId when the account uses the SMS queue. Keep it: it is how you check delivery. A 200 response means the API accepted the message, not that the phone sent it. See Delivery status.

With the JavaScript SDK:

pnpm add @textbee/sdk
JavaScript
import { Textbee } from '@textbee/sdk'

const textbee = new Textbee({ apiKey: process.env.TEXTBEE_API_KEY })

const result = await textbee.sendSms({
  recipients: ['+12015550123'],
  message: 'Hello from textbee',
})
// result.smsBatchId when the account uses the SMS queue

Read messages

GET https://api.textbee.dev/api/v1/gateway/messages

One endpoint for every device on the account. The useful filters for agents:

  • direction: received for inbound only (replies, verification codes), sent to review outgoing messages and their delivery status, all for both.
  • smsBatchId: only messages from one send, using the smsBatchId the send returned. This is the delivery-status lookup: each row carries a status (pending, dispatched, sent, delivered, failed, unknown, received). delivered and failed are final. Many carriers send no delivery report, so a message can stay sent. See Delivery status for each state.
  • search: free-text match across the message body and the other party's number.
  • from / to: time window, ISO 8601 with an explicit timezone. from is inclusive, to exclusive, so consecutive windows never double-count a message.
  • order: desc (default, newest first) or asc to walk forward when polling.
  • cursor: opaque position from a previous response's meta.nextCursor.
Shell
curl "https://api.textbee.dev/api/v1/gateway/messages?direction=received&limit=25" \
  -H 'x-api-key: YOUR_API_KEY'

Check the delivery of a send:

Shell
curl "https://api.textbee.dev/api/v1/gateway/messages?smsBatchId=BATCH_ID_FROM_SEND" \
  -H 'x-api-key: YOUR_API_KEY'

Poll for new messages without duplicates

Webhooks are the push option (see Webhooks); this is the reliable pull option:

  1. First call: GET /gateway/messages?direction=received&order=asc&from=2026-08-01T00:00:00Z
  2. Follow meta.nextCursor until meta.hasMore is false.
  3. On the next poll, resume from the last nextCursor you saw.

The cursor is your resume token: no re-read pages, no missed messages, no duplicates. Message history documents every filter and field. The SDK wraps this as an async iterator:

JavaScript
for await (const message of textbee.iterateMessages({ direction: 'received' })) {
  handle(message)
}

Use only the endpoints on this page. Older device-scoped routes still work for existing integrations but are deprecated and should never appear in new code.

Rate limits and quotas

Sends count against your plan's daily and monthly quotas, enforced server-side. A send over quota fails with HTTP 429 and a body containing hasReachedLimit: true plus your remaining counts. Do not retry those automatically; the quota does not reset because you retried.

Next steps

  • MCP server - The same capabilities as tools for Claude, Cursor, and other MCP clients
  • Webhooks - Get pushed incoming messages instead of polling
  • Messages not sending - Diagnose a send that stays pending or fails
  • API reference - Every field of every endpoint

Need help? Check our FAQ or contact support.