---
title: "Get Data from Dataset (v2)"
method: POST
path: "/api/v2/datasets/{datasetName}/data"
tags: ["Datasets", "Public API"]
---

# Get Data from Dataset (v2)

`POST /api/v2/datasets/{datasetName}/data`

Queries the named dataset and returns the matching rows. The response is a JSON object with `data` (an array of row objects, each wrapping its values under a `fields` object whose keys match the requested field names), `links` (pagination navigation URLs; `prev` and/or `next`, omitted at boundaries), and `meta` (with `page`, `pageSize`, `totalPages`, `totalItems`).

Use this for tabular reporting, custom field selection across many employees, and OData-style filtering against the full dataset field catalog. For multiple employees, use List Employees (`list-employees`). For the full set of fields on a single employee, use Get Employee (`get-employee`).

Discover valid `datasetName` values via List Datasets (v1.2) (`list-datasets-v1-2`). Discover valid field names for a dataset via Get Fields from Dataset (v1.2) (`get-fields-from-dataset-v1-2`); the field set is dataset-specific.

Field-name vocabulary differs from the dedicated employee endpoints. The `employee` dataset uses fully qualified names such as `jobInformationJobTitle`, `jobInformationDepartment`, `jobInformationReportsTo`, and `employmentStatus`, where the employee endpoints use endpoint-specific shorter names — for example `jobTitleName` on List Employees (`list-employees`), and `jobTitle`, `department`, `supervisor`, and `workEmail` on Get Employee (`get-employee`). Identifier and contact fields also differ: the dataset's internal employee identifier is `eeid` (`get-employee` returns the same value as `id`, and `list-employees` as `employeeId`); the dataset's work-email field is `email` (`get-employee` uses `workEmail`). The dataset also exposes `employeeNumber` — the editable Employee # value, not the internal employee ID. Do not pass `employeeNumber` to employee ID inputs such as `filter[ids]` or `{id}` path parameters; it may fail with `404` or resolve to a different employee whose internal employee ID happens to match. Always source field names from Get Fields from Dataset (v1.2) (`get-fields-from-dataset-v1-2`) — names from the employee endpoints will not necessarily resolve here.

