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

# Pagination

> Traverse Biqli list endpoints safely with forward and backward public-ID cursors.

Biqli list endpoints use cursor pagination. Cursors remain stable when records
share the same creation time and avoid the shifting-page problems associated
with page numbers.

List results are ordered from newest to oldest by creation time. A public
resource ID from the current result set acts as the cursor.

## Supported endpoints

The same pagination contract is used when listing:

* [Links](/docs/api-reference/links/list)
* [Domains](/docs/api-reference/domains/list)
* [Folders](/docs/api-reference/folders/list)
* [Tags](/docs/api-reference/tags/list)
* [Tracking pixels](/docs/api-reference/tracking-pixels/list)
* [QR codes](/docs/api-reference/qr-codes/list)
* [Biolinks](/docs/api-reference/biolinks/list)

Each endpoint supports additional filters documented on its reference page.

## Parameters

| Parameter        | Type    | Default | Description                                                     |
| :--------------- | :------ | :------ | :-------------------------------------------------------------- |
| `page_size`      | integer | `50`    | Number of resources to return. Minimum `1`, maximum `100`.      |
| `starting_after` | string  | —       | Return the next, older page after this public resource ID.      |
| `ending_before`  | string  | —       | Return the previous, newer page before this public resource ID. |

`starting_after` and `ending_before` are mutually exclusive. Sending both
returns `422 validation_error`.

The cursor must use the resource type expected by the endpoint. For example,
the links endpoint accepts `biq_lnk_...`, while the domains endpoint accepts
`biq_dom_...`.

## Pagination object

Every list response contains its named collection and a `pagination` object:

```json theme={null}
{
  "links": [
    {
      "id": "biq_lnk_01M17YFAR0FEN7R66T99JH820N",
      "short_url": "https://biq.li/r4Q87",
      "long_url": "https://example.com/newer"
    },
    {
      "id": "biq_lnk_01M17Y6XGP07PR11S3DRFCX6Y9",
      "short_url": "https://biq.li/KUpd3Em",
      "long_url": "https://example.com/older"
    }
  ],
  "pagination": {
    "page_size": 2,
    "has_more": true,
    "next_cursor": "biq_lnk_01M17Y6XGP07PR11S3DRFCX6Y9",
    "previous_cursor": null
  },
  "status": "success"
}
```

| Field             | Description                                                                   |
| :---------------- | :---------------------------------------------------------------------------- |
| `page_size`       | Requested page size, even when fewer resources are returned.                  |
| `has_more`        | `true` when another older page is available.                                  |
| `next_cursor`     | Send as `starting_after` to retrieve that older page; `null` at the end.      |
| `previous_cursor` | Send as `ending_before` to retrieve the newer page; `null` on the first page. |

Use the returned cursor values directly. Do not construct them from array
positions or assume the last result always has another page.

## Move forward through results

<Steps>
  <Step title="Request the first page">
    Omit both cursors:

    ```bash theme={null}
    curl --request GET \
      --url 'https://biq.li/api/v1/link?page_size=2' \
      --header "Authorization: Bearer $BIQLI_API_KEY" \
      --header 'Accept: application/json'
    ```
  </Step>

  <Step title="Read next_cursor">
    If `pagination.next_cursor` is not `null`, copy it exactly from the
    response.
  </Step>

  <Step title="Request the next page">
    Send that value as `starting_after` while preserving the same filters:

    ```bash theme={null}
    curl --request GET \
      --url 'https://biq.li/api/v1/link?page_size=2&starting_after=biq_lnk_01M17Y6XGP07PR11S3DRFCX6Y9' \
      --header "Authorization: Bearer $BIQLI_API_KEY" \
      --header 'Accept: application/json'
    ```
  </Step>

  <Step title="Stop at the end">
    Continue until `next_cursor` is `null`. `has_more` will also be `false`.
  </Step>
</Steps>

## Move backward

To return to the previous, newer page, send the current response's
`previous_cursor` as `ending_before`:

```bash theme={null}
curl --request GET \
  --url 'https://biq.li/api/v1/link?page_size=2&ending_before=biq_lnk_01M17YFAR0FEN7R66T99JH820N' \
  --header "Authorization: Bearer $BIQLI_API_KEY" \
  --header 'Accept: application/json'
```

The returned collection is still ordered newest to oldest. Biqli does not
reverse the visible ordering when moving backward.

## Iterate through every result

The following server-side examples retrieve every link using the largest page
size and stop only when `next_cursor` becomes `null`:

<CodeGroup>
  ```bash cURL and jq theme={null}
  API_BASE='https://biq.li/api/v1'
  cursor=''

  while true; do
    url="${API_BASE}/link?page_size=100"
    if [ -n "$cursor" ]; then
      url="${url}&starting_after=${cursor}"
    fi

    response="$(curl --fail-with-body --silent --show-error \
      --request GET \
      --url "$url" \
      --header "Authorization: Bearer $BIQLI_API_KEY" \
      --header 'Accept: application/json')" || exit 1

    jq -c '.links[]' <<<"$response"
    cursor="$(jq -r '.pagination.next_cursor // empty' <<<"$response")"

    [ -n "$cursor" ] || break
  done
  ```

  ```javascript Node.js theme={null}
  const apiKey = process.env.BIQLI_API_KEY;
  let cursor;

  do {
    const url = new URL('https://biq.li/api/v1/link');
    url.searchParams.set('page_size', '100');
    if (cursor) url.searchParams.set('starting_after', cursor);

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${apiKey}`,
        Accept: 'application/json',
      },
    });
    const data = await response.json();

    if (!response.ok) {
      throw new Error(`${data.error?.code}: ${data.error?.message}`);
    }

    for (const link of data.links) {
      console.log(link);
    }

    cursor = data.pagination.next_cursor;
  } while (cursor);
  ```
</CodeGroup>

## Keep filters stable

A cursor is resolved inside the workspace and the filters used for that
request. Repeat the same filters and `page_size` on every page:

```http theme={null}
GET /link?active=true&tag_ids=biq_tag_...&page_size=100
GET /link?active=true&tag_ids=biq_tag_...&page_size=100&starting_after=biq_lnk_...
```

Changing a filter can make the cursor unavailable in the new result set. Biqli
then returns `404 resource_not_found` for the pagination cursor.

<Warning>
  Cursor pagination is not a database snapshot. Resources created while you
  traverse older pages can appear on newer pages, and deleting the resource
  used as a cursor can invalidate the next request. Restart from the first page
  if a cursor is no longer available.
</Warning>

## Common mistakes

| Problem                                    | Result                                                     |
| :----------------------------------------- | :--------------------------------------------------------- |
| Sending both cursor parameters             | `422 validation_error`                                     |
| Using the wrong resource-prefix cursor     | `422 validation_error` or `404 resource_not_found`         |
| Using a cursor from another workspace      | `404 resource_not_found`                                   |
| Changing filters between pages             | The cursor may return `404 resource_not_found`             |
| Treating `page_size` as a page number      | Repeated or missing data in client logic                   |
| Stopping only when the collection is empty | One unnecessary request; stop when `next_cursor` is `null` |

Read [Errors](/docs/api-reference/errors) for the complete error envelope and
retry guidance.
