API reference

Documents API

Everything that is signed, shared or filed in a room starts as a document. Write one from Markdown or HTML, upload a PDF, edit a spreadsheet row by row, add the signature blocks signers will complete, and read back versions and comments.

Base URL https://xdocs.io/api/v1, with Authorization: Bearer xdk_… on every request. Pagination, rate limits, scopes and error codes are the same for every endpoint and are described once, in how the API behaves.

Documents

The editable source of truth. An authored document (kind: xdoc) carries its body as structured content; a spreadsheet (sheet) carries a grid of cells and formulas, worked out here and projected into its body as a table of values; an uploaded one (pdf) carries a PDF; a shared file (file) carries any other type, uploaded in the app. Authored documents and PDFs take a signing list; all four take share links, rooms and workspace folders. A spreadsheet cannot be sent for signature or saved as a template.

GET/api/v1/documentsdocuments:read

Your documents, most recently edited first.

Query parameters

qstring
Searches titles and body text.
statusstring
draft or locked (out for signature). Anything else is a 400.
kindstring
xdoc for authored, sheet for a spreadsheet, pdf for uploaded, file for a shared file. Anything else is a 400.
folderIdstring
Documents filed in one workspace folder.
inLibraryboolean
true for the curated library only.
trashedboolean
true lists deleted documents instead of live ones.
limitinteger
Rows per page, up to 100. Defaults to 50.
cursorstring
The previous page's nextCursor. Opaque — pass it back unchanged.

Returns

{ data: Document[], nextCursor } — id, title, kind, status, pageCount, activeEnvelopeId, createdAt, updatedAt

POST/api/v1/documentsdocuments:write

Create a document from Markdown, HTML, document JSON, or one of your saved templates — or a spreadsheet from CSV or cells.

Body (JSON)

titlestring
1–200 characters. Required, unless templateId is given — then it defaults to the template's name.
kindstring
xdoc (the default) or sheet. A spreadsheet is built from the five fields below instead of a body, and sending any of them implies sheet; it takes no recipients, because it cannot be sent for signature.
csvstring
Spreadsheet: the grid as CSV, up to 200,000 characters. Comma, semicolon and tab separators are detected. A cell beginning with = is a formula, worked out here. Rows past 2,000 or columns past AZ are left out and said so in warnings; an empty CSV is a 400.
cellsobject
Spreadsheet: address → what to type, e.g. {"A1": "Revenue", "B2": 1200, "B5": "=SUM(B2:B4)"}, typed over the CSV when both are sent. A number or boolean is taken as the text a person would type; "" or null clears a cell. Functions: SUM, AVERAGE, MIN, MAX, COUNT, COUNTA, MEDIAN, PRODUCT, ROUND, ROUNDUP, ROUNDDOWN, ABS, SQRT, POWER, IF, AND, OR, NOT, IFERROR, CONCAT, LEN, UPPER, LOWER, TRIM, LEFT, RIGHT, VLOOKUP, HLOOKUP, INDEX, MATCH, SUMIF, COUNTIF, AVERAGEIF, SUMPRODUCT. Up to 2,000 characters a cell and 20,000 filled cells; the grid runs to column AZ and row 2,000.
formats{range, numberFormat?, decimals?, bold?, align?, fill?}[]
Spreadsheet: how cells read, per cell or range (B2:B9). numberFormat is text, number, currency or percent (a percent cell holds the fraction: 0.45 reads 45%); decimals 0–10; align is left, center or right; fill a background colour like #fde68a, or null to take it off. Formatting changes how a figure reads everywhere it is shown — never its value.
headerRowboolean
Spreadsheet: draw and print row 1 as headings.
freeze{rows?, columns?}
Spreadsheet: how many rows (top) and columns (left) stay in place while scrolling, 0–10 each. Carried into .xlsx as a frozen pane.
sort{column, direction?}
Spreadsheet: reorders the data rows — below the header row and any frozen rows — by one column (C), asc (default) or desc. Whole rows move with their formats, and each row's formulas follow it as a filled formula does; empty cells sort last. Applied after the other changes.
filters{ [column]: string[] } | null
Spreadsheet: column → the shown values to keep, merged over the sheet's filters; an empty list clears that column, null clears them all. A view saved with the sheet: it hides rows in the editor, and the body, the PDF, CSV and .xlsx keep every row.
size{columns?, rows?}
Spreadsheet: how much grid to draw, up to 52 columns and 2,000 rows. It is never smaller than what is filled.
markdownstring
The body most callers have, up to 500,000 characters. CommonMark and GFM are read — headings, lists, quotes, code fences, rules, pipe tables with column alignment, images, footnotes, reference links, $…$ / $$…$$ mathematics — along with common inline HTML (<b>, <a>, <img>, <br>). The file GET /documents/:id/export?format=md writes reads back as the same document: signature fields, comment anchors, tracked changes, alignment and highlight included. Converted on the way in, and conversion losses come back in warnings.
htmlstring
An alternative body, for callers holding HTML. Up to 1,000,000 characters.
contentobject
Raw document JSON with type: "doc", for callers holding that. Up to 1,000,000 characters serialized.
templateIdstring
Start from a saved template, signature blocks already in place. Cannot be combined with a body. Any recipients sent alongside fill the template's slots in order, so those blocks stay bound to them; a recipient who omits role or routingOrder keeps the slot's.
recipients{name, email, role?, routingOrder?, accessCode?}[]
The draft signing list, up to 30. role is signer (the default) or viewer; routingOrder 1–99, defaulting to 1; accessCode (4–40 characters) is an out-of-band code that person must enter before signing.
folderIdstring
File it in one of your workspace folders.

