# Pagination, filtering, sorting, and sparse fields

> Bound JSON:API collections explicitly and combine operation-declared filters, sort fields, includes, and sparse fieldsets safely.

Collection controls are declared per JSON:API operation. Do not assume every
collection has the same default, maximum, sortable fields, filter object, or
relationship includes. The [API reference](/reference) is authoritative for the
individual path.

## Bound every collection

Use deep-object page parameters and disable cURL's bracket globbing:

```bash
curl --silent --show-error --globoff \
  --header "Accept: application/vnd.api+json" \
  --header "Authorization: Bearer $TOKEN" \
  "$HODOFLOW_BASE_URL/api/json/v1/workflows?page[limit]=10&page[offset]=0"
```

`page[limit]` is the maximum number of resources requested; `page[offset]`
skips matching resources. Some operations also expose keyset values such as
`page[after]` and `page[before]`. Follow returned `links.next` and `links.prev`
instead of constructing cursors yourself.

An operation without a `page` parameter does not accept pagination. In
particular, never infer that omission means a universal default page size.

## Filters

Generated filters use a deep object. The available names and operators derive
from reviewed public attributes. A simple equality filter looks like:

```text
filter[is_active]=true
```

Nested operators, when declared, remain under the field name. Encode values as
query data; do not interpolate untrusted text into a URL by hand. A field that
is readable is not automatically filterable.

For user-provided filter values, use your HTTP library's query encoder. With
raw cURL, use `--get --data-urlencode` so reserved characters are encoded.

## Sorting

The `sort` parameter is a comma-separated list of allowed fields. Prefix a field
with `-` for descending order:

```text
sort=-inserted_at,name
```

Only fields matched by the operation's OpenAPI pattern are supported. Use a
stable tie-breaker where the contract exposes one; otherwise follow the
pagination links and do not assume ordering across concurrent writes.

## Sparse fieldsets

Request only needed attributes with a fieldset keyed by JSON:API type:

```text
fields[workflow]=name,is_active,version_number
```

The server still returns `type` and `id`. If included resources use another
type, provide a separate fieldset for that type. Sparse fields reduce response
size but do not grant access to hidden or non-public attributes.

## Includes

When the operation declares relationship paths, `include` sideloads them into
the top-level `included` array:

```text
include=steps,triggers
```

An include unsupported by that operation is an invalid request. Avoid broad
include trees in polling loops; retrieve only the relationships required for
the current decision.

## Edge cases and recovery

- Invalid page values, filter names, or sort patterns are client errors; correct
  them instead of retrying unchanged.
- An empty page can be valid, especially after an offset reaches the end.
- A missing `next` link means paging is complete.
- Keep `x-request-id` when a generated validation error is unclear.
- A `429` pauses the entire credential's request stream for `Retry-After`, not
  only the current page.

Apply these controls in [List and inspect workflows](/guides/list-workflows).
