Sheets API

Workbooks, tabs, columns, cells with formulas, rows, CSV and XLSX import and export, and links with records.

Sheets is a spreadsheet that lives next to your data. This API creates workbooks, reads and writes cells (with formulas recalculated for you), changes the layout, imports and exports files, and moves data between sheets and records.

If the Sheets app is switched off in Settings › Apps, these routes answer 404 NOT_FOUND.

Endpoints

Permissions are checked against the workbook (its key is the resource). sheets:read opens and reads, sheets:write edits cells, rows, columns and tabs, sheets:manage creates, renames and deletes workbooks.

Method Path Permission What it does
GET /orgs/{orgId}/sheets/workbooks sheets:read List workbooks
POST /orgs/{orgId}/sheets/workbooks sheets:manage Create a workbook
GET /orgs/{orgId}/sheets/workbooks/{wbId} sheets:read Get a workbook with its tabs and columns
PATCH /orgs/{orgId}/sheets/workbooks/{wbId} sheets:manage Rename
DELETE /orgs/{orgId}/sheets/workbooks/{wbId} sheets:manage Delete
POST /orgs/{orgId}/sheets/workbooks/{wbId}/tabs sheets:write Add a tab
PATCH · DELETE /orgs/{orgId}/sheets/workbooks/{wbId}/tabs/{tabId} sheets:write Rename or delete a tab
POST …/tabs/{tabId}/columns sheets:write Add a column
PATCH · DELETE …/tabs/{tabId}/columns/{colId} sheets:write Change or delete a column
GET …/tabs/{tabId}/rows sheets:read Read a window of rows
POST …/tabs/{tabId}/cells sheets:write Write cells
POST …/tabs/{tabId}/rows/insert sheets:write Insert rows
POST …/tabs/{tabId}/rows/delete sheets:write Delete rows
GET …/tabs/{tabId}/export.csv sheets:read Download a tab as CSV
POST …/workbooks/{wbId}/tabs/import sheets:write Import a CSV as a new tab
GET …/workbooks/{wbId}/export.xlsx sheets:read Download the workbook as XLSX
POST /orgs/{orgId}/sheets/import sheets:manage Import an XLSX as a new workbook
POST /orgs/{orgId}/sheets/from-records sheets:manage Open records of a type as an editable sheet
POST /orgs/{orgId}/sheets/from-query sheets:manage Turn a query result into a sheet
POST …/tabs/{tabId}/push-records sheets:write Write a record-linked tab's edits back to records

(… is /orgs/{orgId}/sheets/workbooks/{wbId}.) Several people can edit the same workbook at once; the last write to a cell wins.

Workbooks

{
  "id": "0190fc00-…",
  "key": "budget-2026",
  "name": "Budget 2026",
  "size_bytes": 20480,
  "tabs": [
    {
      "id": "0190fc01-…",
      "name": "Sheet1",
      "pos": 0,
      "max_row": 41,
      "columns": [ { "id": "0190fc02-…", "key": "A", "label": "Item", "pos": 0, "type": "text" } ]
    }
  ],
  "state": "warm",
  "created_at": "2026-10-01T08:00:00Z",
  "updated_at": "2026-10-04T09:00:00Z"
}

GET /workbooks answers { "workbooks": [ … ] } (without tabs). POST /workbooks takes name (required) and key (optional) and answers 201 with the workbook (no tabs or state yet; read it with GET). DELETE answers 200 { "deleted": true }. Errors: 409 KEY_TAKEN, 402 LIMIT_EXCEEDED. A new tab starts with 26 columns (A to Z).

POST …/columns takes label, type (default text) and an optional config (select options, currency unit) and answers 201 with the column. Column type is text, number, currency, date, checkbox or select. The type cannot change once created. A cell holds a value of its column's type, or a formula. Limits: 20 tabs per workbook, 200 columns per tab, 100,000 rows per tab (lower on some plans, then 402 LIMIT_EXCEEDED). max_row is the highest row in use, or -1 for an empty tab.