Returns

201 { data: Document, warnings: string[] } — the document in the shape GET /documents/:id returns; a spreadsheet also carries sheet, as GET /documents/:id?include=sheet shows it

  • 400 No title for a document that is not from a template; a template and a body in the same request; a spreadsheet with a body, a template or recipients; or a cell address that is not one.
  • 404 No such template.
  • 422 The plan's document ceiling has been reached, the folder is not in your workspace, or a spreadsheet is past its limits — a cell outside the grid, more than 20,000 filled cells, or more than 2,000 characters in one.
POST/api/v1/documents/uploaddocuments:write

Create a document from a file — anything the app's upload takes. A PDF becomes a pdf document ready for signature fields; an .xlsx, .xls or .ods workbook becomes one editable sheet with a sheet per visible worksheet, in the file's order and under its tab names (up to 30; the workbook kept as its source file); a .docx, .md, .html or .txt becomes an editable xdoc (the original kept as its source file), or a file when no text can be read from it; decks, images, video, audio, zips, emails, CSV and Numbers spreadsheets are stored as a file.

Query parameters

titlestring
Overrides the filename (less its extension) as the title.

Body (multipart or raw)

filemultipart file field
Or send the raw bytes as the body with an x-filename header. The extension decides the type; without a filename the body's content-type stands in, and an unrecognised one is read as a PDF. On the hosted service a request body tops out at 4.5 MB, so send anything larger through POST /uploads instead. Accepted: pdf, docx, md, markdown, txt, html, htm, csv, xlsx, xls, ods, numbers, pptx, ppt, key, png, jpg, jpeg, gif, webp, svg, mp4, m4v, webm, mov, mp3, m4a, wav, ogg, zip, msg, eml.

Returns

201 { data: Document & { category, hasTextLayer, warnings, siblings: [{ id, title }] } } — siblings is always empty now that a workbook's worksheets become the sheets of one spreadsheet; it is kept so callers that read it do not break. category is pdf, document, spreadsheet, presentation, image, video, audio, archive, email or other. hasTextLayer false on a PDF means a scan whose words cannot be read or searched; elsewhere it says whether any text was found. warnings lists what could not be carried over (e.g. Excel features a grid cannot hold). A workbook that cannot be read (password-protected, damaged) is stored as a file instead; the format is read from the bytes, so an .xls saved under an .xlsx name still opens.

  • 400 A type that is not accepted, or a PDF that is not one, is unreadable, or has no pages.
  • 422 Larger than 100 MB, or the plan's ceiling has been reached.
  • 503 File storage failed or is not configured; retry.
POST/api/v1/uploadsdocuments:write

