> ## Documentation Index
> Fetch the complete documentation index at: https://developer.payrollintegrations.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination & Filtering

> Learn how to use pagination and filtering with endpoints in the Payroll Integrations API

## Introduction

When a response from the Payroll Integrations API would include many results, the data is paginated and a subset is returned.
For example, `/v1/connections/{connection}/employees` by default only returns the first 20 employees even if there are more employees available.

This page outlines how the response is structured for all paginated endpoints and how to configure the amount and order of the results.

## Request

### Query Parameters

<ParamField query="limit" type="number" default="20">
  The maximum number of records provided in the current response.
</ParamField>

<ParamField query="offset" type="number" default="0">
  The starting point for retrieving records with respect to the beginning of the list.

  The resulting list is **inclusive** and **zero-indexed** with respect to the offset value.
</ParamField>

<ParamField query="sort" type="string">
  The current sort order and direction.

  `sort` is a single string value following the regular expression format of `^(-?\w+)(,-?\w+)*$`.

  See the [Sort](#sort) section below for more information.
</ParamField>

<CodeGroup>
  ```html Request theme={null}
  /v1/foobar?limit=3&offset=5&sort=displayName
  ```

  ```json Request Schema theme={null}
  {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "Link": {
        "type": "string",
        "pattern": "<([^>]+)>;\\s*rel=\"(self|prev|next|last|first)\""
      }
    }
  }
  ```
</CodeGroup>

## Response

### Data

All returned records are located in the `data` property in the response.

### Metadata

Pagination metadata is provided in the `meta` property in the response. It contains the following properties:

<ParamField path="limit" type="number" default="20">
  The maximum number of records provided in the current response.
</ParamField>

<ParamField path="offset" type="number" default="0">
  The starting point for retrieving records with respect to the beginning of the list.

  The resulting list is **inclusive** and **zero-indexed** with respect to the offset value.
</ParamField>

<ParamField path="sort" type="string">
  The current sort order and direction.

  See the [Sort](#sort) section below for more information.
</ParamField>

<ParamField path="currentCount" type="number">
  The number of records in the current response.

  Note: This is not the same as `limit`. For example, a request can be configured to return a maximum of 20 records (`limit`) and only return 3 records (`currentCount`).
</ParamField>

<ParamField path="totalCount" type="number">
  The total number of records available.
</ParamField>

<ParamField path="filters" type="object">
  The filters applied to the request. This object contains key-value pairs where each key is a field name and each value is either a simple equality value or an object with operator-based filters.

  When filters are applied, the `totalCount` reflects the total number of records matching the filter criteria, not the total number of all records.
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
      "meta": {
          "limit": 3,
          "offset": 5,
          "sort": "displayName",
          "currentCount": 5,
          "totalCount": 103,
          "filters": {}
      },
      "data": [
          {
              "id": "34567a89-f6d8-4c47-b1db-c41d981a555e",
              "displayName": "Azalea Town Landscaping"
          },
          {
              "id": "1226ecf9-136b-43bc-8943-4f7b39a32b0c",
              "displayName": "Blackthorn City Mining"
          },
          {
              "id": "b70b563c-6983-4d79-858d-07477ccb4bd1",
              "displayName": "Cherrygrove City Sailing"
          }
      ]
  }
  ```

  ```json Response Schema theme={null}
  {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "data": {
        "type": "array",
        "items": {
          // This would be the data type of the list
        }
      },
      "meta": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 0
          },
          "offset": {
            "type": "integer",
            "minimum": 0
          },
          "sort": {
            "type": "string",
            "pattern": "^(-?\\w+)(,-?\\w+)*$"
          },
          "currentCount": {
            "type": "integer",
            "minimum": 0
          },
          "totalCount": {
            "type": "integer",
            "minimum": 0
          },
          "filters": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "limit",
          "offset",
          "sort",
          "currentCount",
          "totalCount",
          "filters"
        ]
      }
    },
    "required": [
      "data",
      "meta"
    ]
  }
  ```
</CodeGroup>

## Sort

The default sort property and direction varies depending on the endpoint.

If the provided value is not a valid sortable property for the records, the endpoint returns [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/400 "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/400"), detailing which fields can be used for sorting.
Even if the provided value is a valid property of the record schema, it does not guarantee that sorting is supported for that property.

If the first character of `sort` is `-` (e.g. `-sortProperty`), the list of records is returned in descending order.

To sort by multiple properties, separate the values by commas. The descending order syntax is also allowed for individual properties (e.g. `sort=sortProperty,-anotherProperty`).

Note: This returns a [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/400 "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/400") if the multi-sort configuration is not supported.

### Examples

#### Ascending Sort

<CodeGroup>
  ```html Request (ASC) theme={null}
  GET `/v1/foobar?limit=3&offset=5&sort=displayName` (with total count being 103)
  ```

  ```json Response (ASC) theme={null}
  {
      "meta": {
          "limit": 3,
          "offset": 5,
          "sort": "displayName",
          "currentCount": 3,
          "totalCount": 103,
          "filters": {}
      },
      "data": [
          {
              "id": "34567a89-f6d8-4c47-b1db-c41d981a555e",
              "displayName": "Azalea Town Landscaping"
          },
          {
              "id": "1226ecf9-136b-43bc-8943-4f7b39a32b0c",
              "displayName": "Blackthorn City Mining"
          },
          {
              "id": "b70b563c-6983-4d79-858d-07477ccb4bd1",
              "displayName": "Cherrygrove City Sailing"
          }
      ]
  }
  ```

  ```html Link Headers (ASC) theme={null}
  Link: <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=5>; rel="self"; <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=0>; rel="prev", <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=10>; rel="next", <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=100>; rel="last", <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=0>; rel="first"
  ```
</CodeGroup>

#### Descending Sort

<CodeGroup>
  ```html Request (DESC) theme={null}
  GET `/v1/foobar?limit=3&sort=-displayName` (with total count being 103)
  ```

  ```json Response (DESC) theme={null}
  {
      "meta": {
          "limit": 3,
          "offset": 0,
          "sort": "-displayName",
          "currentCount": 3,
          "totalCount": 103,
          "filters": {}
      },
      "data": [
          {
              "id": "34567a89-f6d8-4c47-b1db-c41d981a555e",
              "displayName": "Viridian City Waste Management"
          },
          {
              "id": "1226ecf9-136b-43bc-8943-4f7b39a32b0c",
              "displayName": "Vermilion City Power Company"
          },
          {
              "id": "b70b563c-6983-4d79-858d-07477ccb4bd1",
              "displayName": "Saffron City Hotel"
          }
      ]
  }
  ```

  ```html Link Headers (DESC) theme={null}
  Link: <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=0&sort=-displayName>; rel="self", <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=5&sort=-displayName>; rel="next", <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=100&sort=-displayName>; rel="last", <https://api.payrollintegrationsapp.com/v1/foobar?limit=3&offset=0&sort=-displayName>; rel="first"
  ```
</CodeGroup>

### Using `Link` Headers

Each response also includes a [Link header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link#pagination_through_links "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link#pagination_through_links") that contains the previous, next, first, and last “page” URLs that will need to be iterated through.

`limit` is preserved for all relevant links provided while `offset` is changed depending on the page.

Listed below are the link headers generated for a request with query parameters `limit=30&offset=60` and a total of 160 records:

```
Link: <https://api.payrollintegrationsapp.com/v1/payroll-platforms?limit=30&offset=60>; rel="self",
<https://api.payrollintegrationsapp.com/v1/payroll-platforms?limit=30&offset=30>; rel="prev",
<https://api.payrollintegrationsapp.com/v1/payroll-platforms?limit=30&offset=90>; rel="next",
<https://api.payrollintegrationsapp.com/v1/payroll-platforms?limit=30&offset=150>; rel="last",
<https://api.payrollintegrationsapp.com/v1/payroll-platforms?limit=30&offset=0>; rel="first"
```

The `Link` header provides the URL for the previous, next, first, and last page of results:

* The URL for the current page is followed by `rel="self"`.
* The URL for the previous page is followed by `rel="prev"`.
* The URL for the next page is followed by `rel="next"`.
* The URL for the last page is followed by `rel="last"`.
* The URL for the first page is followed by `rel="first"`.

In some cases, only a subset of these links are available. For example, the link to the previous page is not be included on the first page of results, and the link to the next page is not be included on the last page of results.

## Filtering

Many endpoints in the Payroll Integrations API support filtering to retrieve a subset of records that match specific criteria.

For example, `/v1/payroll-connections/:payroll_connection/employee-data?updatedAt[gt]=2026-01-01` returns only employee data which has been updated after January 1st, 2026.

This section outlines the filtering syntax, supported operators, and how to combine multiple filters.

### Query Parameters

Filters are applied using query parameters with the following syntax:

<ParamField query="field" type="string">
  Simple equality filter. Returns records where the field exactly matches the
  value.
</ParamField>

<ParamField query="field[operator]" type="string">
  Operator-based filter. Returns records where the field matches the value according to the specified operator.

  See the [Operators](#operators) section below for the full list of supported operators.
</ParamField>

<CodeGroup>
  ```html Simple Equality theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt=2024-01-01
  ```

  ```html With Operator theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[gte]=2024-01-01
  ```

  ```html Multiple Filters theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[gte]=2024-01-01&updatedAt[lte]=2024-12-31
  ```
</CodeGroup>

### Operators

The following operators are supported for filtering. Not all operators are available for every field; the allowed operators depend on the field's data type.

| Operator       | Description                          | Example                              |
| -------------- | ------------------------------------ | ------------------------------------ |
| (none)         | Equal (default)                      | `?status=active`                     |
| `eq`           | Equal                                | `?status[eq]=active`                 |
| `ne`           | Not equal                            | `?status[ne]=deleted`                |
| `gt`           | Greater than                         | `?amount[gt]=100`                    |
| `gte`          | Greater than or equal                | `?createdAt[gte]=2024-01-01`         |
| `lt`           | Less than                            | `?amount[lt]=500`                    |
| `lte`          | Less than or equal                   | `?createdAt[lte]=2024-12-31`         |
| `in`           | Matches any value in list            | `?status[in]=active,pending`         |
| `not_in`       | Does not match any value in list     | `?status[not_in]=deleted,archived`   |
| `contains`     | Case-insensitive substring inclusion | `?displayName[contains]=middle`      |
| `not_contains` | Case-insensitive substring exclusion | `?displayName[not_contains]=another` |
| `empty`        | Field is null                        | `?deletedAt[empty]=true`             |
| `not_empty`    | Field is not null                    | `?email[not_empty]=true`             |

### Combining Multiple Filters

Multiple filters can be applied in a single request by including multiple query parameters. All filters are combined using **AND** logic, meaning records must match all specified conditions.

<CodeGroup>
  ```html Request theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[gte]=2024-01-01&id[in]=10,20
  ```
</CodeGroup>

### Array Operators

The `in` and `not_in` operators accept comma-separated values:

<CodeGroup>
  ```html IN Operator theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?id[in]=1,2,3
  ```

  ```html NOT IN Operator theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?id[not_in]=4,5,6
  ```
</CodeGroup>

### Null Checking

Use the `empty` and `not_empty` operators to filter on null values. These operators accept `true` or `false` as values:

* `[empty]=true` or `[not_empty]=false` — field is null
* `[empty]=false` or `[not_empty]=true` — field is not null

<CodeGroup>
  ```html Check for NULL theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[empty]=true
  ```

  ```html Check for NOT NULL theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[not_empty]=true
  ```

  ```html Inverted (NOT NULL) theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[empty]=false
  ```
</CodeGroup>

### Filter Error Handling

The API returns [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/400) in the following cases:

* **Unknown filter field**: Attempting to filter on a field that does not support filtering
* **Invalid operator**: Using an operator that is not allowed for the specified field
* **Invalid value**: Providing a value that does not match the expected field type (e.g., non-UUID value for a UUID field)

<CodeGroup>
  ```json Unknown Field Error theme={null}
  {
      "error": {
          "code": "BAD_REQUEST",
          "message": "Unknown filter field 'invalidField'"
      }
  }
  ```

  ```json Invalid Operator Error theme={null}
  {
      "error": {
          "code": "BAD_REQUEST",
          "message": "Operator 'contains' is not allowed for field 'id'. Allowed operators: eq, ne, in, not_in"
      }
  }
  ```

  ```json Invalid Value Error theme={null}
  {
      "error": {
          "code": "BAD_REQUEST",
          "message": "Invalid value for field 'id': 'not-a-uuid'"
      }
  }
  ```
</CodeGroup>

### Response Metadata

When filters are applied to a request, the response metadata includes a `filters` object that reflects the active filters:

<CodeGroup>
  ```html Request theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[gte]=2024-01-01&updatedAt[lte]=2024-12-31
  ```

  ```json Response theme={null}
  {
      "meta": {
          "limit": 20,
          "offset": 0,
          "currentCount": 15,
          "totalCount": 15,
          "filters": {
              "updatedAt": {
                  "gte": "2024-01-01",
                  "lte": "2024-12-31"
              }
          }
      },
      "data": [
          ...
      ]
  }
  ```
</CodeGroup>

### Combining Filtering with Pagination

Filters can be combined with pagination parameters. Filters are applied first, then pagination is applied to the filtered results:

<CodeGroup>
  ```html Request theme={null}
  /v1/payroll-connections/:payroll_connection/employee-data?updatedAt[gte]=2024-01-01&limit=10&offset=20
  ```

  ```json Response theme={null}
  {
      "meta": {
          "limit": 10,
          "offset": 20,
          "currentCount": 10,
          "totalCount": 156,
          "filters": {
              "updatedAt": {
                  "gte": "2024-01-01"
              }
          }
      },
      "data": [
          ...
      ]
  }
  ```
</CodeGroup>

In this example, the `totalCount` reflects the total number of records matching the filter criteria (156 records with `updatedAt >= 2024-01-01`), not the total number of all records.
