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

# Pagination

> Keyset cursors: follow next_cursor until it is null, and what invalidates one.

The API pages a list with an opaque keyset cursor, not an offset. Ask for a page. Read `page.next_cursor`. Send it
back unchanged. If it is `null`, stop.

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.investorlift.com/v1/deals?lat=33.476917&lng=-111.920385&radius_miles=2&sort=date_desc&limit=100" \
    -H "Authorization: Bearer $GM_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    lat: "33.476917",
    lng: "-111.920385",
    radius_miles: "2",
    sort: "date_desc",
    limit: "100",
  });
  const res = await fetch(`https://api.investorlift.com/v1/deals?${params}`, {
    headers: { Authorization: `Bearer ${process.env.GM_API_KEY}` },
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const body = await res.json();
  ```

  ```python Python theme={null}
  import os

  import requests

  r = requests.get(
      "https://api.investorlift.com/v1/deals",
      params={
          "lat": 33.476917,
          "lng": -111.920385,
          "radius_miles": 2,
          "sort": "date_desc",
          "limit": 100,
      },
      headers={"Authorization": f"Bearer {os.environ['GM_API_KEY']}"},
      timeout=30,
  )
  r.raise_for_status()
  body = r.json()
  ```
</CodeGroup>

```json theme={null}
{
  "data": [],
  "page": { "next_cursor": "eyJ2IjoxLCJydW4iOiIxNzg4NDY5ODE5Iiw...", "limit": 100, "returned": 100 }
}
```

The next page is the same request with `cursor=` added. Change nothing else:

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.investorlift.com/v1/deals?lat=33.476917&lng=-111.920385&radius_miles=2&sort=date_desc&limit=100&cursor=eyJ2IjoxLCJydW4iOiIxNzg4NDY5ODE5Iiw" \
    -H "Authorization: Bearer $GM_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    lat: "33.476917",
    lng: "-111.920385",
    radius_miles: "2",
    sort: "date_desc",
    limit: "100",
    cursor: "eyJ2IjoxLCJydW4iOiIxNzg4NDY5ODE5Iiw",
  });
  const res = await fetch(`https://api.investorlift.com/v1/deals?${params}`, {
    headers: { Authorization: `Bearer ${process.env.GM_API_KEY}` },
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const body = await res.json();
  ```

  ```python Python theme={null}
  import os

  import requests

  r = requests.get(
      "https://api.investorlift.com/v1/deals",
      params={
          "lat": 33.476917,
          "lng": -111.920385,
          "radius_miles": 2,
          "sort": "date_desc",
          "limit": 100,
          "cursor": "eyJ2IjoxLCJydW4iOiIxNzg4NDY5ODE5Iiw",
      },
      headers={"Authorization": f"Bearer {os.environ['GM_API_KEY']}"},
      timeout=30,
  )
  r.raise_for_status()
  body = r.json()
  ```
</CodeGroup>

`returned` is how many rows this page holds. A short page is not the end of the list. A `null` cursor is the end. Do
not compute a page count: the API never counts the whole set for you. `summary`, where a route has one, is the totals
over the filtered set. It is not a row count you can page through.

## Page sizes

| Endpoint                                                   | Default | Maximum |
| ---------------------------------------------------------- | ------- | ------- |
| `/v1/deals`, `/v1/investors/{id}/deals`, `/v1/str-parcels` | 500     | 500     |
| `/v1/investors`, `/v1/buyers/match`                        | 100     | 200     |
| `/v1/properties/search`                                    | 100     | 500     |

These are the API's own maxima. On `api.investorlift.com`, the API lowers a `limit` above your plan's largest page to
that page before the request runs. `page.limit` says what the API used. [Plans and limits](/guides/plans-and-limits)
has the page per plan.

`/v1/investors/search` and the other name searches are ranked lookups, not lists. They return at most 50 hits, and
`page.next_cursor` is always `null`. `page.capped` is true when more names matched than `limit` allowed, and the API
cut the page there.

## What kills a cursor

The API issues a cursor for one query against one build of the data. The cursor encodes the dataset version of every
market it touched, a fingerprint of the query, and the sort keys of the last row. Any change to those makes it
undecodable, and the API answers `400 invalid_cursor` for the next page:

* The data refreshed while you paged. The dataset version moved, so the row order is no longer the one you walked.
* You changed a filter, the geometry, the sort, the ranking weights or `w_activity` between pages.
* You sent a cursor from a different endpoint.

The only recovery is to restart from page 1 without the cursor. The error says so. A change of `limit` alone is
safe: the limit is not part of the fingerprint.

<Note>
  Cursors are opaque. Their contents are an implementation detail and will change. Read `next_cursor` and pass it back.
  Never parse or construct one.
</Note>

## Sort order

`sort=` fixes the order a cursor walks. Deals take `distance`, `date_desc` or `date_asc`, and `price_desc` or
`price_asc`. `distance` is the default when you gave a point, and sorts nearest first. `date_desc` is the default
with a `bbox` alone. Investors take
`events_desc` (the default), `parcels_desc`, `volume_desc`, `last_bought_desc`, `last_deal_desc`, `flips_desc` or
`holds_desc` (the holdings inside the geometry).

Rows with a null key sort last in **both** directions. So an undated deal stays at the tail if you asked for oldest
first or for newest first. An unpriced one does the same. For `sort=distance` without a reference point, the API
answers `400 sort_requires_point`.


## Related topics

- [What's going on around this address?](/guides/walkthroughs/map.md)
- [Authentication](/guides/concepts/authentication.md)
- [API reference](/api-reference/introduction.md)
- [Versioning](/guides/concepts/versioning.md)
- [The response envelope](/guides/concepts/envelope.md)
