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.
/v1/accounts/{id}/messagesRequest body
| Field | Type | Notes |
|---|---|---|
chatId | string | Required. 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. |
text | string | 1–4096 chars. Free on every tier. |
media | object | Pro. { type, url | base64, filename?, caption?, mimetype? }. type ∈ image, video, audio, document, sticker. |
poll | object | Pro. { name, options[], selectableCount? }. 2–12 options; selectableCount defaults to 1 (single-select). Votes arrive on the poll.vote webhook. |
location | object | Pro. { latitude, longitude, name?, address? }. |
contacts | array | Pro. 1–10 cards of { fullName, phone, organization? }, sent as vCard 3.0. phone becomes the tappable number on the card. |
replyTo | string | A message id to quote. Threads this send as a reply. Works with every content kind. |
linkPreview | boolean | Set false to suppress the URL preview card on a text send. |
priority | 0–3 | Only used if the send is auto-queued. 0 = urgent, 3 = low. Default 2. |
maxRetries | 0–10 | Only 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.
/v1/accounts/{id}/messages/{messageId}/reactionBody: { 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.
/v1/accounts/{id}/messages/readBody: { chatId, messageIds? }. Sends blue ticks. Omit messageIds to
acknowledge everything unread in the chat.
/v1/accounts/{id}/presenceBody: { 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 send | SocialMate 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 bothtextandmedia(or neither), or a field failed validation.402— a Pro-only field (anymediasend) on a Free key; the body names the missingfeature(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 themessage.sentwebhook 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
/v1/accounts/{id}/messages/mediaKept 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.