---
title: "Diff two renders of the dashboard (pixelmatch)."
method: POST
path: "/v1/dashboards/{id}/diffs"
tags: ["diffs"]
---

# Diff two renders of the dashboard (pixelmatch).

`POST /v1/dashboards/{id}/diffs`

## Request

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

Queues a pixel diff (DVT-25) between two **succeeded** renders of the dashboard and
returns **202** with the diff record; poll `GET …/diffs/{diffId}` for status, the
changed-pixel metrics, and the diff-image URL. The two renders must belong to this
dashboard, have succeeded with a stored artifact, and share width/height/format
(pixelmatch requires equal dimensions) — otherwise **422** problem type
`diff-input-invalid`. The visual-regression substrate for the agent critique/iterate
loop. Any org member may diff renders of a dashboard they can view; render sessions may
not. Returns **503** problem type `diff-unavailable` while no render service is configured.

## Request body

- CreateDiffRequest: The two renders to compare. Both must be SUCCEEDED renders of this dashboard that share width, height, and format (pixelmatch requires equal dimensions). They must differ.
  - `baseRenderId` string, uuid, required: The "before" render (the baseline).
  - `headRenderId` string, uuid, required: The "after" render (compared against the baseline).

## Response `202`

Diff queued.

- DiffEnvelope
  - `data` Diff, required
    - `id` string, uuid, required
    - `dashboardId` string, uuid, required
    - `baseRenderId` string, uuid, required
    - `headRenderId` string, uuid, required
    - `status` 'queued' | 'diffing' | 'succeeded' | 'failed', required
    - `error` string: Failure detail; present only when status is `failed`.
    - `diffPixels` integer: Number of changed pixels; present only when status is `succeeded`.
    - `totalPixels` integer: Total comparable pixels; present only when status is `succeeded`.
    - `diffRatio` number, double: diffPixels / totalPixels (0.0 = identical); present only when status is `succeeded`.
    - `url` string, uri: Pre-signed, expiring URL to the diff image; present only when status is `succeeded`.
    - `createdAt` string, date-time, required
    - `completedAt` string, date-time
  - `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.
- `404`: Resource does not exist.
- `422`: Problem type `diff-input-invalid` — the two renders aren't a valid diff pair.
- `429`: Per-org hourly diff budget exhausted (`rate-limited`, title "Too Many Diffs"): a fixed 60 diffs/hour per org in every edition (`server/internal/api/diffs.go` `diffRateLimit`); it is NOT governed by `RENDER_RATE_LIMIT`. The budget 429 carries no `Retry-After` header (the separate per-actor `RateLimitDefault` 429 does).
- `503`: Problem type `diff-unavailable` — no render service is configured.

---

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