Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pagination

This page describes how to walk the pages of endpoints that return lists. In Open Play there is no single pagination format: most endpoints use an opaque cursor, while a few use a different method. The table below shows which applies on which endpoint and the page size limits.

Cursor (keyset) pagination

Most core list endpoints are cursor-based:

  • Request: ?limit=<n>&cursor=<cursor>. On the first page you do not provide cursor.
  • Response: {"items":[…],"next_cursor":"…"}. If next_cursor is null, you are on the last page.
  • Next page: give the next_cursor from the previous response exactly as the cursor.
  • The cursor is opaque: do not parse its contents, do not modify it, and do not assume you can store it and use it after a long time. A broken cursor gets 400.
  • The ordering is fixed and OFFSET is not used; if new records are added between pages, no skipping or repetition occurs (records are ordered by a stable key).
  • The page size is given with limit; a value outside the bounds is clamped on most endpoints (it does not give an error). The exceptions are noted in the table.
GET /v1/characters?limit=2 HTTP/1.1
Authorization: Bearer crx_live_<prefix>_<secret>
{"items":[{"id":"…","definition":"hero","version":3}],"next_cursor":"<opaque-cursor>"}
GET /v1/characters?limit=2&cursor=<opaque-cursor> HTTP/1.1
Authorization: Bearer crx_live_<prefix>_<secret>

Formats by endpoint

EndpointFormatDefaultLimit
GET /v1/charactersCursor (definition filter)50clamped between 1 and 200
GET /v1/characters/{id}/events, …/timelineCursor (ascending seq)50clamped between 1 and 200
GET /v1/rules, GET /v1/webhooksCursor50clamped between 1 and 200
GET /v1/ledgerCursor (wallet_id required)50clamped between 1 and 200
GET /v1/items, GET /v1/player/itemsCursor (with owner)50clamped between 1 and 200
GET /v1/assetsCursor50 (0 or absent = default)at most 200
GET /v1/catalog/items, GET /v1/catalog/charactersCursor50clamped between 1 and 100
GET /v1/notificationsCursor (status, limit, cursor)50clamped between 1 and 100
GET /v1/leaderboards/{key}/entriesCursor20 (0 or absent = default)at most 100; more gets 400 (not clamped)
GET /v1/leaderboards/{key}/around-meAround the player (player_id, radius)—No pages; the surrounding windows are bounded
GET /v1/player/leaderboards/{key}Single page (limit)201 to 50; outside that 400
GET /v1/chat/channels/{channel_id}/messagesTime-based (before_ms + limit)501 to 100 (the service validates)
GET /v1/questsoffset + limit (search filter)20 (0 or absent = default)limit at most 100, offset at most 100000; more gets 400
GET /v1/auth/admin/list-playersCursor (UUID)the endpoint’s defaultEndpoint-specific
GET /v1/clans, GET /v1/partiesNo pagination; the whole result (a player_id filter for parties)——
GET /v1/wallets, GET /v1/inventories/{owner}No pagination (the whole list by owner)——
Plugin queries (/v1/plugins/…/queries/…)Plugin-specific; defined in the input schema—The plugin’s schema document

Note

In the response, the cursor field comes as next_cursor (snake_case) in core REST. In chat messages, to get a new page you give the oldest message’s sent_at_ms value as before_ms. The pagination of the panel APIs (offset/grid) is for the panel interface’s internal use and is outside the scope of this documentation.

Pagination in the SDK

The TypeScript SDK offers iterators for cursor lists: iterateListCharacterEvents, iterateGetCharacterTimeline, iterateListRules, iterateListWebhooks. These iterators follow next_cursor for you. Important rules:

  • The cursor is opaque; the iterator gives it back without converting it.
  • An empty next_cursor ends the loop; a cursor loop (the same cursor coming back) or exceeding maxPages is not a silent cut-off but an error of type pagination.
  • limit is u32 on the wire; the server clamps it to the range 1 to 200.

Patterns

Walking everything (server-to-server work): build a loop until the cursor comes back empty; put a short upper bound on pages (for example at most 1000 pages).

cursor = none
repeat:
    page = GET /v1/rules?limit=200[&cursor=cursor]
    process page.items
    cursor = page.next_cursor
    stop if cursor is empty

An in-game “load more” list: use a small limit (20 to 50), keep next_cursor in memory, and request the next page as the user scrolls. Do not store the cursor persistently.

Consistency: cursor pages walk live data; new records added while walking may not be visible if they fall behind the cursor. For reports that need a snapshot, record the query time (for example as of) on your side.

Related: endpoints, rate limits.