SQL queries

Run read-only DuckDB SQL over any workbook. Each sheet's used range becomes a table named after the sheet. Quote the name: SELECT * FROM "Sheet1".

How sheets become tables

  • If the first row is all non-empty, distinct text, it becomes the column names and data starts on row 2. Otherwise columns are named A, B, C and data starts on row 1.
  • Column types are inferred: double, boolean, varchar, or date.
  • GET /workbooks/{id}/schema returns the inferred schema. Read it before writing SQL.

Dates

A spreadsheet stores a date as a serial number and remembers it is a date through the cell format. A column of date-formatted cells becomes a real DATE column, so date literals and date functions work the way they do in any database.

SELECT strftime("Date", '%Y-%m') AS month, SUM("Amount") AS total
FROM "Sheet1" GROUP BY 1 ORDER BY 1

SELECT * FROM "Invoices" WHERE "Due" < DATE '2026-03-15' AND "Status" <> 'paid'

Dates come back as YYYY-MM-DD. Writing to a date column through /mutate keeps it a date in the sheet.

Example

curl -X POST https://sheets.fast/api/v1/workbooks/wbk_a8Kx92mQ3Fpq/query \
  -H 'content-type: application/json' -d '{
  "sql": "SELECT region, SUM(revenue) AS total FROM \"Sheet1\" WHERE active GROUP BY 1 ORDER BY 2 DESC",
  "maxRows": 100
}'
{
  "revision": 4,
  "columns": ["region", "total"],
  "rows": [["East", 1500], ["West", 800]],
  "truncated": false
}

Limits

  • Read-only: statements must start with SELECT, WITH, FROM, PIVOT, DESCRIBE, or SUMMARIZE.
  • One statement per request. File, network, and extension access are disabled.
  • Results cap at 10,000 rows (default 1,000). truncated: true tells you to add a LIMIT or aggregate.
  • Joins across sheets in the same workbook work. Cross-workbook queries do not, yet.

Why this exists

For an agent, one aggregate query costs fewer tokens than reading the rows it summarizes. Ask the database-shaped question instead of paging through cells.

SQL writes

The same tables accept INSERT, UPDATE, and DELETE through the mutate endpoint. Deltas apply to the grid as one atomic revision, so a bulk conditional edit or a keyed merge is a single call:

terminal
sheets mutate wbk_a8Kx92mQ3Fpq '
  UPDATE "Sheet1" SET "Status" = ''stale'' WHERE "Updated" < ''2026-01-01'';
  INSERT INTO "Sheet1" ("Date", "Customer", "Amount") VALUES (''2026-08-30'', ''Globex'', 4800)'

Three rules keep it safe. Formula cells refuse silent overwrites (pass overwriteFormulas to replace them with literals). Appended rows inherit formula columns automatically, so a Subtotal column keeps computing itself. And every mutation is one revision: sheets revisions shows who wrote what, and sheets revert restores any earlier snapshot.

Materialize a result

Pass into and the result is written to a new sheet, header row included. One call from question to grid:

terminal
sheets query wbk_a8Kx92mQ3Fpq 'SELECT region, SUM(revenue) FROM "Sheet1" GROUP BY 1' --into Summary