Reading data
What accounts.read lets you call, how money is represented, and what a failed sync tells you.
Die Partnerdokumentation wird auf Englisch veröffentlicht.
#The surface
accounts.read permits:
| Call | Returns |
|---|---|
GET /api/accounts | the user's accounts with balances |
GET /api/accounts/{id}/transactions?from&to&limit&offset | a page of the statement, newest first |
GET /api/connections | the bank connections behind the accounts |
POST /api/accounts/{id}/sync | refresh one account from the bank; body { "uid": "…" } |
POST /api/sync | refresh 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.
#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 starts its statement pull on the server, in the background, and there is no notification when it finishes. Your first read after connecting can legitimately return zero accounts. Render "syncing…" and poll until data lands (cap it — the reference implementation uses 90 seconds), or drive it yourself with syncAll().
#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.
| Status | reason | What it means | What to do |
|---|---|---|---|
| 409 | reconnect_needed | the consent needs the user again | send them through a reconnect |
| 409 | consent_withdrawn | terminal for this account | remove the connection and authorise afresh |
| 429 | rate_limited | the bank is throttling | back off hard; honour Retry-After |
| 502 | bank_error | the bank answered, and answered no | do not hammer it |
| 503 | transient | something retrying may clear | retry with backoff |
| 503 | partner_app_inactive | your Enable Banking application is not activated | nothing 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).