---
title: "Request a render of the dashboard."
method: POST
path: "/v1/dashboards/{id}/renders"
tags: ["renders"]
---

# Request a render of the dashboard.

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

## Request

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

Queues a PNG/PDF render of the dashboard (ADR-0024) and returns **202** with the render
record; poll `GET …/renders/{renderId}` for status and the artifact URL. The revision is
pinned at request time (default: the dashboard's current version), so the artifact is
reproducible. Any org member may render a dashboard they can view; render sessions
themselves may not. Returns **503** problem type `render-unavailable` while no render
service is configured on the instance. The per-org hourly budget is operator-configurable (`RENDER_RATE_LIMIT`; default 10/hour on SaaS, no hourly cap in the native app).

Native app (DVT-1364, `exportDelivery: render_app_stage`): the API renders and stores the
artifact write-once on the app-owned stage `@app_spcs.renders`; the succeeded record's
`url` is the app-authenticated `GET …/renders/{renderId}/artifact` route (no signed URL),
and renders are listed only to their requester. A **429** with `Retry-After` there also
means too many renders are in flight on this app instance.

## Request body

- CreateRenderRequest: All fields optional — an empty body renders the current version as a 1280×800 PNG.
  - `purpose` 'validation': Async renders only (DVT-1364); rejected on `/renders/inline`. Only accepted in the Snowflake native app (render_app_stage) — a non-native-app deployment 400s regardless of caller role, since `source='validation'` is not a retention category there. `validation` stores the render as an expiring release-check render (deleted after `RENDER_VALIDATION_RETENTION_HOURS`, default 24) instead of a personal `user` render (which expires after `RENDER_USER_RETENTION_HOURS`, default the validation TTL); in the native app it additionally requires the admin-ranked `render:validate` capability (403 for a non-admin caller there). Any other value is 400.
  - `format` 'png' | 'pdf'
  - `revision` integer: Dashboard revision to render. Default — the current version, pinned at request time.
  - `width` integer: Viewport width in px.
  - `height` integer: Viewport height in px.
  - `page` integer: Zero-indexed page of a multi-page dashboard to capture. Default 0 (the first page); single-page dashboards ignore it. An in-range value past the last page clamps to the last page. Each render captures one page — to capture every page, request a render per page index.
  - `panelId` string: Optional panel id to render in isolation (DVT-860). When provided, the artifact captures only the named panel rather than the full page; the R2 key gains an `-el-<enc>` segment so panel artifacts never collide with whole-page artifacts. The artifact's own dimensions are the PANEL's, not the viewport's (DVT-4358): the panel is laid out edge-to-edge at `width` and the capture is clipped to its bounding box, so a PNG comes back `width` × 2 (the capture device pixel ratio) wide and as tall as the panel. `height` sizes only the viewport the panel is laid out in — it does not bound the image. Do not assume `width` × `height` × 2. The server validates only that the value is non-empty; panel-id resolution against the dashboard spec happens in the render page (client-side). If the id does not match any panel, the render will transition to `failed` rather than returning a 4xx error. When absent, the whole page is rendered (existing behavior, byte-identical artifact key).
  - `includeSummary` boolean: Only honored by `POST /renders/inline` (DVT-4428); ignored by the async `createRender`. When true, the 200 response is a JSON envelope carrying the image plus a machine-readable per-panel render summary, instead of raw artifact bytes — lets an agent verify a render (e.g. "drew 0 points") without a vision read of the image alone. Default false, byte-identical to today's behavior.

## Response `202`

Render queued.

- RenderEnvelope
  - `data` Render, required
    - `id` string, uuid, required
    - `dashboardId` string, uuid, required
    - `revision` integer, required: The dashboard revision the artifact captures (pinned at request time).
    - `format` 'png' | 'pdf', required
    - `width` integer, required
    - `height` integer, required
    - `page` integer: The 0-indexed page the artifact captures (default 0). Renders of different pages of the same revision are distinct artifacts.
    - `status` 'queued' | 'rendering' | 'succeeded' | 'failed', required
    - `error` string: Failure detail; present only when status is `failed`.
    - `url` string, uri-reference: Present only when status is `succeeded`. On SaaS/enterprise (R2), a pre-signed, expiring artifact URL. In the Snowflake native app (DVT-1364, render_app_stage), an app-authenticated artifact route (`GET …/renders/{renderId}/artifact`) returned as a same-origin relative path (not an absolute URL), not pre-signed and not expiring — only a signed-in human session can fetch it, not the calling MCP session; use `dvt_dashboard_render_inline` there for raw bytes.
    - `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.
- `403`: `purpose: validation` without the `render:validate` capability (admin).
- `404`: Resource does not exist.
- `429`: Per-org hourly render budget exhausted (`rate-limited`). The cap is `RENDER_RATE_LIMIT` (default 10/hour on SaaS; 0 = no hourly cap, the native-app default). The budget 429 carries no `Retry-After` header (the separate per-actor `RateLimitDefault` 429 does).
- `503`: Problem type `render-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)
