Skip to main content

Pagination

All list endpoints return paginated results using cursor-based pagination. This approach is faster and more reliable than offset-based pagination, especially when data changes between requests.

How It Works

┌──────────────────────────────────────────────────────────────────┐
│ GET /tokens?limit=3 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ T1 │ │ T2 │ │ T3 │ │ T4 │ │ T5 │ │ T6 │ │ T7 │ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ ╰──── Page 1 ────╯ ╰──── Page 2 ────╯ ╰─ Page 3 ─╯ │
│ starting_after=T3 → starting_after=T6 → │
└──────────────────────────────────────────────────────────────────┘

Each response includes a has_more flag. When true, use the last item's id as the starting_after cursor to fetch the next page.

Parameters

ParameterTypeDefaultDescription
limitinteger20Items per page (1–100)
starting_afterstringCursor: ID of the last item from the previous page

Response Format

{
"data": [
{ "id": "tok_abc123", "name": "Gold", "symbol": "GLD", ... },
{ "id": "tok_def456", "name": "Silver", "symbol": "SLV", ... },
{ "id": "tok_ghi789", "name": "Bronze", "symbol": "BRZ", ... }
],
"has_more": true,
"total": 42
}
FieldTypeDescription
dataarrayPage of results
has_morebooleantrue if more pages exist
totalintegerTotal count of matching items

SDK Example — Full Pagination

async function getAllTokens() {
const allTokens = [];
let cursor: string | undefined;

do {
const page = await urblock.tokens.list({
limit: 100,
starting_after: cursor,
});

allTokens.push(...page.data);
cursor = page.has_more ? page.data[page.data.length - 1].id : undefined;
} while (cursor);

return allTokens; // All tokens across all pages
}

const tokens = await getAllTokens();
console.log(`Fetched ${tokens.length} tokens`);

curl Example

# Page 1
curl -s https://api.urblock.io/tokens?limit=3 \
-H "Authorization: Bearer $TOKEN" | jq

# Page 2 — use last ID as cursor
curl -s "https://api.urblock.io/tokens?limit=3&starting_after=tok_ghi789" \
-H "Authorization: Bearer $TOKEN" | jq

Why Cursor-Based?

FeatureCursor-BasedOffset-Based
Consistency when data changes✅ Stable❌ Can skip/duplicate
Performance on large datasets✅ O(1) seek❌ O(n) skip
Deep pagination (page 1000+)✅ Fast❌ Slow
Random page access❌ Sequential only✅ Any page

Cursor-based pagination guarantees you will never miss or duplicate items, even if new items are created between requests.

Cursor Stability

Cursors are item IDs, not opaque tokens. They remain valid as long as the referenced item exists. If the item is deleted, the API will return results from the next available item.

Filtering & Sorting

When filtering (e.g., ?status=confirmed), the cursor still works correctly — the API skips to the next matching item after the cursor.

Best Practices

  1. Use limit: 100 for bulk exports — minimizes round trips
  2. Use limit: 20 for UI pages — faster response, better UX
  3. Always check has_more — don't assume page count from total
  4. Store cursors, not page numbers — enables resume after failures
  5. Handle empty pagesdata: [] with has_more: false means you're done

Next Steps