Status lifecycle
The current public implementation mainly emits
pending and posted; the remaining values are reserved for broader coverage.
Two ways to read transactions
Historical browse — GET /v1/transactions
Use this endpoint for backfills, date-range queries, and reconciliation views. It supports filtering by accountId, connectionId, entityId, status, rail, postedDateGte, and postedDateLt. Results are cursor-paginated and can be sorted by postedDate or updatedAt.
List cursors are valid only for the same query and sort order. They are not interchangeable with sync cursors.
Incremental sync — GET /v1/transactions/sync
Use this endpoint to maintain a current mirror of transactions over time. The sync stream returns change events ordered by the server’s sync cursor ascending, not by postedDate or raw updatedAt.
No-gap bootstrap
- Call
GET /v1/transactions/syncwithout a cursor. This returns no events — its only purpose is to establish the stream head. - Save the returned
nextCursoras your stream head. - Backfill historical data from
GET /v1/transactions. - Resume polling
GET /v1/transactions/syncfrom the saved cursor. - If
hasMoreistrue, call again immediately with the returnednextCursor.
transaction.id.
Persist
nextCursor only after the full page has been durably applied. If a sync call is retried, reuse the same cursor.Empty responses are normal
A sync call that returns zero events means nothing has changed in that stream since your cursor — keep the same cursor and poll again later. Sync does deliver transactions: once data is created or changes, it arrives asadded and modified events on the next poll. A zero-event response simply means there is nothing new since your cursor; it does not mean the connection has no transactions. Use GET /v1/transactions to read transactions that already exist (your historical set), and sync to stay up to date from there. If you expect changes but consistently see zero events, confirm you are polling with the cursor you saved and the same stream-defining filters you bootstrapped with (see below).
Sync event types
removed currently represents transaction deletions. Account visibility changes without transaction mutations are intentionally out of scope for the current sync stream.
Cursor and retry rules
- Sync cursors are valid only for the same stream-defining filters:
accountId,connectionId, andentityId. If you bootstrap with one of these filters, keep polling with the same filter value. - Sparse
fields=selections do not change stream membership and are not part of cursor compatibility. - Sync cursors expire after 90 days.
invalid_cursormeans the cursor is malformed or does not match the filter set. Changing the filters between bootstrap and polling returns this error — it does not silently fall back to zero events. Rebootstrap with the original filters.cursor_expiredmeans you need to rebootstrap from scratch.- If rate limited, honor
Retry-Afterwhen present and retry with the same cursor.
Key fields and rails
amount— Signed decimal string. Positive = inflow, negative = outflow.entryType—creditordebit, as reported by the bank.rail— Payment rail such asinternalTransfer,card,ach,pix,caEft,auNpp,auBecsDirectEntry,auRtgs,sepaCredit,sepaDebit,wire,swift,fasterPayments,check,cash,crypto,other, orunknown.paymentReference— Remittance reference for reconciliation (e.g. invoice number).bankReference— Bank-assigned operational tracking ID.counterpartyName/counterpartyAccountMasked— Available on many rail types.
transactions.sync_available webhook when fresh data is ready, so you do not need to poll blindly. Treat that webhook as a prompt to call GET /v1/transactions/sync, not as a replacement for the sync stream itself.