Start a large upload: returns a presigned URL to PUT the file straight to storage. Use this for anything over about 4 MB — POST /documents/upload passes the file through a server function, and the hosted service caps a request body at 4.5 MB. Three steps: POST here, PUT the raw bytes to uploadUrl (e.g. curl -T file.pdf "$uploadUrl", no auth header — the URL carries it), then POST /uploads/complete with the pathname. Nothing is created until the last step.

Body (JSON)

filenamestring
Required. The extension decides the type; the same types as POST /documents/upload are accepted.
sizenumber
Bytes. Optional; when given, a file over the limit is refused here rather than at the PUT.

Returns

201 { data: { uploadUrl, method: "PUT", pathname, expiresAt, maxBytes } } — the URL is good for one hour and one file of up to maxBytes, and a second PUT to it is refused. The PUT answers 200 with the stored blob; a body over the limit gets 403.

  • 400 A filename whose type is not accepted.
  • 422 A declared size over the limit, or the plan's document ceiling has been reached.
  • 503 File storage is not available; retry.
POST/api/v1/uploads/completedocuments:write

Finish a large upload: makes the file PUT to a presigned URL into a document, exactly as POST /documents/upload would have — a PDF ready for fields, a workbook as spreadsheets, anything else stored with what could be read of it. The size and type are read from storage. Safe to retry: completing a path that already made a document returns that document with 200 and alreadyCompleted: true instead of making another.

Body (JSON)

pathnamestring
Required. As returned by POST /uploads.
filenamestring
Overrides the name the upload was started with, for the type and title.
titlestring
Overrides the filename (less its extension) as the title.
folderIdstring
A workspace folder to file it, and any sibling spreadsheets, into.

Returns

201 { data: Document & { category, hasTextLayer, warnings, siblings: [{ id, title }] } } — the same shape as POST /documents/upload. On a repeat, 200 with alreadyCompleted: true, hasTextLayer null and no siblings listed.

  • 400 A type that is not accepted, or a PDF that is not one, is unreadable, or has no pages.
  • 404 A path outside this workspace, nothing PUT to it yet, or a folder that is not yours.
  • 422 The plan's document ceiling has been reached.
  • 503 File storage failed; retry.
GET/api/v1/documents/:iddocuments:read

One document, with its signing list and what file sits under it — and, for a spreadsheet, its grid.

Query parameters

includestring
Comma-separated. content adds the body, which is the largest thing stored and so is opt-in; for a spreadsheet that is the grid worked out and projected into a table. sheet adds a spreadsheet's grid itself, formulas included, as the people editing it currently see it — null on any other kind.
sheetstring
With include=sheet: which sheet to show in full — its id, name or position counted from 1. The first by default; an unknown one is a 404.

Returns

{ data: Document & { folderId, inLibrary, starred, pageSetup, deletedAt, recipients: [{ id, name, email, role, routingOrder, color, hasAccessCode }], source{filename, contentType, byteSize, hasTextLayer} | null, content?, sheet?: { sheetId, sheetName, columns, rows, headerRow, frozen: { rows, columns }, filters, filledCells, cells: { A1: { raw, value, format?, decimals?, bold?, align?, fill? } }, errors: [{ cell, error }], sheets: [{ id, name, position, columns, rows, filledCells }] } } } — an access code is reported as set, never repeated back. sheet is one sheet of the spreadsheet in full and sheets lists them all; in it, raw is what was typed (a formula, if one) and value what it reads as, formatted; errors lists the cells showing #DIV/0!, #VALUE!, #NAME?, #REF!, #N/A or #CYCLE!

PATCH/api/v1/documents/:iddocuments:write

Change a draft. A new body replaces the old one, which is snapshotted to version history first. A spreadsheet takes cell changes instead of a body.

Body (JSON)