For routine `employee` dataset lookups, a useful starter field set is: `firstName`, `lastName`, `eeid` (internal employee ID), `employeeNumber` (Employee # / editable employee number), `email` (work email), `mobilePhone`, `hireDate`, `employmentStatus`, `jobInformationJobTitle`, `jobInformationDepartment`, `jobInformationLocation`, `jobInformationReportsTo` (supervisor's name), and `supervisorEid` (supervisor's `eeid`). The full catalog (over 450 fields) is discoverable via Get Fields from Dataset (v1.2) (`get-fields-from-dataset-v1-2`) — use it when you need anything beyond this starter set.

Filter expressions use OData-style syntax with comparison operators `eq`, `ne`, `lt`, `le`, `gt`, `ge`, the list-membership operator `in (...)`, and logical operators `and` / `or`. The parser does not support `not in`; use `ne` for the inverse of `eq`, and on options-type fields `ne` already applies set-membership negation under the hood. A single filter expression may use `and` or `or`, but not both; mixed logical expressions return `422` with code `INVALID_FILTER`. Operator support varies by field type: `eq` / `ne` work on text, numeric, date, and options-type (single-select enum) fields like `status`, `employmentStatus`, and `jobInformationDepartment`; ordering operators `lt`, `le`, `gt`, `ge` are supported on numeric and date fields. The list operator `in` is the natural fit on options-type fields (e.g. `status in ('Active','Inactive')`), and is also accepted on text, numeric, and date fields when used with a single value (e.g. `firstName in ('Ashley')`, equivalent to `firstName eq 'Ashley'`); multi-value `in` on non-options fields returns `422`. To check a field's type, see the `type` value returned by Get Fields from Dataset (v1.2). Filter fields do not need to appear in the `fields` array; rows are filtered server-side. String literals in filter expressions are wrapped in single quotes, e.g. `firstName eq 'Ashley'` or `status in ('Active','Inactive')`.

On date fields, the ordering operators also accept a relative-date duration literal in OData v4 / ISO 8601 form, e.g. `hireDate ge duration'P30D'`. The literal resolves against today at query time: with `lt` / `le` it resolves to today minus the duration, with `gt` / `ge` to today plus the duration (so `terminationDate lt duration'P6M'` matches dates before six months ago, and `hireDate ge duration'P30D'` matches dates from 30 days in the future onward). A negative duration reverses the direction — `hireDate ge duration'-P30D'` matches hire dates from 30 days ago onward (i.e. anyone hired in the last 30 days), including any future-dated hires. Exactly one date component is required — `P<n>Y` (years), `P<n>M` (months), `P<n>W` (weeks), or `P<n>D` (days). Combined components (`P1Y6M`), time components (`PT12H`), and duration literals with `eq` / `ne` / `in` return `422` with code `INVALID_FILTER`.

`orderBy` is a comma-separated list of `<field> <asc|desc>` rules. Every field used in `orderBy` must also appear in the `fields` array; omitting it returns a `400` with code `UE-1001`. `orderBy` is optional; with no `orderBy` the row order is unspecified.

Field-level access is enforced server-side based on the authenticated caller's permissions and OAuth scopes. When a caller lacks permission to read a requested field, the field is still returned in each row with an empty value, and its canonical name is listed in that row's `_restrictedFields` array (e.g., `_restrictedFields: ["employee_employeeNumber"]`) — the request is not rejected, and other requested fields still come back. This differs from Get Employee (`get-employee`), which silently omits unauthorized fields with no marker, leaving an absent field ambiguous between "not requested" and "not permitted". Callers without dataset-level access receive `403`, and a dataset the caller cannot see at all returns `404` ("not accessible" rather than disclosing existence).

Bad field names in `fields` or `filter` return `400` with `code: INVALID_ARGUMENT` and the offending name in the `detail` field (e.g., `"Invalid field name: employee_workEmail"`). The reported name carries the internal `<dataset>_` prefix; the actual misspelling is the unprefixed form. Confirm spelling against Get Fields from Dataset (v1.2) (`get-fields-from-dataset-v1-2`). Note: the field name appears in `detail` (singular); the `details` field (plural) remains empty in this error class. (Bad names in `orderBy` hit the must-be-in-`fields` check above first and return `UE-1001` rather than `INVALID_ARGUMENT`.)

Pagination: `page` defaults to `1` and `pageSize` defaults to `100` (maximum `1000`). `meta.totalItems` is the count of rows matching the query across all pages, `meta.totalPages` is `ceil(totalItems / pageSize)`, and `data` contains at most `pageSize` rows for the requested page. `links.next` is present when more pages exist; `links.prev` is present when not on the first page. Errors follow RFC 7807 (`application/problem+json`).

Note: Compared to Get Data from Dataset (v1) (`get-data-from-dataset-v1`), this version uses `filter` and `orderBy` request fields instead of the legacy `filters` and `sortBy` structures, moves pagination inputs to body fields (`page`, `pageSize`) instead of query parameters, returns pagination in `links` / `meta` objects instead of a legacy `pagination` object, and returns RFC 7807 compliant error responses with an `X-Request-ID` correlation header.

OAuth Scopes: report

## Path parameters

- `datasetName` string, required

## Request body

- object
  - `fields` string[], required — Field names to return for each row. Names are dataset-specific; discover valid names via Get Fields from Dataset (v1.2) (`get-fields-from-dataset-v1-2`). The field-name vocabulary differs from the dedicated employee endpoints (e.g., the `employee` dataset uses `jobInformationReportsTo` / `jobInformationDepartment` / `employmentStatus` rather than List Employees' `reportsTo` / `department` / `status`).
  - `filter` string, nullable — OData-style filter expression. Comparison operators: `eq`, `ne`, `lt`, `le`, `gt`, `ge`. List-membership operator: `in (...)` (the parser does not support `not in`; use `ne` for the inverse). Logical operators: `and`, `or`. A single filter expression may use `and` or `or`, but not both; mixed logical expressions return `422` with code `INVALID_FILTER`. Operator support varies by field type. `eq` / `ne` work on text, numeric, date, and options-type (single-select enum) fields. Ordering operators (`lt`, `le`, `gt`, `ge`) work on numeric and date fields. `in (...)` is the natural fit on options-type fields like `status`, `employmentStatus`, and `jobInformationDepartment`, and is also accepted on text, numeric, and date fields when the list has a single value (equivalent to `eq`). Multi-value `in` on non-options fields returns `422`. On date fields, the ordering operators also accept a relative-date ISO 8601 duration literal with a single date component, e.g. `hireDate ge duration'P30D'` — resolved against today (`lt` / `le`: today minus the duration; `gt` / `ge`: today plus the duration). A negative duration reverses the direction: `hireDate ge duration'-P30D'` matches dates from 30 days ago onward, including future dates. Combined components (`P1Y6M`), time components (`PT12H`), and duration literals with `eq` / `ne` / `in` return `422`. To check a field's type, see the `type` value returned by Get Fields from Dataset (v1.2). String literals are wrapped in single quotes. Filter fields do not need to appear in `fields`.
  - `orderBy` string, nullable — Comma-separated sort rules; each rule is a field name followed by `asc` or `desc`. Every field referenced in `orderBy` must also appear in `fields` — otherwise the request is rejected with `400` and code `UE-1001`. Omit to leave row order unspecified.
  - `page` integer — Page number to retrieve (1-indexed). Defaults to 1.
  - `pageSize` integer — Number of records per page. Defaults to 100. Maximum is 1000.

## Response `200`

Object containing `data` (array of record objects each wrapping requested values under a `fields` object), `links` (pagination URLs; `prev` and/or `next`, omitted at boundaries), and `meta` (`page`, `pageSize`, `totalPages`, `totalItems`).

- object
  - `data` object[] — Array of record objects. Each object contains a `fields` object whose keys are the requested field names. Values may be strings, numbers, booleans, arrays/objects, or null depending on the dataset field and value.
    - `fields` object
  - `links` object — Pagination navigation links. Contains `next` and/or `prev` URLs when applicable.
    - `next` string, nullable
    - `prev` string, nullable
  - `meta` object — Pagination metadata.
    - `page` integer
    - `pageSize` integer
    - `totalPages` integer
    - `totalItems` integer

## Other responses

- `400` — Missing or invalid parameters.
- `401` — Unauthorized.
- `403` — Insufficient permissions to access the requested dataset.
- `404` — The specified dataset does not exist or is not accessible.
- `413` — The request produced too much data.
- `422` — Validation failed, such as an invalid filter expression or pagination value.
- `500` — Internal server error.

---

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