Spreadsheet Sheet Data Guide#

The Workiva Spreadsheets API lets you read cell data and formatting from sheets, write values, apply formatting across multiple ranges in a single request, and manage rows, columns, merges, and borders programmatically.


Table of Contents#


Prerequisites & Authentication#

All requests to the Workiva API require:

  • A valid OAuth 2.0 Bearer token

  • The X-Version: 2026-01-01 header on every request

  • The appropriate OAuth scopes for each operation (noted per endpoint below)

export TOKEN="your_access_token_here"
export SPREADSHEET_ID="your-spreadsheet-id"
export SHEET_ID="your-sheet-id"

Important: Always include X-Version: 2026-01-01 on all requests. Omitting this header causes the API to default to the 2022-01-01 version, which uses different path prefixes and may return unexpected results.


Key Concepts#

Concept

Description

SheetUpdate

The request body for the update sheet content endpoint. Each request can set only one top-level update field (e.g., applyFormats, editCells, applyBorders), but the value of that field can contain an array of operations applied in a single request. For example, applyFormats.formats accepts an array of format objects, each targeting different ranges.

SheetData

The response from the retrieve data from a sheet endpoint. Contains cell values, formatting (both directly applied and effective/inherited), column and row metadata, and merged ranges.

Range

A 0-indexed object with startRow, startColumn, stopRow, and stopColumn (all inclusive). Used in the update endpoint for targeting cells. Set a field to null to make the range unbounded in that direction.

A1 Notation

A string like A1:C3 used by the $cellrange query parameter on GET .../sheetdata and the values/{range} path parameter. Row numbers are 1-indexed; columns use letters (A=1, B=2, etc.).

Async Operations

All update operations return 202 Accepted with an operationLocation URL. Poll that URL until the operation reaches completed status.

Note: The Range object (0-indexed, used in update request bodies) and A1 notation (1-indexed, used in query parameters and the values endpoint path) are two different ways to address cells. They are not interchangeable.


Understanding “One Update Field Per Request”#

The update sheet content endpoint documentation states:

“Each SheetUpdate can have only one update field set per request.”

This sentence is easy to misread. Here is what it actually means, and what it does not mean.

What it means: Each request body can include only one top-level field from the SheetUpdate object. You pick one of applyFormats, editCells, editRange, applyBorders, insertRows, deleteColumns, etc. You cannot combine applyFormats and editCells in the same request.

What it does NOT mean: It does not mean you can only apply one format, edit one cell, or target one range per request. The value inside each top-level field is typically an array. You can pack as many operations as you need into that array, all executed in a single API call.

The wrong approach vs. the right approach#

Suppose you need to format a 50-row financial report: bold the header, apply currency formatting to 10 data columns, shade alternating rows, and add a border under the totals row.

Wrong approach (what causes rate-limit escalations):

Send one request per format, per range. This turns a simple formatting job into dozens of API calls:

Request 1:  applyFormats  → bold header row
Request 2:  applyFormats  → currency format column B
Request 3:  applyFormats  → currency format column C
Request 4:  applyFormats  → currency format column D
...
Request 12: applyFormats  → shade row 2 background
Request 13: applyFormats  → shade row 4 background
...
Request 40: applyBorders  → border under totals

At 60 requests per minute (the update endpoint’s workspace-wide rate limit), this workflow stalls almost immediately.

Right approach (one request per update type):

Batch everything into the formats array. All 50 rows of formatting go in a single call:

Request 1:  applyFormats  → formats: [ bold header, currency col B-K, shading rows 2/4/6/... ]
Request 2:  applyBorders  → borders: [ bottom border on totals row ]

Two requests instead of 40+. The formats array and borders array each accept multiple objects, and each object can target multiple ranges.

Important: The update endpoint is rate-limited to as low as 60 requests per minute, shared across your entire workspace. Every unnecessary request counts against that limit for all users and integrations in the workspace. Always batch operations into arrays rather than sending one-operation-per-request.

Quick reference: what can be batched within a single request#

