Sheet Zaber · Reference

/api/v1 documentation

The contract in docs/backend-api-contract.md §1, rendered from the same snippet builders the playground uses. Private platform, public documentation — no signup on these pages.

Docs · 04 Response envelope

Response envelope

There are exactly two response shapes on /api/v1, and the boundary between them is the status code: 2xx carries { data, meta }, every non-2xx carries { error }. Nothing else is ever returned, so a client needs one parser and one switch on error.code.
Success
data + meta
Every refusal
error: code, message, category
Categories
auth · rate_limit · validation · google · not_found · internal
Content type
application/json
Body ceiling
1 MB in; larger is 413

4.1Success

docs/backend-api-contract.md §1.2, quoted as written

2xx · illustrative contract example, not live data
{
  "data": <result>,
  "meta": {
    "api_id": "…",
    "count": 12,
    "limit": null,
    "offset": 0,
    "duration_ms": 143
  }
}

The two members

MemberShapeMeaning
data<result>What the operation returns, and nothing around it: an array of record objects for a list or search, one object for a single record, { rows, headers } for a range, a write counter for POST/PATCH/DELETE.
metaobjectThe five fields §1.2 lists: api_id, count, limit, offset, duration_ms.

What each meta field is stated to mean

FieldExample in §1.2Documented meaning
api_id"…"The API that answered — api_configurations.id, which is also the path segment you used.
count12§1.3 defines it for the list operation: the rows returned. It is not documented as the total size of the tab, so do not use it as one.
limitnullA number or null, and §1.2's own example shows null alongside a non-zero count. §1.3 gives limit a documented default of all rows. The contract does not state more about what is echoed, so this page does not either.
offset0The skip position of this page; 0 is the start, and page/per_page are the alternative way to arrive at the same offset.
duration_ms143The engine's own measured duration — §4.8 makes it the same number the request-log row carries and the number the playground reports, so a slow call is slow in every view of it.

4.2Error

docs/backend-api-contract.md §1.2, quoted as written

Non-2xx · illustrative contract example, not live data
{
  "error": {
    "code": "record_not_found",
    "message": "No record with id=42",
    "category": "not_found"
  }
}
MemberTypeWhat it carries
codestringThe machine-readable reason, from the documented set on the Error reference page. Branch on this, not on the message.
messagestringHuman text for a log line or a support ticket. It is safe to show to your own users: §4.4 requires messages to never contain SQL, stack traces, tokens, keys, cell contents or another user's data — and the 500 row of §1.2 states the same for internal_error.
categoryenumOne of auth, rate_limit, validation, google, not_found, internal (§4.4 's ErrorCategory). It is the class of the failure, which helps group failures; use the specific code and operation to decide whether to retry.

4.3Reading a response

docs/backend-api-contract.md §1.2 · §1.3 · §4.4

The worked pair below is a single-record read: the request plate is a real SnippetRequest, and the two bodies are the shapes above with data filled in the way §1.3 specifies for that operation.

The call

cURL
curl 'https://sheet.zaber.tech/api/v1/<api-id>/<record>' \
  -H "Authorization: Bearer sz_live_…"

If it succeeds

200 · §1.3 says data is one object
{
  "data": { "<header cell>": "<cell value>" },
  "meta": {
    "api_id": "<api-id>",
    "count": <rows returned>,
    "limit": <the page size you asked for, or null>,
    "offset": <rows skipped, or 0>,
    "duration_ms": <server milliseconds>
  }
}

If the value matches no row

404 · §1.3 Single record
{
  "error": {
    "code": "record_not_found",
    "message": "No record with id=42",
    "category": "not_found"
  }
}

The message is §1.2's own example. The row is not found and the response says so in the one shape every refusal uses.

  • The contract uses two success boundaries. §1.2 defines the public error envelope as all non-2xx, while §4.6 defines a successful log row as status below 400. Those definitions differ for redirects; use the 2xx boundary for response handling and leave the log-filter discrepancy for the contract maintainers.
  • Retry by code and operation, not category alone. Rate limits require waiting for Retry-After; reauth requires consent, and inaccessible sheets require an access fix. Check the outcome of a failed write before repeating it — the contract does not promise idempotent retries.
  • There is no partial envelope. A response is either data+meta or error; the contract does not define a body that carries both.

4.4The dashboard API is a different envelope

docs/backend-api-contract.md §4.4, §2

Sheet Zaber has a second surface, /api/internal, used by the signed-in dashboard with a session cookie. Its success body is { data: … } — no meta — and §4.4 states plainly that the two are not interchangeable. These pages document /api/v1. The internal surface appears here in one place only: the Error reference page, because §4.4 is what fixes the category-to-status mapping.