Skip to content

API reference

The REST API for sending WhatsApp messages programmatically from your connected number.

Preview. The REST API is available on the Growth and Scale plans. New endpoints are being added — this page documents what is live today: sending messages, and the Read API for conversations and external AI agents. See Documentation for a guided start and for outbound webhooks.

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 (POST bodies may also be form-encoded).
  • Successful responses include "ok": true.
  • Errors return "ok": false with a human-readable error, an optional machine code, and a matching HTTP status.

Send a message

POST /api/messages.php

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

GET /api/agent/thread.php
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_before is the id to pass as before for the next, older page — null on the last page.
  • direction is inbound or outbound. For outbound messages sender is bot (built-in chatbot or an automation), api (your agent, see above), phone (typed on the WhatsApp Business app) or agent (a person in the dashboard); otherwise null.
  • text, caption and transcript are null when empty. created_at is ISO-8601 UTC.
  • Internal notes written by your team are never returned. An unknown contact returns 404.

Download an attachment

GET /api/agent/media.php

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 its Content-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

POST /api/agent/transcribe.php
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

POST /api/agent/handoff.php
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.