GET /transactions and GET /rules) use cursor-based pagination rather than offset-based pagination. You navigate through results by passing an opaque cursor token from one response into the next request, rather than specifying a page number or offset.
Cursor pagination is stable: if new transactions are synced while you are iterating through pages, you will not see duplicates or skip records. This makes it the right choice for transaction data, which can be modified by background syncs at any time.
Smaller list endpoints (GET /accounts, GET /categories, GET /connections, GET /tags, GET /users, GET /reports) are not paginated — they return every matching record in a single response (either as a top-level array or wrapped in a resource-named envelope like {"tags": [...]}).
Response envelope
Each paginated endpoint wraps its current page inside a resource-named array alongsidenext_cursor and has_more. The array key is different per endpoint (transactions or rules) — the pagination fields are identical.
The current page of results, keyed by resource name. May be an empty array when no records match the filters.
An opaque, base64url-encoded token. Pass this value as the
cursor query parameter in your next request to retrieve the following page. An empty string when there are no more pages.true if additional pages exist beyond the current one. When has_more is false, you have retrieved all matching records.Request parameters
Opaque pagination cursor from a previous response’s
next_cursor field. Omit this parameter to fetch the first page. Do not attempt to construct or parse cursors manually — treat them as opaque strings.Number of records to return per page. Minimum
1, maximum 500. The default for most endpoints is 50, but check each endpoint’s documentation for its specific default.How cursors work
Internally, a cursor encodes a(date, id) pair that lets the database resume from the exact position where the previous page ended. This is why cursor pagination only works with the default date sort — if you switch to sorting by amount or name, pagination is not supported and you must fetch all results in a single request (using a high limit).
If you provide a cursor that is malformed or refers to a record that no longer exists, the API returns a 400 error with code INVALID_CURSOR. In that case, restart pagination from the first page.
Fetching all pages
To retrieve every record matching a set of filters, keep fetching pages untilhas_more is false:
Tips
- Use the maximum
limitof 500 when you need to export all data — fewer round trips means faster completion. - Do not cache cursors across sessions. Cursors may become invalid after schema migrations or server restarts. Always start fresh from the first page in new sessions.
- Filters must be consistent across pages. If you change filter parameters mid-pagination (for example, changing
start_date), the cursor will produce incorrect results or a400error. Keep all parameters constant and only changecursorbetween requests. - The
/transactions/countendpoint lets you know how many records a filter returns before you begin paginating — useful for progress tracking in long exports.