API Reference · v1.0.0

aulm API

WhatsApp messaging over HTTP — QR/session based, no Meta approval required.

Notice: AULM is not the official WhatsApp Business Platform

AULM is built on an unofficial WhatsApp Web session layer. We are not affiliated with Meta or WhatsApp and we do not provide the WhatsApp Business API (WABA). Use the service only for legitimate, opt-in communication. Spam, bulk marketing, or unusual sending behaviour may result in your number being restricted by WhatsApp — we do not guarantee delivery, ban-immunity, or Meta approval. For official business use cases, follow Meta's WhatsApp Business API requirements.

Introduction

aulm is a WhatsApp messaging API. You connect a phone by scanning a QR code in the dashboard, then send messages and receive inbound events over HTTP. There is no Meta business verification, no template approval, no WABA.

Why aulm

  • Refund on failure. Transient upstream failures refund your quota slot automatically.
  • Traceable. Every response carries x-request-id. Log it; quote it.
  • Honest rate limits. Send endpoints stamp retry-after on every 429.
  • Signed webhooks. Published retry ladder — delivery is our problem, not yours.

Set up with AI

Developer AI assistants (Claude, ChatGPT, Cursor) work best when given a clear specification. Copy the prompt below into your assistant to get correct, production-ready aulm code for your project immediately.

AI System Prompt

You are helping me integrate the aulm WhatsApp API into my project...

ABOUT

aulm sends WhatsApp messages via an HTTP API. It is unofficial...

Recommended for Cursor, Claude 3.5 Sonnet, & GPT-4o

Authentication

Every request takes a Bearer token in the Authorization header. Keys are generated in the dashboard and match the regex ^alm_sk_(live|test)_[0-9a-f]{64}$.

  • alm_sk_live_… — real sends. Billable. Counts against your plan quota.
  • alm_sk_test_… — every call is simulated. The messaging service is never invoked, no WhatsApp message is sent, the response shape is identical to live with simulated: true and message_id shaped sim_<hex>. Test sends are never billed.

Keys may also be restricted to specific session IDs. Requests against a non-whitelisted session return 403 session_not_allowed.

bash
Authorization: Bearer alm_sk_live_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

Quickstart — first message in under 60 seconds

1

Connect a session.

Go to the dashboard → Sessions → New session → scan the QR code with the WhatsApp app on the phone you want to send from. Wait until status is connected.

2

Grab the session ID and an API key.

Copy the session UUID from the dashboard. Generate an alm_sk_test_… key first — you can ship the same code with alm_sk_live_… later.

3

Send.

bash
curl -X POST https://api.aulm.io/v1/messages/send-text \
-H "Authorization: Bearer alm_sk_test_..." \
-H "Content-Type: application/json" \
-d '{"session_id":"<SESSION_ID>","to":"+14155551234","body":"Hello!"}'

Sessions are dashboard-only.

There is no public API endpoint to create a session, pair it via QR, or disconnect it. All session lifecycle is managed in the aulm dashboard. The API only operates on sessions that are already connected. GET /v1/sessions/{id}/status is read-only.

Base URL & versioning

All endpoints are served from a single base URL. The path is versioned with /v1/. Breaking changes get a new path prefix; additive changes (new optional fields, new response headers) ship under /v1/.

text
https://api.aulm.io

Responses & request IDs

