Send WhatsApp messages from your own server with a connection-scoped Avox API key - overview, request flow, and a 5-minute quickstart.
The Avox API lets your own server send WhatsApp messages through a number you've connected to Avox. You call one endpoint with an Avox API key, and Avox sends the message to WhatsApp for you. Your server never needs Meta's own access token - that stays inside Avox.
POST https://api.avox.com/api/messages/send
code you can branch on, with a link to its
explanation on API errors.Idempotency-Key header and a retried request never sends the same
message twice. See Idempotency.avx_live_...) sends real messages. A test key
(avx_test_...) runs every check and answers with a pretend result, so you can build without
messaging anyone. See Testing.You need a WhatsApp number connected to Avox (see Getting started) and an active plan that includes API access - the plan cards on Plans list API access on the plans that have it. Then:
Create an API key
In the dashboard, open Settings > API Keys and click Create a key. In Create an API key, pick the Switchboard and the WhatsApp number, choose Live key (or Test key while you build), give it a Key name (2 to 60 characters) and, if you like, What it's for. Click Email me a code and enter the 6-digit code we email you. You can also create a key from a connection's own page. Creating keys needs an Admin (or a member with the Manage API keys permission) - see API keys for the full walkthrough.
Copy the key right away
The dialog Your new API key shows the full key exactly once. A live key starts with
avx_live_, a test key with avx_test_. Copy it and click Done. If you lose it, rotate
the key or create a new one - a key can't be shown again.
Store it as a server secret
Put it in an environment variable (for example AVOX_API_KEY) or your secrets manager on the
server that will send messages. Never put it in a browser page or a mobile app.
Check the key
Back in Settings > API Keys, click Test next to the key. It confirms the key works and which number it sends from, without sending a message. See Testing.
Send your first message
Run one of the examples below with your own WhatsApp number as to. Your number must have
messaged the connected number in the last 24 hours for a plain text message to go through -
otherwise send an approved template. See Messaging window.
See it in the audit trail
Open API logs in the dashboard's sidebar. Your send is at the top of the Sent tab with
its key (and a Live or Test badge), status 200, the recipient's number partly hidden,
and its request id. Click the row for Request details. The arrow next to a key in
Settings > API Keys also shows that key's Recent requests.
curl -X POST https://api.avox.com/api/messages/send \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1d2c1e-8a4b-4c7e-9a51-3b0f2d9e7c10" \
-d '{
"to": "15551234567",
"type": "text",
"text": { "body": "Hello from Avox!" }
}'
import { randomUUID } from "node:crypto";
const res = await fetch("https://api.avox.com/api/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AVOX_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
to: "15551234567",
type: "text",
text: { body: "Hello from Avox!" },
}),
});
const data = await res.json();
if (data.ok) {
console.log("Sent:", data.message_id);
} else {
console.error(res.status, data.error.code, data.error.message);
}
import os
import uuid
import requests
res = requests.post(
"https://api.avox.com/api/messages/send",
headers={
"Authorization": f"Bearer {os.environ['AVOX_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"to": "15551234567",
"type": "text",
"text": {"body": "Hello from Avox!"},
},
timeout=30,
)
data = res.json()
if data["ok"]:
print("Sent:", data["message_id"])
else:
print(res.status_code, data["error"]["code"], data["error"]["message"])
A successful send returns 200:
{
"ok": true,
"request_id": "req_4f1c9a0b7e2d36a8c5b1f0e9",
"message_id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI5",
"result": {
"messaging_product": "whatsapp",
"contacts": [{ "input": "15551234567", "wa_id": "15551234567" }],
"messages": [{ "id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI5" }]
}
}
Keep message_id. It's WhatsApp's id for the message, and every later status update (sent,
delivered, read, failed) carries the same id. See
Delivery statuses and replies.
The body always has to (the recipient's full international number) and type, plus one object
named after the type:
{
"to": "15551234567",
"type": "template",
"template": {
"name": "order_update",
"language": { "code": "en_US" },
"components": [
{ "type": "body", "parameters": [{ "type": "text", "text": "A-1042" }] }
]
}
}
The accepted types are text, template, image, video, audio, document, sticker,
location, contacts, interactive and reaction. Avox adds messaging_product for you, and
any top-level field it doesn't recognise is dropped rather than passed on to WhatsApp.
Every request goes through the same checks, in this order, and stops at the first one that fails. The error you get back is always the most basic thing to fix first.
Size and format
The body must be a JSON object under 64 KB. Otherwise:
payload_too_large or
invalid_payload.
Your key
The Authorization: Bearer header must hold a real key that hasn't been revoked or expired,
used from an allowed address. Only API keys work here - being signed in to the dashboard
doesn't count. Codes: missing_api_key,
invalid_api_key, key_revoked,
key_expired, ip_not_allowed.
The key's number
The key sends only from the number it was created for. That number must still exist, be
connected, and not be paused. Codes:
connection_not_found,
connection_paused,
connection_inactive.
Your account
API access must be on, the account in good standing, and the plan active (not paused) and one
that includes API access. While a failed payment is still being retried, sending keeps working.
Codes: api_access_disabled,
account_suspended,
subscription_inactive,
api_not_in_plan.
Limits
Each key and each account can only send so fast, and some keys have a daily allowance. Codes:
rate_limited, quota_exceeded.
See Rate limits.
The message
The recipient must be a valid international number and the message valid for its type. Codes:
invalid_recipient,
unsupported_message_type,
invalid_payload.
Idempotency
If you sent an Idempotency-Key, Avox checks whether it has seen it before. A repeat of a
finished request returns the first result without sending again. Codes:
invalid_idempotency_key,
idempotency_conflict,
idempotency_in_progress.
Sent to WhatsApp
Only now is the message handed to WhatsApp. If WhatsApp refuses it, you get a clear code such
as template_not_found or
outside_messaging_window instead of WhatsApp's raw
reply, plus WhatsApp's own number in meta_code. A test key stops here with a pretend result
instead - see Test mode.
Once Avox has matched a request to one of your keys (even a revoked or expired one), its outcome,
successful or not, is written to the API logs, so you can look
up its request_id later. A request that never names a real key can't be tied to your account,
so it isn't listed.
Every response, successful or not, carries two headers:
X-Request-Id - a unique id for this request, like req_4f1c9a0b7e2d36a8c5b1f0e9. Log it, and
include it if you contact Support. You can paste it into the search box on
API logs to find the request.Cache-Control: no-storeSome responses carry more:
A failed request returns "ok": false and an error object:
{
"ok": false,
"error": {
"code": "outside_messaging_window",
"message": "It's been more than 24 hours since this person last messaged you. Send an approved template instead.",
"docs_url": "https://docs.avox.com/api/errors#outside_messaging_window",
"request_id": "req_9b2e5d7a1c4f80e36d2a7b15",
"meta_code": 131047
}
}
Branch on error.code - codes never change once published. details (which fields to fix) and
meta_code (WhatsApp's own error number) appear only when they apply.
Every code, its causes, and the fix are on API errors.
Integrations written before this error format read error as a plain string. Read
error.message instead, or better, branch on error.code.
Managers start without See API keys and Manage API keys; an Admin can give either one to a Manager person by person - see Permissions. See Members and roles for what each role can do elsewhere in the dashboard.
API access depends on your plan: each plan either includes it or doesn't. On a plan without it, the
dashboard shows "API access isn't included in your plan" with a link to the plans that include it.
Your keys stay listed, but new keys can't be created or rotated, and existing keys stop sending with
api_not_in_plan until your plan includes API access again. A paused
plan has no API access either. See API access and your plan.
A key is send-only. It sends from its own number on this one endpoint, and the response
holds only the outcome: request_id, WhatsApp's message_id and its reply to the send, or an
error with its code. A key can't read your contacts, conversations or anything else in Avox.
Incoming messages reach your server through
Message forwarding instead. The details are on
What a key can do, and what it can't.
Ready-made examples: Node.js, Laravel, Shopify, n8n, Make, Zapier, Chatwoot.
Last updated