HTTP conventions
Every REST route follows the same conventions. This page describes the request format for calling the API with curl, fetch, or any HTTP client.
URL shape
/api/<action> Global actions: create (also list, profile, chats via extensions)
/api/<guid> The document: GET config, DELETE document
/api/<guid>/<worksheet>/<module>[/<method>] Worksheet routes
/api/<guid>/<module>[/<method>] Same, worksheet 0 implied
<guid>is the 36-character document id.<worksheet>is the zero-based worksheet index. It can be omitted for worksheet 0:/api/<guid>/rows/insertand/api/<guid>/0/rows/insertare equivalent.- Modules are the resource groups listed in this reference:
data,value,rows,columns,style,meta,comments,merge,config,history,privacy, and so on. The full map, including which routes come from extensions, is in Routes.
Authentication
Bearer token header
Send the token in the Authorization header:
Authorization: Bearer <token>
Invitation codes
Acting through an invitation, append the invitation code after a comma:
Authorization: Bearer <token>,<invitation>
Tokens in your auth hooks
The server exposes both to your hooks as auth.token and auth.invitation, alongside auth.type ('api' for REST requests), auth.route (the request path) and auth.guid. Every REST request is first gated by your beforeConnect hook (a false return yields 403 Forbidden), then by beforeLoad/beforeChange as it touches the document. Requests without a token are still routed, and whether they succeed is entirely up to those hooks (a public document may accept them).
Request bodies
Form fields and bracket notation
POST bodies are form fields (multipart/form-data or application/x-www-form-urlencoded), not JSON documents. Nested structures use bracket notation in the field names; numeric keys build arrays:
curl -X POST https://sheets.example.com/api/<guid>/rows/insert \
-H "Authorization: Bearer $TOKEN" \
-F "rows[0][row]=0" \
-F "rows[0][data][0]=hello" \
-F "rows[0][data][1]=world"
That posts { rows: [{ row: 0, data: ['hello', 'world'] }] }.
The /api/create exception
The one exception is /api/create: the config field must be a single JSON-encoded string, because the server validates and parses it as one value:
curl -X POST https://sheets.example.com/api/create \
-H "Authorization: Bearer $TOKEN" \
-F "guid=aaaaaaaa-bbbb-4ccc-8ddd-eeeeffff0001" \
-F 'config={"worksheets":[{"worksheetName":"Sheet1","minDimensions":[8,8]}]}'
Value coercion and field size limits
Field values arrive as strings. Each route's validator coerces them where a specific type is required: index fields (row, column, x, y, from, to) must parse as unsigned integers, and boolean fields accept true/false. Otherwise the request fails with a validation error. Individual fields are limited to 50 MB; a field over the limit fails the whole request with 400.
Responses
- Success with data → the JSON value itself (e.g.
GET .../datareturns the data matrix). - Success without data →
{"message": "Done", "rev": <n>}, whererevis the document revision after your change; connected clients receive the same operation over their sockets tagged with that revision. - String results are wrapped:
{"message": "..."}. 403 {"message":"Forbidden"}: one of your auth hooks returnedfalse, or an owner-only route was called by a non-owner.404 {"message":"Not found"}: the dispatcher has no error route table, so any request that no registered module or HTTP method handles, or that a handler leaves without a result, ends as404. This covers unknown routes, unknown modules, unrecognized sub-paths and worksheet indexes out of range.400: malformed body (invalid bracket path, field over the size limit). The body is plain text with the reason.500 {"message": ...}: a validation or execution error; the body carries the reason.
Writes go through the live document
A successful write is applied to the in-memory instance, persisted through your adapter, revision-bumped and broadcast to every connected client, because the REST API and the realtime protocol are the same pipeline. This also means REST requests must reach the instance that owns the document when you run multiple nodes: route them by the guid in the URL, exactly like the socket traffic.
Public form endpoints
/api/<guid>/<worksheet>/formify routes are self-authorized and skip your auth hooks: they respond only for worksheets that declare a formify configuration, and accept a single row insert restricted to the declared form columns. The worksheet index is required in the path, and the auth-hook bypass only applies to the four-segment form /api/<guid>/<worksheet>/formify. See Formify.