All /v1/* responses are JSON. Success bodies use { data: ..., request_id: ... }; errors use { error: { code, message } }.

Every response — success or error — sets x-request-id. Log it on your side and quote it to support; we can find the exact send in one query.

Send endpoints additionally stamp x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset (ISO 8601), and retry-after on 429s and on upstream-overload 503s.

Platform headers to ignore

x-deployment-idInjected by the hosting edge on 2xx responses. Not part of the aulm contract — ignore.

Reliability

When a send fails, two things matter: did it cost you a quota slot, and should you retry the same request. aulm publishes both answers explicitly.

Quota refund matrix

What happens to your reserved quota slot when a send fails. Transient = refunded and not billed. Client error = slot kept.

ConditionStatuserror.codeRefunded?Billable?Retry?
Upstream timeout504messaging_service_timeoutYesNoYes, same request
Network / unreachable502messaging_service_unreachableYesNoYes, same request
Upstream 5xx502messaging_service_errorYesNoYes, same request
Upstream auth failure502messaging_service_errorYesNoYes (ops alerted)
Upstream 429 (overload)503service_unavailableYesNoYes, honor Retry-After
Recipient unreachable (upstream 404)404invalid_requestNoYesNo — fix recipient
Session not ready (upstream 409/412/422)409session_not_readyNoYesNo — connect session first
Other client error (4xx)400invalid_requestNoYesNo — fix request

Webhook retries

After a failed attempt, the next attempt is scheduled per this table. Attempt count is 1-indexed and matches `x-aulm-attempt`. `Retry-After` from your response overrides the ladder (capped at 1h).

Attempt just failedNext retry
11 minute
25 minutes
330 minutes
42 hours
512 hours
6Dead-lettered
  • Default max attempts per delivery: 6.
  • Endpoint auto-disabled after 25 consecutive failures (across deliveries).
  • Per-attempt HTTP timeout: 10s.
  • Target first-delivery latency: ~60s.

Errors

Errors always come back as { error: { code, message } }. The HTTP status tells you the broad class; the error.code tells you the exact cause. See the full error-code index at the bottom of this page.

json
{
"error": {
"code": "session_not_ready",
"message": "Session is not ready to send messages. Connect the session first."
}
}

Send a text message

POST/v1/messages/send-text
Sends a WhatsApp text message from a connected session to a phone number in E.164 format. Consumes one quota slot. If the messaging service fails transiently (5xx / timeout / unreachable / upstream 429), the slot is refunded automatically and the send is not billable. See the `x-aulm-reliability.quota_refund_matrix` extension at the bottom of this spec for the full matrix. **Test mode:** if the API key is a `test` key or the session is a test fixture, the call is simulated — no WhatsApp message is sent, the response status is `201`, `simulated: true`, and the `message_id` is shaped `sim_<hex>`.

Responses

  • 201Message accepted by the messaging service.
  • 400
  • 401
  • 402
  • 403
  • 404
  • 409
  • 429
  • 500
  • 502
  • 503
  • 504
bash
curl -X POST https://api.aulm.io/v1/messages/send-text \
-H "Authorization: Bearer $AULM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab",
"to": "+14155551234",
"body": "Hello from aulm!"
}'
Example response
json
{
"data": {
"message_id": "true_14155551234@c.us_3EB0...",
"session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab",
"to": "+14155551234",
"status": "sent",
"simulated": false
},
"request_id": "9c8b7a6d-1234-4abc-def0-112233445566"
}

Send an image message

POST/v1/messages/send-image
Sends a WhatsApp image with an optional caption. The `image` field accepts either: - A base64 data URI: `data:image/(png|jpeg|gif|webp);base64,...` The encoded payload is capped at roughly **5 MB of decoded image** (the raw request field has an absolute character cap of ~8 MB to allow for base64 overhead). - An `https://` URL pointing to a publicly resolvable host. The server probes the URL's `Content-Type` and refuses non-image responses. **SSRF protection:** private, loopback, and link-local addresses are rejected. Same quota, refund, and test-mode behavior as `/v1/messages/send-text`.

Responses

  • 201Image accepted by the messaging service.
  • 400
  • 401
  • 402
  • 403
  • 404
  • 409
  • 429
  • 500
  • 502
  • 503
  • 504
bash
curl -X POST https://api.aulm.io/v1/messages/send-image \
-H "Authorization: Bearer $AULM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab",
"to": "+14155551234",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
"caption": "Receipt attached"
}'
Example response
json
{
"data": {
"message_id": "true_14155551234@c.us_3EB0...",
"session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab",
"to": "+14155551234",
"status": "sent",
"simulated": false
},
"request_id": "9c8b7a6d-1234-4abc-def0-112233445566"
}

Get session connection status

GET/v1/sessions/{id}/status
Returns the cached connection status for a session owned by the caller's API key. Does **not** consume quota and does **not** call the messaging service — it reads aulm's cached row. Sessions themselves are created and QR-paired in the dashboard. There is no public endpoint to create a session.

Responses

  • 200Current session status.
  • 400
  • 401
  • 403
  • 404
  • 500
bash
curl https://api.aulm.io/v1/sessions/<SESSION_ID>/status \
-H "Authorization: Bearer $AULM_API_KEY"
Example response
json
{
"data": {
"session_id": "f1e2d3c4-5678-4abc-9def-0123456789ab",
"status": "connected"
}
}

Webhooks

Register a single HTTPS endpoint in the dashboard. aulm POSTs JSON events to it with a signed header, retries failures per the published ladder, and gives you a stable x-aulm-delivery-id for idempotency.

Request headers

x-aulm-eventEvent name: inbound_message or message_status.
x-aulm-delivery-idUUID for this logical event. Use as your idempotency key — the same value may arrive across retries.
x-aulm-signaturet=<unix>,v1=<hex>. HMAC_SHA256(secret, `${t}.${rawBody}`). Verify over the raw bytes.
x-aulm-attempt1-indexed attempt number.

Verifying the signature

Always verify over the raw request body bytes — do not re-serialize the JSON.

javascript
const crypto = require("crypto");

function verifyAulmSignature(rawBody, header, secret) {
  // header looks like: "t=1716893742,v1=abc123..."
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  // timing-safe compare
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1 ?? "", "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Events

EVENTx-aulm-event: inbound_message

A WhatsApp message was received on a connected session.

Delivered to your registered webhook endpoint. The `x-aulm-event` request header equals `inbound_message`. Test deliveries triggered from the dashboard include `"test": true` at the top level of the body.

Payload
json
{
"event": "inbound_message",
"delivery_id": "00000000-0000-4000-8000-000000000000",
"timestamp": "2026-05-28T10:15:42.000Z",
"session_id": "00000000-0000-4000-8000-000000000000",
"test": false,
"message": {
"id": "string",
"from": "string",
"to": "string",
"chat_id": "string",
"type": "string",
"body": "string",
"is_group": false,
"from_me": false,
"media_url": "https://example.com/...",
"received_at": "2026-05-28T10:15:42.000Z"
}
}
EVENTx-aulm-event: message_status

Delivery status changed for a message you sent.

Delivered to your registered webhook endpoint. The `x-aulm-event` request header equals `message_status`. The `status` field is one of: `sent`, `delivered`, `read`, `played`, `failed`.

Payload
json
{
"event": "message_status",
"delivery_id": "00000000-0000-4000-8000-000000000000",
"timestamp": "2026-05-28T10:15:42.000Z",
"session_id": "00000000-0000-4000-8000-000000000000",
"message_id": "string",
"status": "sent",
"from": "string",
"to": "string",
"status_at": "2026-05-28T10:15:42.000Z"
}

Plans & limits

Sessions per plan

PlanActive sessionsMessage limit
trial120 total across a fixed 7-day window (not per day)
starter11000 / day (UTC)
pro210000 / day (UTC)
business6100000 / day (UTC)

Trial is a fixed 7-day window with a monotonic counter — it ends at whichever comes first: hitting the cap or 7 days from signup. Daily plans reset at next UTC midnight.

Error-code index

Statuserror.codeMeaningQuota slot
400invalid_jsonRequest body is not valid JSON.
400invalid_requestValidation failed (missing field, bad phone, bad image).
401missing_authorizationNo Authorization header.
401invalid_authorizationAuthorization header isn't Bearer scheme.
401invalid_api_keyKey does not match any active record.
401api_key_revokedKey was revoked or soft-deleted.
401api_key_expiredKey passed its expiry date.
401account_deactivatedCustomer account is soft-deleted.
402subscription_requiredNo active plan and trial is over.
403session_not_allowedAPI key is restricted from this session.
404session_not_foundSession UUID doesn't exist for this account.
404invalid_requestRecipient unreachable on the messaging service.Kept
409session_not_readySession exists but is not connected.Kept
429trial_quota_exceededHit your trial cap. See x-ratelimit-reset.
429daily_quota_exceededHit your daily cap. Resets at next UTC midnight.
500internal_errorServer-side failure. Quote x-request-id to support.
502messaging_service_unreachableNetwork failure to upstream.Refunded
502messaging_service_errorUpstream returned 5xx or auth-failed.Refunded
503service_unavailableUpstream overload. Honor Retry-After.Refunded
504messaging_service_timeoutUpstream did not respond in time.Refunded