Table API
Every Table node on an ixi canvas is a real database table — dynamic typed columns, rows your team and its resident pixie edit live. This API exposes those tables to your own software as a headless CMS: query, search, and CRUD the same rows from your server. Edits made here appear on the open canvas within seconds (and vice versa).
Base URL: https://api.ixi.so
Authentication
All requests use an org API key (ixi_sk_…) as a bearer token — the same
keys as the Pixie Run API:
curl https://api.ixi.so/graph/api/tables \
-H "Authorization: Bearer ixi_sk_..."
Create keys in Org settings → API keys (org admins only; the raw key is shown once). Keys are server-side credentials — never ship one to a browser. A key sees exactly the tables belonging to its org.
Data model
A table has a columns schema (order matters — it's the canvas display order):
{
"id": "tbl_9f2c81d4e6a07b35",
"name": "Products",
"columns": [
{ "id": "col_a1b2c3d4", "name": "Title", "type": "text" },
{ "id": "col_e5f6a7b8", "name": "Price", "type": "number" },
{ "id": "col_c9d0e1f2", "name": "Status", "type": "select", "options": ["draft", "live"] },
{ "id": "col_13579bdf", "name": "Photo", "type": "image" }
],
"rowCount": 128,
"canvasId": "cnv_...", "nodeId": "table-...",
"createdAt": 1753228800000, "updatedAt": 1753315200000
}
Column types: text, number, boolean, select, multiselect, date,
url, image, video, audio, json. Media cells hold https URLs. date
and url are strings (use ISO 8601 for dates). A media column can set
"multiple": true — its cells then hold an array of URLs (a single string
you send is wrapped automatically).
Rows key their cells by column id (not name — renames never break your integration):
{
"id": "row_5d3f9a1c7e2b8064",
"cells": { "col_a1b2c3d4": "Muse lamp", "col_e5f6a7b8": 149, "col_c9d0e1f2": "live" },
"createdBy": "api:key_1a2b3c4d5e6f7081",
"createdAt": 1753228800000, "updatedAt": 1753315200000
}
The schema itself (columns) is managed on the canvas — by your team or the table's pixie. The API reads the schema and works with rows.
Endpoints
| Method + path | What it does |
|---|---|
GET /graph/api/tables | List the org's tables (schema + row counts) |
GET /graph/api/tables/:id | One table's schema |
GET /graph/api/tables/:id/rows | List rows — ?limit=, ?cursor=, ?q= (search) |
POST /graph/api/tables/:id/query | Filtered / sorted / searched reads (body below) |
POST /graph/api/tables/:id/rows | Insert rows — {"rows": [{"cells": {…}}, …]}, max 100 per call |
PATCH /graph/api/tables/:id/rows/:rowId | Merge a cells patch into one row (null clears a cell) |
DELETE /graph/api/tables/:id/rows/:rowId | Delete one row |
Querying
POST /graph/api/tables/:id/query takes:
{
"q": "cozy lighting for small rooms",
"filters": [
{ "columnId": "col_c9d0e1f2", "op": "eq", "value": "live" },
{ "columnId": "col_e5f6a7b8", "op": "lte", "value": 200 }
],
"sort": { "columnId": "col_e5f6a7b8", "dir": "asc" },
"limit": 50,
"cursor": null
}
qis hybrid search: keyword (full-text) and semantic (embedding) matches, merged. "cozy lighting" finds rows about warm lamps even when no cell contains those words. Results are relevance-ranked;sort/cursordon't apply whileqis set.filtersall must match (AND). Ops:eq,ne,contains,gt,lt,gte,lte,empty,not_empty.- Pagination: responses are
{ "rows": […], "nextCursor": "…" | null, "total": n }. PassnextCursorback ascursorfor the next page.limitcaps at 200.
Writing
# Insert
curl -X POST https://api.ixi.so/graph/api/tables/tbl_9f2c.../rows \
-H "Authorization: Bearer ixi_sk_..." -H "Content-Type: application/json" \
-d '{"rows": [{"cells": {"col_a1b2c3d4": "Muse lamp", "col_e5f6a7b8": 149}}]}'
# Update (merge; null clears)
curl -X PATCH https://api.ixi.so/graph/api/tables/tbl_9f2c.../rows/row_5d3f... \
-H "Authorization: Bearer ixi_sk_..." -H "Content-Type: application/json" \
-d '{"cells": {"col_c9d0e1f2": "live", "col_e5f6a7b8": null}}'
# Delete
curl -X DELETE https://api.ixi.so/graph/api/tables/tbl_9f2c.../rows/row_5d3f... \
-H "Authorization: Bearer ixi_sk_..."
Values are validated against the column type ("42" coerces to 42 for a
number column; an unknown select option is rejected). Unknown column ids are
errors, not silently dropped. Cells cap at 32 KB; tables cap at 10,000 rows.
Derived columns
Some columns are derived: their value is computed by a model, tool, or
process run whenever their input columns change (configured on the canvas).
Derived cells are read-only through this API — writing one is an error; write
to its input columns instead and the value recomputes automatically (your
writes trigger derivations exactly like canvas edits). While a cell computes,
row responses carry a cellsMeta field:
{ "cellsMeta": { "col_…": { "status": "queued" | "running" | "failed", "error": "…" } } } —
absent once the value has landed.
Errors & limits
| Status | Meaning |
|---|---|
401 | Missing/invalid/revoked key |
404 | No such table/row in your org (foreign ids look identical) |
400 | Validation failure — the error field says what |
429 | Daily write limit reached (5,000 writes/key/day by default) |
Reads are unmetered. Search indexing (keyword + semantic) happens automatically moments after each write.
Live canvas sync
Every write through this API nudges the table's canvas node, so anyone with the canvas open sees the new data without refreshing — and the table's resident pixie reads the same store, so you can mix automation (this API), conversation ("keeper, clean up the drafts"), and direct edits freely.