Top-level field

Array field inside it

What you can batch

applyFormats

formats (array of format objects)

Multiple format combinations, each targeting different ranges

applyBorders

borders (array of border objects)

Multiple border styles, each targeting different ranges

editCells

cells (array of cell edits)

Many individual cell value updates across the sheet

clearFormats

ranges (array of Range objects)

Multiple ranges to clear in one pass

clearBorders

ranges (array of Range objects)

Multiple ranges to clear in one pass

insertRows

insertions (array of Insertion objects)

Multiple row insertions at different positions

insertColumns

insertions (array of Insertion objects)

Multiple column insertions at different positions

deleteRows

intervals (array of Interval objects)

Multiple row intervals to delete

deleteColumns

intervals (array of Interval objects)

Multiple column intervals to delete

mergeRanges

ranges (array of Range objects)

Multiple ranges to merge


Retrieving Sheet Data#

Use retrieve data from a sheet to get cell values, formatting, column/row metadata, and merged ranges. This is the richest read endpoint, returning everything about each cell including its raw value, calculated value, directly applied formats, and effective (inherited) formats.

Required scope: file:read

Basic Retrieval#

GET /spreadsheets/{spreadsheetId}/sheets/{sheetId}/sheetdata

curl -X GET "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/sheetdata" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01"

Response (200 OK):

{
  "@nextLink": "<opaque_url>",
  "data": {
    "cells": [
      [
        {
          "calculatedValue": 2,
          "effectiveFormats": {
            "cellFormat": {
              "backgroundColor": "#d0e8ff",
              "horizontalAlign": "RIGHT",
              "verticalAlign": "BOTTOM"
            },
            "textFormat": {
              "bold": true,
              "fontColor": "#000000",
              "fontFamily": "Arial",
              "fontSize": 10
            },
            "valueFormat": {
              "valueFormatType": "CURRENCY",
              "precision": { "auto": false, "value": 2 },
              "showCurrencySymbol": true,
              "showThousandsSeparator": true
            }
          },
          "formats": {
            "cellFormat": { "backgroundColor": "#d0e8ff" },
            "textFormat": { "bold": true },
            "valueFormat": { "valueFormatType": "CURRENCY" }
          },
          "value": "=1+1"
        }
      ]
    ],
    "columnMetadata": [
      { "hidden": false, "size": 75 }
    ],
    "rowMetadata": [
      { "filtered": false, "hidden": false, "size": 16 }
    ],
    "merges": [],
    "range": {
      "startColumn": 0,
      "startRow": 0,
      "stopColumn": 0,
      "stopRow": 0
    }
  }
}

Tip: The cells array is row-major order. Each element in the outer array is a row; each element in the inner array is a cell. The formats object shows formats directly applied to the cell, while effectiveFormats includes inherited formats (from row, column, or sheet defaults).

Important: This endpoint is rate-limited to as low as 600 requests per minute, shared across your workspace. When you receive a 429 Too Many Requests response, check the Retry-After header and wait that many seconds before retrying.

Retrieving a Specific Cell Range#

Use the $cellrange query parameter with A1 notation to limit the response to a specific area.

GET /spreadsheets/{spreadsheetId}/sheets/{sheetId}/sheetdata?$cellrange=A1:D10

curl -X GET "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/sheetdata" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01" \
  --data-urlencode '$cellrange=A1:D10'

Tip: Omitting $cellrange returns the entire sheet. For large sheets, always specify a range to avoid hitting the 50,000-cell pagination limit.

Filtering Response Fields#

Use the $fields parameter to return only the cell properties you need. Field paths are rooted at the data object.

GET /spreadsheets/{spreadsheetId}/sheets/{sheetId}/sheetdata?$fields=cells.calculatedValue,cells.formats.valueFormat

curl -X GET "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/sheetdata" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01" \
  --data-urlencode '$cellrange=A1:C5' \
  --data-urlencode '$fields=cells.calculatedValue,cells.formats.valueFormat'

