---
title: "Dry-run a recurrence — compute the next N fire times without persisting."
method: POST
path: "/v1/dashboards/{id}/export-schedules/preview"
tags: ["exports"]
---

# Dry-run a recurrence — compute the next N fire times without persisting.

`POST /v1/dashboards/{id}/export-schedules/preview`

## Request

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

Validates a cron expression or preset and returns the next N fire times in UTC
without creating any persistent record. This is the agent "see before committing"
tool — call it to confirm a schedule fires at the intended times before calling
`createExportSchedule`. Requires `dashboard:write` (same gate as create).

**Recurrence — cron or preset, not both:**
Supply exactly one of `cron` or `preset` (same XOR rule as `createExportSchedule`).

`timezone`: IANA timezone name (default `"UTC"`). The cron is evaluated in this
timezone, but all `nextRuns` timestamps are returned in UTC (RFC 3339).

`count`: number of occurrences to return (default 5, clamped 1–20).

**Errors:** 400 on invalid cron expression, or both/neither of cron/preset
supplied. 422 when `timezone` cannot be resolved to an IANA zone (DVT-4321) —
the failing value is echoed in a `timezone` extension. The `detail` field
describes the exact rejection.
403 if the caller lacks `dashboard:write`. 404 if the dashboard does not exist
or is not visible to the caller.

## Request body

- PreviewExportScheduleRequest: Body for POST /v1/dashboards/{id}/export-schedules/preview. Provide either `cron` (a raw 5-field cron expression) or `preset` — not both. All fields except `cron`/`preset` are optional.
  - `cron` string: A canonical 5-field cron expression (validated server-side).
  - `preset` ExportSchedulePreset: A convenience preset that normalises to a canonical 5-field cron expression server-side (ADR-0051 §1). Provide either `preset` or `cron`, not both.
    - `kind` 'hourly' | 'daily' | 'weekly' | 'monthly', required
    - `atMinute` integer: Minute-of-hour (0-59). Defaults to 0 when omitted.
    - `atHour` integer: Hour-of-day (0-23) for daily/weekly/monthly presets.
    - `dayOfWeek` integer: Day of the week (0=Sunday, 6=Saturday) for the weekly preset.
    - `dayOfMonth` integer: Day of the month (1-28) for the monthly preset.
  - `timezone` string: IANA timezone name (e.g. "America/New_York"). Defaults to UTC. A blank value, or `"Local"`, normalizes to `"UTC"`; any other name that cannot be resolved is a 422.
  - `count` integer: Number of next-fire times to return (clamped to 1–20).

## Response `200`

Computed fire times.

- PreviewExportScheduleEnvelope
  - `data` PreviewExportScheduleResponse, required: The computed next-fire times for the validated recurrence.
    - `cron` string, required: The normalised 5-field cron expression (after preset expansion, if applicable).
    - `timezone` string, required: The IANA timezone in which the cron is evaluated.
    - `nextRuns` string[], required: The next N fire times in UTC (RFC 3339), computed by chaining `nextCronOccurrence` starting from the server's current 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`: The caller's role does not permit this action.
- `404`: Resource does not exist.
- `422`: The `timezone` value could not be resolved to an IANA zone (DVT-4321). Problem `type` is `https://docs.dvt.dev/errors/unresolvable-timezone`, and the failing value is echoed in a `timezone` extension field. 422 rather than 400 because the body itself parsed: exactly one value failed semantic resolution. Two causes share this status and the response cannot tell them apart, so it names the zone and lets the caller judge: - the name is not an IANA zone — a typo, an abbreviation like `PST`, or a Windows zone id; send a current IANA name such as `America/Los_Angeles`; or - the name IS a valid IANA zone but the server build has no zone database. That was the `FROM scratch` runtime image before `cmd/api` embedded `time/tzdata`; every named zone failed there while `UTC` (which needs no lookup) succeeded. If a well-known zone draws this response, the deployment is broken, not the request.

---

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