---
title: "Validate a dashboard spec without persisting."
method: POST
path: "/v1/specs/validate"
tags: ["specs"]
---

# Validate a dashboard spec without persisting.

`POST /v1/specs/validate`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/specs/validate`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Validates the request body against the dvt dashboard JSON Schema. Returns
`{ "valid": true }` on success, or a 422 Problem whose `fields` extension lists each
violation. Used by the MCP server's preview/dry-run mode and the `dvt validate` CLI.

## Request body

- object

## Response `200`

The spec is valid.

- ValidateResponseEnvelope
  - `data` object, required
    - `valid` true, required
    - `conformance` 'core' | 'full': The spec's portability layer (ADR-0014), computed during validation: `core` when the spec uses only the renderer-neutral dvt Core profile (the portable "industry standard" layer), `full` when it reaches for any escape hatch (ECharts `option` passthrough on a chart panel, a raw `html` panel, a `media` block, a `$dvtRef`, `chart:custom`, or an arbitrary-CSS canvas background). A reported level, never a failure — Full specs are valid. Optional for back-compat.
    - `warnings` object[]: Advisory lint on a passing spec (ADR-0022/DVT-243). Kinds, tagged by the `category` field: `echarts-key` (unknown ECharts option / wrong type), `data-binding` (panel schema-passes but renders empty), `collision` (grid items overflow the column count or overlap, per breakpoint), `contrast:token-pair` (a static theme-token or raw-hex-override text/bg pair below WCAG AA), `contrast:coverage` (an INFORMATIONAL note that the static contrast lint could not analyze some colors — $dvtRef / gradient — so it is not a complete WCAG pass; fires on valid specs), `layout` (e.g. an orphaned layout item with no matching panel), `query-sql` (DVT-1242: a panel's `data.query` is not executable SQL — a natural- language description written for a baked/rows panel), `brief-faithfulness` (DVT-1424: `meta.brief`/`purpose`/`keyQuestions` backtick-reference a panel or term absent from the spec's panels/pages), `hidden-page` (ADR-0036: an INFORMATIONAL reminder that `hidden` controls navigation, not access; fires on valid specs), and `interaction-stranding` (DVT-3138: an `onClick` or `contextMenu` action with `type:"drill"` whose `targetPage` is `hidden:true`, or — DVT-4218 — an `action-button` whose `action` is `{kind:"navigate", page}` at a `hidden:true` page; the viewer has no page-nav way back; use `openOverlay` instead). Valid specs may carry warnings; warnings never block persistence.
      - `path` string, required
      - `message` string, required
      - `category` string: Finding class (DVT-243): echarts-key | data-binding | collision | contrast:token-pair | contrast:coverage | layout | query-sql | brief-faithfulness | hidden-page | interaction-stranding. Omitted on findings that predate categorization.
    - `provenance` ProvenanceSuggestion[]: Advisory provenance suggestions (DVT-249, ADR-0004 Amendment 1) on a schema-passing spec. Records authoring intent so read endpoints can reason about where the right dashboard lives. NEVER blocks: the raw schema and the HTTP layer stay permissive (a hard 4xx would re-break restore/re-apply of pre-contract dashboards) — the `gate` severity is enforced at the authoring boundary (the MCP client / DVT-244), not here. Surfaced in the apply preview and on create/update responses. Omitted when there is nothing to suggest.
      - `field` string, required: Dotted spec path the suggestion concerns (e.g. `meta.brief`, `meta.panels.p1.serves_question`).
      - `severity` 'gate' | 'warn' | 'info', required: `gate` — the Tier-1 authoring gate (`meta.brief` on a 3+ panel dashboard); advisory at the server, treated as blocking by the authoring boundary. `warn` — a Tier-2 gap (missing `purpose`/`audience`/`keyQuestions` at 3+ panels, or a `serves_question` index past `keyQuestions`). `info` — a Tier-3 nudge (no `dataAsOf` on a data-bound dashboard, no per-panel provenance, an agent-asserted-but-unvalidated claim, or an orphan `meta.panels` key).
      - `message` string, required: Human/agent-readable guidance for resolving the suggestion.
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `422`: The spec failed JSON Schema validation. `fields` lists each violation.

---

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