Tip: Use $fields to reduce response size when you only need values or only need formatting. For example, $fields=cells.value,cells.calculatedValue returns just the raw and calculated values without any formatting data.

Pagination#

The default and maximum page size is 50,000 cells. If the requested range contains more cells, the response includes an @nextLink URL. Follow it to get the next page.

GET /spreadsheets/{spreadsheetId}/sheets/{sheetId}/sheetdata?$maxcellsperpage=10000

curl -X GET "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/sheetdata" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01" \
  --data-urlencode '$cellrange=A1:Z1000' \
  --data-urlencode '$maxcellsperpage=10000'

When @nextLink is present in the response, fetch that URL directly (it includes all necessary parameters) to retrieve the next page of results.


Retrieving Range Values#

Use retrieve a list of range values when you need just the cell values without formatting or metadata. This endpoint is lighter-weight than sheetdata and uses A1 notation in the URL path.

Required scope: file:read

GET /spreadsheets/{spreadsheetId}/sheets/{sheetId}/values/{range}

curl -X GET "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/values/A1:C3" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01"

Response (200 OK):

{
  "data": [
    {
      "range": "A1:C3",
      "values": [
        ["First", "Second", "Third"],
        [1, 2, ""],
        [3, 4, 5]
      ]
    }
  ]
}

Raw vs. Calculated Values#

Use $valuestyle to control whether formulas return their formula string or their computed result.

GET /spreadsheets/{spreadsheetId}/sheets/{sheetId}/values/A1:B2?$valuestyle=calculated

curl -X GET "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/values/A1:B2" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01" \
  --data-urlencode '$valuestyle=calculated'

$valuestyle

Formula cell =1+1 returns

Non-formula cell Hello returns

raw (default)

"=1+1"

"Hello"

calculated

2

"Hello"

When to Use Each Read Endpoint#

Need

Use

Why

Cell values only, no formatting

GET .../values/{range}

Lighter response, simpler structure

Cell values with formatting

GET .../sheetdata

Returns formats and effectiveFormats per cell

Column widths, row heights, hidden state

GET .../sheetdata

Returns columnMetadata and rowMetadata

Merged ranges

GET .../sheetdata

Returns merges array

Formula strings and calculated results

Either

sheetdata returns both value and calculatedValue; values supports $valuestyle

Important: Both endpoints always return values in Ones scale, regardless of the cell’s enteredIn or shownIn formatting. If a cell displays “1.5” in Thousands scale, the API returns 1500.


Writing Cell Values#

There are three ways to write values to cells. Each has different strengths.

Approach

Best For

Addressing

Async?

editCells (via update)

Scattered, non-contiguous cells

0-indexed row/column

Yes

editRange (via update)

Contiguous rectangular block (0-indexed)

0-indexed Range object

Yes

PUT .../values/{range}

Contiguous rectangular block (A1 notation)

A1 notation in URL path

Yes

Writing Individual Cells with editCells#

Use editCells when you need to update specific cells that aren’t in a contiguous block.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Request Body:

