Pagination
Collections page with an opaque cursor. Follow next_cursor until it is null, with the same query.
How It Works
A paginated collection answers with a page of data and a pagination object:
{
"status": { "request_id": "req_7Hk2pQ9xLm", "error_code": 0 },
"data": [ ... ],
"pagination": {
"next_cursor": "eyJ2IjoxLCJvIjoyNX0"
}
}- Make the first request with the parameters you want and, optionally, a
limit. - If
next_cursoris a string, repeat the same request withcursor=<next_cursor>added. Keep every other parameter the same. - Stop when
next_cursorisnull. That page is the last.
A page can hold fewer items than limit without being the last one; only a null cursor ends the traversal.
async function* pages(path, params) {
let cursor = null;
do {
const url = new URL(path, "https://api.cantoncap.cc");
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, String(value));
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers: { "X-API-Key": process.env.CANTONCAP_API_KEY },
});
const body = await response.json();
if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
yield* body.data;
cursor = body.pagination?.next_cursor ?? null;
} while (cursor);
}
// Every market Temple supplies, 100 at a time.
for await (const market of pages("/v1/venues/temple/markets", { limit: 100 })) {
console.log(market.market_id);
}Rules for Cursors
- Opaque. A cursor is a token, not a format. Do not build, parse or edit one; its contents can change between releases.
- Bound to its query. Use a cursor only with the query that returned it. A cursor sent with different filters, or a malformed one, answers
400 invalid_request. - Pinned in time. On history endpoints, the cursor pins the range and the data retained before the first page: points recorded later never enter that traversal, so pages never duplicate, skip or reorder. Start a new query to see newer data.
- Re-checked on every page. Each page applies the current publication rules, so a record withdrawn mid-traversal disappears from the next page.
- Not for keeping. Do not store cursors across releases: cursors from an earlier release of the candle endpoints answer
400 invalid_request. Restart without the cursor.
Page Size
limit sets the page size. Your plan caps it: asking for more than the plan allows answers 403 quota_exceeded, and leaving limit out when an endpoint's default is above your plan's cap gives you the cap instead. On the history endpoints limit counts points across every requested series together. See Plans and limits.
| Endpoint | Default limit | Maximum limit |
|---|---|---|
/v1/assets/map | 100 | 500 |
/v1/assets/listings/latest | 100 | 500 |
/v1/assets/quotes/historical | 100 | 500 |
/v1/assets/ohlcv/historical | 100 | 500 |
/v1/assets/{asset_id}/market-chart | 100 | 500 |
/v1/assets/{asset_id}/supply/historical | 100 | 500 |
/v1/assets/market-pairs/latest | 100 | 100 |
/v1/markets | 100 | 500 |
/v1/markets/{market_id}/trades | 100 | 500 |
/v1/markets/{market_id}/ohlcv | 100 | 500 |
/v1/pools | 100 | 500 |
/v1/venues/{venue_id}/markets | 100 | 100 |
/v1/issuers | 100 | 500 |
/v1/assets/{asset_id}/registry-configurations | 20 | 100 |
Not Paginated
Fixed batches answer in one response with no pagination object: /v1/simple/price, /v1/assets/quotes/latest, /v1/assets/info and /v1/activity/markets/latest each take up to 100 IDs. Activity history (/v1/activity/{entity_kind}/{entity_id}) pages by time instead: use the oldest returned as_of as the next time_end.
403 quota_exceeded. See Candles and history.