titlestring
1–200 characters.
markdownstring
Replaces the body. Same limits as POST.
htmlstring
Replaces the body.
contentobject
Replaces the body.
recipients{id?, name, email, role?, routingOrder?, accessCode?}[]
Replaces the signing list, up to 30. Send back a recipient's id — or their unchanged address — to correct a name or email in place: their id is what the signature fields in the body are bound to, and a recipient who arrives without one is treated as somebody new, with nothing yet to sign.
folderIdstring | null
A workspace folder, or null to take it out of one.
inLibraryboolean
Add it to or remove it from the curated library.
iconstring | null
A preset icon shown in lists — one of folder, document, contract, checklist, briefcase, building, users, handshake, scale, receipt, banknote, landmark, home, car, plane, heart, shield, lock, graduation, book, calendar, megaphone, rocket, lightbulb, code, camera, package, tag, flag, star, archive — optionally with a colour after a colon (red, orange, amber, green, teal, blue, violet, pink, slate), e.g. contract:red. Null puts back the default. Reads come back the same way, or as "uploaded" for an image chosen in the app, which the API cannot set. Changing only the icon does not move updatedAt.
versionLabelstring
Names the snapshot taken before the body is replaced, or before a spreadsheet is changed. Up to 120 characters.
pageSetup{ headerText?, footerText?, pageNumbers?, size?: "letter" | "legal" | "a4" | "a5" | "5x8" | "5.5x8.5" | "6x9" | "7x10" | "custom", width?, height?, orientation?: "portrait" | "landscape", margins?: {top, right, bottom, left} | null, layout?: "pageless" | "paged" }
How the printed and exported copy is laid out: the running header and footer (up to 200 characters each), page numbers, the paper size, orientation and margins, and whether the editor shows it as pages. Omitted fields are kept. width and height are inches (2–24) and apply to a custom size only — sent with the document on any other size they are a 422; margins are inches (0.25–3) each side, and null puts back the size's defaults. Authored documents only.
starredboolean
Put it on, or take it off, the Starred list on Documents. A star is one person's bookmark, and a key acts for the workspace's owner, so this is the owner's star — the one they see in the app. Changes nothing about the item and does not move updatedAt.
cellsobject
Spreadsheet only: address → what to type, merged into the grid; "" or null clears a cell. Same values and limits as POST. A request that leaves the grid as it was takes no snapshot and writes no audit row.
csvstring
Spreadsheet only: replaces the whole grid. Applied before cells, so a request may replace the grid and then type over it. An empty CSV is a 400; rows or columns past the grid are left out and reported in warnings.
formats{range, numberFormat?, decimals?, bold?, align?, fill?}[]
Spreadsheet only: as on POST, applied after cells.
headerRowboolean
Spreadsheet only: row 1 as headings, or not.
size{columns?, rows?}
Spreadsheet only: how much grid to draw. Shrinking never removes a filled cell — DELETE /documents/:id/rows does that.
freeze{rows?, columns?}
Spreadsheet only: as on POST; a field left out is kept.
sort{column, direction?}
Spreadsheet only: as on POST, applied after the other changes.
filters{ [column]: string[] } | null
Spreadsheet only: as on POST.
sheetstring
Spreadsheet only: which sheet the changes above are to — its id, its name, or its position counted from 1. The first sheet when absent; an unknown one is a 404 that lists the sheets. Sheets themselves are added, renamed, moved and deleted through /documents/:id/sheets.

Returns

{ data: Document, warnings: string[] } — the document in the shape GET /documents/:id returns; when the grid was changed, data.sheet carries it as ?include=sheet would, with the change published to anyone who has it open

  • 409 A new body or page setup for a document out for signature. Void the envelope first.
  • 422 A body for an uploaded file, which is replaced with POST /documents/:id/file instead, or for a spreadsheet, which takes `cells` and `csv`; a signing list for a spreadsheet; a spreadsheet field for a document that is not one; `pageSetup` for anything but an authored document, or a `width` or `height` without a `custom` size; a cell outside the grid or a change past the sheet's limits; or a folder that is not in your workspace.
DELETE/api/v1/documents/:iddocuments:write

Move a document to the trash, where it can be restored for 30 days before it is deleted for good.

Query parameters

purgeboolean
true deletes it outright, and only from the trash.

Returns

204

  • 409 It is out for signature; it is under a legal hold; or a purge was asked for on a document that is not in the trash, that has been sent for signature and is kept as part of that record, or that a retention rule keeps until a later date (the message says when).
POST/api/v1/documents/:id/restoredocuments:write

Bring a document back from the trash.

Returns

{ data: Document }

  • 409 That document is not in the trash.
  • 422 The plan's document ceiling has been reached; trashed documents do not count toward it.
POST/api/v1/documents/:id/duplicatedocuments:write

