Skip to main content
The /property/search endpoint supports two pagination modes: cursor (recommended) and offset (legacy, deprecated). This page explains both, when to use which, and how to handle common workflows.

TL;DR

  • For pagination beyond the first page, always use cursor.
  • Opt in by passing cursor=start on your first request. The response will include metadata.nextCursor for the next page.
  • Cursor pagination is constant-time per page regardless of how deep you’ve paginated.
  • The offset parameter still works for backwards compatibility but does not scale beyond a few thousand records and may time out.
  • If you’re doing incremental sync (fetching only what’s new since your last run), cursor pagination is the natural fit — save the nextCursor from your last response, and pass it back later to continue from exactly where you stopped.
Cursor mode is opt-in. Without the cursor query parameter, the response uses the legacy offset shape (metadata.offset: 0, no nextCursor). To enable cursor pagination, pass cursor=start on your first request. This keeps existing callers’ default responses unchanged.An empty value (cursor=) does not work. Our hosting layer discards query parameters that have no value, so cursor= arrives identical to sending no cursor at all and you get the offset shape back. Earlier versions of this page recommended cursor= — if you built against that, switch to cursor=start.

How cursor pagination works

Each response includes metadata.nextCursor — an opaque string token. Pass that token back as the cursor parameter on the next request to retrieve the next page. When there are no more results, nextCursor is null. To start a fresh pagination, send cursor=start. To continue a previous one, send the saved token.

Basic example

First page — pass cursor=start to opt into cursor mode:
Response:
Next page — pass back the nextCursor value as cursor:
Each response gives you a fresh cursor for the page after it. Repeat until nextCursor is null.

Last page

When you’ve reached the end of the result set, nextCursor is null:

Saving cursors for later

Cursor tokens are stateless and portable. They aren’t tied to a session, a connection, or a time window. You can:
  • Save the token to disk, a database, or environment variable
  • Send it to a different machine
  • Pause for hours, days, or weeks and resume from exactly the same point
This makes cursor pagination the ideal pattern for incremental sync workflows.

Incremental sync example

Suppose you’re keeping a local mirror of all properties in Nevada. You only want to fetch what’s new since your last run. First run (one-time setup): walk to the end of the current dataset and save the final cursor.
Subsequent runs (e.g., daily): load the saved cursor and pull only what’s new.
Each subsequent run only fetches properties added since your last cursor — typically a handful of pages, not the full dataset. Each page returns in around 200 ms regardless of how deep into the dataset you are.

Performance characteristics

The cursor’s per-page latency stays flat at any depth because the database can seek directly to your saved position via an index. Offset pagination has to walk through every record up to your offset on every request.
Known limitation: combine cursor with state, or state + county, only.Cursor pagination currently reads an index keyed on state and county. A zipCode or address filter is not part of that key, so the database has to scan the state in county order looking for matches. If the matching records sit in a later county, the request exceeds the 15-second query timeout and returns 503.The failure depends on where your matches fall, so a walk can succeed for several pages and then start returning 503 partway through — it is not a transient error and retrying does not help.Until this is fixed, keep zipCode- and address-filtered lookups on offset, where they return in roughly 200 ms and are unaffected.

Reference: cursor format

The cursor is an opaque base64url-encoded token. Its internal shape is an implementation detail, it differs between datasets, and it can change without notice. Never construct, parse, or edit one — pass back exactly what the API returned. The only value you may send that the API did not produce is the literal start.

Migrating from offset

If your existing code uses ?offset=N to paginate, the migration is one query parameter. Replace your first call’s offset with cursor=start, then use the returned nextCursor for subsequent pages.
The first response now includes metadata.nextCursor. Pass that value back as cursor on the next request and repeat until nextCursor is null. No need to track or increment offsets — the server hands you the next-page token each time. For codebases with deep pagination (offset > a few thousand), this migration is worth doing soon — offset calls at those depths can time out and return 503.

When to keep using offset

  • You’re prototyping and only ever fetch the first page or two
  • You have a small result set that fits comfortably under a few hundred records
  • You’re working with existing code that already uses offset and shallow depth
For anything else — production sync workflows, large datasets, deep pagination — use cursor. The offset parameter (with offset > 0) produces a Deprecation: true HTTP header and a metadata.deprecationNotice field in the response body.

Endpoints that support cursor pagination

Other paginated endpoints will adopt the same pattern over time. Until they do, cursor is silently ignored on endpoints that don’t support it.