{
  "editCells": {
    "cells": [
      { "column": 0, "row": 0, "value": "Revenue" },
      { "column": 1, "row": 0, "value": "Q1" },
      { "column": 2, "row": 0, "value": "Q2" },
      { "column": 0, "row": 5, "value": "Total" },
      { "column": 1, "row": 5, "value": 15000 },
      { "column": 2, "row": 5, "value": 22000 }
    ]
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Response (202 Accepted):

{
  "operationLocation": "https://api.app.wdesk.com/operations/128f274395254cf17eda6b3eb3d021b9"
}

Tip: The cells array can contain multiple cells in a single request. Batch your edits into one call rather than making separate requests per cell.

Both editCells and editRange support an optional options object:

Option

Type

Description

applyEnteredInScaling

boolean

When true, the written value is scaled by the cell’s enteredIn format. For example, if a cell’s enteredIn is THOUSANDS and you write 1.5 with this option enabled, the stored value becomes 1500. When false (default), values are written in Ones scale.

skipEditMergeChildren

boolean

When true, edits targeting merge child cells (non-primary cells in a merged range) are silently skipped instead of causing an error.

Example with options:

{
  "editCells": {
    "cells": [
      { "column": 1, "row": 1, "value": 1.5 }
    ],
    "options": {
      "applyEnteredInScaling": true,
      "skipEditMergeChildren": true
    }
  }
}

Writing a Contiguous Range with editRange#

Use editRange when updating a rectangular block of cells using 0-indexed coordinates.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Request Body:

{
  "editRange": {
    "range": {
      "startColumn": 0,
      "startRow": 0,
      "stopColumn": 2,
      "stopRow": 1
    },
    "values": [
      ["Revenue", "Q1", "Q2"],
      ["Product A", 15000, 22000]
    ]
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Response (202 Accepted):

{
  "operationLocation": "https://api.app.wdesk.com/operations/128f274395254cf17eda6b3eb3d021b9"
}

Writing Values by A1 Range#

Use update values in a range when you prefer A1 notation.

Required scope: file:write

Warning: This endpoint overwrites the entire specified range. If the provided values array is smaller than the range, all cells not covered by the provided values will be cleared. Use null as a cell value to preserve an existing cell’s value.

PUT /spreadsheets/{spreadsheetId}/sheets/{sheetId}/values/{range}

Request Body:

{
  "values": [
    ["Revenue", "Q1", "Q2"],
    ["Product A", 15000, 22000]
  ]
}
curl -X PUT "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/values/A1:C2" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Response (202 Accepted):

{
  "operationLocation": "https://api.app.wdesk.com/operations/128f274395254cf17eda6b3eb3d021b9"
}

Important: Values are always written in Ones scale. If a cell is formatted to display in Thousands, writing 1500 will display as 1.5 in that cell.


Applying Formats to Cells#

Use applyFormats in the update sheet content endpoint to apply cell formatting, text formatting, and value formatting to one or more ranges. The formats field accepts an array, so you can apply different formats to different ranges in a single request.

Important: The formats field is an array of format objects, not a single object. Each element in the array specifies its own ranges and format properties. This means you can format your header row, data cells, and totals row all in one API call. You do not need to make separate requests for each range or format type. See Understanding “One Update Field Per Request” for a full breakdown of this pattern.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Formatting Multiple Ranges in One Request#

This example applies three different format combinations in a single request:

  1. Header row (row 0): blue background + bold text

  2. Data cells (rows 1-9, columns 1-2): currency number format

  3. Total row (row 10): bold text + bottom border via cell background

Request Body:

{
  "applyFormats": {
    "formats": [
      {
        "ranges": [
          {
            "startColumn": 0,
            "startRow": 0,
            "stopColumn": 2,
            "stopRow": 0
          }
        ],
        "cellFormat": {
          "backgroundColor": "#d0e0f0"
        },
        "textFormat": {
          "bold": true
        }
      },
      {
        "ranges": [
          {
            "startColumn": 1,
            "startRow": 1,
            "stopColumn": 2,
            "stopRow": 9
          }
        ],
        "valueFormat": {
          "valueFormatType": "CURRENCY",
          "showCurrencySymbol": true,
          "showThousandsSeparator": true,
          "precision": {
            "auto": false,
            "value": 2
          },
          "currencySymbol": {
            "currency": {
              "code": "USD",
              "display": "SYMBOL"
            }
          }
        }
      },
      {
        "ranges": [
          {
            "startColumn": 0,
            "startRow": 10,
            "stopColumn": 2,
            "stopRow": 10
          }
        ],
        "textFormat": {
          "bold": true
        },
        "cellFormat": {
          "backgroundColor": "#e8e8e8"
        }
      }
    ]
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Response (202 Accepted):

{
  "operationLocation": "https://api.app.wdesk.com/operations/128f274395254cf17eda6b3eb3d021b9"
}

Format Object Properties#

Each element in the formats array can include any combination of these three format sub-objects. Omitted fields within each sub-object are left unchanged on the target cells.

Sub-object

Controls

Common Properties

cellFormat

Cell appearance

backgroundColor, horizontalAlign, verticalAlign, indent, textRotation

textFormat

Text styling

bold, italic, underline, strikethrough, fontFamily, fontSize, fontColor

valueFormat

Number/date display

valueFormatType, precision, showCurrencySymbol, showThousandsSeparator, currencySymbol

Each format object also supports a clearValueFormatStyles boolean (default false). Set this to true when you change a cell’s valueFormatType and want to remove any value format styles left over from the previous type. For example, switching a cell from CURRENCY to PERCENT without clearing styles could leave currency-specific properties (like showCurrencySymbol) active.

Available valueFormatType values: AUTOMATIC, NUMBER, CURRENCY, ACCOUNTING, PERCENT, DATE, TEXT, PERIOD

Tip: Each format object in the array also requires a ranges array. A single format object can target multiple ranges by including multiple Range objects in its ranges array. This is useful when you want to apply the same format to non-contiguous areas.

Applying a Single Format#

If you only need to apply one format to one range, the formats array still requires an array, but it will contain just one element.

Request Body:

{
  "applyFormats": {
    "formats": [
      {
        "ranges": [
          {
            "startColumn": 0,
            "startRow": 0,
            "stopColumn": null,
            "stopRow": 0
          }
        ],
        "textFormat": {
          "bold": true,
          "fontSize": 12
        }
      }
    ]
  }
}

Note: Setting stopColumn to null makes the range extend to the last column in the sheet. You can use null on any Range field to make the range unbounded in that direction.


Applying Borders#

Use applyBorders to add borders to cell ranges. Like applyFormats, the borders field is an array, so you can define different border styles for different ranges in one request.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Request Body:

{
  "applyBorders": {
    "borders": [
      {
        "ranges": [
          {
            "startColumn": 0,
            "startRow": 0,
            "stopColumn": 2,
            "stopRow": 0
          }
        ],
        "bottom": {
          "color": "#000000",
          "style": "SINGLE",
          "weight": 2
        }
      },
      {
        "ranges": [
          {
            "startColumn": 0,
            "startRow": 1,
            "stopColumn": 2,
            "stopRow": 9
          }
        ],
        "bottom": {
          "color": "#cccccc",
          "style": "DASHED1",
          "weight": 1
        },
        "left": {
          "color": "#cccccc",
          "style": "SINGLE",
          "weight": 1
        },
        "right": {
          "color": "#cccccc",
          "style": "SINGLE",
          "weight": 1
        }
      }
    ]
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Response (202 Accepted):

{
  "operationLocation": "https://api.app.wdesk.com/operations/128f274395254cf17eda6b3eb3d021b9"
}

Available border styles: SINGLE, DOUBLE, DASHED1, DASHED2, DASHED3, DASHED4, DASHED5

Border positions: top, bottom, left, right, diagonalUp, diagonalDown, innerHorizontal, innerVertical

Note: The innerHorizontal and innerVertical positions apply borders between cells within the range, not on the outer edges. Use top, bottom, left, right for outer borders and innerHorizontal, innerVertical for gridlines inside the range.


Clearing Formats and Borders#

Clearing Formats#

Use clearFormats to remove formatting from cells. You can clear specific format fields or use "*" to clear all fields in a category.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Request Body:

{
  "clearFormats": {
    "ranges": [
      {
        "startColumn": 0,
        "startRow": 0,
        "stopColumn": 5,
        "stopRow": 20
      }
    ],
    "cellFormatFields": ["*"],
    "textFormatFields": ["*"],
    "valueFormatFields": ["*"],
    "clearValueFormatStyles": true
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Tip: To clear only specific format properties, list them by name instead of using "*". For example, "textFormatFields": ["bold", "italic"] clears only bold and italic without affecting font size, color, or other text formatting.

Note: Set clearValueFormatStyles to true when clearing valueFormatFields that include valueFormatType. Without this, cells may retain value format styles from the previous type. When cleared, cells revert to the Automatic value format with no styles.

Clearing Borders#

Use clearBorders to remove all borders from the specified ranges.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Request Body:

{
  "clearBorders": {
    "ranges": [
      {
        "startColumn": 0,
        "startRow": 0,
        "stopColumn": 5,
        "stopRow": 20
      }
    ]
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

Managing Rows and Columns#

All row and column operations use the update sheet content endpoint. Each operation uses intervals (0-indexed, inclusive start and end) to specify which rows or columns to affect.

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Inserting Rows#

Request Body:

{
  "insertRows": {
    "inheritFrom": "BEFORE",
    "insertions": [
      { "index": 5, "count": 3 }
    ]
  }
}

inheritFrom Value

Behavior

BEFORE

New rows inherit formatting from the row above the insertion point

AFTER

New rows inherit formatting from the row below the insertion point

NONE

New rows have no formatting

Inserting Columns#

Request Body:

{
  "insertColumns": {
    "inheritFrom": "BEFORE",
    "insertions": [
      { "index": 2, "count": 1 }
    ]
  }
}

Deleting Rows#

Request Body:

{
  "deleteRows": {
    "force": true,
    "intervals": [
      { "start": 5, "end": 7 }
    ]
  }
}

Warning: Setting force to true deletes the rows even if they contain source links, XBRL facts, or connections. Set to false to prevent deletion of rows containing these elements.

Deleting Columns#

Request Body:

{
  "deleteColumns": {
    "force": true,
    "intervals": [
      { "start": 2, "end": 3 }
    ]
  }
}

Hiding and Unhiding Rows#

Request Body (hide):

{
  "hideRows": {
    "force": true,
    "intervals": [
      { "start": 7, "end": 9 }
    ]
  }
}

Request Body (unhide):

{
  "unhideRows": {
    "intervals": [
      { "start": 7, "end": 9 }
    ]
  }
}

Note: The force field on hideRows and hideColumns controls whether rows/columns containing footnotes can be hidden.

Hiding and Unhiding Columns#

Request Body (hide):

{
  "hideColumns": {
    "force": true,
    "intervals": [
      { "start": 4, "end": 5 }
    ]
  }
}

Request Body (unhide):

{
  "unhideColumns": {
    "intervals": [
      { "start": 4, "end": 5 }
    ]
  }
}

Resizing Rows and Columns#

Resize to a specific size (in points):

Request Body (resize rows):

{
  "resizeRows": {
    "resizeIntervals": [
      {
        "intervals": [
          { "start": 0, "end": 0 }
        ],
        "size": 32
      }
    ]
  }
}

Request Body (resize columns):

{
  "resizeColumns": {
    "resizeIntervals": [
      {
        "intervals": [
          { "start": 0, "end": 2 }
        ],
        "size": 120
      }
    ]
  }
}

Auto-size to fit content:

Request Body (auto-fit columns):

{
  "resizeColumnsToFit": {
    "intervals": [
      { "start": 0, "end": 5 }
    ]
  }
}

Request Body (auto-fit rows):

{
  "resizeRowsToFit": {
    "intervals": [
      { "start": 0, "end": 20 }
    ]
  }
}

Note: The size field for resizing is in points. Valid range is 3 to 10,000 points.


Merging and Unmerging Cells#

Merging Ranges#

Required scope: file:write

POST /spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

Request Body:

{
  "mergeRanges": {
    "force": true,
    "mergeType": "HORIZONTAL",
    "ranges": [
      {
        "startColumn": 0,
        "startRow": 0,
        "stopColumn": 2,
        "stopRow": 0
      }
    ]
  }
}
curl -X POST "https://api.app.wdesk.com/spreadsheets/${SPREADSHEET_ID}/sheets/${SHEET_ID}/update" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-Version: 2026-01-01" \
  -d @request.json

mergeType

Behavior

HORIZONTAL

Merges cells horizontally across columns within each row

VERTICAL

Merges cells vertically across rows within each column

ALL

Merges all cells in the range into a single cell

Warning: Setting force to true merges even when cells contain data that would be lost. When force is false, the operation fails if any non-primary cells in the merge range contain values.

Unmerging Ranges#

Request Body:

{
  "unmergeRanges": {
    "ranges": [
      {
        "startColumn": 0,
        "startRow": 0,
        "stopColumn": 2,
        "stopRow": 0
      }
    ]
  }
}

Tip: unmergeRanges unmerges any merges that intersect with the provided ranges, not just merges that are fully contained within them.


Polling for Completion#

All write operations (POST .../update, PUT .../values/{range}) return 202 Accepted with an operationLocation field in the response body and a Location header. Poll this URL until the operation completes.

Step 1 — Get the Operation Location#

The operationLocation field in the 202 response body contains the URL to poll. This is the same URL returned in the Location response header.

Step 2 — Poll Until Complete#

GET /operations/{operationId}

curl -X GET "https://api.app.wdesk.com/operations/{operationId}" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "X-Version: 2026-01-01"

Response (in progress):

{
  "id": "<operationId>",
  "status": "started",
  "created": { "dateTime": "2026-01-15T10:00:00Z" },
  "updated": { "dateTime": "2026-01-15T10:00:05Z" }
}

Response (completed):

{
  "id": "<operationId>",
  "status": "completed",
  "resourceUrl": "https://api.app.wdesk.com/operations/<operationId>/results",
  "created": { "dateTime": "2026-01-15T10:00:00Z" },
  "updated": { "dateTime": "2026-01-15T10:00:30Z" }
}

Important: Always respect the Retry-After response header when polling. The operations endpoint is rate-limited at approximately 1 request per second. Polling too frequently may result in 429 Too Many Requests errors.


Common Issues#

Issue

Solution

“Only one format applied per request”

The applyFormats.formats field is an array. Include multiple format objects in the array to apply different formats to different ranges in a single request. You don’t need one request per format.

“Values disappeared after PUT”

PUT .../values/{range} clears all cells in the range not covered by the provided values. If your values array is smaller than the range, uncovered cells are cleared. Use null to preserve existing values.

“429 Too Many Requests when polling”

Respect the Retry-After header. The operations endpoint is rate-limited to ~1 req/sec. The update endpoint is rate-limited to ~60 req/min, shared across the workspace. The sheetdata endpoint is rate-limited to ~600 req/min, also shared across the workspace.

“Scale mismatch in values”

The values endpoint always uses Ones scale regardless of cell formatting. Writing 1500 to a cell formatted as Thousands will display as 1.5.

“Which write endpoint should I use?”

Use editCells for scattered cells, editRange for contiguous blocks with 0-indexed coordinates, and PUT .../values/{range} for contiguous blocks with A1 notation.

“Only one update field per request”

Each SheetUpdate request can set only one top-level field (e.g., applyFormats or editCells, not both). To apply formats and edit values, make two separate requests. But within each field, you can include arrays of operations.

“Borders not showing on inner cells”

Use innerHorizontal and innerVertical for borders between cells within a range. top, bottom, left, right only apply to the outer edges.


Summary of API Endpoints#

Operation

Method

Endpoint

Required Scope

Retrieve sheet data (with formatting)

GET

/spreadsheets/{spreadsheetId}/sheets/{sheetId}/sheetdata

file:read

Retrieve range values (values only)

GET

/spreadsheets/{spreadsheetId}/sheets/{sheetId}/values/{range}

file:read

Update values by range (A1 notation)

PUT

/spreadsheets/{spreadsheetId}/sheets/{sheetId}/values/{range}

file:write

Update sheet content (edit cells, apply formats, manage rows/columns, etc.)

POST

/spreadsheets/{spreadsheetId}/sheets/{sheetId}/update

file:write

List sheets in a spreadsheet

GET

/spreadsheets/{spreadsheetId}/sheets

file:read

Poll async operation

GET

/operations/{operationId}

(same as originating request)