AutoMyra Developer Docs ← Back to automyra.in

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 (sentdeliveredread, 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

Webhook endpoints — scope webhooks:manage


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

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: