Reading data

What accounts.read lets you call, how money is represented, and what a failed sync tells you.

La documentazione per i partner è pubblicata in inglese.

#The surface

accounts.read permits:

CallReturns
GET /api/accountsthe user's accounts with balances
GET /api/accounts/{id}/transactions?from&to&limit&offseta page of the statement, newest first
GET /api/connectionsthe bank connections behind the accounts
POST /api/accounts/{id}/syncrefresh one account from the bank; body { "uid": "…" }
POST /api/syncrefresh every account; body { "items": [{ "accountId": "…", "uid": "…" }] }

Everything else answers 403; a revoked or expired key answers 401. Sensitive fields — including the bank-side uid the sync calls need — are encrypted envelopes, so use an SDK: it decrypts locally and posts the uid for you.

js

const accounts = await client.getAccounts();
const page = await client.getTransactions(accounts[0].id, { limit: 50 });
await client.sync(accounts[0].id);

#Money

Inside the envelopes — what the SDKs return — amounts are decimal strings ("1234.56"), never numbers, so no float rounding can creep in. The plaintext amount field on the raw wire is not the value; only the decrypted one is. The sign lives in creditDebitIndicator (CRDT or DBIT), not in the amount; apply it at the presentation edge. Balances carry their ISO 20022 type code (ITBD booked, ITAV available, …).

#Connected but empty

A fresh bank connection is zero-knowledge: the server lists the accounts and fetches balances once at onboarding, but it cannot read the identifier it would need to pull transactions — only a client holding the user's key can. So for every user with a registered key (every Connect user), call syncAll() (or POST /api/accounts/{id}/sync) yourself after connecting; transactions do not arrive on their own, and polling without a sync of your own polls forever. Your first read can legitimately return zero accounts while the bank session settles — render "syncing…" and retry for a bounded time (the reference implementation uses 90 seconds), then sync.

#Sync failures

A failed POST /api/accounts/{id}/sync reports the class of failure in the status and a stable reason in a ProblemDetails body, so you branch without parsing prose. POST /api/sync answers 200 and reports the same vocabulary per account in failures[] — except consent_withdrawn: the bulk call leaves withdrawn accounts out silently, so only the per-account call ever answers it.

StatusreasonWhat it meansWhat to do
409reconnect_neededthe consent needs the user againsend them through a reconnect
409consent_withdrawnterminal for this accountremove the connection and authorise afresh
429rate_limitedthe bank is throttlingback off hard; honour Retry-After
502bank_errorthe bank answered, and answered nodo not hammer it
503transientsomething retrying may clearretry with backoff
503partner_app_inactiveyour Enable Banking application is not activatednothing the user can do; activate it

Branch on reason, never on the status — two reasons share 409. Retrying only helps for 429 and 503.

bankErrorCode is present only when the failure originated at the bank and the value it sent is shaped like a code (for example EXPIRED_SESSION); treat it as detail for logs, never as the field you branch on.

#Two kinds of 429

A 429 with a body (reason: "rate_limited") is the bank throttling and carries a deliberately long Retry-After; Enable Banking suspends an application that over-runs, so do not retry sooner. A 429 with no body is the per-IP quota in the reference — slow your loop down.

#Webhooks

There are none yet. Poll GET /api/accounts after a connect and after a sync; keep the interval kind (a page view's worth, not a tight loop).