# Spreadsheet endpoints

All endpoints use the base URL `https://app.visigrid.app/api/connector` and
require [bearer authentication](https://docs.visigrid.app/api/#authentication). Examples use synthetic
data. Replace `{pid}` with the `pid` UUID returned by the list endpoint.

## List spreadsheets

**`GET /sheets`** · Scope: `read` · Response: **200**

Returns an array of accessible spreadsheets, including shared workbooks.
There is no pagination envelope. An empty result is `[]`.

```json
[
  {
    "id": 42,
    "pid": "00000000-0000-4000-8000-000000000042",
    "name": "Leads",
    "revision": 3,
    "folder_id": null,
    "role": "owner",
    "can_manage_sharing": true,
    "created_at": "2026-10-08T12:00:00Z",
    "updated_at": "2026-10-08T12:05:00Z"
  }
]
```

`role` is `owner`, `editor`, or `viewer`. Use `pid` in URLs. Integrations
that write should offer only workbooks with an owner or editor role.
`can_manage_sharing` describes the account's permission; these REST endpoints
do not expose sharing management.

## Create a spreadsheet

**`POST /sheets`** · Scope: `create` · Response: **201**

| Field | Required | Description |
| --- | --- | --- |
| `name` | Yes | Workbook name, 1–255 characters after trimming whitespace. |
| `document` | Yes | Complete workbook JSON object. Use the v2 format below for cell operations. |

```json
{
  "name": "Leads",
  "document": {
    "format": "visigrid-json",
    "version": 2,
    "sheets": [
      {
        "name": "Sheet1",
        "cells": [
          { "row": 0, "col": 0, "value": "Email" },
          { "row": 0, "col": 1, "value": "Name" }
        ]
      }
    ]
  }
}
```

Returns a spreadsheet object with the same fields as a list entry, an owner
role, and revision `0`. Creation through this endpoint puts the workbook in
the account's root; `folder_id`, if supplied, is ignored.

Document cells use **zero-based** `row` and `col`, so `(0, 0)` is A1. Each
worksheet needs a nonempty name unique within the workbook, ignoring case.
For an empty workbook, use a single worksheet with `"cells": []`.

Creation stores the supplied document; it does not recalculate formulas.
Use [Write cells](#write-cells) to calculate formula edits.

## Get workbook structure

**`GET /sheets/{pid}/workbook`** · Scope: `read` · Response: **200**

Lists worksheet tabs without returning all their cells.

```json
{
  "sheet_id": "00000000-0000-4000-8000-000000000042",
  "name": "Leads",
  "revision": 0,
  "sheets": [
    {
      "index": 0,
      "name": "Sheet1",
      "cell_count": 2,
      "used_range": "A1:B1",
      "frozen_rows": 0,
      "frozen_cols": 0,
      "has_charts": false,
      "has_conditional_formatting": false
    }
  ]
}
```

Use `index` as `sheet_index` for range reads and cell writes. Indexes are
zero-based and may change when tabs are reordered. `used_range` is `null`
for a worksheet without stored cells. Stored formatting-only cells can count
toward `cell_count` and `used_range`; neither field identifies a table's last
data row.

## Read a range

**`GET /sheets/{pid}/range?range=A1:B20&sheet_index=0`** · Scope: `read` · Response: **200**

| Query parameter | Required | Description |
| --- | --- | --- |
| `range` | Yes | One cell (`A1`) or an A1 rectangle (`A1:B20`), at most 5,000 positions. |
| `sheet_index` | No | Nonnegative worksheet index; defaults to `0`. |

Use `sheet_index` to select the worksheet; do not prefix the range with a
worksheet name. Unknown or repeated query parameters are rejected.

```bash
curl --fail-with-body --get \
  -H "Authorization: Bearer $VISIGRID_API_TOKEN" \
  --data-urlencode 'range=A1:B20' \
  --data-urlencode 'sheet_index=0' \
  "https://app.visigrid.app/api/connector/sheets/$VISIGRID_SHEET_ID/range"
```

Example response:

```json
{
  "sheet_id": "00000000-0000-4000-8000-000000000042",
  "revision": 0,
  "sheet": "Sheet1",
  "sheet_index": 0,
  "range": "A1:B20",
  "cells": [
    { "ref": "A1", "row": 0, "col": 0, "value": "Email" },
    { "ref": "B1", "row": 0, "col": 1, "value": "Name" }
  ]
}
```

The cell list is sparse, ordered by row then column. Unstored blank cells
are omitted; stored cells may contain null values or formatting only.
Cells can also include `formula` and `format`. A single-cell range is
normalized to a rectangle such as `A1:A1`.

## Get a snapshot

**`GET /sheets/{pid}/snapshot`** · Scope: `read` · Response: **200**

Returns the full stored workbook document together with its revision.

```json
{
  "sheet_id": "00000000-0000-4000-8000-000000000042",
  "revision": 0,
  "document": {
    "format": "visigrid-json",
    "version": 2,
    "sheets": [
      {
        "name": "Sheet1",
        "cells": [
          { "row": 0, "col": 0, "value": "Email" },
          { "row": 0, "col": 1, "value": "Name" }
        ]
      }
    ]
  }
}
```

Use this endpoint when an operation depends on data across the whole
workbook, or when preparing a complete document save. Preserve fields you
do not modify, including formatting, charts, and other workbook metadata.

## Write cells

**`POST /sheets/{pid}/cells`** · Scope: `write` · Response: **200**

| Field | Required | Description |
| --- | --- | --- |
| `expected_revision` | Yes | Nonnegative integer revision from the preceding read. |
| `sheet_index` | No | Zero-based worksheet index; defaults to `0`. |
| `edits` | Yes | Array of 1–1,000 edits with distinct cell references. |

Each edit contains `ref` and **exactly one** of `value` or `formula`:

| Edit | Meaning |
| --- | --- |
| `{"ref":"A2","value":"person@example.com"}` | Write literal text. |
| `{"ref":"B2","value":12}` | Write a number. Booleans are also supported. |
| `{"ref":"C2","formula":"=B2*2"}` | Write and evaluate a formula. |
| `{"ref":"D2","value":null}` | Clear the value and any formula, preserving formatting. |

A string in `value` is always literal, even if it starts with `=`. Arrays
and objects are not cell values. A literal replaces any existing formula;
the response reports the formulas replaced this way.

This example assumes the preceding read returned revision `0`. Substitute
the revision you actually read before sending a request.

```bash
curl --fail-with-body \
  -H "Authorization: Bearer $VISIGRID_API_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"expected_revision":0,"sheet_index":0,"edits":[{"ref":"A2","value":"person@example.com"},{"ref":"B2","value":"Taylor"}]}' \
  "https://app.visigrid.app/api/connector/sheets/$VISIGRID_SHEET_ID/cells"
```

Example response:

```json
{
  "sheet_id": "00000000-0000-4000-8000-000000000042",
  "revision": 1,
  "replaced_formulas": [],
  "cells": [
    { "ref": "A2", "row": 1, "col": 0, "value": "person@example.com" },
    { "ref": "B2", "row": 1, "col": 1, "value": "Taylor" }
  ]
}
```

`cells` contains read-back cells when available and may be empty after a
successful live write. Read the range if you need the resulting values.
`replaced_formulas` entries contain `ref` and the previous `formula`.
Formula edits are recalculated through VisiGrid's engine.

## Save a complete snapshot

**`POST /sheets/{pid}/save`** · Scope: `write` · Response: **200**

Replaces the entire document, subject to the same revision check as cell
writes. Use cell writes for ordinary data updates. Omitting existing content
from a complete save removes that content.

```json
{
  "expected_revision": 0,
  "document": {
    "format": "visigrid-json",
    "version": 2,
    "sheets": [
      {
        "name": "Sheet1",
        "cells": [
          { "row": 0, "col": 0, "value": "Updated heading" }
        ]
      }
    ]
  }
}
```

Both fields are required. `document` must be a JSON object. Returns the
updated spreadsheet object with the same fields as a list entry.
This endpoint stores the supplied document without recalculating formulas.
Use a snapshot read as your starting point and preserve all other fields.

See [errors and limits](https://docs.visigrid.app/api/errors/) before implementing retries or batch writes.