sheets

Create a Sheet definition.

Creates a root-level Sheet (ADR-0031). Stores only metadata — no row data. kind must be standard (a manual mapping table) — standard is the only creatable kind today. kind: linked and any non-empty seedQuery are REJECTED with 400 (DVT-2928): the legacy seed-copy shape (a linked sheet materialized once from seedQuery) never had a working execution path and is retired. linked is reserved for a future live-join sheet surface (DVT-2932) with different semantics — not yet ratified. 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, connectionId may be omitted — it auto-binds to the boot-provisioned Host Snowflake Writeback connection (ADR-0077 r3, DVT-2905), which requires an admin to have provisioned its in-account dvt_writeback database first (Sheets page → Set up writeback).

Changed 2026-08-10: columns names must be unique when matched case-insensitively, and a definition that breaks this is rejected with 400 (DVT-2476). Declaring both Title and title was previously accepted and has no defined meaning: the warehouse's quoted identifiers are case-sensitive, so the two are distinct names there, while every lookup around them collapses the pair to a single entry — the server's type check, the engine's projection, and the warehouse catalog the flush reconciles against.

Depending on the two declared types, the warehouse, and the target table's current columns, such a sheet either fails every flush that carries an upsert, or flushes while writing the same submitted value into both columns. Where the two exist in the table with different types AND both declared types are accepted against the column dvt matches them to, the outcome is value-dependent: a flush that succeeded for earlier rows can start failing on a later value that only one of the two accepts — so rows may already have been written before the sheet jams — and where both accept it, the two can hold values that differ by the warehouse's own coercion. Once a flush fails it keeps failing until the definition is repaired.

A definition that repeats a name exactly, rather than differing only in case, declares one column twice rather than two columns, and no batch containing an upsert can ever flush.

(Only Postgres and Snowflake reach these outcomes. A Redshift sheet reaches none of them: the flusher skips Redshift connections before the table is created, so nothing is written and nothing fails. Every other warehouse type is rejected outright by the engine's writeback gate.) Existing sheets are not migrated — appending to one that is already in this state returns 400 until it is corrected here or via PATCH /v1/sheets/{sheetId}.

post/v1/sheets

Request

  • Base URL: http://localhost:8080
  • URL: http://localhost:8080/v1/sheets
  • Auth: one of:
    • API key in cookie dvt_session
    • HTTP bearer

Query parameters

reasonstring

Optional change note recorded as this revision's summary (≤280 chars); when absent the server writes a default summary. May also be supplied via the X-Dvt-Change-Reason header. Over-cap values return 400.

Request body

namestring required
kind'standard' | 'linked' required

'standard' = manual writeback table, the default. 'linked' (v2, DVT-2932, ADR-0078 D1) creates a live-join sheet bound to a base warehouse relation — requires the admin or owner role (sheet:linked:create; a viewer/editor gets 403), defaults to visibility 'private' and rejects 'public' (org-public linked access is reserved for a future caller's-rights base, DVT-2935), and requires baseConnectionId/baseRelation/primaryKey (see below). The legacy alias 'blank' (pre-DVT-1870) is still accepted and silently normalized to 'standard'. Edition note (pr-review round-1 fix, H1; wording corrected round-2, A-HIGH-1): creating kind='linked' additionally requires the sheetLinkedCreate edition capability cell (GET /v1/me → editionCapabilities.sheetLinkedCreate). Gallery ✅; self-host frozen (unverified — DVT-2025); ✅ in the Snowflake Native App edition since DVT-3220 (ADR-0078 D5/R1: app-identity appends, caller's-rights base reads). In snowflake mode the operative gates are per-org and per-request, not the edition cell: the org's linkedSheetsEnabled flag (else 400 linked-base-org-disabled) and the Snowflake ingress caller token (else 400 linked-base-caller-token-missing), ahead of the baseConnectionId validation errors below.

connectionIdstring uuid nullable
targetDatabasestring

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, X-Dvt-Error-Code). Defaults to the install's writeback database when omitted.

columnsobject[]

kind='standard' only. For kind='linked', 'columns' is not client-suppliable — it is composed server-side from 'primaryKey' + 'extensionColumns' (primary key columns first, then extensions; the extension set is always derived as columns minus primaryKey).

primaryKeystring[]

Required, non-empty for kind='linked' (400 if empty or omitted) — the base relation's key column names. Each must exist in the base relation, be declared NOT NULL, and have an engine-mappable type (integer, text, uuid, or date); validated server-side at create time against the base connection's live catalog (closes DVT-2543). Immutable for kind='linked' after create — any PATCH attempting to change it is rejected with 400, regardless of whether the sheet has flushed yet (pr-review round-1 fix, H2/H3). Also accepted (optional) for kind='standard' — a rare composite-PK declaration; standard-kind primaryKey is NOT validated against a live catalog and stays mutable via PATCH pre-flush (see PatchSheetRequest.primaryKey). For both kinds, no entry may use the reserved 'dvt' prefix (dvt-managed columns) — rejected with 400, same rule as 'extensionColumns' below.

seedQuerystring nullable

RETIRED (DVT-2928). A non-empty value is rejected with 400 — seed-copy linked sheets are no longer supported. Omit it.

visibility'private' | 'shared' | 'public'

Defaults to 'private'. For kind='linked', 'public' is rejected with 400 — only 'private' (default) or 'shared' are allowed (ADR-0078 D1/sec-M1).

descriptionstring nullable
baseConnectionIdstring uuid

kind='linked' only; required. The pushdown-kind connection to read the base relation through (GET /v1/connections). Must not be an oauth_passthrough connection, must have the same warehouse account/host as this request's 'connectionId' (the writeback connection), and must have been opted in for linked-sheet base reads by an admin (PATCH /v1/connections/{id} {"linkedBaseOptIn":true}) — each is a distinct typed 400 otherwise. Ignored for kind='standard'.

Response

Sheet created.

Changes

No recorded changes to this operation across all 1 revision of this API.