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

# Pagination

> Page through Chataigne list endpoints with cursor-based pagination, the list envelope, and timestamp filters.

All Chataigne list endpoints return a consistent **list envelope** and use **cursor-based pagination**. Cursors are existing resource IDs, so paging stays stable even as records are created or deleted while you iterate.

## The list envelope

Every list response—on both `/v1` and `/admin/v1`—shares the same shape:

```json List response theme={null}
{
  "object": "list",
  "data": [
    { "id": "scl_8Hk2...", "object": "special_closing", "...": "..." },
    { "id": "scl_3Qm9...", "object": "special_closing", "...": "..." }
  ],
  "has_more": true,
  "url": "/v1/locations/loc_9aZ.../special_closings"
}
```

<ResponseField name="object" type="string">
  Always `"list"` for list responses.
</ResponseField>

<ResponseField name="data" type="array">
  The array of resource objects for this page, ordered by `created_at` by default.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  `true` when more records exist beyond this page. Use it as your stop condition when iterating—do not rely on `data.length`.
</ResponseField>

<ResponseField name="url" type="string">
  The path of the list endpoint that produced this response.
</ResponseField>

## Parameters

<ParamField query="limit" type="integer" default="10">
  Number of records to return per page. Must be between `1` and `100`.
</ParamField>

<ParamField query="starting_after" type="string">
  A resource `id` cursor. Returns the page of records **immediately after** this object in the default `created_at` order. Use it to page forward.
</ParamField>

<ParamField query="ending_before" type="string">
  A resource `id` cursor. Returns the page of records **immediately before** this object in the default `created_at` order. Use it to page backward.
</ParamField>

<Warning>
  `starting_after` and `ending_before` are **mutually exclusive**. Sending both in the same request returns `400 invalid_request_error`.
</Warning>

## Paging forward

To walk a list from the beginning, request the first page, then pass the **last** item's `id` as `starting_after` until `has_more` is `false`.

<CodeGroup>
  ```bash curl theme={null}
  # First page
  curl https://server.chataigne.ai/v1/locations/loc_9aZ4cR2pK1/special_closings?limit=2 \
    -H "x-api-key: ch_org_live_5fK..."

  # Next page — pass the last id from the previous page
  curl "https://server.chataigne.ai/v1/locations/loc_9aZ4cR2pK1/special_closings?limit=2&starting_after=scl_3Qm9vT7nB4" \
    -H "x-api-key: ch_org_live_5fK..."
  ```

  ```js Node.js theme={null}
  const BASE_URL = "https://server.chataigne.ai";
  const headers = { "x-api-key": process.env.CHATAIGNE_API_KEY };

  /**
   * Fetch a single page of special closings for a location.
   */
  async function listSpecialClosings({ locationId, limit = 100, startingAfter }) {
    const params = new URLSearchParams({ limit: String(limit) });
    if (startingAfter) params.set("starting_after", startingAfter);
    const res = await fetch(
      `${BASE_URL}/v1/locations/${locationId}/special_closings?${params}`,
      { headers },
    );
    if (!res.ok) throw new Error(`Request failed: ${res.status}`);
    return res.json();
  }

  /**
   * Iterate every special closing for a location, page by page.
   */
  async function getAllSpecialClosings(locationId) {
    const all = [];
    let startingAfter;
    while (true) {
      const page = await listSpecialClosings({ locationId, startingAfter });
      all.push(...page.data);
      if (!page.has_more) break;
      startingAfter = page.data[page.data.length - 1].id; // cursor = last id
    }
    return all;
  }

  const closings = await getAllSpecialClosings("loc_9aZ4cR2pK1");
  console.log(`Fetched ${closings.length} special closings`);
  ```
</CodeGroup>

<Note>
  Set `limit` to its maximum of `100` when bulk-syncing to minimize round trips. Keep it small for interactive UIs.
</Note>

## Paging backward

To page toward older records—for example, when scrolling up in a UI—use `ending_before` with the **first** item's `id` from the current page.

```bash curl theme={null}
curl "https://server.chataigne.ai/v1/locations/loc_9aZ4cR2pK1/special_closings?limit=2&ending_before=scl_8Hk2wF6dJ0" \
  -H "x-api-key: ch_org_live_5fK..."
```

## Timestamp filters

List endpoints accept timestamp filters to narrow results to a window. Combine them freely with the pagination cursors and `limit`.

<ParamField query="created_after" type="string">
  Only return records created strictly after this ISO 8601 timestamp.
</ParamField>

<ParamField query="created_before" type="string">
  Only return records created strictly before this ISO 8601 timestamp.
</ParamField>

<ParamField query="updated_after" type="string">
  Only return records updated strictly after this ISO 8601 timestamp.
</ParamField>

<ParamField query="updated_before" type="string">
  Only return records updated strictly before this ISO 8601 timestamp.
</ParamField>

All timestamps are ISO 8601 in UTC (e.g. `2026-05-01T00:00:00Z`). Results remain ordered by `created_at`.

```bash curl theme={null}
# Special closings created in May 2026, newest-friendly bulk page
curl -G "https://server.chataigne.ai/v1/locations/loc_9aZ4cR2pK1/special_closings" \
  -H "x-api-key: ch_org_live_5fK..." \
  --data-urlencode "created_after=2026-05-01T00:00:00Z" \
  --data-urlencode "created_before=2026-06-01T00:00:00Z" \
  --data-urlencode "limit=100"
```

```js Node.js theme={null}
const params = new URLSearchParams({
  created_after: "2026-05-01T00:00:00Z",
  created_before: "2026-06-01T00:00:00Z",
  limit: "100",
});
const res = await fetch(
  `https://server.chataigne.ai/v1/locations/loc_9aZ4cR2pK1/special_closings?${params}`,
  { headers: { "x-api-key": process.env.CHATAIGNE_API_KEY } },
);
const page = await res.json();
```

<Note>
  Timestamp filters apply to the same set of records the cursor walks. When using `created_after`/`created_before` with `starting_after`, keep the filter values constant across every page of a single iteration so the cursor stays consistent.
</Note>

## Best practices

* **Iterate on `has_more`, not on counts.** A full page (`data.length === limit`) can still be the last page.
* **Treat cursors as opaque.** Always pass back an `id` from a previous response; never construct or guess one.
* **Pick one direction.** Use `starting_after` *or* `ending_before`—never both.
* **Carry the same filters across pages** within a single iteration to keep the cursor stable.

<Card title="List endpoints" icon="list" href="/location-organization-management/overview">
  See which resources support listing—organizations, locations, and special closings—and their filterable fields.
</Card>
