---
title: "Poll an async query job."
method: GET
path: "/v1/data/query/jobs/{jobId}"
tags: ["data"]
---

# Poll an async query job.

`GET /v1/data/query/jobs/{jobId}`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/data/query/jobs/{jobId}`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Returns the current status of a query submitted asynchronously via
`POST /v1/data/query` (DVT-708, ADR-0050). While running, polls the engine's
stateless `/internal/query/poll` endpoint (re-sending the resolved connection
bundle so the engine carries no per-job state). When the job has already reached
a terminal state, serves the result from the in-memory cache (when still warm) or
returns the terminal status without rows (cache expired).

**AUTHZ**: the calling actor must be in the same org as the job AND their
warehouse identity (the role dimension, computed the same way as the submit path)
must equal the job's frozen `role_dim` — so an `oauth_passthrough` user cannot
poll another user's job, and a differently-role-mapped service caller cannot poll
a job keyed for a different role. Mismatch → 404 (indistinguishable from
not-found).

## Path parameters

- `jobId` string, uuid, required

## Response `200`

Job status (running, succeeded, failed, or canceled).

- QueryJobStatus: Poll response for GET /v1/data/query/jobs/{jobId} (DVT-708, ADR-0050). All fields except status and elapsed_ms are conditional on the job's state.
  - `data` object, required
    - `status` 'running' | 'succeeded' | 'failed' | 'canceled', required: Current job state.
    - `elapsed_ms` integer, required: Milliseconds since started_at (approximate when still running).
    - `rows` object[]: Present and populated when status is "succeeded" and the result is still in the in-memory cache. May be absent if the cache entry has expired since the first successful poll.
    - `columns` string[]: Column names, in the same order as each row's values. Present with rows.
    - `rowCount` integer: Row count when status is "succeeded". Present with rows.
    - `truncated` boolean: True when the result was cut off at the engine's row ceiling. Present with rows.
    - `warehouse_query_id` string: Warehouse-native query id (Snowflake sfqid / BigQuery job id). Present after the job has succeeded.
    - `error` string: Error message when status is "failed".
  - `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.
- `404`: Job not found, or the caller's identity does not match the submitter's (indistinguishable).
- `429`: Two causes, distinguished by the Problem `type` (DVT-3464 split this from a single shared `errors/rate-limited`, which made an engine-capacity shed indistinguishable from an actual rate limit): - `https://docs.dvt.dev/errors/rate-limited` — a rate or concurrency limiter for this operation rejected the request before it reached the engine. See `components/responses/RateLimited` for the general per-actor SEC-2 tier; some operations stack an additional operation-specific limiter (the query submit rate limit, the per-org running-job cap, or the trial query ceiling). - `https://docs.dvt.dev/errors/engine-at-capacity` — the engine's own admission control (ADR-0053) shed the request before it reached the warehouse — a dvt engine limit, not a warehouse or rate problem (DVT-765). `suggestion` names the actual wait in seconds and, when the engine reports a scope, whether the per-org or global engine concurrency cap was hit. Both carry `Retry-After` (seconds); back off for at least that long before retrying.
- `502`: The analytics engine could not be reached (`https://docs.dvt.dev/errors/engine-unavailable`) or returned an error (`https://docs.dvt.dev/errors/engine-error`). One 502 on this shape is NOT an engine fault and says so: when Snowflake refuses the caller's-rights LOGIN, the Problem type is `https://docs.dvt.dev/errors/caller-connect-refused` — no session was established and the caller's SQL was never reached, so the remediation is a fresh dvt session rather than the engine logs (DVT-4345).

---

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