Skip to content
SocialMate

SocialMate Local API

Sending messages

The unified send endpoint, the anti-ban auto-queue fallback, media, and priority.

There is one endpoint for all outbound messages. Send exactly one of text, media, poll, location or contacts. Every send runs through the 8-layer anti-ban pipeline; the API has no bypass, which is what keeps automated numbers safe.

Three further actions — reacting, marking read and the typing indicator — are free on every tier. They produce no chat bubble, so they are budgeted on an independent signal rate limit and never consume your message allowance or raise your anti-ban risk score.

POST /v1/accounts/{id}/messages

scope: sendFree: textPro: media, poll, location, contacts

Request body

FieldTypeNotes
chatIdstringRequired. The recipient as a phone number in international format (see below), or a group JID. Also accepts a full JID (…@s.whatsapp.net or group …@g.us). 1–256 chars.
textstring1–4096 chars. Free on every tier.
mediaobjectPro. { type, url | base64, filename?, caption?, mimetype? }. type ∈ image, video, audio, document, sticker.
pollobjectPro. { name, options[], selectableCount? }. 2–12 options; selectableCount defaults to 1 (single-select). Votes arrive on the poll.vote webhook.
locationobjectPro. { latitude, longitude, name?, address? }.
contactsarrayPro. 1–10 cards of { fullName, phone, organization? }, sent as vCard 3.0. phone becomes the tappable number on the card.
replyTostringA message id to quote. Threads this send as a reply. Works with every content kind.
linkPreviewbooleanSet false to suppress the URL preview card on a text send.
priority0–3Only used if the send is auto-queued. 0 = urgent, 3 = low. Default 2.
maxRetries0–10Only used if auto-queued. Default 3.

Media accepts either a url (max 2048 chars) or inline base64 (up to 20,000,000 characters, ≈15 MB of file). Prefer url — it can be auto-queued; base64 cannot.

Auto-queue does not cover rich content. If anti-ban blocks a poll, location or contacts send, you get 429 with queueable: false on every tier — the queue stores a single text body and one media path, with nowhere to put a poll's options or a pin's coordinates. Retry after retryAfterMs.

Conversational signals

These three are free on every tier and share an independent rate limit (20/min on Free, 60/min on Pro). Exceeding it returns 429 with reason: "signal_rate_limit" — distinct from a message rate limit, so your send budget is untouched.

POST /v1/accounts/{id}/messages/{messageId}/reaction

scope: sendFree

Body: { chatId, emoji }. Send a single emoji to react; send "" to remove your reaction. WhatsApp allows one reaction per person per message, so a new emoji replaces your previous one. Inbound reactions arrive on the message.reaction webhook.

POST /v1/accounts/{id}/messages/read

scope: sendFree

Body: { chatId, messageIds? }. Sends blue ticks. Omit messageIds to acknowledge everything unread in the chat.

POST /v1/accounts/{id}/presence

scope: sendFree

Body: { chatId, state } where state is composing, recording or paused. Show composing while your agent thinks, then send the reply. WhatsApp expires the indicator after about 10 seconds — refresh it if you need it held longer.

Phone number format

You can send straight from your own contacts/CRM — just pass the recipient's phone number in international format, including the country code. SocialMate cleans it up for you, so any of these work and resolve to the same recipient:

You sendSocialMate uses
15551234567[email protected]
+15551234567
+1 (555) 123-4567
0015551234567 (00 prefix)

The country code is required — a national or local number without it can't be delivered (SocialMate won't guess the country). For a group, pass its JID ending in …@g.us. A full …@s.whatsapp.net JID is also accepted if you already have one.

Immediate success — 200

curl http://localhost:3456/v1/accounts/ACCOUNT_ID/messages \
  -H "x-api-key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "chatId": "15551234567", "text": "Your order #1024 shipped 📦" }'
{
  "data": {
    "sent": true,
    "messageId": "3EB0…",
    "chatId": "[email protected]",
    "timestamp": 1717245600000,
    "status": "sent"
  }
}

When anti-ban blocks the send

If the pipeline decides now isn't a safe moment (rate cap, warming, risk, night mode…), behaviour depends on your tier:

Pro — auto-queued (202)

The message is enqueued and retried at the next eligible moment. You get 202 and never have to touch the queue API:

{
  "data": {
    "queued": true,
    "itemId": "5f3a9c2b8d1e4f6a7c0b2d9e",
    "reason": "rate_limit",
    "retryAfterMs": 18000,
    "priority": 2
  }
}

Free — rate-limited (429)

Free returns 429 with the block reason, a Retry-After header, and an upgrade hint. Back off and retry, or upgrade to Pro for auto-queueing:

{
  "error": {
    "code": "rate_limited",
    "message": "…",
    "reason": "rate_limit",
    "retryAfterMs": 18000,
    "hint": "Upgrade to Pro to auto-queue blocked sends and have them retried when the block clears.",
    "upgrade": { "tier": "pro", "feature": "apiSmartQueue" }
  }
}

The HTTP error.code is the status-derived rate_limited; the specific block is in error.reason (one of the anti-ban reasons).

See Anti-ban status for the full reason list and how to read your live limits before sending.

Other error responses

  • 400 — you sent both text and media (or neither), or a field failed validation.
  • 402 — a Pro-only field (any media send) on a Free key; the body names the missing feature (apiWriteEnabled).
  • 404 — no account with that :id.
  • 409 — the account exists but isn't connected — link it first.
  • 502 — the send to WhatsApp failed (WhatsApp send failed). The message was not delivered, but do not blind-retry: confirm via the message.sent webhook or the chat before resending, or you risk a double-send.

Sending media (Pro)

curl http://localhost:3456/v1/accounts/ACCOUNT_ID/messages \
  -H "x-api-key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
        "chatId": "[email protected]",
        "media": { "type": "image", "url": "https://example.com/receipt.png", "caption": "Your receipt" }
      }'

A media send without Pro returns 402 (feature: apiWriteEnabled). A url-based media send that anti-ban blocks auto-queues on Pro; a base64 one cannot be queued and returns 429.

Legacy media endpoint

POST /v1/accounts/{id}/messages/media

scope: sendPro

Kept for backward compatibility. New integrations should use the unified POST /messages with a media object — it gains the auto-queue fallback.

It is formally deprecated and responses carry deprecation headers:

Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: </docs#unified-send>; rel="alternate"; title="Unified send endpoint"

Sending to many opted-in contacts? For per-recipient, templated sends to contacts who opted in, push rows through the Smart Queue instead of looping this endpoint — it paces delivery through anti-ban for you.

Free forever · no card Download free