---
title: "Update a Sheet (optimistic concurrency)."
method: PATCH
path: "/v1/sheets/{id}"
tags: ["sheets"]
---

# Update a Sheet (optimistic concurrency).

`PATCH /v1/sheets/{id}`

## Request

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

Updates any subset of the Sheet's definitional fields. `kind` is immutable.
Requires the current `version`; a stale version returns 409. Every successful
update writes a new revision. If `connectionId` is provided it must reference a
connection with `kind: writeback`; a pushdown connection is rejected with 400
(DVT-904). In the Snowflake Native App a sheet created without `connectionId` is
already auto-bound to the boot-provisioned Host Snowflake Writeback connection
(ADR-0077 r2, DVT-2905); patching `connectionId` there is only needed to point at
a different connection.

Changed 2026-08-10: a `columns` array whose names are not unique when matched
case-insensitively is rejected with `400` (DVT-2476) — the same rule
`POST /v1/sheets` now applies, described in full there. This is also the
endpoint used to repair a sheet created before the rule existed: drop one of
the colliding columns and appends will be accepted again.

Note that changing a column's declared type does NOT re-check edits already
sitting in the write-ahead log — their payloads are sealed (ADR-0012) and only
the engine can read them. An edit appended while a column was undeclared, or
declared with a different type, is checked at flush against the definition in
force then, and a failure there blocks every subsequent flush of the sheet.

## Query parameters

- `reason` string

## Request body

- PatchSheetRequest: Provide `version` plus any subset of the updatable fields. `kind` is immutable. `targetSchema`/`targetTable` are read-only and rejected with 400 if sent — see Sheet.targetSchema (DVT-1895). For a kind='linked' sheet (DVT-2932), `baseConnectionId`/`baseRelation` are also immutable post-create — sending either is rejected with 400 (delete and re-create against the new base instead); and `primaryKey`/`columns` are immutable from create, unconditionally — not just after first flush (pr-review round-1 fix, H2/H3) — sending either key in the request body at all, even byte-identical to the sheet's current value, is rejected with 400 ("immutable for linked sheets — recreate the sheet") regardless of pin state; omit both keys entirely on a linked PATCH. `visibility` cannot be set to 'public' for a kind='linked' sheet — same rule as create (400, ADR-0078 D1/sec-M1).
  - `version` integer, required: The current version, from the latest GET. Required for optimistic concurrency.
  - `name` string
  - `visibility` 'private' | 'shared' | 'public': For kind='linked', 'public' is rejected with 400 — only 'private' or 'shared' are allowed, same as create (ADR-0078 D1/sec-M1). Unrestricted for kind='standard'.
  - `connectionId` string, uuid, nullable
  - `targetDatabase` string: The install's writeback database (DVT_WRITEBACK by default on the native app; `dvt_writeback` elsewhere); any other value is rejected with writeback-database-unsupported (400). Use it to repair an unpinned sheet that stores a different database. On a sheet whose writeback target is already pinned (it has flushed), a value differing from the stored one (compared case-insensitively) is rejected with 409 `writeback-target-pinned`.
  - `columns` object[]: Updatable for kind='standard'. For kind='linked', immutable from create unconditionally (pr-review round-1 fix, H2/H3) — sending this key at all, even byte-identical to the sheet's current columns, is rejected with 400 regardless of whether the sheet has flushed yet; omit it entirely on a linked PATCH (see this schema's top-level description).
  - `primaryKey` string[]: Updatable for kind='standard'. For kind='linked', immutable from create unconditionally (pr-review round-1 fix, H2/H3) — sending this key at all, even byte-identical to the sheet's current primary key, is rejected with 400 regardless of whether the sheet has flushed yet; omit it entirely on a linked PATCH (see this schema's top-level description). For either kind, no entry may use the reserved '_dvt_' prefix (dvt-managed columns) — rejected with 400, same rule as create's 'primaryKey'.
  - `seedQuery` string, nullable: RETIRED (DVT-2928). A non-empty value is rejected with 400 — seed-copy linked sheets are no longer supported. Omit it.
  - `description` string, nullable

## Response `200`

Sheet updated.

