Pagination and filters
How to page through lists with keyset pagination (cursor/nextCursor) and which filters are shared across list endpoints.
Lists in V3 Custody use cursor-based keyset pagination: you page through results by passing the cursor from the previous page. Unlike offset pagination, keyset pagination is stable under inserts, so rows are never skipped or duplicated between pages.
Keyset pagination
A list returns its items along with the cursor for the next page:
{
"items": [ ... ],
"nextCursor": "eyJpZCI6..."
}
cursor: pass the previous page'snextCursorhere to get the next page.limit: the page size.- When
nextCursoris empty or missing, there are no more pages.
Sorting
By default, lists are sorted newest first: descending by a time key, with id as a tiebreaker. The cursor follows the same order:
| List | Sort key (descending) |
|---|---|
| ledger | postedAt, id |
| operations feed | createdAt, id |
| counterparties | createdAt |
Common filters
The available set depends on the endpoint, but the same filters come up repeatedly:
| Filter | Meaning | Where it's used |
|---|---|---|
from / to | time window (by createdAt / postedAt) | ledger, feed, counterparties |
direction | in / out | operations feed |
token | limit to a token | balances, addresses, ledger |
status | status | approval requests, operations |
search | substring | addresses, counterparties |
access=shared | only what's shared with you | addresses |
mine / pendingMine | mine / awaiting my vote | approval requests |
userId | admin: whose activity to show | lists with activity |
Available filters vary by endpoint. See the page for the specific list in the API Reference for the exact set.
Example: iterate through all pages
# a single page; for the next one, pass nextCursor as ?cursor=
curl "[BASE_URL]/api/v1/vaults/acme-otc/history?direction=in&limit=100" \
-H "Authorization: Bearer $V3_TOKEN"
let cursor;
const all = [];
do {
const url = new URL("[BASE_URL]/api/v1/vaults/acme-otc/history");
url.searchParams.set("direction", "in");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const page = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.V3_TOKEN}` },
}).then((r) => r.json());
all.push(...page.items);
cursor = page.nextCursor;
} while (cursor);
cursor, items = None, []
while True:
params = {"direction": "in", "limit": 100}
if cursor:
params["cursor"] = cursor
page = requests.get(
"[BASE_URL]/api/v1/vaults/acme-otc/history",
params=params,
headers={"Authorization": f"Bearer {os.environ['V3_TOKEN']}"},
).json()
items += page["items"]
cursor = page.get("nextCursor")
if not cursor:
break
Common mistakes
| Symptom | Cause |
|---|---|
| duplicates or gaps between pages | you expected offset pagination; this is keyset, so page with the cursor |
| infinite loop | cursor isn't updated from nextCursor |
| cursor "doesn't work" | the cursor came from a different list or sort order; cursors aren't portable |
| filter is ignored | this endpoint doesn't support the filter (see the API Reference) |
| unexpected order | lists are sorted newest first (descending) by default |