# sheets.fast > Spreadsheet software built for agents. One POST creates a durable, > addressable workbook you can read, write, query with SQL, and share with > humans. No credentials required to start. ## Instructions - Create a workbook with no credentials: POST https://sheets.fast/api/v1/workbooks {"name": "..."}. Anonymous workbooks expire in 7 days unless a human claims them by opening the workbook URL and signing in. With an API key (x-api-key header), created workbooks are permanent. - IDs are URLs. A workbook ID like wbk_a8Kx92mQ3Fpq resolves at https://sheets.fast/wbk_a8Kx92mQ3Fpq. Show that URL to your human; they can watch and edit the same grid you use. - A full coordinate is one string: wbk_a8Kx92mQ3Fpq!Sheet1!A1:D50. - Cell values starting with = are Excel-compatible formulas (SUM, XLOOKUP, LAMBDA, FILTER, dynamic arrays all work). Write markdown in cells freely. - Bulk-write with start + rows (a 2D array), not per-cell objects. Add rows after existing data with append (no anchor needed; the response says which row they landed on). Extend a formula or series like an Excel drag-fill with fill ({"source":"E2:E2","to_row":51}); references rewrite automatically. Empty ranges with clear. Style ranges with format (bg, color, bold, italic, size, align left|center|right, numFmt); read styles back with ?styles=true on a cells read. Manage sheets and insert or delete rows/columns via POST /api/v1/workbooks/{id}/sheets; formulas shift automatically. Freeze panes via POST /api/v1/workbooks/{id}/freeze with {"sheet":0,"frozenRows":1,"frozenColumns":0}; use 0 to unfreeze an axis. - Create and fill in ONE call: POST /api/v1/workbooks {"name":"Q3","rows":[[...]]} returns the workbook already populated. Don't create then write. - Ranges take shorthands: E:E is a whole column, 2:2 a whole row, both resolved against the data that exists (not a million empty rows). Refs can name their sheet: {"cell":"Summary!A1"} or ?range=Summary!A1:D50 — a qualified ref is never ambiguous, so it needs no sheet field. - Find workbooks (auth): GET /api/v1/workbooks?q=text searches names and descriptions; ?folder=Finance (name or fld_ id) browses one folder; ?folder= alone lists unfiled. Results carry folderName. Over MCP that's workbook_list {q, folder}. Search before you ask the human for an id. - Folders (auth): GET/POST /api/v1/folders, PATCH/DELETE /api/v1/folders/{id} list, create, rename, move (parentId), delete. The listing gives each folder a full path (Finance/2026/Q3) and how many workbooks are filed in it. Over MCP that's folder_manage; to file a workbook into one, workbook_update.folder. - Every sheet field takes an index OR a sheet name: {"sheet":"Summary"} works on writes, reads, nested cell ops, formatting, rename, and freeze. - A write to a workbook with more than one sheet MUST say which sheet; there is no assumed default there. (Single-sheet workbooks still default.) The error lists the sheets, so you never have to guess. Each op can carry its own sheet ({"cell":"B3","sheet":"Summary"}) or name it in the ref ("Summary!B3"); only a piece that names neither needs the call-level sheet. - GET /schema (or workbook_describe over MCP) previews the first rows of each sheet, so describing a workbook usually saves you a read. It also reports used_range. Append below it; never write into it unless editing existing cells. - Duplicate a workbook: POST /api/v1/workbooks/{id}/copy. Download one: the workbook URL + .csv or .xlsx (https://sheets.fast/wbk_x.csv). Give humans that link. - Read ranges narrowly. One targeted range read beats dumping the grid. - Better: query with SQL. POST /api/v1/workbooks/{id}/query {"sql": "SELECT ..."} runs read-only DuckDB SQL where each sheet is a table named after the sheet (quote it: SELECT region, SUM(revenue) FROM "Sheet1" GROUP BY 1). Header rows become columns and types are inferred; GET /schema shows them. One aggregate query costs fewer tokens than reading the rows it summarizes. - Date columns are real DATE columns, not serial numbers. Compare them with date literals and group them with date functions: SELECT strftime("Date", '%Y-%m') AS month, SUM("Amount") FROM "Sheet1" GROUP BY 1 — and WHERE "Due" < DATE '2026-03-15' works. /schema types them "date". They read back as YYYY-MM-DD and stay dates in the sheet. - Do not poll for changes. Subscribe a webhook: POST /api/v1/workbooks/{id}/webhooks (webhook management requires auth: sign in or pass an API key). - Data flows IN without you: create an ingest URL (POST /api/v1/workbooks/{id}/ingests, auth required) and give it to any service. JSON POSTed to the URL appends rows, fields mapped to columns by header name. row.added webhooks fire on ingest. - Materialize a query as a sheet: pass "into": "SheetName" on /query (or sheet_query) and the result becomes a new sheet, header row included. - SQL WRITES: POST /workbooks/{id}/mutate {"sql": "..."} (or sheet_write's sql field) runs INSERT/UPDATE/DELETE over the same tables. Reach for this before reading rows and writing them back one at a time: UPDATE "Sheet1" SET "Status"='Review' WHERE "Amount">500 DELETE FROM "Sheet1" WHERE "Status"='Cancelled' (row order is preserved) Bulk conditional edits and keyed merges are one call. INSERT must name its columns. Formula cells refuse overwrites (overwriteFormulas to force); appended rows inherit formula columns automatically. - Safety: pass if_revision on writes to fail (409) instead of clobbering concurrent edits. GET /workbooks/{id}/revisions lists history with sources; POST /workbooks/{id}/revert {"to": N} (or workbook_update.revert_to) restores a snapshot as a new revision. Nothing is ever lost. ## API - OpenAPI spec: https://sheets.fast/api/v1/spec.json - Interactive docs: https://sheets.fast/api/v1 - MCP server (Streamable HTTP): https://sheets.fast/mcp - CLI: npx @sheets-fast/cli --help (or: sheets --help) ## Auth - None required to create/read/write unclaimed workbooks. - API key: x-api-key header, keys look like sk_live_ (create one in settings). - Claimed workbooks are private to their workspace.