- SheetEnvelope
  - `data` Sheet, required: A dvt Sheet definition (ADR-0031): a root-level, org-scoped, versioned warehouse- writeback object. Metadata only — the row data lives in the customer's warehouse (the dvt_writeback namespace), never in dvt, so this object has no row-data field.
    - `id` string, uuid, required
    - `folderId` string, uuid, nullable, required: The containing library folder, or null when unfoldered (DVT-1563). Set via PUT /v1/sheets/{id}/folder. Only the sheet's owner (or an org admin/owner) may move it between folders.
    - `ownerId` string, uuid, required: The user who created the sheet.
    - `name` string, required
    - `kind` 'standard' | 'linked', required: 'standard' = manual mapping table, freely editable via the row-write API. 'linked' (v2, DVT-2932, ADR-0078 D1) = a live-join sheet bound at create time to a base warehouse relation (baseConnectionId/baseRelation): row-write is restricted to the declared extensionColumns — every edit must still SEND a value for every primaryKey column (required, non-null; they identify which base row the edit applies to), but primaryKey values are not editable content, only the declared extensionColumns are (pr-review round-1 fix, L4: earlier wording said PK values come "never from the client", which described what's writable, not what's sent) — and creation requires the admin or owner role (sheet:linked:create). The legacy seed-copy shape ('linked' materialized once from seedQuery) was retired (DVT-2928/0091) and never shared this behavior — existing pre-DVT-2932 'linked' rows are unaffected.
    - `connectionId` string, uuid, nullable, required: The writeback connection (a connection id). Null until wired. DVT-904 enforces at bind time (POST/PATCH) that this must reference a connection with kind 'writeback'; a pushdown connection is rejected with 400. The append path re-asserts this as defense in depth (409) before sealing WAL entries.
    - `targetDatabase` string, required: The dvt-managed writeback namespace: the install's writeback database (DVT_WRITEBACK by default on the native app; `dvt_writeback` elsewhere). dvt never writes outside it. Any other value is rejected on create/update with writeback-database-unsupported.
    - `targetSchema` string, nullable, required: Read-only, server-managed (DVT-1895): derived from the sheet's folder path and pinned write-once at first flush. Null until the sheet has flushed. Never settable via POST/PATCH — a client-supplied value is rejected with 400.
    - `targetTable` string, nullable, required: Read-only, server-managed (DVT-1895): derived from the sheet's name (and folder subpath) and pinned write-once at first flush. Null until the sheet has flushed. Never settable via POST/PATCH — a client-supplied value is rejected with 400.
    - `columns` object[], required: Declared column definitions. Reconciled against the live catalog at write time (ADR-0031 D1a).
    - `primaryKey` string[], required: Declared primary key / composite PK (column names) for linked sheets. Empty for standard.
    - `seedQuery` string, nullable, required: RETIRED (DVT-2928): the seed SQL a legacy seed-copy 'linked' sheet was materialized from. Never executed at write time — nothing ever ran this query. A non-empty seedQuery is rejected with 400 on POST/PATCH; new sheets can never have one. Only present (non-null) on a 'linked' sheet row created before this retirement.
    - `visibility` 'private' | 'shared' | 'public', required
    - `version` integer, required: Optimistic-concurrency version. Pass this back on PATCH.
    - `schemaVersion` integer, required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
    - `access` object: The CALLER's access to this sheet (ADR-0091 D3.1). Present only on GET /v1/sheets/{id}; omitted on list/create/patch. UX-only: the server enforces on every request and never reads this from a client. `read` is always true on a 200 (a non-reader gets 404). `write` is true when the caller passes the same checks as a write (sheet:write capability, per- resource ACL, owner, admin bypass, Snowflake live-tier viewer cap).
      - `read` boolean, required
      - `write` boolean, required
    - `rowCount` integer, nullable: Best-effort COUNT(*) observed against the warehouse target at the sheet's most recent successful WAL flush (DVT-1598). Null until the first flush, or when the observation attempt failed — never fails the flush itself. An observation, not stored row data (ADR-0011).
    - `description` string, nullable: Freeform markdown authoring notes about this sheet (DVT-1865 Description tab) — author-written text, never customer row data (ADR-0011). Null until an editor sets one via PATCH.
    - `baseConnectionId` string, uuid, nullable, required: kind='linked' v2 only (DVT-2932, ADR-0078 D1). The pushdown-kind connection this linked sheet reads its base relation through. Null for kind='standard'. Set once at create (POST /v1/sheets) and immutable thereafter — a PATCH that includes this field is rejected with 400; changing a linked sheet's base connection is a delete-and-recreate operation, not an update.
    - `baseRelation` object, nullable: kind='linked' v2 only (DVT-2932, ADR-0078 D1/D3). The base relation's TYPED identifier parts — never a dotted FQN string. Absent for kind='standard'. Set once at create and immutable thereafter (same PATCH-rejection rule as baseConnectionId).
      - `database` string: Optional — resolves to the base connection's default database when omitted.
      - `schema` string
      - `table` string
    - `baseSchemaFingerprint` object, nullable: kind='linked' v2 only (DVT-2932). A create-time snapshot of the base relation's column shape, captured from the engine's describe result and sorted by column name — the input to the DVT-2931 reconcile-on-read drift check (not yet implemented; this slice only captures the snapshot). Absent for kind='standard'.
      - `cols` object[]
        - `name` string
        - `dataType` string: Warehouse-native type string as reported by the engine describe call, e.g. 'NUMBER(38,0)' or 'character varying'.
        - `nullable` boolean
  - `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`: Two shapes, distinguished by the Problem `type` (also echoed in the `X-Dvt-Error-Code` response header): - VersionConflict — the sheet was modified since you last fetched it (optimistic concurrency); refetch and retry. - `https://docs.dvt.dev/errors/writeback-target-pinned` (DVT-5156) — the sheet has already flushed, so its writeback target database is pinned and `targetDatabase` cannot be changed to a different value (compared case-insensitively).

---

[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)