A draft copy to work from — the same copy the dashboard's Duplicate makes. The body, the file under an upload, a spreadsheet's grid and the page setup come across; recipients keep their slots and the fields bound to them but lose their addresses and access codes, so the copy can never quietly go to whoever the original was sent to. Comments, versions, links and room filings stay with the original.

Body (JSON)

titlestring
1–200 characters. Defaults to “<title> (copy)”. The body may be left out entirely.

Returns

201 { data: Document } — the copy, in the shape GET /documents/:id returns. Raises document.created.

  • 422 The plan's document ceiling has been reached.
POST/api/v1/documents/:id/redactdocuments:write

A redacted copy: every occurrence of each term is replaced with a block in a new draft beside the original, which is left untouched. Recipients are not carried over — the copy exists to go to different people.

Body (JSON)

termsstring[]required
What to remove, 1–200 terms of 2–200 characters, matched exactly as written. Choose them yourself: the app's model scan that suggests terms is billed per scan and is not offered here.

Returns

201 { data: Document, replaced, unmatched: string[], absent: string[] } — replaced counts occurrences removed. Check unmatched: a term still in the copy (split across formatting) or never in the document at all; absent names the ones the document never contained. Raises document.redacted.

  • 409 An uploaded PDF, whose text cannot be removed from the file, or a document with no text.
  • 422 The plan's document ceiling has been reached.
GET/api/v1/documents/:id/collaboratorsdocuments:read

People let into the document by address, to read, comment or edit in the app — distinct from share links, which need no account.

Returns

{ data: Collaborator[] } — id, documentId, email, role (viewer, commenter or editor), acceptedAccount (whether they have signed in to claim it), createdAt

POST/api/v1/documents/:id/collaboratorsdocuments:write

Let somebody in by address and mail them an invitation. They need no account yet: the invitation is matched by address when they sign up. Inviting somebody already on the document at a different level moves them to it; at the level they have, nothing changes and nobody is mailed.

Body (JSON)

emailstringrequired
rolestringrequired
viewer reads, commenter also comments, editor also changes the text.
notifyboolean
false grants access without the invitation mail. Defaults to true.

Returns

201 { data: Collaborator, previousRole: null, delivery: { delivered, reason } | null } — 200 with previousRole set when they were already on it. Access is granted whether or not the mail was delivered; delivery says which.

  • 422 That address owns the document.
PATCH/api/v1/documents/:id/collaborators/:collaboratorIddocuments:write

Move somebody up or down. Nobody is mailed; it takes effect the next time they load the document.

Body (JSON)

rolestringrequired
viewer, commenter or editor.

Returns

{ data: Collaborator }

DELETE/api/v1/documents/:id/collaborators/:collaboratorIddocuments:write

Take somebody's access away, at once and silently.

Returns

204

GET/api/v1/documents/:id/filedocuments:read

The file under an uploaded document: the current source PDF, or a shared file (kind: file) in its own type.

Returns a file

The file bytes, with the stored content type and filename.

  • 404 An authored document or a spreadsheet, which has no source file — export it instead.
POST/api/v1/documents/:id/filedocuments:write

Replace the file under an uploaded document. It keeps its id and every link to it; the outgoing file becomes a version.

Body (multipart or raw)

filemultipart file field
Or the raw PDF as the body with an x-filename header. 25 MB maximum, and on the hosted service a request body tops out at 4.5 MB — send larger files by pathname.
pathnamestring
Instead of a file: JSON { pathname, filename? } naming a PDF already PUT to storage through POST /uploads.

Returns

{ data: { documentId, previousVersionId, pageCount, previousPageCount, hasTextLayer, droppedFields } } — previousVersionId holds the outgoing file; droppedFields counts field placements on pages the new file no longer has

  • 400 Not a PDF, unreadable, or has no pages.
  • 404 A `pathname` outside this workspace, or nothing PUT to it yet.
  • 409 The document is out for signature, or is not an uploaded PDF.
  • 422 Larger than 25 MB.
  • 503 The file could not be stored. Nothing changed; safe to retry.
GET/api/v1/documents/:id/exportdocuments:read

The document as a file, typeset the way the app exports it.

Query parameters

