API reference
The REST API for sending WhatsApp messages programmatically from your connected number.
Authentication
Send your secret API key as a Bearer token on every request. Create and revoke keys in Developer Studio → API keys in your dashboard.
Authorization: Bearer ag_live_sk_xxxxxxxxxxxxxxxxxxxx
Missing or revoked keys return 401. Keys are bound to your workspace’s active WhatsApp connection.
Base URL
https://wagate.app
Conventions
- All endpoints accept and return
application/json(POSTbodies may also be form-encoded). - Successful responses include
"ok": true. - Errors return
"ok": falsewith a human-readableerror, an optional machinecode, and a matching HTTP status.
Send a message
Sends a WhatsApp message through your connected number and logs the attempt. Supports text, template, media, and interactive messages.
Request body
| Field | Type | Description |
|---|---|---|
to |
string | Required. Recipient phone number in international format (digits only, e.g. 15551234567). |
type |
string | One of text (default), template, image, document, audio, video, sticker, interactive. |
body |
string | Message text for type: text (alias: text). |
template |
string | Approved template name for type: template (alias: template_name). |
language |
string | Template language code, e.g. en_US. Defaults to the template’s language. |
components |
array | Template variable components, in the WhatsApp Cloud API format. |
media_id / link |
string | Media handle or public URL for media message types. |
caption |
string | Optional caption for image, video, and document messages. |
phone_number_id |
string | Optional. Send from a specific connected number; defaults to your first number. |
Example — text
curl -X POST https://wagate.app/api/messages.php \
-H "Authorization: Bearer ag_live_sk_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "to": "15551234567", "type": "text", "body": "Your order has shipped!" }'
Example — template
{
"to": "15551234567",
"type": "template",
"template": "order_confirmation",
"language": "en_US",
"components": [
{ "type": "body", "parameters": [ { "type": "text", "text": "#RS-48217" } ] }
]
}
Response 200
{ "ok": true, "wamid": "wamid.HBgLMTU1NTEyMzQ1Njc...", "status": "sent" }
Sender tag
The sender of a message is decided by the server, never by the request. Messages sent with an API key are stored with no sender — except while an external AI agent is switched on for the connection (Developer Studio → AI agent: Send chats to my agent and Pause built-in chatbot). Then they are tagged sender: "api", so the built-in chatbot does not mistake your agent’s reply for a human teammate taking over. You will see the tag as sender in the Read API below and in message.status webhook events.
Read API — conversations & agents
These endpoints let your own software — typically an AI agent fed by outbound webhooks — read a conversation, fetch its attachments, transcribe voice notes and escalate to a person. They live under /api/agent/ and use the same Bearer key and plan as /api/messages.php (Growth or Scale; a lapsed subscription is refused with 402).
Rate limit: 120 requests per minute per key. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; beyond the limit you get 429 with a Retry-After header and "code": "rate_limited". Each endpoint accepts one HTTP method only (anything else returns 405).
A contact is identified by the same id the webhooks give you in contact.id: the WhatsApp number in digits (15551234567), or an id such as fb_… (Messenger) / ig_… (Instagram).
Read a conversation
| Parameter | Type | Description |
|---|---|---|
contact |
string | Required. The contact id. |
limit |
integer | Messages per page, 1–100. Default 50. |
before |
integer | A message id: return the messages older than it. Use the next_before from the previous page. |
curl "https://wagate.app/api/agent/thread.php?contact=15551234567&limit=20" \
-H "Authorization: Bearer ag_live_sk_xxxxxxxxxxxx"
{
"ok": true,
"messages": [
{
"id": 48213,
"wamid": "wamid.HBgLMTU1NTEyMzQ1Njc...",
"direction": "inbound",
"type": "audio",
"text": null,
"caption": null,
"transcript": "Hi, can I move my appointment to Thursday?",
"sender": null,
"created_at": "2026-10-12T14:03:18Z",
"status": null,
"media": { "id": "48213", "mime": "audio/ogg", "filename": null, "url": "https://wagate.app/api/agent/media.php?message_id=48213" },
"reply_to": null
}
],
"next_before": 48190
}
- Messages come oldest first.
next_beforeis the id to pass asbeforefor the next, older page —nullon the last page. directionisinboundoroutbound. For outbound messagessenderisbot(built-in chatbot or an automation),api(your agent, see above),phone(typed on the WhatsApp Business app) oragent(a person in the dashboard); otherwisenull.text,captionandtranscriptarenullwhen empty.created_atis ISO-8601 UTC.- Internal notes written by your team are never returned. An unknown contact returns
404.
Download an attachment
Query parameter message_id (required) — the message’s id, which is also the media.id in webhook payloads and the message_id in every media.url. Send the same Bearer key (that is why media.url can be handed to your agent as it is).
200— the file itself, with itsContent-Type, as an attachment.413— larger than 25 MB. The body still carries"media": { "id", "mime", "filename", "size", "url" }so your agent can tell the customer what arrived.404— no such message, or it has no attachment ("code": "no_media").502— the attachment could not be retrieved from the channel ("code": "media_unavailable").
Meta download links, tokens and file paths are never exposed — files are always fetched through this endpoint.
Transcribe a voice note
| Field | Type | Description |
|---|---|---|
message_id |
integer | Required. The id of an inbound voice note. |
{ "ok": true, "text": "Hi, can I move my appointment to Thursday?", "cached": false }
Quota rule. A fresh transcription is one AI action from your plan’s monthly allowance — the same pool as the dashboard’s AI assist (Growth 500, Scale 2,000). A voice note is transcribed at most once: the text is stored on the message, so asking again — or reading the thread — returns it ("cached": true) at no cost, even when the allowance is used up. A first-time request with no allowance left is refused (402, 403 or 429, with a code in the body). Voice notes delivered through webhooks follow the same rule — see Voice-note transcripts.
Hand off to a person
| Field | Type | Description |
|---|---|---|
contact |
string | Required. The contact id. |
state |
string | Required. requested puts the conversation in the inbox’s Needs agent list — exactly what the built-in chatbot does when it escalates. clear takes the flag down without sending anything. |
{ "ok": true, "state": "requested", "changed": true }
changed is false when the flag was already in that state. A person replying from the inbox clears the flag by itself — and you get a handoff webhook with state: "human_active".
Outbound webhooks
Outbound webhooks are configured in Developer Studio → AI agent, not through this API. WAGate sends your endpoint a signed POST for message.received, message.status, handoff and webhook.test events, retries failures on a back-off schedule (up to nine attempts), and lets you re-deliver from the log. The full contract — headers, signature verification with PHP and Node.js samples, event payloads, the retry table and agent mode — is in the Documentation.
Errors
Errors share one envelope:
{ "ok": false, "error": "Recipient \"to\" is required.", "code": "..." }
| HTTP | Meaning |
|---|---|
401 |
Missing, invalid, or revoked API key. |
402 |
Read API: your subscription has lapsed — reactivate your plan (plan_locked). On transcribe.php it can also mean the AI allowance is used up. |
403 |
API access is not included in your plan — upgrade to Growth. |
404 |
Read API: unknown contact, message or voice note (not_found, no_media). |
405 |
Read API: wrong HTTP method for this endpoint (method); see the Allow header. |
409 |
No active WhatsApp connection on the workspace. |
413 |
Read API: the attachment is larger than 25 MB (too_large). |
422 |
Invalid request — a required field is missing or malformed. |
429 |
Read API: more than 120 requests a minute on this key (rate_limited) — wait Retry-After seconds. transcribe.php also uses it when the monthly AI allowance is used up. |
502 |
Upstream error from the WhatsApp Cloud API while sending, or the attachment / transcription could not be retrieved. |