Pagination 📄
Every index endpoint in the Cardda API uses offset-based pagination controlled by query parameters.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
_start | integer | 0 | Zero-based offset of the first record to return. |
_end | integer | _start + 25 | Exclusive upper bound. The server returns records [_start, _end). |
_order | asc / desc | asc | Sort direction. Most endpoints expect lowercase; a handful (InternalRevenueServiceAPI and a couple of legacy BankingAPI resources) accept uppercase ASC / DESC instead. If you need "newest first" you must pass _order=desc explicitly — the server default is ascending. |
_field | string | created_at | Field to sort by. Most endpoints accept created_at, updated_at, and the resource's "natural" fields (e.g. amount on transactions). |
Maximum page size.
_end - _startmust not exceed 2 000. Requests that ask for a wider window are rejected with416 Range Not Satisfiable(the server does not silently truncate — a partial page would be indistinguishable from the last page of the result set and could cause a sync script to mistake a capped response for "we've reached the end" and stop early). The 2 000 ceiling is a safety net; the practical page size we recommend for normal usage is 100, which is what the iteration recipes below use. (The basic example further down uses_end=25for readability — it's the parameter's default, not a recommendation.) Use the iteration recipe to walk longer result sets one 100-row page at a time.
Response headers
Each paginated response includes:
| Header | Example | Meaning |
|---|---|---|
X-Total-Count | 1234 | Total number of records that match the query (across all pages). |
Content-Range | 0-24/1234 | <from>-<to_inclusive>/<total>. Note Cardda emits the raw triple without the unit prefix (items) some Content-Range consumers expect — parse accordingly. |
Access-Control-Expose-Headers | Content-Range, X-Total-Count | Lets browser clients read the headers above. |
The body is always an array of resource objects (no data envelope on index endpoints — the envelope is only used on resources where pagination cursors live in the body, like GET /v1/companies).
Example
curl -i 'https://api.cardda.com/v1/banking/bank_transactions?_start=0&_end=25&_order=desc&_field=created_at' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'company-id: 550e8400-e29b-41d4-a716-446655440000'HTTP/1.1 200 OK
X-Total-Count: 1234
Content-Range: 0-24/1234
[
{ "id": "...", "amount": 12500, "status": "authorized", ... },
{ "id": "...", ... },
...
]Iterating through every page
async function* listAllBankTransactions({ apiKey, companyId, query = "" }) {
const PAGE = 100;
let start = 0;
while (true) {
const url = new URL("https://api.cardda.com/v1/banking/bank_transactions");
url.search = query;
url.searchParams.set("_start", start);
url.searchParams.set("_end", start + PAGE);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}`, "company-id": companyId },
});
if (res.status === 429) {
const wait = Number(res.headers.get("Retry-After") ?? "1");
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const items = await res.json();
if (items.length === 0) return;
yield* items;
if (items.length < PAGE) return; // last page
start += PAGE;
}
}
for await (const tx of listAllBankTransactions({ apiKey, companyId })) {
console.log(tx.id, tx.amount);
}import requests, time
def list_all_bank_transactions(api_key, company_id, query=None):
page = 100
start = 0
base = "https://api.cardda.com/v1/banking/bank_transactions"
headers = {"Authorization": f"Bearer {api_key}", "company-id": company_id}
while True:
params = dict(query or {})
params["_start"] = start
params["_end"] = start + page
r = requests.get(base, headers=headers, params=params, timeout=30)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
r.raise_for_status()
items = r.json()
if not items:
return
yield from items
if len(items) < page:
return
start += pageBest practices
- Always sort. Without
_orderand_field, results are technically deterministic but the server default (_order=asc, _field=created_at) gives you the oldest record first. Pass_order=descexplicitly for "newest first" feeds — forgetting this is the single most common mistake. - Filter before paginating. Combine with filters — e.g.
?status[$in]=["pending","authorized"]&created_at[$gte]=2026-01-01— to keepX-Total-Countsmall. - Use the recommended page size for backfills. Use
_end - _start = 100for migration jobs — it's the sweet spot between latency and per-request memory. The hard server cap is 2 000, but anything above 100 is overkill for typical workloads and you'll trip the416if you go past 2 000. - Use a cursor for live feeds. For continuous listening, query for
created_at[$gt]=<last_seen>&_order=ascrather than offset-based pagination. - Respect rate limits. See Rate limits for the policy and back-off recommendations.
Related
- Filters — narrow the result set with MongoDB-style operators.
- Rate limits — back-off when paginating large windows.
Updated 12 days ago
