ixi

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 + pathWhat it does
GET /graph/api/tablesList the org's tables (schema + row counts)
GET /graph/api/tables/:idOne table's schema
GET /graph/api/tables/:id/rowsList rows — ?limit=, ?cursor=, ?q= (search)
POST /graph/api/tables/:id/queryFiltered / sorted / searched reads (body below)
POST /graph/api/tables/:id/rowsInsert rows — {"rows": [{"cells": {…}}, …]}, max 100 per call
PATCH /graph/api/tables/:id/rows/:rowIdMerge a cells patch into one row (null clears a cell)
DELETE /graph/api/tables/:id/rows/:rowIdDelete 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
}
  • 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

# 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

StatusMeaning
401Missing/invalid/revoked key
404No such table/row in your org (foreign ids look identical)
400Validation failure — the error field says what
429Daily 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.