AutoMyra WhatsApp Public API (v1)
Send and receive WhatsApp messages through AutoMyra's WhatsApp Business plumbing from any external software. The API is REST/JSON over HTTPS.
Base URL: https://<your-automyra-host>/api/whatsapp/v1
Authentication
Every request is authenticated with an API key — a single bearer token that also identifies your tenant, so no organisation code is needed in the URL.
Authorization: Bearer wsk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Create keys in AutoMyra under WhatsApp → API keys (or via the authenticated management API below). The plaintext key is shown once, at creation — store it securely. Keys can be scoped and are rate-limited (default 60 requests/minute).
Scopes: messages:send, messages:read, templates:read,
conversations:read, webhooks:manage. A key with no scopes has full access.
Errors
All errors share one envelope:
{ "error": { "code": "unauthorized", "message": "Invalid or revoked API key." } }
| HTTP | code | meaning |
|---|---|---|
| 401 | unauthorized |
missing / invalid / revoked key |
| 403 | insufficient_scope |
key lacks the required scope |
| 404 | not_found |
resource does not exist |
| 409 | not_connected |
tenant has no connected WhatsApp account |
| 422 | validation / invalid_recipient / flow_not_registered |
bad request |
| 429 | rate_limited |
too many requests (see Retry-After) |
| 502 | meta_send_failed / meta_upload_failed |
WhatsApp/Meta rejected the request |
Endpoints
List accounts — GET /accounts
Lists the tenant's connected WhatsApp business numbers so you can pick which one to send from. No scope required (returns only your own account metadata).
{ "data": [
{ "id": 3, "phone_number_id": "1099...", "display_phone_number": "15551234567",
"waba_id": "1088...", "is_active": true }
] }
Send a message — POST /messages · scope messages:send
Every send requires waba_id — Meta's WhatsApp Business Account id
(string) — naming which connected number to send from. List your waba_ids at
GET /accounts. An unknown or disconnected value returns 422 unknown_account.
// Text — waba_id (required) selects the sending account
{ "to": "919812345678", "waba_id": "108812345678901", "type": "text", "message": "Hello from AutoMyra!" }
// Template (Meta template payload)
{
"to": "919812345678",
"waba_id": "108812345678901",
"type": "template",
"template": {
"name": "order_update",
"language": { "code": "en_US" },
"components": [
{ "type": "body", "parameters": [ { "type": "text", "text": "1234" } ] }
]
}
}
// Flow (by local flow id)
{ "to": "919812345678", "waba_id": "108812345678901", "type": "flow", "flow_id": 7, "flow_cta": "Open Form" }
201 Created → a message object (see schema below). Outside the 24-hour customer
service window only template messages are deliverable.
Template components
The template object is forwarded to Meta unchanged, so it uses Meta's Cloud API
shape. Add one entry to components for each part that has a variable — a
header, the body, and/or individual buttons. Parts without variables are
omitted. Parameter count and order must match the approved template or Meta
rejects the send (502 meta_send_failed). List a template's expected parts with
GET /templates (each returns a variables map: placeholder count per part).
Header — the type must match how the template was approved.
// Text header ({{1}} in the header)
{ "type": "header", "parameters": [ { "type": "text", "text": "Diwali Sale" } ] }
// Image header — upload via POST /media first, then pass the returned id
// (a public "link" works too: { "image": { "link": "https://.../banner.jpg" } })
{ "type": "header", "parameters": [
{ "type": "image", "image": { "id": "MEDIA_ID" } }
] }
// Video header
{ "type": "header", "parameters": [
{ "type": "video", "video": { "id": "MEDIA_ID" } }
] }
// Document header — "filename" is optional; when omitted AutoMyra fills it from the
// uploaded file's name (falling back to "<template>.pdf") so it isn't shown as "Untitled".
{ "type": "header", "parameters": [
{ "type": "document", "document": { "id": "MEDIA_ID", "filename": "invoice.pdf" } }
] }
// Location header
{ "type": "header", "parameters": [
{ "type": "location", "location": {
"latitude": "22.5726", "longitude": "88.3639",
"name": "Our Store", "address": "Park Street, Kolkata"
} }
] }
Body — positional {{1}}, {{2}}… in order. Text is the common case; typed
parameters (currency, date-time) are also supported.
{ "type": "body", "parameters": [
{ "type": "text", "text": "Anil" },
{ "type": "text", "text": "1234" },
{ "type": "currency", "currency": { "fallback_value": "₹1,200", "code": "INR", "amount_1000": 1200000 } },
{ "type": "date_time", "date_time": { "fallback_value": "July 3, 2026" } }
] }
Buttons — one component per button, addressed by its 0-based index (template
order). Only buttons that carry a variable need a component.
// Dynamic URL button (template button URL ends in {{1}}) — supply the suffix
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [ { "type": "text", "text": "order/1234" } ] }
// Quick-reply button — payload string echoed back to your webhook when tapped
{ "type": "button", "sub_type": "quick_reply", "index": "0",
"parameters": [ { "type": "payload", "payload": "CONFIRM_ORDER_1234" } ] }
// Copy-code (coupon) button
{ "type": "button", "sub_type": "copy_code", "index": "0",
"parameters": [ { "type": "coupon_code", "coupon_code": "DIWALI20" } ] }
Full example — image header + body variables + dynamic URL button:
{
"to": "919812345678",
"type": "template",
"template": {
"name": "order_shipped",
"language": { "code": "en_US" },
"components": [
{ "type": "header", "parameters": [
{ "type": "image", "image": { "id": "MEDIA_ID" } }
]},
{ "type": "body", "parameters": [
{ "type": "text", "text": "Anil" },
{ "type": "text", "text": "1234" }
]},
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [ { "type": "text", "text": "track/1234" } ] }
]
}
}
Get message status — GET /messages/{id} · scope messages:read
{id} is the AutoMyra message id or the Meta wa_message_id. Returns the message
object with its current status (sent → delivered → read, or failed).
Upload media — POST /media · scope messages:send
multipart/form-data with a file field. Returns { "media_id": "..." } — use it
as an image/document parameter id in a subsequent template/media send.
List templates — GET /templates?status=APPROVED · scope templates:read
Returns approved (sendable) templates by default; ?status=all widens the list.
Each item includes components and a variables map (placeholder count per part).
Conversations — scope conversations:read
GET /conversations?search=&per_page=— paginated threads, newest first.GET /conversations/{id}?per_page=— a thread plus a page of messages (oldest → newest).
Webhook endpoints — scope webhooks:manage
GET /webhook-endpointsPOST /webhook-endpoints— body{ url, events?, secret? }. Returns the endpoint and the signing secret once (generated if you don't supply one).DELETE /webhook-endpoints/{id}
Message object
{
"id": 42,
"wa_message_id": "wamid.HBg...",
"conversation_id": 7,
"account": "15551234567",
"direction": "outbound",
"type": "text",
"status": "sent",
"text": "Hello from AutoMyra!",
"error": null,
"error_code": null,
"sent_at": "2026-07-01T10:00:00+00:00",
"delivered_at": null,
"read_at": null,
"failed_at": null,
"created_at": "2026-07-01T10:00:00+00:00"
}
Outbound webhooks
Register callback URLs to receive events as they happen. Each event is delivered as
a JSON POST:
{
"event": "message.received",
"data": { /* message object, same schema as above */ },
"created_at": "2026-07-01T10:00:05+00:00"
}
Events
message.received— a new inbound message from a customer.message.status— an outbound message's status changed (sent/delivered/read/failed).
Signature verification — every delivery carries headers:
X-Automyra-Event: message.received
X-Automyra-Signature: sha256=<hex hmac of the raw body, keyed with your endpoint secret>
Verify it before trusting the payload. Node example:
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}
Delivery is retried with backoff on non-2xx responses; an endpoint that keeps
failing is automatically disabled. Respond 2xx quickly to acknowledge.
Managing keys (authenticated AutoMyra session)
For building an in-app settings screen. These use the normal session/Sanctum auth
and the {tenant} prefix, guarded by the WHATSAPP-API-ACCESS permission:
GET /api/{tenant}/whatsapp/api-credentialsPOST /api/{tenant}/whatsapp/api-credentials—{ name, scopes?, rate_limit? }; returns the plaintext key once.DELETE /api/{tenant}/whatsapp/api-credentials/{id}— revoke.