Pagination 📄

Every index endpoint in the Cardda API uses offset-based pagination controlled by query parameters.

Query parameters

ParameterTypeDefaultDescription
_startinteger0Zero-based offset of the first record to return.
_endinteger_start + 25Exclusive upper bound. The server returns records [_start, _end).
_orderasc / descascSort 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.
_fieldstringcreated_atField 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 - _start must not exceed 2 000. Requests that ask for a wider window are rejected with 416 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=25 for 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:

HeaderExampleMeaning
X-Total-Count1234Total number of records that match the query (across all pages).
Content-Range0-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-HeadersContent-Range, X-Total-CountLets 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 += page

Best practices

  • Always sort. Without _order and _field, results are technically deterministic but the server default (_order=asc, _field=created_at) gives you the oldest record first. Pass _order=desc explicitly 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 keep X-Total-Count small.
  • Use the recommended page size for backfills. Use _end - _start = 100 for 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 the 416 if you go past 2 000.
  • Use a cursor for live feeds. For continuous listening, query for created_at[$gt]=<last_seen>&_order=asc rather 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.