# Flows with the API

> Create draft WhatsApp Flows from premades or JSON, validate, change, publish and deprecate them, and set their live data, with an Avox API key.

Source: https://docs.theavoxai.com/avox-api/flows

Make [WhatsApp Flows](https://docs.theavoxai.com/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](https://docs.theavoxai.com/tables) through [Avox's endpoint](https://docs.theavoxai.com/flows/live-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](https://docs.theavoxai.com/avox-api/builder).

The JavaScript examples use the `avox()` helper from [Builder API reference](https://docs.theavoxai.com/avox-api/reference).

## How a Flow is made

**1. 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`.

**2. 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.

**3. 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`.

### GET /api/v1/flows

Query: `status`, `limit` (1 to 100), `cursor`.

**curl**

```bash
curl "https://api.avox.com/api/v1/flows?status=published" -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { data } = await avox("/flows?status=draft");
```

### POST /api/v1/flows

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](https://docs.theavoxai.com/flows/write-with-ai)
for the JSON.

**curl**

```bash
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" }'
```

**JavaScript**

```js
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`.

### POST /api/v1/flows/validate

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**

```bash
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
```

**JavaScript**

```js
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": { ... } }`.)

### GET /api/v1/flows/\{flowId\}

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**

```bash
curl https://api.avox.com/api/v1/flows/FLOW_ID -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { data } = await avox(`/flows/${flowId}`);
```

### PATCH /api/v1/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**

```bash
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)" }'
```

**JavaScript**

```js
await avox(`/flows/${flow.id}`, { method: "PATCH", headers: { "If-Match": flow.etag }, body: JSON.stringify({ flowJson: fixedJson }) });
```

### POST /api/v1/flows/\{flowId\}/validate

Runs the same checks as `POST /api/v1/flows/validate` on the Flow as saved (with its live parts).
Nothing changes.

**curl**

```bash
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/validate -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { data } = await avox(`/flows/${flowId}/validate`, { method: "POST" });
```

### POST /api/v1/flows/\{flowId\}/publish

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**

```bash
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/publish -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { ok, error } = await avox(`/flows/${flowId}/publish`, { method: "POST" });
```

### POST /api/v1/flows/\{flowId\}/deprecate

Retires a published Flow (it stops opening). Needs **Delete Flows**.

**curl**

```bash
curl -X POST https://api.avox.com/api/v1/flows/FLOW_ID/deprecate -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
await avox(`/flows/${flowId}/deprecate`, { method: "POST" });
```

### DELETE /api/v1/flows/\{flowId\}

Deletes a draft at WhatsApp and in Avox (a published Flow answers `flow_not_draft`: deprecate it
instead). Needs **Delete Flows**.

**curl**

```bash
curl -X DELETE https://api.avox.com/api/v1/flows/FLOW_ID -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
await avox(`/flows/${flowId}`, { method: "DELETE" });
```

## Live data

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](https://docs.theavoxai.com/flows/live-endpoint); the schema is in
[/openapi.json](https://docs.theavoxai.com/openapi.json). Starting from a live premade Flow is the easiest way to get them
right.

### GET /api/v1/flows/\{flowId\}/live

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**

```bash
curl https://api.avox.com/api/v1/flows/FLOW_ID/live -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { data } = await avox(`/flows/${flowId}/live`);
```

### PUT /api/v1/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**

```bash
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
```

**JavaScript**

```js
await avox(`/flows/${flowId}/live`, { method: "PUT", body: JSON.stringify({ config }) });
```

### POST /api/v1/flows/\{flowId\}/live/mode

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**

```bash
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" }'
```

**JavaScript**

```js
await avox(`/flows/${flowId}/live/mode`, { method: "POST", body: JSON.stringify({ action: "new_version", mode: "avox", config }) });
```

## Logs and answers

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).

### GET /api/v1/flows/\{flowId\}/logs

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**

```bash
curl "https://api.avox.com/api/v1/flows/FLOW_ID/logs?outcome=errors&limit=25" -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { data } = await avox(`/flows/${flowId}/logs?outcome=errors`);
```

### GET /api/v1/flows/\{flowId\}/sends

Every send of the Flow (by automations or the API) and how far it got: sent, delivered, read,
completed, failed. Also `status`.

**curl**

```bash
curl "https://api.avox.com/api/v1/flows/FLOW_ID/sends?limit=50" -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
const { data } = await avox(`/flows/${flowId}/sends`);
```

### GET /api/v1/flows/\{flowId\}/responses

The answers people sent, newest first, under the questions' labels (`columns`).

**curl**

```bash
curl "https://api.avox.com/api/v1/flows/FLOW_ID/responses?limit=100" -H "Authorization: Bearer $AVOX_API_KEY"
```

**JavaScript**

```js
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);
```

## Errors you might see

| Code | What to do |
| --- | --- |
| [`flow_invalid`](https://docs.theavoxai.com/api/errors#flow_invalid) | Fix each item in `details` (the JSON or the live settings) and try again. |
| [`meta_refused`](https://docs.theavoxai.com/api/errors#meta_refused) | WhatsApp's findings are in `details`; fix the JSON and try again. |
| [`flow_not_draft`](https://docs.theavoxai.com/api/errors#flow_not_draft) | Published Flows can't change: create a new Flow. |
| [`flow_live_setup_needed`](https://docs.theavoxai.com/api/errors#flow_live_setup_needed) | Create the table `details` names, or do the dashboard step it names. |
| [`flow_endpoint_not_public`](https://docs.theavoxai.com/api/errors#flow_endpoint_not_public) | Contact Avox support. |
| [`whatsapp_not_connected`](https://docs.theavoxai.com/api/errors#whatsapp_not_connected) | Connect or reconnect a number in the dashboard. |
| [`plan_limit_reached`](https://docs.theavoxai.com/api/errors#plan_limit_reached) | Delete or deprecate a Flow, or move to a bigger plan. |

## Related
