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.
Make WhatsApp Flows from your own code or an AI assistant: start from a premade Flow
or your own Flow JSON, check it, change it while it's a draft, then publish it. Live Flows read
and write your Tables through Avox's endpoint, set up exactly
as the dashboard does it. Needs flows:read / flows:write (deleting and deprecating also need
Delete Flows for the person who set the key's permissions). Basics:
Builder API quick start.
The JavaScript examples use the avox() helper from Builder API reference.
Create a draft
POST /api/v1/flows makes the Flow at WhatsApp as a draft and uploads its JSON. It counts
against your plan's Flows. WhatsApp's findings on the JSON come back as validationErrors.
Validate and fix
POST /api/v1/flows/{flowId}/validate runs Avox's checks and WhatsApp's validator. Fix
everything it lists with PATCH /api/v1/flows/{flowId} (a new flowJson, live settings, name
or categories), and validate again.
Publish
POST /api/v1/flows/{flowId}/publish. After that the Flow can't change; a change means a new
Flow (or new_version for live data, below).
A Flow's fields: id, metaFlowId, name, category, status (draft, published,
deprecated, error), liveMode (off, avox, passthrough, direct), liveInit (its first
screen loads data: send it with flow_action: "data_exchange"), premadeId, createdAt,
updatedAt, publishedAt, etag.
Query: status, limit (1 to 100), cursor.
curl "https://api.avox.com/api/v1/flows?status=published" -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox("/flows?status=draft");
Creates a draft Flow, in one of two ways. Send an Idempotency-Key.
From a premade Flow (ids in GET /api/v1/catalog): kind: "template", templateId. A live
premade needs its tables: name them in presetTables (the premade's table ref to your table's
id or API name), or leave it out and Avox uses your newest active table made from the premade
table each one needs. With none, the answer is flow_live_setup_needed and details says which
table to create (POST /api/v1/tables with that premadeId). timeZone sets the time zone of
its date and time values.
From your own JSON: kind: "custom", name, categories (WhatsApp's: SIGN_UP,
SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, SHOPPING, CONTACT_US,
CUSTOMER_SUPPORT, SURVEY, OTHER), flowJson, and optionally live (live data settings,
below) or endpointUri (your own https server). See Write a Flow with any AI
for the JSON.
curl -X POST https://api.avox.com/api/v1/flows \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2a9d6c1e-7f3b-4c8a-b5e4-0d1c2b3a4f5e" \
-d '{ "kind": "template", "templateId": "appointment_booking", "presetTables": { "slots": "time_slots", "bookings": "appointments" }, "timeZone": "Asia/Kolkata" }'
const flowJson = JSON.parse(await fs.promises.readFile("feedback-flow.json", "utf8"));
const { ok, data, error } = await avox("/flows", {
method: "POST",
headers: { "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ kind: "custom", name: "Visit feedback", categories: ["SURVEY"], flowJson }),
});
if (ok && data.validationErrors.length) console.log("Fix before publishing:", data.validationErrors);
Answers 201 with { flow, validationErrors, liveInit }. validationErrors items have a
message, WhatsApp's error name, line and column in the JSON, and pointers.
Checks Flow JSON you send without saving anything: { "flowJson", "live"?, "endpointUri"? }.
Answers { validation: { valid, validationErrors, lint: { errors, warnings, live } } }:
lint.errors are Avox's shape checks (every screen needs an id, a Form and a Footer that does
something), lint.warnings are answers that never reach Avox and passwords sent back into the
chat, lint.live the live data checks, validationErrors WhatsApp's findings. Test keys may use it.
curl -X POST https://api.avox.com/api/v1/flows/validate \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d @flow.json
const { data } = await avox("/flows/validate", { method: "POST", body: JSON.stringify({ flowJson }) });
if (!data.validation.valid) console.log(data.validation);
(flow.json here holds { "flowJson": { ... } }.)
The Flow with its flowJson, its live settings (mode, config, screens with their
inputs and data, issues for links that need attention, checks), Avox's lint and WhatsApp's
own meta status and validation errors.
curl https://api.avox.com/api/v1/flows/FLOW_ID -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox(`/flows/${flowId}`);
Changes a draft (status draft, or error after a failed publish in the dashboard): name,
categories, flowJson (uploaded to WhatsApp again; its findings come back as
validationErrors) and live (the table links; same data source). A published Flow answers
flow_not_draft. Send If-Match with the Flow's ETag.
curl -X PATCH https://api.avox.com/api/v1/flows/FLOW_ID \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H 'If-Match: "Qa8..."' \
-d '{ "name": "Visit feedback (short)" }'
await avox(`/flows/${flow.id}`, { method: "PATCH", headers: { "If-Match": flow.etag }, body: JSON.stringify({ flowJson: fixedJson }) });
Runs the same checks as POST /api/v1/flows/validate on the Flow as saved (with its live parts).
Nothing changes.
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/validate -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox(`/flows/${flowId}/validate`, { method: "POST" });
Publishes a draft. Avox checks first: the JSON's shape, the live data settings against your
tables, and for live Flows on Avox's endpoint, that the endpoint is reachable
(flow_endpoint_not_public otherwise). WhatsApp then checks again (it calls a live Flow's
endpoint first); its findings come back as meta_refused with details.
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/publish -H "Authorization: Bearer $AVOX_API_KEY"
const { ok, error } = await avox(`/flows/${flowId}/publish`, { method: "POST" });
Retires a published Flow (it stops opening). Needs Delete Flows.
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/deprecate -H "Authorization: Bearer $AVOX_API_KEY"
await avox(`/flows/${flowId}/deprecate`, { method: "POST" });
Deletes a draft at WhatsApp and in Avox (a published Flow answers flow_not_draft: deprecate it
instead). Needs Delete Flows.
curl -X DELETE https://api.avox.com/api/v1/flows/FLOW_ID -H "Authorization: Bearer $AVOX_API_KEY"
await avox(`/flows/${flowId}`, { method: "DELETE" });
A live Flow's settings (live): { "version": 1, "mode": "avox", "timeZone": "Asia/Kolkata", "screens": [ { "screenId", "open": [steps], "submit": [steps], "next"? } ] }.
Each step reads or writes one table (list rows, look up a row, check one exists, add or update a
row, add to or take from a number, book a slot, calculate). Table and column ids come from
GET /api/v1/tables/{tableId}. The step shapes and what each does are on
Live Flows and the Avox endpoint; the schema is in
/openapi.json. Starting from a live premade Flow is the easiest way to get them
right.
The live data settings, the screens of the JSON (their inputs and data), links needing attention (a column deleted, a table archived), and the checks.
curl https://api.avox.com/api/v1/flows/FLOW_ID/live -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox(`/flows/${flowId}/live`);
Saves changed table links: { "config": { ... } } with the same mode. They take effect at once,
also on a published Flow; a change its screens can't take (new data a published JSON doesn't
declare) answers flow_needs_new_version.
curl -X PUT https://api.avox.com/api/v1/flows/FLOW_ID/live \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d @live-config.json
await avox(`/flows/${flowId}/live`, { method: "PUT", body: JSON.stringify({ config }) });
Changes where the live data comes from: mode (avox, passthrough with passthroughUrl, or
direct with directUrl) and action: "switch" (drafts) or "new_version" (publishes a new
Flow with the new source and your config, counted against your plan; the old one keeps working
until you deprecate it). Pass-through needs its signing secret, made in the dashboard first.
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/live/mode \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "action": "switch", "mode": "avox" }'
await avox(`/flows/${flowId}/live/mode`, { method: "POST", body: JSON.stringify({ action: "new_version", mode: "avox", config }) });
These take limit (10, 25, 50 or 100) and cursor, plus from and to (times with their
offset, like 2026-10-01T00:00:00+05:30, at most 366 days apart).
Every live data request WhatsApp sent for the Flow: screen, what happened, how long it took,
each step's outcome and the AVX code when something went wrong, plus stats (per day, timings,
errors by code, this month's live requests against your plan). Also outcome (all or
errors) and days (7, 30 or 90).
curl "https://api.avox.com/api/v1/flows/FLOW_ID/logs?outcome=errors&limit=25" -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox(`/flows/${flowId}/logs?outcome=errors`);
Every send of the Flow (by automations or the API) and how far it got: sent, delivered, read,
completed, failed. Also status.
curl "https://api.avox.com/api/v1/flows/FLOW_ID/sends?limit=50" -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox(`/flows/${flowId}/sends`);
The answers people sent, newest first, under the questions' labels (columns).
curl "https://api.avox.com/api/v1/flows/FLOW_ID/responses?limit=100" -H "Authorization: Bearer $AVOX_API_KEY"
let cursor = null;
do {
const page = await avox(`/flows/${flowId}/responses?limit=100${cursor ? `&cursor=${cursor}` : ""}`);
page.data.responses.forEach((r) => console.log(r));
cursor = page.next_cursor;
} while (cursor);
Last updated