---
title: "Query a page of a Sheet's logical row view."
method: POST
path: "/v1/sheets/{id}/rows/query"
tags: ["sheets"]
---

# Query a page of a Sheet's logical row view.

`POST /v1/sheets/{id}/rows/query`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/sheets/{id}/rows/query`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Serves one page of a sheet's logical row view: decrypted pending WAL edits merged
over flushed warehouse rows (engine-mediated; dvt's server never decrypts the
sealed WAL — only the engine holds the DEK — ADR-0012). Supports keyset pagination, an optional server-side text
or range filter (up to 20 filter clauses), and an optional sort on 1-3 columns.
POST (not GET) so filter text — which may contain data values — stays out of
URLs and access logs. Requires `sheet:read` and a writeback connection — auto-bound
at create time in the Snowflake Native App (ADR-0077 r2, DVT-2905), declared
explicitly elsewhere.

## Request body

- QuerySheetRowsRequest: POST /v1/sheets/{id}/rows/query request body (all fields optional).
  - `pageSize` integer: Number of rows to return per page. Default 100. An integer outside [1, 500] is rejected with 400.
  - `cursor` string: Opaque keyset cursor from a prior response's `nextCursor`. Omit to start from the first page. A malformed cursor is rejected with 400.
  - `sort` union: Sort on 1-3 columns. Send one `{column, dir}` object (the original single-key shape, still accepted) or an array of 1-3 distinct keys, most significant first. Columns are matched case-insensitively and may not repeat; an empty array or more than 3 keys is rejected with 400. Omit for default (unspecified) ordering.
    - SheetRowSort: One sort key.
      - `column` string, required: A declared sheet column name to sort by.
      - `dir` 'asc' | 'desc', required: Sort direction.
    - SheetRowSort[]
      - `column` string, required: A declared sheet column name to sort by.
      - `dir` 'asc' | 'desc', required: Sort direction.
  - `filters` object[]: Up to 20 filter clauses, ANDed together. Each clause must specify exactly one kind: `text` (case-insensitive contains; text/uuid columns), `min`/`max` (number/integer columns — may use either or both), `after`/`before` (date/ timestamp columns; `before` is inclusive of the whole selected day), `eq` (exact match against one scalar), or `in` (exact match against any of 1..1000 scalars). A clause that mixes kinds is rejected with 400. `eq`/`in` apply to text, uuid, integer, boolean and date columns; text matching is exact and case-sensitive. Values may be string, number or boolean — never null — and a string value is at most 256 characters. `in` is capped at 1000 values in aggregate across all clauses. A value that cannot be coerced to the column's type is rejected with 400 and `code: filter_value_invalid`.
    - `column` string, required: The declared sheet column name this clause applies to.
    - `text` string: Case-insensitive substring match (text/uuid columns).
    - `min` number: Lower bound, inclusive (number/integer columns).
    - `max` number: Upper bound, inclusive (number/integer columns).
    - `after` string: Inclusive lower date bound (date/timestamp columns). ISO 8601 date.
    - `before` string: Inclusive upper date bound (date/timestamp columns; inclusive of the whole selected day). ISO 8601 date.
    - `eq` union: Exact match against one scalar (text, uuid, integer, boolean, date columns). Text comparison is case-sensitive. Not null; strings at most 256 characters. An uncoercible value is a 400 with `code: filter_value_invalid`.
      - string
      - number
      - boolean
    - `in` union[]: Exact match against any of the listed scalars (same column kinds and rules as `eq`). 1..1000 values, counted in aggregate across all clauses in the request. Members are never null.
      - union
        - string
        - number
        - boolean
  - `rankSpecs` object[]: Optional (DVT-5453). Ask for the n-th largest (`top`) or smallest (`bottom`) value of numeric columns over the whole filtered population, returned as `rankThresholds` so a topN/bottomN conditional-format rule can be evaluated exactly rather than over the loaded page. Honoured on the first page only: ignored when `cursor` is present. At most 8 entries; a malformed entry, or a column that is not a declared `number`/`integer` column (matched case-insensitively), is rejected with 400, so send numeric columns only.
    - `column` string, required: A declared numeric column name.
    - `dir` 'top' | 'bottom', required
    - `n` integer, required

## Response `200`

One page of the logical row view.

- object
  - `data` QuerySheetRowsResponse, required: One page of a sheet's logical row view (POST /v1/sheets/{id}/rows/query). For a linked sheet (DVT-2931), this page can contain MORE rows than the requested `pageSize`: rows whose primary key has an unflushed pending edit but which fell outside the merged-read page are hydrated by a bounded keyed lookup and appended after the page rows (each with `pending: true`) so read-your-writes holds even when the edited row isn't on the current page. A linked-sheet page with filters may be shorter than pageSize, even empty, while hasMore is true; page on hasMore/nextCursor, not row count.
    - `rows` object[], required: The rows in this page.
      - `rowUid` string, uuid, required: The row's stable identifier. For a standard sheet this is the server-assigned row UUID. For a linked sheet (DVT-2931) it is instead server-derived deterministically from the row's declared primary-key values — still a canonical UUID, opaque to the caller, and valid to echo back on an upsert's `rowUid`. Linked sheets reject tombstones unconditionally (400) — there is no tombstone use of a linked-sheet rowUid.
      - `pending` boolean, required: True when this row reflects an unflushed WAL edit (not yet written to the warehouse).
      - `values` object, required: Column name → cell value. Nullable values are permitted. For standard sheets only declared columns are present; missing columns were not stored. For kind='linked' sheets, values also carry the base relation's snapshotted (baseSchemaFingerprint) columns; those are read-only and never accepted on write.
      - `pkAmbiguous` boolean: Linked sheets only (DVT-2931): true when this row's primary key is NULL or duplicated in the base relation's changelog-key set, making its extension-column values unattributable — those cells are suppressed (returned null) and read-only. Absent (never false) on a standard sheet.
      - `editedBy` string, uuid: DVT-3375: the UUID of the actor who made the row's last edit (per `row_uid` for a standard sheet, per declared primary key for a linked sheet — the max-seq WAL/changelog entry's author). Absent when unknown: rows flushed before the metadata columns existed, or edited by a system actor with no author UUID recorded (in which case `editedAt` may still be present). On a linked sheet, a row with an unflushed pending edit reports the last *flushed* edit's author; the pending edit's own author is not yet reflected here.
      - `editedAt` string, date-time: DVT-3375: when the row's last edit was made (the same WAL/ changelog entry `editedBy` is drawn from, when present). Absent when no edit metadata is known for this row (e.g. rows flushed before the metadata columns existed), and also for every *flushed* row of a sheet in the metadata-skipped fail-open state (its warehouse table collides with, or can't be given, the two managed columns — see docs/editions.md); on such a sheet a row with an unflushed pending edit is still stamped from the WAL entry, so both fields appear on edit and vanish at the next flush. Do NOT read an absent `editedAt` on a `pending: false` row as "never edited". Otherwise NOT absent under the same conditions as `editedBy`: a system-authored edit with a NULL actor still reports `editedAt`. On a linked sheet, a row with an unflushed pending edit reports the last *flushed* edit's time; the pending edit's own time is not yet reflected here.
    - `nextCursor` string, nullable, required: Pass back as `cursor` to fetch the next page. Null when hasMore is false. A linked-sheet page with filters may be shorter than pageSize, even empty, while hasMore is true; page on hasMore/nextCursor, not row count.
    - `hasMore` boolean, required: True when more rows exist beyond this page.
    - `pendingTruncated` boolean: Linked sheets only (DVT-2931): true when this sheet has more unflushed per-cell pending edits than the read path's bounded overlay/hydration budget could apply or fetch — some pending edits may not be reflected in this page. Never a silent drop: the omitted edits are still queued and will flush normally. Absent (never false) on a standard sheet.
    - `basePendingCount` integer: Linked sheets only (DVT-3374): the number of unflushed WAL rows on the dvt sheet that owns this linked sheet's base relation, present only when that base relation IS itself another dvt sheet's writeback target. Omitted when the base is a plain customer table (no dvt sheet owns it) — this is the common case — or if the lookup could not be completed; never fails the read either way.
    - `rankThresholds` object[]: DVT-5453, best-effort: one entry per requested `rankSpecs` entry the engine answered, in request order. Absent means "not computed" (no specs, a cursor page, or the whole computation failed - see `rankThresholdsError`); the rows are returned regardless.
      - `column` string, required
      - `dir` 'top' | 'bottom', required
      - `n` integer, required
      - `value` number, nullable: The cut-off a cell is compared against (topN: cell >= value; bottomN: cell <= value). Null when the filtered population has no numeric value, so the rule matches nothing. Absent when `error` is set.
      - `error` string: Per-spec failure code (rank_column_invalid, rank_column_not_numeric, rank_n_invalid, rank_n_too_large); no `value`.
    - `rankThresholdsError` string: DVT-5453: why the whole threshold computation was skipped (rank_timeout, rank_truncated or rank_failed). Rows are still returned. Absent otherwise.
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Resource does not exist.
- `409`: Several shapes, distinguished by the Problem `type` (DVT-2905, ADR-0077 r2 D6r): - `https://docs.dvt.dev/errors/writeback-not-provisioned` — snowflake-mode only (`editionCapabilities.spcsWritebackConnection`): either the sheet has no bound connection because the in-account `dvt_writeback` database isn't provisioned yet, or the engine rejected the read against a spcs_token connection whose `dvt_writeback` database is missing or not authorized to this application. Ask an admin to provision the dvt_writeback database (Sheets page → Set up writeback), then retry. - `https://docs.dvt.dev/errors/no-warehouse` — the engine rejected the read because the in-account writeback connection has no resolvable warehouse (mirrors `POST /v1/data/query`'s `errors/no-warehouse` above). Ask a Snowflake admin to bind the app's `consumer_warehouse` reference. - `https://docs.dvt.dev/errors/no-writeback-connection` — non-snowflake editions' equivalent of the first case: the sheet has no writeback connection set (set `connectionId` via PATCH). - `https://docs.dvt.dev/errors/not-writeback-connection` — the configured connection is not a writeback-kind connection. - `https://docs.dvt.dev/errors/sheet-linked-not-rendered` — a render session asked for a linked sheet's rows; linked sheets are never rendered (ADR-0091 D3.4). Open the dashboard in dvt to view them. Linked sheets only (DVT-2931): the sheet's base-relation grant posture is re-validated on every read (fail-closed — a create-time opt-in does not grandfather a later revoke), distinguished by the Problem `type` (also echoed in the `X-Dvt-Error-Code` response header): - `https://docs.dvt.dev/errors/linked-base-opt-in-revoked` — the base connection's `linkedBaseOptIn` has been revoked since the sheet was created. Ask an admin to re-enable it, or unlink the sheet. - `https://docs.dvt.dev/errors/linked-base-passthrough` — the base connection uses OAuth-passthrough auth, which linked-sheet reads do not support. Rebind the base connection to a service_secret, service_keypair, cloud_iam, or spcs_token connection. - `https://docs.dvt.dev/errors/linked-base-not-pushdown` — the base connection could not be resolved as a live pushdown connection (gone, unbound, or drifted off pushdown kind). Rebind it via `PATCH /v1/sheets/{id}`. - `https://docs.dvt.dev/errors/linked-base-account-mismatch` — the base connection no longer resolves to the same warehouse account/host as the sheet's writeback connection. Rebind the base connection to one on the same account. - `https://docs.dvt.dev/errors/linked-base-database-unresolvable` — Snowflake only: neither the base connection's config nor the sheet's stored base relation has a database to read from. Set `database` on the base connection's config, or re-create the linked sheet's base relation with one. - `https://docs.dvt.dev/errors/linked-base-org-disabled` — snowflake mode only: the org-wide `linkedSheetsEnabled` admin kill switch is off (ADR-0078 R1, DVT-3220). Ask an org admin to re-enable it via `PATCH /v1/orgs/{orgId}/settings {"linkedSheetsEnabled":true}`. - `https://docs.dvt.dev/errors/linked-base-caller-token-missing` — snowflake mode only, `spcs_token` base connections: the linked read runs under the caller's own Snowflake session and the request carried no ingress caller token (ADR-0078 R1, DVT-3220). Retry as an authenticated Snowflake Native App session. Writeback target (DVT-5156): - `https://docs.dvt.dev/errors/writeback-database-mismatch` — the sheet's writeback target is already pinned (it has flushed) to a database that is not this install's writeback database, so the read is refused. The target cannot be changed on a pinned sheet.
- `501`: Row reads are not yet supported for Redshift connections.
- `502`: The analytics engine could not be reached (`https://docs.dvt.dev/errors/engine-unavailable`) or returned an error (`https://docs.dvt.dev/errors/engine-error`). One 502 on this shape is NOT an engine fault and says so: when Snowflake refuses the caller's-rights LOGIN, the Problem type is `https://docs.dvt.dev/errors/caller-connect-refused` — no session was established and the caller's SQL was never reached, so the remediation is a fresh dvt session rather than the engine logs (DVT-4345).
- `503`: Row reads are not configured on this deployment (feature not enabled), or the sheet has too many pending WAL edits to serve a merged view — retry after the next flush tick. The `Retry-After` response header (when present) carries the number of seconds to wait.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
