{"openapi":"3.1.0","info":{"title":"Wisegrid Public API","description":"The Wisegrid public REST API (v1). Authenticate with a scoped API key: `Authorization: Bearer wg_live_...`. Per-object scopes, partial-success bulk writes, idempotent retries, cursor pagination, 600 req/min/key.","version":"1.0.0"},"servers":[{"url":"/api/v1"}],"paths":{"/me":{"get":{"tags":["meta"],"summary":"Identify the authenticated key","description":"Return the key id, owner id, the key's scopes, and the current rate-limit\nwindow. NO email, NO member list, NO owner PII beyond ownerId.","operationId":"getMe","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponseV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}}},"security":[{"HTTPBearer":[]}]}},"/sheets":{"get":{"tags":["sheets"],"summary":"List sheets the key can read","description":"List the sheets reachable by this KEY = (owner's readable sheets) ∩ (key\nscopes). REUSES `_readable_sheets` for the owner's authoritative readable set,\nthen applies the key's scope ceiling per sheet via the Phase-1 boundary\n(`effective_role >= viewer`). A sheet the owner can see but the key isn't\nscoped to is simply absent — never leaked, never counted.\n\nCursor-paginated by sheet id ascending (stable keyset, no COUNT).","operationId":"listSheets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"page size (default 100, max 500)","title":"Limit"},"description":"page size (default 100, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"Cursor"},"description":"opaque keyset cursor"}],"responses":{"200":{"description":"Sheets the key can read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SheetListV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sheets/{sheet_id}":{"get":{"tags":["sheets"],"summary":"Get a sheet with its columns and first page of rows","description":"A single sheet: SheetV1 + its columns + the first page of rows inline.\nScope-checked via `assert_api_role(read)` — out-of-scope OR missing → opaque\n404 (no existence oracle; the assert raises 404 for the read verb).","operationId":"getSheet","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"rows page size (default 100, max 500)","title":"Limit"},"description":"rows page size (default 100, max 500)"}],"responses":{"200":{"description":"Sheet + columns + first row page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SheetV1WithChildren"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sheets/{sheet_id}/columns":{"get":{"tags":["sheets"],"summary":"List a sheet's columns","description":"The sheet's columns as ColumnV1 (ordered by position). Columns are bounded\n(a sheet has a small number), so a single page is acceptable — still wrapped\nin the list envelope for shape consistency, with nextCursor always null.","operationId":"listColumns","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}}],"responses":{"200":{"description":"The sheet's columns.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ColumnListV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sheets/{sheet_id}/rows":{"get":{"tags":["sheets"],"summary":"List a sheet's rows (cursor-paginated)","description":"The sheet's rows as RowV1, cursor-paginated (keyset by id ascending, no\nCOUNT). Scope-checked via `assert_api_role(read)` → opaque 404 out-of-scope.\nWorks identically on a >500K-cell sheet — same keyset page, no degraded\nmode (BET-API-1).","operationId":"listRows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"page size (default 100, max 500)","title":"Limit"},"description":"page size (default 100, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"Cursor"},"description":"opaque keyset cursor"}],"responses":{"200":{"description":"Cursor-paginated rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowListV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reports":{"get":{"tags":["reports"],"summary":"List reports the key can read","description":"The reports reachable by this KEY = every report whose PROJECT the key\nresolves `effective_role >= viewer` on (owner access ∩ key scope). A report in a\nproject the key can't reach is simply absent — never leaked, never counted.\nCursor-paginated by report id ascending (stable keyset, no COUNT).","operationId":"listReports","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"page size (default 100, max 500)","title":"Limit"},"description":"page size (default 100, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"Cursor"},"description":"opaque keyset cursor"}],"responses":{"200":{"description":"Reports the key can read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportListV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reports/{report_id}":{"get":{"tags":["reports"],"summary":"Get a report's metadata","description":"A report's PUBLIC metadata (id, name, type, project) — NEVER the raw\ndefinition / SQL text. Scope-checked on the report's PROJECT → opaque 404\nout-of-scope or missing (no existence oracle).","operationId":"getReport","security":[{"HTTPBearer":[]}],"parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}}],"responses":{"200":{"description":"The report's public metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportSummaryV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reports/{report_id}/run":{"post":{"tags":["reports"],"summary":"Run a report and return the result as JSON","description":"Run a saved report and return its rows as JSON — the data-source pull. Scoped\nto the key (opaque 404 out-of-scope), gated to a PAID-tier OWNER (`PAID_TIERS`),\nrun through the SAME engine + per-source ACL fan-out the web uses (with the API\naccess provider). Cursor-paginated; every cap / compile error is a clean 4xx\nenvelope, never a 500.","operationId":"runReport","security":[{"HTTPBearer":[]}],"parameters":[{"name":"report_id","in":"path","required":true,"schema":{"type":"integer","title":"Report Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","description":"rows per page (default 100, max 500)","title":"Limit"},"description":"rows per page (default 100, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque forward cursor","title":"Cursor"},"description":"opaque forward cursor"}],"responses":{"200":{"description":"The report run result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRunEnvelopeV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"402":{"description":"The report owner is not on a paid plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"413":{"description":"The report/join exceeds a run cap.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"The report could not be compiled/run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}}}}},"/sheets/{sheet_id}/rows:bulkAdd":{"post":{"tags":["rows"],"summary":"Bulk-add rows (partial-success)","description":"Insert many rows through the SINGLE internal `insert_row` path (one call\nper row, `source=\"api\"`). Per-row partial-success: a row that fails coercion\n(422) or hits the per-sheet capacity cap (409) becomes a `failed` result item\nWITHOUT aborting the rows that succeed. HTTP 200 even on partial failure.\n\nThe capacity guard inside `insert_row` is the SAME tier cap the web app hits\n— a row that would cross the cap is surfaced as `capacity_exceeded`, never\nsilently dropped (Area-4's cruelest failure mode).","operationId":"bulkAddRows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkAddRequestV1"}}}},"responses":{"200":{"description":"Per-row results (HTTP 200 even on partial failure).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkWriteResponseV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"403":{"description":"Key lacks write scope on this sheet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"409":{"description":"Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sheets/{sheet_id}/rows:bulkUpdate":{"post":{"tags":["rows"],"summary":"Bulk-update rows (partial-success, version-CAS)","description":"Update many rows through the SINGLE internal `bulk_update_rows` path. We\nbuild the internal `BulkRowUpdate` and call it verbatim — it ALREADY returns\nper-row `updated/conflict/forbidden/not_found/invalid/limit`; we re-serialize\nthat to the public partial-success envelope (no reinvented bulk logic).\n\nA stale `version` on one row → that row `failed` version_conflict carrying\n`current`+`currentVersion`; the others still `succeeded`. HTTP 200 always.","operationId":"bulkUpdateRows","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateRequestV1"}}}},"responses":{"200":{"description":"Per-row results; version_conflict items carry currentVersion+current.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkWriteResponseV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"403":{"description":"Key lacks write scope on this sheet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"409":{"description":"Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sheets/{sheet_id}/rows/{row_id}":{"patch":{"tags":["rows"],"summary":"Update one row's cells (version-CAS)","description":"Cell-addressable single-row update via the internal `update_row` (coerce +\nversion-CAS). A stale `version` → 409 version_conflict carrying `current` +\n`currentVersion` so the client can diff/retry. The row must belong to\n`sheet_id` (else opaque 404 — no cross-sheet write through a sheet's path).","operationId":"patchRow","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}},{"name":"row_id","in":"path","required":true,"schema":{"type":"integer","title":"Row Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowPatchV1"}}}},"responses":{"200":{"description":"The updated row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowV1"}}}},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"403":{"description":"Key lacks write scope on this sheet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"409":{"description":"Stale version (version_conflict; carries current+currentVersion).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"A cell value failed type validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}}}},"delete":{"tags":["rows"],"summary":"Delete a row (and its subtree)","description":"Delete a row via the internal `delete_row` (cascades the subtree, frees\ncounters + attachments, emits ROW_DELETED). The row must belong to\n`sheet_id` (else opaque 404). Idempotency-Key replay returns 204 again\nwithout re-deleting.","operationId":"deleteRow","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sheet_id","in":"path","required":true,"schema":{"type":"integer","title":"Sheet Id"}},{"name":"row_id","in":"path","required":true,"schema":{"type":"integer","title":"Row Id"}}],"responses":{"204":{"description":"Row deleted (no body)."},"401":{"description":"Missing/invalid/revoked/expired key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"403":{"description":"Key lacks write scope on this sheet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"404":{"description":"Not found or not accessible with this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorV1"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ApiErrorV1":{"properties":{"errorCode":{"type":"string","title":"Errorcode"},"message":{"type":"string","title":"Message"},"detail":{"anyOf":[{},{"type":"null"}],"title":"Detail"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["errorCode","message","requestId"],"title":"ApiErrorV1","description":"The frozen error envelope on EVERY 4xx/5xx: `{errorCode, message, detail?,\nrequestId}`. `errorCode` is a STABLE string slug from the v1 set (`unauthorized`,\n`forbidden`, `not_found`, `validation_failed`, `version_conflict`,\n`capacity_exceeded`, `rate_limit_exceeded`, `idempotency_key_reused`,\n`idempotency_in_progress`, `payload_too_large`, `internal_error`)."},"BulkAddRequestV1":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowAddItemV1"},"type":"array","maxItems":2000,"minItems":1,"title":"Rows"}},"type":"object","required":["rows"],"title":"BulkAddRequestV1","description":"POST /api/v1/sheets/{id}/rows:bulkAdd body. An array of rows to insert.\nBounded (max 2000) to mirror the internal bulk-update cap and keep one\nrequest from smuggling an unbounded write."},"BulkResultItemV1":{"properties":{"index":{"type":"integer","title":"Index"},"status":{"type":"string","title":"Status"},"row":{"anyOf":[{"$ref":"#/components/schemas/RowV1"},{"type":"null"}]},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"additionalProperties":true,"type":"object","required":["index","status"],"title":"BulkResultItemV1","description":"One item in a bulk-write result. `status` is `succeeded` (carries `row`) or\n`failed` (carries `error`). The error shape is the frozen `{errorCode, message,\ndetail?}` envelope (anti-PII; conflict detail carries `currentVersion`+`current`\nRowV1)."},"BulkUpdateRequestV1":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowUpdateItemV1"},"type":"array","maxItems":2000,"minItems":1,"title":"Rows"}},"type":"object","required":["rows"],"title":"BulkUpdateRequestV1","description":"POST /api/v1/sheets/{id}/rows:bulkUpdate body."},"BulkWriteResponseV1":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BulkResultItemV1"},"type":"array","title":"Results"},"succeededCount":{"type":"integer","title":"Succeededcount"},"failedCount":{"type":"integer","title":"Failedcount"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["results","succeededCount","failedCount","requestId"],"title":"BulkWriteResponseV1","description":"`POST .../rows:bulkAdd` and `:bulkUpdate` — partial-success envelope. HTTP\n200 even on partial failure; per-item results carry the failures."},"ColumnListV1":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ColumnV1"},"type":"array","title":"Data"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nextcursor"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["data","requestId"],"title":"ColumnListV1","description":"`GET /sheets/{id}/columns` — column list envelope (nextCursor always null;\ncolumns are bounded)."},"ColumnV1":{"properties":{"id":{"type":"integer","title":"Id"},"sheetId":{"type":"integer","title":"Sheetid"},"name":{"type":"string","title":"Name"},"type":{"type":"string","title":"Type"},"position":{"type":"integer","title":"Position"},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Options"},"primary":{"type":"boolean","title":"Primary","default":false}},"type":"object","required":["id","sheetId","name","type","position"],"title":"ColumnV1","description":"Public column DTO. `options` is a flat list of option KEYS (the\nround-trippable cell values) — present ONLY for option-bearing types\n(dropdown/multi_select/symbol); null/omitted otherwise. NO internal\n`metadata`, NO `formula`, NO `format`."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MeResponseV1":{"properties":{"keyId":{"type":"integer","title":"Keyid"},"ownerId":{"type":"integer","title":"Ownerid"},"scopes":{"items":{"$ref":"#/components/schemas/ScopeGrantV1"},"type":"array","title":"Scopes"},"rateLimit":{"$ref":"#/components/schemas/RateLimitV1"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["keyId","ownerId","scopes","rateLimit","requestId"],"title":"MeResponseV1","description":"GET /api/v1/me — the authenticated principal's identity + the key's scopes\n+ the current rate-limit window. NO email, NO member list, NO owner PII beyond\nownerId. The \"is my key working?\" endpoint every integrator hits first."},"RateLimitV1":{"properties":{"limit":{"type":"integer","title":"Limit"},"remaining":{"type":"integer","title":"Remaining"},"reset":{"type":"integer","title":"Reset"}},"type":"object","required":["limit","remaining","reset"],"title":"RateLimitV1"},"ReportListV1":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ReportSummaryV1"},"type":"array","title":"Data"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nextcursor"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["data","requestId"],"title":"ReportListV1","description":"`GET /reports` — cursor-paginated report-summary list envelope."},"ReportRunColumnV1":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"type":{"type":"string","title":"Type"}},"type":"object","required":["id","name","type"],"title":"ReportRunColumnV1","description":"One output column of a report run."},"ReportRunEnvelopeV1":{"properties":{"data":{"$ref":"#/components/schemas/ReportRunV1"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nextcursor"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["data","requestId"],"title":"ReportRunEnvelopeV1","description":"`POST /reports/{id}/run` — the run result inside the standard envelope\n(`data` is the ReportRunV1 object; `nextCursor` walks additional pages)."},"ReportRunRowV1":{"properties":{"cells":{"type":"object","title":"Cells"}},"type":"object","required":["cells"],"title":"ReportRunRowV1","description":"One output row: `cells` keyed by output column id (string)."},"ReportRunV1":{"properties":{"reportId":{"type":"integer","title":"Reportid"},"reportName":{"type":"string","title":"Reportname"},"mode":{"type":"string","title":"Mode"},"columns":{"items":{"$ref":"#/components/schemas/ReportRunColumnV1"},"type":"array","title":"Columns"},"rows":{"items":{"$ref":"#/components/schemas/ReportRunRowV1"},"type":"array","title":"Rows"},"scannedRows":{"type":"integer","title":"Scannedrows"},"totalMatched":{"type":"integer","title":"Totalmatched"},"sourcesOmittedForAccess":{"type":"integer","title":"Sourcesomittedforaccess"}},"type":"object","required":["reportId","reportName","mode","columns","rows","scannedRows","totalMatched","sourcesOmittedForAccess"],"title":"ReportRunV1","description":"`POST /reports/{id}/run` — the data-source pull (the KEY requirement). A\nflat/aggregated grid; the client neither knows nor cares it was authored as SQL,\na pivot, or the visual builder. `sourcesOmittedForAccess` tells a BI client the\npull was partial (a source outside the key's scope ∩ owner access was dropped —\ncount only, no leak)."},"ReportSummaryV1":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"type":{"type":"string","title":"Type"},"projectId":{"type":"integer","title":"Projectid"}},"type":"object","required":["id","name","type","projectId"],"title":"ReportSummaryV1","description":"`GET /reports` + `GET /reports/{id}` — a report's PUBLIC metadata. NEVER the\nraw definition / SQL text / another tier's internals; just what a BI client needs\nto discover + address a runnable report."},"RowAddItemV1":{"properties":{"cells":{"type":"object","title":"Cells","default":{}}},"type":"object","title":"RowAddItemV1","description":"One row to add via POST .../rows:bulkAdd. `cells` is keyed by\nstr(column_id) → raw cell value (same shape RowV1 returns), so a read→write\nround-trip is symmetric."},"RowListV1":{"properties":{"data":{"items":{"$ref":"#/components/schemas/RowV1"},"type":"array","title":"Data"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nextcursor"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["data","requestId"],"title":"RowListV1","description":"`GET /sheets/{id}/rows` — cursor-paginated row list envelope."},"RowPatchV1":{"properties":{"version":{"type":"integer","minimum":1.0,"title":"Version"},"cells":{"type":"object","title":"Cells","default":{}},"baseCells":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Basecells"}},"type":"object","required":["version"],"title":"RowPatchV1","description":"PATCH /api/v1/sheets/{id}/rows/{rowId} body — cell-addressable single-row\nupdate. `cells` carries ONLY the changed cells; `version` is the expected\nversion for the CAS (stale → 409 version_conflict)."},"RowUpdateItemV1":{"properties":{"id":{"type":"integer","title":"Id"},"version":{"type":"integer","minimum":1.0,"title":"Version"},"cells":{"type":"object","title":"Cells","default":{}},"baseCells":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Basecells"}},"type":"object","required":["id","version"],"title":"RowUpdateItemV1","description":"One row to update via POST .../rows:bulkUpdate. `id`+`version` drive the\nper-row version-CAS (optimistic concurrency); `cells` overwrites (not\nmerges), identical to the internal bulk-update contract. `baseCells` is the\nOPTIONAL advisory snapshot the conflict diff classifies against — never\ntrusted for the write (matches internal BulkRowItem.base_cells)."},"RowV1":{"properties":{"id":{"type":"integer","title":"Id"},"sheetId":{"type":"integer","title":"Sheetid"},"version":{"type":"integer","title":"Version"},"cells":{"type":"object","title":"Cells"},"createdAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updatedat"},"updatedBy":{"type":"object","title":"Updatedby"}},"type":"object","required":["id","sheetId","version","cells","updatedBy"],"title":"RowV1","description":"Public row DTO. `cells` is keyed by str(column_id) and carries the RAW\nstored cell value (scalars or {value,format}/{formula,value} wrappers,\npreserved verbatim). `updatedBy` is `{source}` ONLY — anti-PII: NO user_id,\nNO name, NO email. NO internal parent_row_id/depth/position."},"ScopeGrantV1":{"properties":{"verb":{"type":"string","title":"Verb"},"scopeType":{"type":"string","title":"Scopetype"},"scopeId":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scopeid"}},"type":"object","required":["verb","scopeType"],"title":"ScopeGrantV1","description":"One grant in a key's scope list. camelCase out; the stored JSON uses\nsnake_case (verb/scope_type/scope_id) — we translate at the edge.\n\nAn OBJECT grant carries scopeType ∈ {project,folder,sheet} + an int scopeId.\nAn ACCOUNT (full-access) grant carries scopeType=\"account\" + scopeId=null —\nit confers the owner's full standing authority, so it isn't tied to any\nobject."},"SheetListV1":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SheetV1"},"type":"array","title":"Data"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nextcursor"},"requestId":{"type":"string","title":"Requestid"}},"type":"object","required":["data","requestId"],"title":"SheetListV1","description":"`GET /sheets` — cursor-paginated sheet list envelope."},"SheetV1":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"projectId":{"type":"integer","title":"Projectid"},"columnCount":{"type":"integer","title":"Columncount"},"rowCount":{"type":"integer","title":"Rowcount"},"createdAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updatedat"}},"type":"object","required":["id","name","projectId","columnCount","rowCount"],"title":"SheetV1","description":"Public sheet DTO. `columnCount`/`rowCount` come from the maintained\ncounters on the sheet row (no COUNT(*)). NO config blobs, NO membership data,\nNO folder_id."},"SheetV1WithChildren":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"projectId":{"type":"integer","title":"Projectid"},"columnCount":{"type":"integer","title":"Columncount"},"rowCount":{"type":"integer","title":"Rowcount"},"createdAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updatedat"},"columns":{"items":{"$ref":"#/components/schemas/ColumnV1"},"type":"array","title":"Columns"},"rows":{"type":"object","title":"Rows"}},"type":"object","required":["id","name","projectId","columnCount","rowCount","columns","rows"],"title":"SheetV1WithChildren","description":"GET /api/v1/sheets/{id} — the sheet + its columns + the first page of\nrows inline (so an integrator gets a usable snapshot in one call)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}