{
  "openapi": "3.1.0",
  "info": {
    "title": "dvt REST API",
    "version": "0.1.0",
    "summary": "The authoritative contract for the dvt REST API.",
    "description": "The OpenAPI specification is the **authoritative contract** for dvt's REST API\n([ADR-0007](../docs/adr/0007-mcp-and-rest-contract.md)). Frontend TypeScript bindings, the\nMCP tool schemas, and API documentation all derive from this document. It is served live at\n`GET /openapi.json`.\n\nThis document describes the **currently served** surface: dashboards, spec validation, and\ndata query. Connection-management endpoints (`/v1/connections/*`) are specified in\n[data-source-auth.md](../docs/03-api-and-mcp/data-source-auth.md) and ADR-0012; they will be\nadded to this contract when their Go handlers ship. Until then they are intentionally absent\nhere so generated clients never bind to an unserved route.\n\n**Conventions**\n- Success responses use the dvt envelope: `{ \"data\": <payload>, \"meta\": { \"requestId\": ... } }`.\n- Errors are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) Problem Details\n  (`application/problem+json`), extended with a `suggestion` field written for LLM agents.\n- All `/v1` endpoints accept either authentication scheme: a sealed `dvt_session` cookie\n  from the WorkOS AuthKit flow at `/auth/login` (browsers, ADR-0019/0020), or a dvt-issued\n  API key sent as `Authorization: Bearer dvt_live_...` (CI, SDKs, MCP agents \u2014 created at\n  `POST /v1/api-keys`, admin/owner only, secret shown once). A key inherits its creator's\n  user, organization, and role.\n- CSRF guard: session-cookie authentication is rejected (403, problem type\n  `cross-origin-cookie`) on state-changing requests whose `Origin` is not the app's own.\n  Cross-origin and programmatic callers must use an API key.\n",
    "license": {
      "name": "Apache-2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    },
    "contact": {
      "name": "dvt",
      "url": "https://dvt.dev"
    }
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Local development (make dev)"
    },
    {
      "url": "https://app.dvt.dev/api",
      "description": "dvt Cloud (production) \u2014 single-origin; the edge proxies /api/* to the API (ADR-0020). api.dvt.dev is reserved for the Phase 2.1 token-only programmatic API."
    }
  ],
  "tags": [
    {
      "name": "meta",
      "description": "Service metadata \u2014 health and the API contract itself."
    },
    {
      "name": "auth",
      "description": "WorkOS AuthKit browser login flow and the authenticated principal (ADR-0019)."
    },
    {
      "name": "dashboards",
      "description": "Dashboard metadata and spec CRUD."
    },
    {
      "name": "folders",
      "description": "The org's dashboard folder tree (hierarchical organization)."
    },
    {
      "name": "specs",
      "description": "Spec validation (no persistence)."
    },
    {
      "name": "data",
      "description": "Data query \u2014 proxied to the analytics engine via warehouse pushdown."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "meta"
        ],
        "summary": "Liveness/readiness probe.",
        "description": "Returns 200 when the API and its database pool are healthy, 503 otherwise.",
        "security": [],
        "responses": {
          "200": {
            "description": "Healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Unhealthy \u2014 database unreachable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "unhealthy"
                    },
                    "db": {
                      "type": "string",
                      "example": "unreachable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "meta"
        ],
        "summary": "This API contract.",
        "description": "Returns this OpenAPI document as JSON. Part of the API surface (ADR-0007).",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "get": {
        "operationId": "authLogin",
        "tags": [
          "auth"
        ],
        "summary": "Start the WorkOS AuthKit login flow.",
        "description": "Sets a short-lived CSRF state cookie and redirects (302) to WorkOS AuthKit. On success\nWorkOS redirects back to `/auth/callback`. Browser-only \u2014 not for programmatic clients.\n",
        "security": [],
        "responses": {
          "302": {
            "description": "Redirect to the WorkOS AuthKit authorization URL."
          }
        }
      }
    },
    "/auth/callback": {
      "get": {
        "operationId": "authCallback",
        "tags": [
          "auth"
        ],
        "summary": "Complete the WorkOS AuthKit login flow.",
        "description": "Verifies the CSRF state, exchanges the authorization code with WorkOS, JIT-provisions\nthe local user/org membership (first member becomes owner, later members editor), seals\nthe `dvt_session` cookie (AES-256-GCM, httpOnly), and redirects to the app.\n",
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Session established; redirect to the app."
          },
          "400": {
            "description": "Missing/invalid OAuth state or authorization code."
          },
          "502": {
            "description": "The WorkOS code exchange failed."
          }
        }
      }
    },
    "/auth/logout": {
      "get": {
        "operationId": "authLogout",
        "tags": [
          "auth"
        ],
        "summary": "End the session.",
        "description": "Clears the session cookie and redirects to the app.",
        "security": [],
        "responses": {
          "302": {
            "description": "Session cleared; redirect to the app."
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "auth"
        ],
        "summary": "The authenticated principal.",
        "description": "Returns the current user, their organization, and their org-level role.",
        "responses": {
          "200": {
            "description": "The current principal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Me"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but the account holds no active organization membership (the invitation gate, or a membership revoked after the session or key was issued). Problem type `no-membership`. Clients must not retry the login flow on this \u2014 re-authenticating cannot change the outcome.\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api-keys": {
      "get": {
        "operationId": "listApiKeys",
        "tags": [
          "auth"
        ],
        "summary": "List the organization's API keys.",
        "description": "Non-secret fields only \u2014 the plaintext is never retrievable. Admin/owner only.",
        "responses": {
          "200": {
            "description": "The org's keys, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Caller's role is below admin.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "auth"
        ],
        "summary": "Create an API key.",
        "description": "Mints a dvt-issued key scoped to the caller's user + organization (the key acts AS its\ncreator \u2014 same role, same tenant). The response carries the plaintext **once**; only its\nSHA-256 is stored. Admin/owner only.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human label, e.g. 'ci-bot'."
                  },
                  "expiresInDays": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Omit for no expiry."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new key. Store `data.key` now \u2014 it is never shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "description": "The full secret (dvt_live_...), shown once."
                        },
                        "apiKey": {
                          "$ref": "#/components/schemas/ApiKey"
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Caller's role is below admin.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api-keys/{id}": {
      "delete": {
        "operationId": "revokeApiKey",
        "tags": [
          "auth"
        ],
        "summary": "Revoke an API key.",
        "description": "Revocation (not deletion \u2014 the row stays for audit) takes effect immediately. Admin/owner only; cross-tenant is a 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked (idempotent)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Caller's role is below admin.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/me/favorites": {
      "get": {
        "operationId": "listFavorites",
        "tags": [
          "dashboards"
        ],
        "summary": "The caller's favorited dashboards.",
        "description": "Personal, user-scoped curation (the home screen's Favorites row), newest\nfavorite first. Only live dashboards in the caller's org surface. The `data`\narray is always present (never null). Spec field omitted (use GET /v1/dashboards/{id}).\n",
        "responses": {
          "200": {
            "description": "Favorited dashboards.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Collection"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Dashboard"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/me/favorites/{dashboardId}": {
      "parameters": [
        {
          "name": "dashboardId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "put": {
        "operationId": "addFavorite",
        "tags": [
          "dashboards"
        ],
        "summary": "Star a dashboard.",
        "description": "Idempotent. Not role-gated \u2014 favorites are personal and mutate no shared\nstate; viewers pinning the dashboards they read is the core use case. The\ndashboard must be live in the caller's org (cross-tenant 404s).\n",
        "responses": {
          "204": {
            "description": "Favorited (idempotent)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "removeFavorite",
        "tags": [
          "dashboards"
        ],
        "summary": "Un-star a dashboard.",
        "description": "Idempotent \u2014 removing a non-favorite is a no-op 204. Cross-tenant dashboards 404.",
        "responses": {
          "204": {
            "description": "Removed (idempotent)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/orgs/{orgId}/activity": {
      "parameters": [
        {
          "name": "orgId",
          "in": "path",
          "required": true,
          "description": "Must be the caller's own organization (anything else 404s).",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "listOrgActivity",
        "tags": [
          "dashboards"
        ],
        "summary": "Recent org activity.",
        "description": "The 20 most recent audit events for the organization \u2014 the real activity\nfeed. Entries carry the actor's display name and a best-effort resource\nlabel (the dashboard title, when resolvable). Events older than the\naudit_logs.org_id migration (0010) are unattributed and not returned.\n",
        "responses": {
          "200": {
            "description": "Recent audit events, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Collection"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ActivityEvent"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/folders": {
      "get": {
        "operationId": "listFolders",
        "tags": [
          "folders"
        ],
        "summary": "List the organization's folders.",
        "description": "The whole folder tree, flat (id + parentId, name-ordered) \u2014 clients assemble the\nhierarchy. The `data` array is always present (never null).\n",
        "responses": {
          "200": {
            "description": "All folders in the org.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Collection"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Folder"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createFolder",
        "tags": [
          "folders"
        ],
        "summary": "Create a folder.",
        "description": "Creates a folder, root-level by default or nested under `parentId`. Editor or above.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Display name, e.g. 'Marketing'."
                  },
                  "parentId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Omit for a root-level folder."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Folder created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FolderEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The parent folder does not exist (cross-tenant parents are indistinguishable).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/folders/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Folder ID (UUID).",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "operationId": "updateFolder",
        "tags": [
          "folders"
        ],
        "summary": "Rename and/or move a folder.",
        "description": "Renames the folder and/or moves it to a new parent. `parentId: null` moves it to\nroot; an absent `parentId` leaves the parent unchanged. Moving carries the folder's\nwhole subtree; a move into the folder's own subtree is rejected (409 folder-cycle).\nRequires the folder's current `version` (optimistic concurrency). Editor or above.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "version"
                ],
                "properties": {
                  "version": {
                    "type": "integer"
                  },
                  "name": {
                    "type": "string"
                  },
                  "parentId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "New parent, or null for root. Omit to keep the current parent."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated folder (version incremented).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FolderEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale version (version-conflict) or a move into the folder's own subtree (folder-cycle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteFolder",
        "tags": [
          "folders"
        ],
        "summary": "Delete an empty folder.",
        "description": "Soft-deletes the folder. Refused (409 folder-not-empty) while the folder still\ncontains dashboards or child folders \u2014 move or delete the contents first. Editor\nor above.\n",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The folder still contains dashboards or child folders.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/dashboards": {
      "get": {
        "operationId": "listDashboards",
        "tags": [
          "dashboards"
        ],
        "summary": "List dashboards.",
        "description": "Returns up to 50 dashboards. The `data` array is always present (never null).",
        "responses": {
          "200": {
            "description": "A collection of dashboards.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Collection"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Dashboard"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createDashboard",
        "tags": [
          "dashboards"
        ],
        "summary": "Create a dashboard from a spec.",
        "description": "Validates the spec against the dvt dashboard JSON Schema, decomposes its panels and\n`lg`-breakpoint layout into elements, and persists a new dashboard. If the spec omits\n`id`, the server generates one and injects it into the stored spec.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDashboardRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dashboard created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/dashboards/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        }
      ],
      "get": {
        "operationId": "getDashboard",
        "tags": [
          "dashboards"
        ],
        "summary": "Get a dashboard by ID.",
        "responses": {
          "200": {
            "description": "The dashboard, including its full current-revision spec.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "patchDashboard",
        "tags": [
          "dashboards"
        ],
        "summary": "Update a dashboard (optimistic concurrency).",
        "description": "Updates `title`, `visibility`, and/or the full `spec`. Requires the current `version`\nfor optimistic concurrency; a stale version returns 409. If a new spec is provided it is\nre-validated before persistence.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchDashboardRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dashboard updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/VersionConflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/dashboards/{id}/folder": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        }
      ],
      "put": {
        "operationId": "moveDashboard",
        "tags": [
          "folders"
        ],
        "summary": "Move a dashboard into a folder (or unfile it).",
        "description": "Sets the dashboard's containing folder. `folderId: null` unfiles it (root level).\nThe target folder must exist in the caller's org (cross-tenant folders 404).\nEditor or above, and owner-scoped: only the dashboard's owner (`ownerId`) may\nmove it \u2014 org admins/owners override. A non-owner editor gets a 403 with\nproblem type `https://docs.dvt.dev/errors/not-owner`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "folderId"
                ],
                "properties": {
                  "folderId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Target folder, or null to unfile."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Moved."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/dashboards/{id}/revisions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        }
      ],
      "get": {
        "operationId": "listDashboardRevisions",
        "tags": [
          "dashboards"
        ],
        "summary": "List a dashboard's revision history.",
        "description": "Returns the dashboard's revisions newest-first (ADR-0021 Phase 3: pointer-set revisions).\nEach entry is a header \u2014 version, actor, change summary, timestamp. Use a version with\nPOST .../restore to revert the whole dashboard.\n",
        "responses": {
          "200": {
            "description": "The dashboard's revision history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardRevisionCollection"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/dashboards/{id}/restore": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        }
      ],
      "post": {
        "operationId": "restoreDashboard",
        "tags": [
          "dashboards"
        ],
        "summary": "Restore a dashboard to a prior version.",
        "description": "Reverts the whole dashboard to the chosen revision. Restore is a forward edit \u2014 a new\nrevision is written and the dashboard's version increments \u2014 so history stays append-only.\nThe primary path is identity-preserving: it restores the manifest spine and sets each page\nand element back to its pinned version from the target revision's pointer set (a per-child\nforward edit). When that is not possible \u2014 the target revision predates pointer sets, or a\nlater full-spec update re-keyed its children \u2014 it rebuilds from the revision's stored snapshot.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dashboard restored; returns the dashboard at its new version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        }
      ],
      "get": {
        "operationId": "listPages",
        "tags": [
          "dashboards"
        ],
        "summary": "List a dashboard's pages.",
        "description": "Live pages in display order (position, then slug).",
        "responses": {
          "200": {
            "description": "The dashboard's pages.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Collection"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Page"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createPage",
        "tags": [
          "dashboards"
        ],
        "summary": "Add a page to a dashboard.",
        "description": "Appends an empty page (position = end, empty \"lg\" layout) to a decomposed,\nmulti-page dashboard. Granular structural writes are refused on legacy\n(pre-decomposition) dashboards (409 legacy-dashboard) and on single-page\ndashboards (409 single-page-dashboard) \u2014 convert via a whole-spec update.\nEditor or above.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Optional; generated from the title when omitted."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "page",
                      "modal",
                      "popup",
                      "drilldown"
                    ],
                    "default": "page"
                  },
                  "isHidden": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Page created (version 1).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Slug already in use (slug-taken), legacy dashboard, or single-page dashboard.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/reorder": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        }
      ],
      "post": {
        "operationId": "reorderPages",
        "tags": [
          "dashboards"
        ],
        "summary": "Reorder a dashboard's pages.",
        "description": "Sets the page order. `order` must list every live page exactly once (slugs or\nUUIDs); position becomes the list index. Concurrency is checked against the\nDASHBOARD version (page order is dashboard-level arrangement). Editor or above.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "order",
                  "version"
                ],
                "properties": {
                  "order": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Every live page, by slug or UUID, in the desired order."
                  },
                  "version": {
                    "type": "integer",
                    "description": "The dashboard's current version."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The pages in their new order.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Collection"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Page"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Malformed body, or the order does not cover the live pages exactly (reorder-mismatch).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale dashboard version, legacy dashboard, or single-page dashboard.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        }
      ],
      "delete": {
        "operationId": "deletePage",
        "tags": [
          "dashboards"
        ],
        "summary": "Delete an empty page.",
        "description": "Soft-deletes a page with no live elements (409 page-not-empty otherwise). The\ndashboard must keep at least one page (409 last-page). The page's current\nversion travels via the `version` query parameter or `If-Match` header.\nEditor or above.\n",
        "parameters": [
          {
            "name": "version",
            "in": "query",
            "required": false,
            "description": "The page's current version (or use If-Match).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale version, page not empty, last page, or legacy dashboard.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}/elements": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        }
      ],
      "post": {
        "operationId": "createElement",
        "tags": [
          "dashboards"
        ],
        "summary": "Add an element to a page.",
        "description": "The granular create: adds one panel \u2014 validated against the element schema \u2014\nwithout round-tripping the whole spec. The element is placed into the page's\nlayout at (x, y, w, h) on every breakpoint, the page is versioned, and a new\ndashboard revision is written. Refused on legacy (pre-decomposition)\ndashboards (409 legacy-dashboard). Editor or above.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Optional; generated from the title when omitted."
                  },
                  "type": {
                    "type": "string",
                    "example": "chart:bar"
                  },
                  "title": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "spec": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "overrides": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "x": {
                    "type": "integer",
                    "default": 0
                  },
                  "y": {
                    "type": "integer",
                    "default": 0
                  },
                  "w": {
                    "type": "integer",
                    "default": 1,
                    "minimum": 1
                  },
                  "h": {
                    "type": "integer",
                    "default": 1,
                    "minimum": 1
                  },
                  "zIndex": {
                    "type": "integer",
                    "default": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Element created (version 1), with themeContext.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElementEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Slug already in use (slug-taken) or legacy dashboard.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}/elements/{elementId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        },
        {
          "$ref": "#/components/parameters/ElementId"
        }
      ],
      "get": {
        "operationId": "getElement",
        "tags": [
          "dashboards"
        ],
        "summary": "Get a single element.",
        "description": "Returns one element (type, title, grid position, data, spec, overrides) without\nround-tripping the whole dashboard spec. The response includes a `themeContext` map \u2014\nthe resolved values of every theme token the element references \u2014 so an agent can\ninterpret the element in isolation (mcp-server-design.md \u00a7Token budget).\n",
        "responses": {
          "200": {
            "description": "The element plus its resolved themeContext.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElementEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "patchElement",
        "tags": [
          "dashboards"
        ],
        "summary": "Patch a single element (RFC 6902, optimistic concurrency).",
        "description": "Applies an RFC 6902 JSON Patch to one element. Paths target the element fields\n`/type`, `/title`, `/data/...`, `/spec/...`, `/overrides/...` and the grid coordinates\n`/x`, `/y`, `/w`, `/h`, `/zIndex`. This lets an agent make a surgical edit \u2014 e.g. one\nseries color \u2014 without re-sending the entire spec.\n\nThe body is a pure patch document, so the current element version is supplied\nout-of-band: pass it via the `version` query parameter or an `If-Match` header. A stale\nversion returns 409. The result is re-validated against the dashboard JSON Schema and a\nnew dashboard revision is written so GET /v1/dashboards/{id} stays consistent.\n",
        "parameters": [
          {
            "name": "version",
            "in": "query",
            "required": false,
            "description": "The element's current version (optimistic concurrency). Required unless supplied via If-Match.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": false,
            "description": "Alternative to the `version` query parameter \u2014 the element's current version.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/JsonPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Element updated; returns the new element + themeContext.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElementEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/VersionConflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "operationId": "deleteElement",
        "tags": [
          "dashboards"
        ],
        "summary": "Delete an element.",
        "description": "Soft-deletes the element and removes it from the page's layout (versioning the\npage). The element's revision history is retained; its slug becomes reusable.\nThe element's current version travels via the `version` query parameter or\n`If-Match` header. Refused on legacy dashboards (409 legacy-dashboard).\nEditor or above.\n",
        "parameters": [
          {
            "name": "version",
            "in": "query",
            "required": false,
            "description": "The element's current version (or use If-Match).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale version or legacy dashboard.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}/elements/{elementId}/revisions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        },
        {
          "$ref": "#/components/parameters/ElementId"
        }
      ],
      "get": {
        "operationId": "listElementRevisions",
        "tags": [
          "dashboards"
        ],
        "summary": "List an element's revision history.",
        "description": "Returns the element's revisions newest-first (ADR-0021 Phase 3: per-level versioning).\nEach entry is a header \u2014 version, actor, change summary, timestamp \u2014 without the\nsnapshot body. Use a version with POST .../restore to revert the element's content.\n",
        "responses": {
          "200": {
            "description": "The element's revision history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EntityRevisionCollection"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}/elements/{elementId}/restore": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        },
        {
          "$ref": "#/components/parameters/ElementId"
        }
      ],
      "post": {
        "operationId": "restoreElement",
        "tags": [
          "dashboards"
        ],
        "summary": "Restore an element to a prior version.",
        "description": "Reverts the element's content (type, title, data, spec, overrides) to the chosen\nrevision. Restore is a forward edit \u2014 a new revision is written and the element's\nversion increments \u2014 so history stays append-only. Grid coordinates are page-owned\nand are not changed by an element restore. A new dashboard revision is written so\nGET /v1/dashboards/{id} stays consistent.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Element restored; returns the new element + themeContext.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ElementEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}/revisions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        }
      ],
      "get": {
        "operationId": "listPageRevisions",
        "tags": [
          "dashboards"
        ],
        "summary": "List a page's revision history.",
        "description": "Returns the page's revisions newest-first (ADR-0021 Phase 3: per-level versioning).\nA page is versioned independently of its elements' content: it accrues a revision when\nits arrangement (layout) changes \u2014 e.g. an element move versions its page \u2014 or on\nrestore. Each entry is a header without the snapshot body.\n",
        "responses": {
          "200": {
            "description": "The page's revision history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EntityRevisionCollection"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/dashboards/{id}/pages/{pageId}/restore": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DashboardId"
        },
        {
          "$ref": "#/components/parameters/PageId"
        }
      ],
      "post": {
        "operationId": "restorePage",
        "tags": [
          "dashboards"
        ],
        "summary": "Restore a page to a prior version.",
        "description": "Reverts the page's arrangement (layout) and metadata to the chosen revision. Restore is\na forward edit \u2014 a new revision is written and the page's version increments \u2014 so history\nstays append-only. The promoted element coordinates are re-synced from the restored\nlayout; element content (panel JSON) is untouched, as element histories are independent.\nA new dashboard revision is written so GET /v1/dashboards/{id} stays consistent.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RestoreRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Page restored; returns the new page header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/specs/validate": {
      "post": {
        "operationId": "validateSpec",
        "tags": [
          "specs"
        ],
        "summary": "Validate a dashboard spec without persisting.",
        "description": "Validates the request body against the dvt dashboard JSON Schema. Returns\n`{ \"valid\": true }` on success, or a 422 Problem whose `fields` extension lists each\nviolation. Used by the MCP server's preview/dry-run mode and the `dvt validate` CLI.\n",
        "requestBody": {
          "required": true,
          "description": "A dvt dashboard spec document.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The spec is valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponseEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/v1/data/query": {
      "post": {
        "operationId": "queryData",
        "tags": [
          "data"
        ],
        "summary": "Run a query against a configured data source.",
        "description": "Proxies the query to the Python analytics engine, which executes it against the named\ndata source via warehouse pushdown (ADR-0011) and returns result rows. The frontend never\nqueries data sources directly.\n\nTenant gate: a data source registered to an organization is queryable only by that\norganization \u2014 a cross-tenant `sourceId` returns 404, indistinguishable from a source\nthat does not exist (ADR-0012).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QueryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Query result rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryResponseEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/EngineUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "dvt_session",
        "description": "Sealed httpOnly session cookie (AES-256-GCM), established by the WorkOS AuthKit flow\nat `/auth/login` (ADR-0019). First-party to app.dvt.dev under the single-origin\ntopology (ADR-0020).\n"
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "dvt-issued API key for programmatic access (CI, SDKs, MCP agents). Created at\n`POST /v1/api-keys` (admin/owner; plaintext shown once, only its SHA-256 stored).\nSend as `Authorization: Bearer dvt_live_...`; the key inherits its creator's\nuser, organization, and role, and revocation takes effect immediately.\n"
      }
    },
    "parameters": {
      "DashboardId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Dashboard ID (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "PageId": {
        "name": "pageId",
        "in": "path",
        "required": true,
        "description": "Page reference within the dashboard \u2014 its UUID or its slug.",
        "schema": {
          "type": "string"
        }
      },
      "ElementId": {
        "name": "elementId",
        "in": "path",
        "required": true,
        "description": "Element reference within the dashboard \u2014 its UUID or its slug (e.g. \"panel-revenue\").",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "ApiKey": {
        "type": "object",
        "description": "Non-secret view of a dvt-issued API key.",
        "required": [
          "id",
          "name",
          "prefix",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "Non-secret identifier, e.g. dvt_live_ab12cd34."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "revokedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Me": {
        "type": "object",
        "description": "The authenticated principal \u2014 user, organization, and org-level role.",
        "required": [
          "user",
          "org"
        ],
        "properties": {
          "user": {
            "type": "object",
            "required": [
              "id",
              "email",
              "displayName",
              "type"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": "string"
              },
              "displayName": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "description": "Principal type: user or system."
              }
            }
          },
          "org": {
            "type": "object",
            "required": [
              "id",
              "role"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "role": {
                "type": "string",
                "enum": [
                  "viewer",
                  "editor",
                  "admin",
                  "owner"
                ]
              }
            }
          }
        }
      },
      "Meta": {
        "type": "object",
        "description": "Response metadata.",
        "required": [
          "requestId"
        ],
        "properties": {
          "requestId": {
            "type": "string",
            "description": "Correlates with the X-Request-Id header."
          },
          "total": {
            "type": "integer",
            "description": "Item count",
            "present on collection responses.": null
          }
        }
      },
      "Collection": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {}
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "Dashboard": {
        "type": "object",
        "required": [
          "id",
          "folderId",
          "ownerId",
          "title",
          "visibility",
          "version",
          "schemaVersion",
          "createdAt",
          "updatedAt",
          "spec"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "folderId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The containing folder, or null when unfoldered. Set via PUT /v1/dashboards/{id}/folder."
          },
          "ownerId": {
            "type": "string",
            "format": "uuid",
            "description": "The user who created the dashboard. Only the owner (or an org admin/owner)\nmay move it between folders.\n"
          },
          "title": {
            "type": "string"
          },
          "visibility": {
            "type": "string",
            "enum": [
              "private",
              "shared",
              "public"
            ]
          },
          "version": {
            "type": "integer",
            "description": "Optimistic-concurrency version. Pass this back on PATCH."
          },
          "schemaVersion": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "spec": {
            "type": "object",
            "additionalProperties": true,
            "description": "The full dvt dashboard spec from the current revision snapshot."
          }
        }
      },
      "DashboardEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Dashboard"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "Folder": {
        "type": "object",
        "description": "A node in the org's dashboard folder tree. Folders are returned flat (id +\nparentId); clients assemble the hierarchy. Tenancy is org-wide \u2014 every member\nof the organization sees the same tree.\n",
        "required": [
          "id",
          "parentId",
          "name",
          "version",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The parent folder, or null for a root-level folder."
          },
          "name": {
            "type": "string"
          },
          "version": {
            "type": "integer",
            "description": "Optimistic-concurrency version. Pass this back on PATCH."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ActivityEvent": {
        "type": "object",
        "description": "One org activity-feed entry \u2014 an audit event with joined human context.",
        "required": [
          "id",
          "actorName",
          "actorType",
          "action",
          "resourceType",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "actorId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "actorName": {
            "type": "string",
            "description": "Display name, else email, else 'system'."
          },
          "actorType": {
            "type": "string",
            "enum": [
              "user",
              "agent",
              "system"
            ]
          },
          "action": {
            "type": "string",
            "example": "dashboard.update"
          },
          "resourceType": {
            "type": "string",
            "example": "dashboard"
          },
          "resourceId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "resourceLabel": {
            "type": "string",
            "nullable": true,
            "description": "Dashboard title when resolvable."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FolderEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Folder"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "Element": {
        "type": "object",
        "description": "A single dashboard element, fetched or patched without the rest of the spec.\n`themeContext`/`dvtRefs` are present on responses to let an agent interpret the\nelement in isolation.\n",
        "required": [
          "elementId",
          "slug",
          "pageId",
          "dashboardId",
          "type",
          "x",
          "y",
          "w",
          "h",
          "version"
        ],
        "properties": {
          "elementId": {
            "type": "string",
            "format": "uuid",
            "description": "Durable UUID primary key."
          },
          "slug": {
            "type": "string",
            "description": "Readable",
            "dashboard-scoped reference (the authored panel id)": null,
            "e.g. \"panel-revenue\".": null
          },
          "pageId": {
            "type": "string",
            "format": "uuid"
          },
          "dashboardId": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "example": "chart:bar"
          },
          "title": {
            "type": "string"
          },
          "x": {
            "type": "integer"
          },
          "y": {
            "type": "integer"
          },
          "w": {
            "type": "integer"
          },
          "h": {
            "type": "integer"
          },
          "zIndex": {
            "type": "integer"
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Panel data binding (sourceId, query/metricRef, params, optional baked rows)."
          },
          "spec": {
            "type": "object",
            "additionalProperties": true,
            "description": "The element body (near-passthrough to ECharts); token references stay unresolved."
          },
          "overrides": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-element token overrides."
          },
          "themeContext": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Resolved values of every theme token this element references."
          },
          "dvtRefs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Named $dvtRef registry keys the element uses (e.g. \"formatter:pie-label\")."
          },
          "version": {
            "type": "integer",
            "description": "Optimistic-concurrency version. Pass this back on PATCH."
          }
        }
      },
      "ElementEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Element"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "Page": {
        "type": "object",
        "description": "A single dashboard page header \u2014 identity and metadata plus the optimistic-concurrency\nversion. Arrangement (layout) and membership live in the page's stored fields; this view\nis what page revision/restore operations return.\n",
        "required": [
          "pageId",
          "slug",
          "dashboardId",
          "kind",
          "isHidden",
          "position",
          "version"
        ],
        "properties": {
          "pageId": {
            "type": "string",
            "format": "uuid",
            "description": "Durable UUID primary key."
          },
          "slug": {
            "type": "string",
            "description": "Readable",
            "dashboard-scoped reference (the authored page id)": null,
            "e.g. \"overview\".": null
          },
          "dashboardId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "page",
              "modal",
              "popup",
              "drilldown"
            ]
          },
          "isHidden": {
            "type": "boolean"
          },
          "position": {
            "type": "integer",
            "description": "Ordering among sibling pages."
          },
          "version": {
            "type": "integer"
          }
        }
      },
      "PageEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Page"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "EntityRevision": {
        "type": "object",
        "description": "A page or element revision header (no snapshot body).",
        "required": [
          "revisionId",
          "version",
          "actorType",
          "createdAt"
        ],
        "properties": {
          "revisionId": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": "integer",
            "description": "The entity version this revision captured."
          },
          "actorId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The actor who wrote the revision (null for system writes)."
          },
          "actorType": {
            "type": "string",
            "enum": [
              "user",
              "agent",
              "system"
            ]
          },
          "changeSummary": {
            "type": "string",
            "description": "Human-readable summary",
            "e.g. \"Patched element panel-revenue\".": null
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EntityRevisionCollection": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EntityRevision"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "DashboardRevision": {
        "type": "object",
        "description": "A dashboard-level revision header (no snapshot/pointer-set body).",
        "required": [
          "revisionId",
          "version",
          "actorType",
          "createdAt"
        ],
        "properties": {
          "revisionId": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": "integer",
            "description": "The dashboard version this revision captured."
          },
          "actorId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The actor who wrote the revision (null for system writes)."
          },
          "actorType": {
            "type": "string",
            "enum": [
              "user",
              "agent",
              "system"
            ]
          },
          "changeSummary": {
            "type": "string",
            "description": "Human-readable summary",
            "e.g. \"Restored dashboard to v3\".": null
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DashboardRevisionCollection": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DashboardRevision"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "RestoreRequest": {
        "type": "object",
        "required": [
          "version"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "minimum": 1,
            "description": "The prior version to restore (from GET .../revisions)."
          }
        }
      },
      "JsonPatch": {
        "type": "array",
        "description": "An RFC 6902 JSON Patch document.",
        "items": {
          "$ref": "#/components/schemas/JsonPatchOp"
        }
      },
      "JsonPatchOp": {
        "type": "object",
        "required": [
          "op",
          "path"
        ],
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "add",
              "remove",
              "replace",
              "move",
              "copy",
              "test"
            ]
          },
          "path": {
            "type": "string",
            "example": "/spec/series/0/itemStyle/color"
          },
          "from": {
            "type": "string",
            "description": "Source path for move/copy."
          },
          "value": {
            "description": "New value for add/replace/test."
          }
        }
      },
      "CreateDashboardRequest": {
        "type": "object",
        "required": [
          "spec"
        ],
        "properties": {
          "visibility": {
            "type": "string",
            "enum": [
              "private",
              "shared",
              "public"
            ],
            "default": "private"
          },
          "spec": {
            "type": "object",
            "additionalProperties": true,
            "description": "The full dvt dashboard spec (meta, theme, layout, panels)."
          }
        }
      },
      "PatchDashboardRequest": {
        "type": "object",
        "required": [
          "version"
        ],
        "description": "Provide `version` plus any subset of the updatable fields.",
        "properties": {
          "version": {
            "type": "integer",
            "description": "The current version, from the latest GET. Required for optimistic concurrency."
          },
          "title": {
            "type": "string"
          },
          "visibility": {
            "type": "string",
            "enum": [
              "private",
              "shared",
              "public"
            ]
          },
          "spec": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ValidateResponseEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "valid"
            ],
            "properties": {
              "valid": {
                "type": "boolean",
                "const": true
              },
              "warnings": {
                "type": "array",
                "description": "Advisory ECharts option lint (ADR-0022): unknown-key and wrong-type findings\nfrom checking chart panel specs against the generated per-series ECharts\nschemas. Valid specs may carry warnings; warnings never block persistence.\n",
                "items": {
                  "type": "object",
                  "required": [
                    "path",
                    "message"
                  ],
                  "properties": {
                    "path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "QueryRequest": {
        "type": "object",
        "required": [
          "sourceId",
          "query"
        ],
        "properties": {
          "sourceId": {
            "type": "string",
            "description": "Connection name or UUID (resolved per connection-as-code.md)."
          },
          "query": {
            "type": "string",
            "description": "SQL to execute against the source via pushdown."
          },
          "params": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional named query parameters."
          }
        }
      },
      "QueryResponseEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "rows": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "columns": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "rowCount": {
                "type": "integer"
              }
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "FieldError": {
        "type": "object",
        "description": "A single schema-validation violation, shaped for LLM self-correction.",
        "required": [
          "path",
          "message"
        ],
        "properties": {
          "path": {
            "type": "string",
            "example": "panels[2].type"
          },
          "message": {
            "type": "string"
          },
          "received": {
            "type": "string"
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 Problem Details, extended with a dvt `suggestion` field.",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri-reference",
            "example": "https://docs.dvt.dev/errors/validation"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string",
            "format": "uri-reference"
          },
          "suggestion": {
            "type": "string",
            "description": "dvt extension \u2014 an actionable instruction written for an AI agent."
          }
        }
      },
      "ValidationProblem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Problem"
          },
          {
            "type": "object",
            "properties": {
              "valid": {
                "type": "boolean",
                "const": false
              },
              "fields": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FieldError"
                }
              }
            }
          }
        ]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or expired session.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource does not exist.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The caller's role does not permit this action.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "VersionConflict": {
        "description": "The resource was modified since you last fetched it (optimistic-concurrency).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ValidationError": {
        "description": "The spec failed JSON Schema validation. `fields` lists each violation.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationProblem"
            }
          }
        }
      },
      "EngineUnavailable": {
        "description": "The analytics engine could not be reached or returned an error.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "sessionCookie": []
    },
    {
      "bearerAuth": []
    }
  ]
}
