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

# Pagination and dates

> Walk large result sets, and filter them by date.

## Two kinds of list

Most list endpoints page with a cursor. A few return a complete set that does not page,
and truncate it with `limit` instead. Both shapes carry `has_more` and `next_cursor` so a
single client can read either, but only the paging kind ever sets them.

| Shape        | How to read all of it                            | Endpoints                                                                                                   |
| ------------ | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Cursor       | Follow `next_cursor` until `has_more` is `false` | Brands, prompts, answers, mentions, citations                                                               |
| Complete set | Raise `limit` until `capped` is `false`          | Topics, competitors, recommendations, generations, optimizations, audit reports, cited domains, cited pages |

## Cursor pagination

These endpoints return a page of results with a cursor:

```json theme={null}
{
  "data": [ { "id": "..." } ],
  "has_more": true,
  "next_cursor": "eyJjIjoiMjAyNi0wOC0zMVQwOTo0MTowMFoifQ"
}
```

To fetch the next page, send `next_cursor` back as the `cursor` parameter. Repeat until
`has_more` is `false`.

```python theme={null}
import os, requests

BASE = "https://api.geoptie.com"
headers = {"Authorization": f"Bearer {os.environ['GEOPTIE_API_KEY']}"}

def paginate(path, **params):
    cursor = None
    while True:
        if cursor:
            params["cursor"] = cursor
        r = requests.get(f"{BASE}{path}", headers=headers, params=params, timeout=60)
        r.raise_for_status()
        body = r.json()
        yield from body["data"]
        if not body.get("has_more"):
            return
        cursor = body["next_cursor"]

for brand in paginate("/v1/brands"):
    print(brand["name"])
```

<Note>
  Cursors are opaque. Pass them back exactly as received, and do not build, parse or store
  them.
</Note>

Results are returned newest first.

## Complete sets

The endpoints in the second row above return the whole set in one response. They have no
cursor, so `has_more` is always `false` and `next_cursor` is always `null`. Here `limit`
truncates the result rather than paging it, and two extra fields tell you whether that
happened:

```json theme={null}
{
  "data": [ { "brand_name": "..." } ],
  "has_more": false,
  "next_cursor": null,
  "total_count": 1583,
  "capped": true
}
```

`total_count` is how many rows matched before `limit` was applied. `capped` is `true` when
the response carries fewer than that, so these are the top rows and not all of them.

```python theme={null}
r = requests.get(f"{BASE}/v1/brands/{brand_id}/competitors",
                 headers=headers, params={"limit": 999}, timeout=60)
body = r.json()
if body["capped"]:
    print(f"showing {len(body['data'])} of {body['total_count']}")
```

Rows are ranked, so a capped response is the most significant ones rather than an arbitrary
slice. Raise `limit` to widen it, or narrow `from`/`to` to reduce what matches.

<Note>
  Always check `capped` before reporting a total. Reading `len(data)` as the count is
  correct only when `capped` is `false`.
</Note>

## Dates

All dates are ISO 8601.

| Parameter | Behaviour                    |
| --------- | ---------------------------- |
| `from`    | Inclusive                    |
| `to`      | Exclusive                    |
| Neither   | Defaults to the last 30 days |

Because `to` is exclusive, consecutive ranges line up without overlapping. A full month of
August is `from=2026-08-01&to=2026-09-01`.

Timestamps in responses are UTC, in the form `2026-08-31T09:41:00Z`.

A value that is not valid ISO 8601 returns `400 invalid_date`.

## Next steps

<CardGroup cols={2}>
  <Card title="Rate limits" icon="gauge" href="/docs/rate-limits">
    Pacing a large export
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/docs/errors">
    Handling failures
  </Card>
</CardGroup>