formatstring
pdf (default), docx, md — or csv or xlsx, for a spreadsheet only. Anything else is a 400. As CSV a spreadsheet exports one sheet's worked-out values, never its formulas (the first sheet, or the one sheet names); as xlsx it keeps every sheet, in tab order, with its formulas (each with its worked-out value cached, and marked for Excel to recalculate on open), number formats, bold, alignment, fill colours, column widths and frozen panes (a header row is frozen when nothing else is); as PDF, Word or Markdown it is the table its grid is projected into. Markdown carries the title as front matter and reads back as the same document when sent to POST /documents or PATCH /documents/:id as markdown: what Markdown has no syntax for travels as HTML any renderer shows (<mark>, <ins>/<del>, <div align>), and anything else as an exact xdocs block.
sheetstring
With format=csv: which sheet — its id, name or position counted from 1. The first by default; an unknown one is a 404.

Returns a file

The file bytes.

  • 409 An uploaded PDF can only be exported as PDF, and only a spreadsheet exports as CSV or xlsx.
POST/api/v1/documents/:id/rowsdocuments:write

Insert empty rows into a spreadsheet, before row at. Every formula that points past the insert is rewritten to keep meaning the same cells — =SUM(B2:B9) becomes =SUM(B3:B10) when a row goes in above it, and a range the insert falls inside grows to take it in. Snapshotted first, and published to anyone with the sheet open.

Body (JSON)

atintegerrequired
The row the new ones go before, counted from 1.
countinteger
1–500. Defaults to 1.
sheetstring
Which sheet: its id, its name, or its position counted from 1. The first sheet when absent; an unknown one is a 404 that lists the sheets.

Returns

201 { data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets } } — the grid as GET /documents/:id?include=sheet shows it

  • 409 The document is out for signature.
  • 422 Not a spreadsheet, `at` past the row after the last, or an insert that would take it past 2,000 rows.
DELETE/api/v1/documents/:id/rowsdocuments:write

Delete rows from a spreadsheet, starting at row at. A formula that pointed at a deleted cell shows #REF! rather than quietly using whatever moved into its place; a range the cut fell inside shrinks.

Query parameters

atinteger
The first row to delete, counted from 1.
countinteger
1–500. Defaults to 1.
sheetstring
Which sheet: its id, its name, or its position counted from 1. The first sheet when absent; an unknown one is a 404 that lists the sheets.

Returns

{ data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets } }

  • 409 The document is out for signature.
  • 422 Not a spreadsheet, or rows that do not all exist.
POST/api/v1/documents/:id/columnsdocuments:write

Insert empty columns into a spreadsheet, before column at. Formulas are rewritten as for rows.

Body (JSON)

atstring | integerrequired
The column the new ones go before: a number counted from 1, or its letter (C).
countinteger
1–52. Defaults to 1.
sheetstring
Which sheet: its id, its name, or its position counted from 1. The first sheet when absent; an unknown one is a 404 that lists the sheets.

Returns

201 { data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets } }

  • 409 The document is out for signature.
  • 422 Not a spreadsheet, `at` past the column after the last, or an insert that would take it past 52 columns (AZ).
DELETE/api/v1/documents/:id/columnsdocuments:write

Delete columns from a spreadsheet, starting at column at.

Query parameters

atstring | integer
The first column to delete: a number counted from 1, or its letter.
countinteger
1–52. Defaults to 1.
sheetstring
Which sheet: its id, its name, or its position counted from 1. The first sheet when absent; an unknown one is a 404 that lists the sheets.

Returns

{ data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets } }

  • 409 The document is out for signature.
  • 422 Not a spreadsheet, or columns that do not all exist.
GET/api/v1/documents/:id/sheetsdocuments:read

A spreadsheet's sheets — its tabs — in order. Every cell, row and column endpoint takes sheet to say which one it is about.

Returns

{ data: [{ id, name, position, columns, rows, filledCells }] }

  • 422 Not a spreadsheet.
POST/api/v1/documents/:id/sheetsdocuments:write

Add a sheet to a spreadsheet, empty or from CSV. Snapshotted first, published to anyone with the spreadsheet open, and audited. Formulas do not reach across sheets.

Body (JSON)