Note: state is cold, warming, warm or failed: a workbook is loaded into memory when it is opened, and reads of a cold workbook still work, only more slowly. When your plan's memory allowance is full, loading another workbook is refused with 503 SHEETS_MEMORY. Close other workbooks in the app, or upgrade.

GET …/tabs/{tabId}/rows

Parameter Notes
from First row, starting at 0. Default 0
limit Default 200, at most 500
{
  "data": {
    "rows": [
      { "r": 0, "cells": { "A": { "v": "Rent" }, "B": { "v": 42000 } } },
      { "r": 1, "cells": { "A": { "v": "Total" }, "B": { "v": 42000, "f": "=SUM(B1:B1)" } } }
    ]
  }
}

Cells are keyed by column key. v is the value, f the formula when the cell holds one, and e an error such as #REF!. Rows with no data are not returned.

POST …/tabs/{tabId}/cells

Write up to 10,000 cells in one call. Dependent formulas are recalculated and the cells that changed come back.

Field Type Required Notes
cells array Yes Each { "r": row, "c": column key, "input": "text" }

input is exactly what a person would type. An empty string clears the cell. A leading = makes it a formula. Anything else is read as a value of the column's type.

curl -s -X POST "https://axisiq.co/api/v1/orgs/$ORG/sheets/workbooks/$WB/tabs/$TAB/cells" \
  -H "Authorization: Bearer $AXIS_KEY" -H "Content-Type: application/json" \
  -d '{"cells": [{"r": 0, "c": "A", "input": "Rent"}, {"r": 0, "c": "B", "input": "42000"}, {"r": 1, "c": "B", "input": "=SUM(B1:B1)"}]}'
{
  "data": {
    "changed": [ { "tab_id": "0190fc01-…", "r": 1, "c": "B", "cell": { "v": 42000, "f": "=SUM(B1:B1)" } } ],
    "truncated": false
  }
}

truncated is true when too many cells changed to list; read the rows again. The formula language follows Excel, with about 45 functions, references across tabs, and AXLOOKUP to read your records.

rows/insert and rows/delete take { "at": 5, "count": 2 }: the starting row (counted from 0, like r) and how many rows. Inserting shifts the rows down; deleting removes at up to at + count - 1 and shifts the rest up. Formula references are rewritten, and a reference to a deleted row becomes #REF!. count must be at least 1. Response { "ok": true }.

Import and export

  • CSV: GET …/tabs/{tabId}/export.csv downloads a tab. POST …/tabs/import (multipart, file) adds the CSV as a new tab named after the file. Response 201 { id, name, pos }.
  • XLSX: GET …/export.xlsx downloads the workbook. POST /sheets/import (multipart, file) creates a workbook and answers 201 with it.

Sheets and records

POST /sheets/from-records with { "type_key": "products", "name": "Products", "ids": [ … ] } exports a record type, or just the listed records (up to 500), into a new record-linked tab. ids is optional. Response 201: { "workbook_id", "tab_id", "truncated" }.

Edit the sheet, then POST …/tabs/{tabId}/push-records (no body) writes the changes back as the caller: rows with an id update that record, rows without one create a new record and the id is written back into the sheet. Empty cells are skipped (a push cannot clear a value). At most 2,000 rows per push. The response is { "updated", "created", "failed", "errors": [ { "row", "message" } ] }; a failed row does not stop the others.

POST /sheets/from-query with { "sql": "SELECT …", "name": "…" } runs a read-only query as the caller and puts the result in a new sheet. If the query reads exactly one record type and selects id, the tab is record-linked too. Response 201: { "workbook_id", "tab_id", "truncated", "linked" }.

Errors

Status Code When
400 VALIDATION_ERROR Bad cells (1 to 10,000 per call), unknown column, bad type
402 LIMIT_EXCEEDED Free-plan or plan row and memory allowance reached
403 FORBIDDEN Missing the workbook permission
404 NOT_FOUND No such workbook, tab or column
409 KEY_TAKEN Workbook key already used
503 SHEETS_MEMORY Memory allowance exceeded