Skip to content

Pagination

List endpoints page by cursor:

GET /v1/quizzes?limit=50
GET /v1/quizzes?limit=50&cursor=<next_cursor from the previous page>
  • limit is 20 by default, at least 1 and at most 100.
  • The response is {"data": [...], "has_more": true, "next_cursor": "..."}. When has_more is false, next_cursor is null.
  • A cursor is opaque and positional: it names a place in the list, never a filter. Send it back unchanged with the same filters, and leave it out on the first page (an empty cursor= is refused). A malformed cursor answers 422 validation_error.
  • Cursor lists are newest first and stable under inserts: a row created after the first page was read appears on a later request of the first page, never in the middle of a walk.
  • A query parameter the route does not declare answers 422 validation_error naming it.

Which lists page how

Every cursor list is newest first: quizzes, versions, exports, webhook endpoints, deliveries and events by creation time, people by last seen. A people list also answers total, returning_total and total_capped, and a search sends the same JSON body with every page while cursor and limit stay query parameters (see People). One list is different: the store’s quiz analytics pages by offset and limit (default 25, at most 1000) in the order of your sort and dir, and answers total.

Walking a list

Every walker below sends no cursor on the first page and the previous next_cursor afterwards:

Terminal window
url="https://api.octaneai.com/v1/quizzes?limit=100&status=published"
while :; do
page=$(curl -s "$url" -H "Authorization: Bearer $OCTANE_API_KEY")
echo "$page" | jq -r '.data[] | "\(.id) \(.name)"'
[ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
url="https://api.octaneai.com/v1/quizzes?limit=100&status=published&cursor=$(echo "$page" | jq -r '.next_cursor | @uri')"
done

For a search, the body travels with every page:

import os
import httpx
HEADERS = {"Authorization": f"Bearer {os.environ['OCTANE_API_KEY']}"}
body = {"has_marketing_consent": True} # any search body; see People
def walk_search(body):
params = {"limit": 100}
while True:
r = httpx.post("https://api.octaneai.com/v1/profiles/search", json=body, params=params, headers=HEADERS, timeout=30)
r.raise_for_status()
page = r.json()
yield from page["data"]
if not page["has_more"]:
return
params["cursor"] = page["next_cursor"]
print(sum(1 for _ in walk_search(body))) # how many people matched

Ids

Ids on the REST API and in webhook payloads are prefixed strings: quiz_, ver_, sess_, prof_, exp_, whe_, whd_, evt_, vis_, each followed by 32 hexadecimal characters (the storefront JS API mints its own evt_ ids with a dashed uuid). An id of the wrong kind, of another store, or that does not exist answers 404 not_found.