# 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](/docs/pixie-agent): ```sh 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): ```json { "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): ```json { "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: ```json { "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 } ``` - **`q` is 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`/`cursor` don't apply while `q` is set. - **`filters`** all must match (AND). Ops: `eq`, `ne`, `contains`, `gt`, `lt`, `gte`, `lte`, `empty`, `not_empty`. - **Pagination**: responses are `{ "rows": […], "nextCursor": "…" | null, "total": n }`. Pass `nextCursor` back as `cursor` for the next page. `limit` caps at 200. ## Writing ```sh # 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.