namestring
The tab's name, up to 80 characters; "Sheet N" when absent. A name another sheet has (ignoring case) gets " (2)".
positioninteger
Where it goes, counted from 1. At the end when absent.
csvstring
The new sheet's grid as CSV. Empty when absent; an empty string is a 400.

Returns

201 { data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets }, warnings: string[] } — the new sheet in full, as GET /documents/:id/sheets/:sheetId shows it

  • 409 The document is out for signature.
  • 422 Not a spreadsheet, it already has 30 sheets, or the filled cells across its sheets would pass 40,000.
GET/api/v1/documents/:id/sheets/:sheetIddocuments:read

One sheet in full — cells, formulas and values, formats, frozen panes and filters — read from the live copy. :sheetId may be the sheet's id, its name or its position counted from 1.

Returns

{ data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets } }

  • 404 No such sheet; the message lists the sheets there are.
  • 422 Not a spreadsheet.
PATCH/api/v1/documents/:id/sheets/:sheetIddocuments:write

Rename a sheet, move it among the tabs, or both. Each is its own version and audit entry.

Body (JSON)

namestring
Its new name, up to 80 characters; made unique if another sheet has it.
positioninteger
Its new place in the tab order, counted from 1.

Returns

{ data: { documentId, sheetId, sheetName, columns, rows, headerRow, frozen, filters, filledCells, cells, errors, sheets } }

  • 404 No such sheet.
  • 409 The document is out for signature.
  • 422 Not a spreadsheet.
DELETE/api/v1/documents/:id/sheets/:sheetIddocuments:write

Delete a sheet. Snapshotted first, so restoring the version before it brings it back.

Returns

{ data: { documentId, deleted, sheets: [{ id, name, position, columns, rows, filledCells }] } }

  • 404 No such sheet.
  • 409 The document is out for signature.
  • 422 Not a spreadsheet, or its only sheet.
GET/api/v1/documents/:id/versionsdocuments:read

History, newest first: the restore points somebody named and the snapshots the document took of itself.

Query parameters

limitinteger
Up to 100. Defaults to 50.

Returns

{ data: Version[] } — id, documentId, title, kind, pageCount, label, automatic, createdBy, createdAt. automatic is true for the snapshots the document took of itself, which have no label.

POST/api/v1/documents/:id/versionsdocuments:write

Mark a restore point at the document as it stands.

Body (JSON)

labelstring
What this point is, up to 120 characters. Defaults to naming the API.

Returns

201 { data: Version }

GET/api/v1/documents/:id/versions/:versionIddocuments:read

One version.

Query parameters

includestring
Comma-separated. content adds the body it preserved; sheet adds a spreadsheet version's grid, in the shape GET /documents/:id?include=sheet uses — null for a version taken before history kept grids, which can be read but not restored.

Returns

{ data: Version & { content?, sheet? } }

POST/api/v1/documents/:id/versions/:versionIddocuments:write

Put a version back. What it replaces is snapshotted first, so a restore is itself undoable.

Returns

{ data: Document }

  • 409 The document is out for signature.
  • 422 The file stored with that version could not be read, so restoring it would leave the document half-changed; or a spreadsheet version taken before history kept its grid, which has only worked-out values to put back.
DELETE/api/v1/documents/:id/versions/:versionIddocuments:write

Drop a snapshot. The document is untouched.

Returns

204

  • 409 A tracked link is pinned to this version; `details.links` names them. Re-point them first — an unpinned link quietly starts showing the live document. Or the document is under a legal hold, which keeps every version.
POST/api/v1/documents/:id/signature-blocksdocuments:write

Append somewhere to sign — name, signature and date, bound to one recipient. The step between writing a document through the API and being able to send it, because an envelope refuses to go to a recipient with nothing to complete.

Body (JSON)

recipientIdstring
A recipient on the document. Optional when there is only one.
emailstring
Identify that recipient by address instead.
headingstring
The label above the block, usually the party's role in the agreement. Up to 120 characters; defaults to the recipient's name.

Returns

201 { data: Document, addedFor: { recipientId, name, email }, totalFields } — the body before the block is snapshotted to version history first; totalFields counts every field now in the body, across all recipients

  • 409 The document is out for signature.
  • 422 No recipients yet, a recipient who is not on the document, several recipients and none named, an uploaded PDF — whose fields are placed by page coordinate in the editor — or a spreadsheet, which takes no signature fields.
