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). Two kinds of list are 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. The experiment lists page by offset and limit (default 200, at most 500) and answer {"data": [...]} alone, with no has_more or next_cursor: the walkers below do not fit them, so ask for the next offset until a page comes back shorter than limit.

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_, expt_, arm_, 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.