---
title: "This install's shared dispatcher — observed heartbeat, declared role, and the admin setup SQL (DVT-4797)."
method: GET
path: "/v1/email-dispatcher"
tags: ["dashboards"]
---

# This install's shared dispatcher — observed heartbeat, declared role, and the admin setup SQL (DVT-4797).

`GET /v1/email-dispatcher`

## Request

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

INSTALL-LEVEL, not per-schedule: there is exactly one shared dispatcher
task per account (`DVT_SCHEDULES.PUBLIC.DVT_REPORT_DISPATCHER`), so its
observed state and declared role are an org-wide fact. Returns the
identical `adminSetupSql` text `GET .../email-schedules/{sid}/setup`
returns per schedule, plus when the dispatcher last called in, as
which role, and whether that role matches what an admin declared with
`PUT /v1/email-dispatcher`.

Requires `dashboard:write`, checked at the ORG level (there is no
single dashboard to resource-scope this read against) — the weakest
capability an editor who can open any dashboard's Schedule tab already
holds. Same `404`-feature-disabled gate as the rest of the
email-schedule group, and the same snowflake-mode requirement.

## Query parameters

- `warehouse` string

## Response `200`

The dispatcher's observed state and the admin setup statements.

- object
  - `data` EmailDispatcher, required: GET/PUT `/v1/email-dispatcher`'s response body (DVT-4797): the one, install-wide dispatcher task's observed heartbeat, declared role, and the identical admin setup SQL `GET .../email-schedules/{sid}/setup` returns per schedule.
    - `dispatcherTaskName` string, required: The one, install-wide task name every consumer's account holds at most one of (`DVT_SCHEDULES.PUBLIC.DVT_REPORT_DISPATCHER`) — the same value `EmailScheduleSetup.dispatcherTaskName` carries per schedule.
    - `declaredRole` string, required: This org's declared dispatcher role: an admin's own `PUT` if one has been made, else the same default the admin setup SQL itself falls back to.
    - `firstSeenAt` string, date-time: When the dispatcher first called in for this org. ABSENT (the key itself omitted) when it has never been seen.
    - `lastSeenAt` string, date-time: When the dispatcher most recently called in. ABSENT when it has never been seen.
    - `lastInvokerRole` string: The Snowflake role the most recent heartbeat observed. ABSENT when unknown (no heartbeat yet). Always absent in the native app: `INVOKER_ROLE()` reports the app name there, which is treated as unknown (DVT-4641). Never the invoker USER — that identifies a person, and this response never discloses one.
    - `roleMismatch` boolean, required: True only once a role has actually been observed AND it disagrees with `declaredRole` (exact, case-sensitive compare) — never true while `lastInvokerRole` is absent. A mismatch here means the last successful check-in ran as another role — that check-in carried no dispatcher task name (no role compare ran) or the declared role changed since. Refusals are reported via `lastRefusedAt`/`lastRefusedReason`. In the native app this is never true: the observed role is always unknown (`INVOKER_ROLE()` reports the app name, DVT-4641), so no role compare can run.
    - `anchorEnforced` boolean, required: Whether a call that does NOT carry the dispatcher task's own `TASK_NAME()` at all is refused outright (`true`) or let through with no role compare (`false`, shipped default). This does NOT gate `roleMismatch`: a role mismatch IS refused on the hop today regardless of this flag's value, but only for a call that DOES carry the dispatcher's own `TASK_NAME()` — a call without it never runs the role compare at all, see `roleMismatch`'s own description. False as shipped — pending the patch-10 rig re-drill that measures `TASK_NAME()` the way DVT-4640's drill measured `INVOKER_ROLE()`.
    - `stale` boolean, required: True when `lastSeenAt` is more than three times the dispatcher's own ~5-minute poll interval in the past (15 minutes), or the dispatcher has never been seen at all.
    - `lastRefusedAt` string, date-time: When this org's dispatcher path last REFUSED a call (migration 0112). ABSENT when no refusal has ever happened. Recorded BEFORE the hop answers the refusal to its caller, never load-bearing on that refusal itself.
    - `lastRefusedRole` string: The Snowflake role that refusal observed. ABSENT alongside `lastRefusedAt` when no refusal has ever happened, and also absent for an `anchor-missing` refusal whose own call carried no role to observe.
    - `lastRefusedReason` 'role-mismatch' | 'anchor-missing': Which half of the invoker gate fired: `role-mismatch` (the call carried the dispatcher's own task name but the wrong role) or `anchor-missing` (the call did not carry the dispatcher's task name at all, refused because `anchorEnforced` is true). The wire refusal a caller of the hop itself receives is unaffected by which of these it was — both answer the same published `not-the-dispatcher` shape; this field exists purely so an admin can tell the two apart after the fact. ABSENT when no refusal has ever happened.
    - `adminSetupSql` string, required: The identical text `EmailScheduleSetup.adminSetupSql` returns per schedule — the one-time, install-wide "Admin setup" block.
  - `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

- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Automatic sending is disabled on this deployment (`feature-disabled`) — see `GET .../setup`'s description for the two operator-facing reasons this can be true.
- `409`: Either this install is not a packaged Snowflake native app, so there is no application to qualify the generated statements with (`email-not-configured`), or this org's stored dispatcher role cannot be safely rendered as a Snowflake identifier (`dispatcher-role-invalid`) — the `type` distinguishes the two.
- `422`: `warehouse` does not match the Snowflake identifier grammar (`^[A-Za-z_][A-Za-z0-9_$]{0,254}$`, DVT-4490), or contains two consecutive `$` characters — refused even though the grammar alone admits a lone `$` anywhere after the first character, because an accepted value is spliced unquoted into the generated admin setup SQL, where `"$$"` could close a dollar-quoted block early (DVT-4796 r3). Problem `type` is `https://docs.dvt.dev/errors/validation-failed`, and the failing value is echoed in a `warehouse` extension field.
- `503`: This org's declared dispatcher role could not be read, so the admin setup statements cannot be shown safely (`dispatcher-state-unavailable`). Try again shortly.

---

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