Follow Avox Docs on Google
Pick Avox Docs as a preferred source and Google shows these pages more often when you search for help with Avox.
Follow Avox Docs on Google
Pick Avox Docs as a preferred source and Google shows these pages more often when you search for help with Avox.
The builder API lets your own code, or an AI assistant you trust, do what you do in the dashboard's Tables, Flows, Templates and Automation pages: create a table and fill it, write a template and send it to WhatsApp for review, make and publish a Flow, build an automation and switch it on. It uses the same Avox API keys as sending messages, with permissions you choose per key.
Create a key with the permissions you need
In Settings > API Keys, click Create a key, pick the number, name the key, and under What this key may do set each area to Off, Read or Write. You can only give a key what you may do yourself. Confirm with the code we email you, then copy the key: it's shown once.
Check the key
Call GET /api/v1/me. It shows the key's Switchboard, permissions and every limit with what's used.
Build
Create a table, add rows, and carry on with templates, Flows and automations. Each area has its own page: Tables and rows, Flows, Templates, Automations.
curl https://api.avox.com/api/v1/me \
-H "Authorization: Bearer $AVOX_API_KEY"
curl -X POST https://api.avox.com/api/v1/tables \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3c1d6c4e-2f7a-4f0b-9e3a-1b2c3d4e5f60" \
-d '{
"name": "Price list",
"columns": [
{ "key": "item", "name": "Item", "type": "text", "required": true, "unique": true },
{ "key": "price", "name": "Price", "type": "currency", "required": true, "options": { "currency": "INR" } }
]
}'
const API = "https://api.avox.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.AVOX_API_KEY}`, "Content-Type": "application/json" };
const me = await (await fetch(`${API}/me`, { headers })).json();
console.log(me.data.key.scopes, me.data.limits.tables);
const res = await fetch(`${API}/tables`, {
method: "POST",
headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({
name: "Price list",
columns: [
{ key: "item", name: "Item", type: "text", required: true, unique: true },
{ key: "price", name: "Price", type: "currency", required: true, options: { currency: "INR" } },
],
}),
});
const { ok, data, error } = await res.json();
if (!ok) throw new Error(`${error.code}: ${error.message}`);
console.log(data.table.id, data.table.etag);
Each key holds a list of scopes. Keys made before scopes existed hold send only, so nothing an
existing key could do changes.
A write scope includes reading the same area. GET /api/v1/me and GET /api/v1/catalog work
with any key.
A key acts with the permissions of the person who last set its scopes. If that person leaves the
Switchboard or loses a permission (for example Create and edit tables), the key's calls in
that area answer scope_owner_no_access until an Admin sets its permissions again. Deleting
needs the matching delete permission too (Delete templates, Delete Flows,
Delete automations).
https://api.avox.com/api/v1. Send Authorization: Bearer <key> on every
request, and JSON bodies with Content-Type: application/json.flowJson, triggerType, updatedAt).{ "ok": true, "request_id": "req_...", "data": { ... } }, plus next_cursor on
lists and warnings (things to know, nothing failed) when there are some.{ "ok": false, "error": { "code", "message", "docs_url", "request_id", "details" } }.
details names each field to fix (path) with a message, and sometimes a short code. Every
code is on API errors.slug), columns by their key. Ids
from another Switchboard never match: they answer not_found.Lists take limit and cursor. Pass the next_cursor of one page as cursor to get the next;
next_cursor is null on the last page. A cursor belongs to its list and filters: changing the
filters with an old cursor answers invalid_cursor.
Every table, Flow, template and automation answer carries an ETag header (and an etag field).
To change something safely, send it back as If-Match. If anyone changed the item since you read
it, the change is refused with 412 version_conflict and nothing is written. Read it again and
retry. Rows use their updatedAt: send it with each row you change (rows[].updatedAt), and the
whole request is refused if any of those rows changed.
Every create (tables, rows, row deletes, CSV imports, Flows, templates, automations) accepts an
Idempotency-Key header: 1 to 255 visible characters, a UUID per new item works well. Retrying
with the same key and the same body returns the first answer (with Idempotent-Replayed: true)
and makes nothing twice. The same key with a different body answers idempotency_conflict.
Keys last 24 hours and belong to the key and its rotations. See
Idempotency.
GET /api/v1/me shows them; every answer has Avox-Daily-Limit and
Avox-Daily-Remaining headers. Over the limit: builder_quota_exceeded with Retry-After until
midnight UTC. Sending messages doesn't count.plan_limit_reached.rate_limited with Retry-After.payload_too_large.See Plans for what each plan includes.
A test key (avx_test_...) reads and checks for real (/me, /catalog, lists, validate, a CSV
import dry_run) and is refused every change (test_key_read_only). Use one to build and check
your code, then switch to a live key.
Connecting numbers, billing, team members, API keys and their permissions, uploading a sample file for a template's media header, a Flow's own-server signing secret, and connecting apps.
Last updated