Skip to content

Pagination

Every endpoint that returns a list is cursor-paginated.

{
"data": [ { "id": "rec_…" }, { "id": "rec_…" } ],
"pagination": {
"next_cursor": "eyJvIjoxMDAsInMiOiJ…",
"has_more": true
}
}

Follow next_cursor until has_more is false:

cursor, out = None, []
while True:
r = requests.get(
"https://api.ahoy.ai/rest/v1/objects/ahoy_contact/records/",
headers={"Authorization": f"Bearer {key}"},
params={"cursor": cursor} if cursor else {},
timeout=30,
)
r.raise_for_status()
body = r.json()
out.extend(body["data"])
if not body["pagination"]["has_more"]:
break
cursor = body["pagination"]["next_cursor"]

Cursors are opaque. Pass back exactly the string you were given. Don’t decode, construct, or modify one — a cursor the server doesn’t recognise returns request_invalid with reason: "cursor_invalid".

Trust has_more, not the page size. A page may contain fewer items than you asked for and still not be the last one. has_more is the only reliable signal.

Cursors encode their query. Don’t change the filter, sort, or properties selection midway through a pagination run — start again with a fresh request.

?properties= limits which properties come back, which meaningfully reduces payload size on wide object types:

GET /rest/v1/objects/ahoy_contact/records/?properties=ahoy_email,ahoy_first_name,ahoy_last_name

POST …/records/aggregate/ pages over groups with limit and offset rather than a cursor, and reports has_more. It is the one exception to the cursor rule.