GET/api/v1/documents/:id/commentsdocuments:read

The comment thread, oldest first.

Query parameters

resolvedboolean
false for what is still open.

Returns

{ data: Comment[] } — id, documentId, parentId, authorName, authorEmail, body, quote, resolved, createdAt. A reply carries the parentId of the comment that opened its thread.

POST/api/v1/documents/:id/commentsdocuments:write

Leave a comment. The owner, the thread and anyone @-mentioned are emailed, as they are from the editor.

Body (JSON)

bodystringrequired
Up to 10,000 characters. @name mentions are notified.
parentIdstring
Reply into an existing thread: the id of the comment that opened it. Threads are one level deep.
quotestring
The passage being commented on, up to 2,000 characters.

Returns

201 { data: Comment, notified: string[] } — notified is the addresses emailed

  • 422 `parentId` is not a comment on this document, or is itself a reply.
PATCH/api/v1/documents/:id/comments/:commentIddocuments:write

Resolve or reopen any comment on the document, or correct the wording of one of your own.

Body (JSON)

resolvedboolean
bodystring
Only on a comment this account wrote — a collaborator's words are theirs to change.

Returns

{ data: Comment }

  • 403 `body` was sent for a comment somebody else wrote. Nothing is changed, `resolved` included.
  • 422 `resolved` was sent for a reply. Threads are resolved on their first comment.
DELETE/api/v1/documents/:id/comments/:commentIddocuments:write

Remove one of your own comments and its replies.

Returns

204

  • 403 The comment was written by somebody else. Reply to it or resolve the thread instead.
GET/api/v1/foldersdocuments:read

The workspace's document folders, in the order the dashboard shows them — where the folderId a document is filed under comes from. Folders nest; the whole tree comes back in one call, so build it from parentId rather than asking per level.

Returns

{ data: Folder[] } — id, name, parentId (null at the top level), position, icon, starred, documentCount (live documents filed directly in it, not in its subfolders), createdAt, updatedAt

POST/api/v1/foldersdocuments:write

A folder, after the existing ones in the same place. File documents into it with folderId on POST or PATCH /documents.

Body (JSON)

namestringrequired
1–120 characters.
parentIdstring | null
Nest it inside one of your folders. Omit for the top level.

Returns

201 { data: Folder }

  • 404 No such parent folder in your workspace.
  • 422 That would nest the folder more than 8 levels deep.
PATCH/api/v1/folders/:iddocuments:write

Rename a folder, reorder it among its siblings, move it — with everything under it — into another folder, or star it.

Body (JSON)

namestring
1–120 characters.
positioninteger 0–10,000
Orders it among the folders sharing its parent.
parentIdstring | null
Move it inside another folder, or null for the top level. Omit to leave it where it is.
iconstring | null
A preset icon shown in lists — one of folder, document, contract, checklist, briefcase, building, users, handshake, scale, receipt, banknote, landmark, home, car, plane, heart, shield, lock, graduation, book, calendar, megaphone, rocket, lightbulb, code, camera, package, tag, flag, star, archive — optionally with a colour after a colon (red, orange, amber, green, teal, blue, violet, pink, slate), e.g. contract:red. Null puts back the default. Reads come back the same way, or as "uploaded" for an image chosen in the app, which the API cannot set.
starredboolean
Put it on, or take it off, the Starred list on Documents. A star is one person's bookmark, and a key acts for the workspace's owner, so this is the owner's star — the one they see in the app. Changes nothing about the item and does not move updatedAt.

Returns

{ data: Folder }

  • 404 No such folder, or no such parent folder in your workspace.
  • 422 A move into the folder itself or into one of its own subfolders, or one that would take the branch more than 8 levels deep.
  • 503 Folder stars are not set up on this server yet; nothing else in the request was refused.
DELETE/api/v1/folders/:iddocuments:write

Remove a folder. Its documents and its subfolders move up a level, into whatever it was itself inside, rather than going with it.

Returns

204

  • 409 A legal hold names this folder or one above it.
← The whole API referenceNext: Templates API →