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.
Create and change Tables and their rows from your own code or an AI assistant. Every
change is checked exactly as in the dashboard: column types, required and unique columns, your
plan's tables and rows per table. Needs tables:read / tables:write for tables and columns,
and rows:read / rows:write for rows. Basics (keys, paging, versions, retries, limits):
Builder API quick start.
The JavaScript examples use the avox() helper from Builder API reference.
A table's fields: id, name, slug (its API name, usable instead of the id in every path),
description, icon, color, kind (list, calendar, catalog, lookup, custom),
status (active or archived), premadeId, rowCount, columnCount, usedByCount,
createdAt, updatedAt, archivedAt, etag, and with a single table, settings and columns.
A column's fields: id, key (its API name), name, type (see
Column types; GET /api/v1/catalog lists them), required, unique,
defaultValue, options (choices, currency, decimals, formula expression...), description,
position, hidden.
Active and archived tables, newest change first, with limits (maxTables, usedTables,
maxRowsPerTable). Query: status (active or archived), limit (1 to 100, default 50),
cursor.
curl "https://api.avox.com/api/v1/tables?status=active" -H "Authorization: Bearer $AVOX_API_KEY"
const { data, next_cursor } = await avox("/tables?status=active");
Creates a table, in one of two ways. Counts against your plan's tables; send an
Idempotency-Key.
From a premade table (ids in GET /api/v1/catalog): premadeId, and optionally name,
description, withSamples (add the premade's sample rows) and currency (3 letters, for money
columns).
From your own columns: name, columns (1 to 50, each with a key, name and type, and
optionally required, unique, defaultValue, options, description, hidden), and
optionally description, icon, color, kind, currency. Keys are small letters, numbers
and _, starting with a letter; names and keys must be different from each other. A choice
column's options each need an id (o and 6 to 12 small letters or numbers, unique in the
column), a label and a color (gray, brand, coral, sky, violet, emerald, amber,
rose).
curl -X POST https://api.avox.com/api/v1/tables \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5b0f0c8e-9d7a-4f43-8a1e-6c2b3d4e5f70" \
-d '{
"name": "Orders",
"kind": "list",
"columns": [
{ "key": "order_no", "name": "Order number", "type": "text", "required": true, "unique": true },
{ "key": "customer", "name": "Customer", "type": "phone" },
{ "key": "status", "name": "Status", "type": "select", "options": { "choices": [
{ "id": "oplaced01", "label": "Placed", "color": "gray" },
{ "id": "oshipped1", "label": "Shipped", "color": "sky" },
{ "id": "odeliver1", "label": "Delivered", "color": "emerald" }
] } },
{ "key": "total", "name": "Total", "type": "currency", "options": { "currency": "INR" } }
]
}'
const { ok, data, error } = await avox("/tables", {
method: "POST",
headers: { "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ premadeId: "appointments", withSamples: false }),
});
Answers 201 with { table, samplesAdded } and the table's ETag. Errors you may see:
plan_limit_reached (all your plan's tables are in use), invalid_payload (a column's key,
name or type; details points at it, like columns[2].key), name_taken.
One table with its columns. tableId is the id or the API name.
curl https://api.avox.com/api/v1/tables/orders -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox("/tables/orders");
const statusColumn = data.table.columns.find((c) => c.key === "status");
{
"ok": true,
"request_id": "req_...",
"data": {
"table": {
"id": "0b6c...", "name": "Orders", "slug": "orders", "kind": "list", "status": "active", "rowCount": 0,
"etag": "\"Zx3m...\"",
"columns": [{ "id": "5e1a...", "key": "order_no", "name": "Order number", "type": "text", "required": true, "unique": true, "defaultValue": null, "options": {}, "position": 0, "hidden": false }]
}
}
}
Changes name, description, icon, color, kind, slug (the API name: small letters,
numbers and _) and settings. Archived tables can be renamed. Send If-Match with the
table's ETag to be sure nobody changed it since you read it.
curl -X PATCH https://api.avox.com/api/v1/tables/orders \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H 'If-Match: "Zx3m..."' \
-d '{ "name": "Online orders", "description": "Orders from the website" }'
await avox(`/tables/${table.id}`, { method: "PATCH", headers: { "If-Match": table.etag }, body: JSON.stringify({ name: "Online orders" }) });
Deletes the table, its rows and its "Used by" links. While Flows, automations or templates use
it, the answer is in_use with the list in details; add ?force=true to delete anyway (they
stop finding it). Ask the person first.
curl -X DELETE https://api.avox.com/api/v1/tables/orders -H "Authorization: Bearer $AVOX_API_KEY"
await avox("/tables/orders", { method: "DELETE" });
Archives a table: it can still be read (Flows keep working), but not changed, and it still
counts against your plan. When it's in use the answer is in_use; send { "force": true } to
archive anyway (those uses are marked as needing attention).
curl -X POST https://api.avox.com/api/v1/tables/orders/archive -H "Authorization: Bearer $AVOX_API_KEY"
await avox("/tables/orders/archive", { method: "POST", body: JSON.stringify({ force: false }) });
Makes an archived table active again (and clears the "needs attention" mark its archiving set).
curl -X POST https://api.avox.com/api/v1/tables/orders/restore -H "Authorization: Bearer $AVOX_API_KEY"
await avox("/tables/orders/restore", { method: "POST" });
Adds a column: column (name, type, and optionally key, required, unique,
defaultValue, options, description, hidden), and optionally afterColumn or
beforeColumn (an id or key; else it goes last). Up to 50 columns per table.
curl -X POST https://api.avox.com/api/v1/tables/orders/columns \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "column": { "key": "delivered_on", "name": "Delivered on", "type": "date" }, "afterColumn": "status" }'
await avox("/tables/orders/columns", { method: "POST", body: JSON.stringify({ column: { name: "Notes", type: "long_text" } }) });
Changes a column (columnId is its id or key): name, key, type, options, required,
unique, defaultValue, description, hidden, and timeZone (for converting dates and
times). Changing the type converts every value. When some values won't fit the new type, or
Flows, automations or templates use the column, the answer is confirmation_needed with how
many values would be emptied and what uses it; send the same change with "confirm": true to go
ahead. Making a column unique is refused (rows_invalid, with examples) while values repeat.
Removing a choice from options.choices clears it from the cells that had it.
curl -X PATCH https://api.avox.com/api/v1/tables/orders/columns/total \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "required": true }'
await avox("/tables/orders/columns/customer", { method: "PATCH", body: JSON.stringify({ type: "text", confirm: true }) });
The answer may carry warnings: rows with no value in a column just made required
(empty_required), or a column that couldn't stay unique after a type change (unique_dropped).
Deletes a column and its cells. When Flows, automations, templates or formula columns use it,
the answer is in_use; add ?force=true to delete anyway (they're marked as needing attention).
A table keeps at least one column.
curl -X DELETE "https://api.avox.com/api/v1/tables/orders/columns/notes?force=true" -H "Authorization: Bearer $AVOX_API_KEY"
await avox("/tables/orders/columns/notes", { method: "DELETE" });
A row's fields: id, seq (its number), values (by column key: select columns as their
labels, multiple choice as a list of labels, auto numbers as shown), source (where it was made:
dashboard, api, flow, automation, import...), createdAt, updatedAt, etag.
Writing: send values by column key. Values are checked by each column's type exactly as typed in
the dashboard (a date as 2026-11-02, a phone with its country code, a choice by its label).
Formula and auto number columns are filled in by Avox. Required columns need a value on every
new row, and unique columns refuse a value another row has. Every write request is all or
nothing: one bad row, or going over your plan's rows per table, and nothing is saved.
Query: filters (a JSON array of { "column", "op", "value" }; column is a key, name or id;
ops per type on Column types: is, is_not, contains,
not_contains, starts_with, is_empty, not_empty, gt, gte, lt, lte, has_any,
has_all, is_true, is_false), sort (key:asc or key:desc, up to 3, comma separated;
_created, _updated and _seq work too), q (search), limit (1 to 500, default 100),
cursor. Answers { rows, total }.
curl -G https://api.avox.com/api/v1/tables/orders/rows \
-H "Authorization: Bearer $AVOX_API_KEY" \
--data-urlencode 'filters=[{"column":"status","op":"is","value":"Shipped"}]' \
--data-urlencode 'sort=total:desc' \
--data-urlencode 'limit=50'
const filters = encodeURIComponent(JSON.stringify([{ column: "status", op: "is", value: "Shipped" }]));
let cursor = null;
do {
const page = await avox(`/tables/orders/rows?filters=${filters}&limit=100${cursor ? `&cursor=${cursor}` : ""}`);
for (const row of page.data.rows) console.log(row.values.order_no);
cursor = page.next_cursor;
} while (cursor);
Adds up to 1,000 rows: { "rows": [ { <key>: value } ], "timeZone"?: "Asia/Kolkata" }. Send an
Idempotency-Key so a retry never adds them twice. Answers 201 with { rows, insertedIds }.
curl -X POST https://api.avox.com/api/v1/tables/orders/rows \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1f4e7b2a-8c3d-4e5f-9a0b-1c2d3e4f5a6b" \
-d '{ "rows": [
{ "order_no": "A-1001", "customer": "+919876543210", "status": "Placed", "total": 1499 },
{ "order_no": "A-1002", "customer": "+919812345678", "status": "Shipped", "total": 650 }
] }'
const { ok, data, error } = await avox("/tables/orders/rows", {
method: "POST",
headers: { "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ rows: [{ order_no: "A-1003", status: "Placed", total: 320 }] }),
});
if (!ok && error.code === "rows_invalid") console.log(error.details); // [{ path: "rows[0].total", message: "...", code: "..." }]
A refused request names each problem: rows_invalid with details like
{ "path": "rows[1].order_no", "code": "unique", "message": "Another row already has this value" },
or plan_limit_reached when the rows don't fit in the table on your plan.
Changes up to 1,000 rows: { "rows": [ { "id", "values": { <key>: value }, "updatedAt"? } ] }.
Only the values you send change. With updatedAt (the row's as you read it), the whole request
is refused with version_conflict if any of those rows changed since; read them again and retry.
curl -X PATCH https://api.avox.com/api/v1/tables/orders/rows \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "rows": [ { "id": "7a1b...", "values": { "status": "Delivered" }, "updatedAt": "2026-10-23T08:15:30.123456+00:00" } ] }'
await avox("/tables/orders/rows", { method: "PATCH", body: JSON.stringify({ rows: [{ id: row.id, values: { status: "Delivered" }, updatedAt: row.updatedAt }] }) });
Deletes up to 1,000 rows: { "rows": [ { "id", "updatedAt"? } ] }, all or nothing. Deleted rows
can be brought back from the dashboard for 30 days.
curl -X POST https://api.avox.com/api/v1/tables/orders/rows/delete \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "rows": [ { "id": "7a1b..." } ] }'
await avox("/tables/orders/rows/delete", { method: "POST", body: JSON.stringify({ rows: ids.map((id) => ({ id })) }) });
One row, with its ETag.
curl https://api.avox.com/api/v1/tables/orders/rows/7a1b... -H "Authorization: Bearer $AVOX_API_KEY"
const { data } = await avox(`/tables/orders/rows/${rowId}`);
Changes one row: { "values": { <key>: value } }. With If-Match (the row's ETag) it's refused
when the row changed since.
curl -X PATCH https://api.avox.com/api/v1/tables/orders/rows/7a1b... \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "values": { "status": "Shipped" } }'
await avox(`/tables/orders/rows/${row.id}`, { method: "PATCH", headers: { "If-Match": row.etag }, body: JSON.stringify({ values: { status: "Shipped" } }) });
Deletes one row (If-Match optional).
curl -X DELETE https://api.avox.com/api/v1/tables/orders/rows/7a1b... -H "Authorization: Bearer $AVOX_API_KEY"
await avox(`/tables/orders/rows/${rowId}`, { method: "DELETE" });
Imports CSV text (comma, semicolon or tab separated; up to 10,000 rows and 8 MB):
csv: the text.mode: dry_run (default: reports what would happen, writes nothing) or commit.header: the first line is the headings (default true).columns: file heading to column key, "skip", or { "new": { "name", "type" } }. Headings
you don't map are matched to column keys and names; the rest are skipped (listed in
warnings).matchColumn: a unique column key; rows whose value already exists are updated instead of
added.addChoices, skipInvalid, timeZone.Run a dry run first, read report (rows, inserts, updates, skipped, problems,
issues with the file row and column of each problem, room left in the table), fix what it
says, then commit. A commit is checked again first and refused (import_invalid) when rows have
problems, unless skipInvalid is true. Test keys may run dry runs.
curl -X POST https://api.avox.com/api/v1/tables/orders/rows/import \
-H "Authorization: Bearer $AVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "mode": "dry_run", "matchColumn": "order_no",
"csv": "Order number,Status,Total\nA-1001,Delivered,1499\nA-1004,Placed,899" }'
const csv = await fs.promises.readFile("orders.csv", "utf8");
const dry = await avox("/tables/orders/rows/import", { method: "POST", body: JSON.stringify({ csv, mode: "dry_run" }) });
if (dry.data.report.problems === 0) {
await avox("/tables/orders/rows/import", { method: "POST", headers: { "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ csv, mode: "commit" }) });
}
Last updated