---
title: "Query Dataset Endpoint"
method: POST
path: "/api/v2/datasets/{name}/query"
tags: ["APPS_V2"]
---

# Query Dataset Endpoint

`POST /api/v2/datasets/{name}/query`

Run a dataset query and return the engine's column/row envelope.

`timezone` (from the `x-timezone` header, default `"UTC"`) buckets any
`granularity`-based trend in the caller's timezone, matching v1's `GET
/warehouse/cost/v2/` — see `apps.engine.sql_builder.SqlBuilder.date_trunc`
for how it stays a bound parameter rather than an interpolated value.

## Path parameters

- `name` string, required

## Headers

- `x-tenant` string, required
- `x-user-group-id` string, nullable
- `x-timezone` string, nullable

## Request body

- DatasetQueryRequest — Request payload for `apps.engine.run_dataset_query`. `start_date`/`end_date` are calendar dates, not datetimes — the frontend always sends date-only `YYYY-MM-DD` values. `end_date` is INCLUSIVE for the caller; see `half_open_window` for the half-open datetime conversion the engine actually queries against. `granularity` buckets trend results via `apps.engine.sql_builder.SqlBuilder.date_trunc`. `group_by`/`order_by` are deliberately not accepted: free-text grouping/ordering from a client would be an injection surface against dataset-authored SQL. Datasets bake their own ordering, and grouping comes from `granularity`. `sort` is the one narrow exception: it carries a declared column key, resolved server-side via `resolve_sort` — never a raw SQL fragment. `search` is free-text substring search, ORed across the dataset's `searchable=True` columns (see `DatasetSpec.searchable_columns` and `resolve_search`). This is distinct from `filters`, which are exact IN-list matches against one named dimension each. The distinction mirrors v1's `/warehouse/` endpoint, which exposes BOTH a `warehouseName` exact IN-list param and a separate `search` ILIKE param (`app/service/warehouses.py:154-157`) — one endpoint, two independent matching semantics, because "give me exactly these warehouses" and "give me warehouses whose name contains this" are different questions. `page`/`size` apply ONLY when `DatasetSpec.paginated` is `True` — for a non-paginated (chart/trend) dataset they are accepted but ignored; `limit` continues to govern how many rows THAT dataset's `build` renders. `page` is 1-based to match v1's `GET /warehouse/?page=1&size= 20&...` and the frontend's existing pagination controls exactly; the engine (`apps.engine.sql_builder.SqlBuilder.limit_sql`) is what converts it to a 0-based `OFFSET = (page - 1) * size`, so no caller of this request model ever has to do that arithmetic itself.
  - `filters` object
  - `start_date` string, date, required
  - `end_date` string, date, required
  - `granularity` 'day' | 'week' | 'month'
  - `limit` integer
  - `sort` SortSpec — A request's sort key + direction. `key` is a declared column key, NEVER raw SQL — it is resolved against the dataset's `sortable` columns by `resolve_sort`, which is the only thing that turns it into SQL text. `direction` is lowercase to match the frontend's `SortDirection` (`"asc" | "desc"`) exactly.
    - `key` string, required
    - `direction` 'asc' | 'desc'
  - `search` string, nullable
  - `page` integer
  - `size` integer

## Response `200`

Successful Response

- DatasetEnvelope
  - `columns` ColumnDescriptor[], required
    - `key` string, required
    - `label` string, required
    - `type` string, required
    - `unit` string, nullable
    - `sortable` boolean, required
    - `filterable` boolean, required
    - `searchable` boolean, required
    - `hideable` boolean, required
    - `hidden_by_default` boolean, required
    - `info` string, nullable
    - `edit` EditDescriptor
      - `action` string, required
      - `options_from` string, nullable
    - `header` boolean, required
    - `link` LinkDescriptor — Wire form of `apps.specs.links.LinkSpec` — the in-app route template a header (or other) column's row value resolves into, per that module's docstring.
      - `template` string, required
    - `icon` string, nullable
    - `decoration` DecorationDescriptor — Wire form of `apps.specs.decorations.Decoration` — see that module's docstring for the core design rule (semantic `Tone`, never a colour) this descriptor carries onto the wire unchanged.
      - `display` string, required
      - `tone` string, nullable
      - `values` ValueStyleDescriptor[], required
        - `value` string, required
        - `tone` string, required
        - `label` string, nullable
        - `icon` string, nullable
      - `thresholds` ThresholdDescriptor[], required
        - `tone` string, required
        - `min` number, nullable
        - `max` number, nullable
    - `compose` ComposeDescriptor — Wire form of `apps.specs.compose.ComposeSpec` — the stacked-cell declaration on a composed column. `show_value` renders the composed column's own value as the leading line; `badge` names a sibling BADGE-decorated column whose badge renders under the lines.
      - `lines` ComposeLineDescriptor[], required
        - `parts` ComposePartDescriptor[], required
          - `column` string, required
          - `tone` string, nullable
        - `label` string, nullable
        - `separator` string
        - `when` ComposeWhenDescriptor — Wire form of `apps.specs.compose.ComposeWhen` — render the line only when the row's `column` value equals `equals` (string comparison).
          - `column` string, required
          - `equals` string, required
      - `badge` string, nullable
      - `show_value` boolean
    - `internal` boolean
    - `order` integer, required
  - `rows` object[], required
  - `meta` DatasetMeta, required — Metadata for a `DatasetEnvelope` — TABLE concepts only. No `reference` field: a reference line is chart PRESENTATION (which percentile line to draw over the series), not a fact about the queried data itself — it never belongs to a plain dataset query's result. See `ChartMeta` for where it actually lives.
    - `row_count` integer, required
    - `dataset` string, required
    - `total` integer, nullable
    - `page` integer, nullable
    - `size` integer, nullable
    - `pages` integer, nullable
    - `last_event_time` string, date-time, nullable
    - `prev` integer, nullable, required — The previous page number, or `None` on the first page. DERIVED, never passed in: `prev`/`next` are a pure function of `page` and `pages`, so making them settable fields would create a second source of truth that could disagree with them — a paginator that greys out its "next" arrow on a page that really does have a successor is a UI bug with no server-side symptom. As computed fields they cannot drift, and they still serialize into the JSON response exactly like plain fields.
    - `next` integer, nullable, required — The next page number, or `None` on the last page. `None` whenever `page >= pages`, which also covers the two edge cases worth being explicit about: a `total` of 0 gives `pages == 0`, so there is no next page; and a caller who asks for a page past the end gets `next=None` but a real `prev`, so the UI can walk back into range rather than dead-ending. Shadows the `next` builtin inside this class body only. That is deliberate — the wire contract is what matters here, and the field is named for the JSON key the frontend reads.

## Other responses

- `403` — Not supported for public user
- `422` — Validation Error

---

[API](https://skmtc.net/myaltimate/apis/fastapi.md) · [All operations](https://skmtc.net/myaltimate/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/myaltimate/fastapi/revisions/a71fce1f5c26/schema)
