Menu
Cookbooks
API reference
Bank accounts
The company's bank accounts (cash accounts, whose ids filter transactions) and the bank connections that sync them.
Endpoints
GET/api/v1/companies/:companyId/bank-connections: List PSD2 bank connections with sync freshness and consent expiry.GET/api/v1/companies/:companyId/cash-accounts: List bank/cash accounts with the bank-reported balance.POST/api/v1/companies/:companyId/bank-connections/:connectionId/sync: Sync one bank connection now instead of waiting for the nightly run.
GET /api/v1/companies/:companyId/bank-connections
bank-connections.list · scope companies:read
List PSD2 bank connections with sync freshness and consent expiry.
Returns every bank connection for the company with its status, last successful sync (last_synced_at), consent expiry (consent_expires) and any user-facing error message. Connections sync automatically once a day server-side; this endpoint tells you whether that is still happening.
Use when: You need to verify bank data is current before building on it (liquidity, reconciliation, reports), or to detect a dead connection that needs BankID re-authorisation.
Don't use for: Fetching transactions (use /transactions) or account balances (use /cash-accounts). Triggering a sync: not available on this surface; syncing is automatic.
Pitfalls
- last_synced_at is null until the first sync completes (about a minute after connecting); it does NOT mean the connection is broken.
- A connection can hold status=active with a stale last_synced_at (older than ~36 hours): treat the data as suspect, but do NOT assume re-authorisation fixes it. Common causes are a lapsed subscription (this endpoint then answers with a capability error) or every account deselected in settings.
- status=expired means the PSD2 consent is dead: only the user can fix it, with BankID in a browser.
- error_message is Swedish and user-facing: show it verbatim rather than translating.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Response fields
| Name | Type |
|---|---|
bank_connections[].connection_id | string |
bank_connections[].bank | string | null |
bank_connections[].status | "pending" | "pending_selection" | "active" | "expired" | "error" |
bank_connections[].since | string |
bank_connections[].last_synced_at | string | null |
bank_connections[].consent_expires | string | null |
bank_connections[].error_message | string | null |
Example response
{
"data": {
"bank_connections": [
{
"connection_id": "4f6c…",
"bank": "Swedbank",
"status": "active",
"since": "2026-08-01T00:00:00Z",
"last_synced_at": "2026-08-31T05:04:12Z",
"consent_expires": "2026-11-01T00:00:00Z",
"error_message": null
}
]
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/cash-accounts
cash-accounts.list · scope transactions:read
List bank/cash accounts with the bank-reported balance.
Returns the company's cash accounts (bank accounts, kassa) with their BAS ledger mapping and, for PSD2-connected accounts, the balance the bank itself reported at the last sync: balance (booked), available_balance, and balance_updated_at (when it was fetched). Pass ?enabled_only=true to return only accounts that sync.
Use when: You need the current bank balance per account (e.g. a covering decision before a payment run), or cash_account_id values to filter transaction listings.
Don't use for: The bookkept 19xx balance: use the trial-balance or balance-sheet reports. The two legitimately differ (pending bookings, timing).
Pitfalls
- balance/available_balance are what the BANK reported, refreshed at most every 12h (PSD2 quota): check balance_updated_at before treating them as current.
- balance is null for manual and SIE-imported accounts, and for PSD2 accounts that have not completed a sync since connecting.
- available_balance is null when the bank reports no available balance type; that does not mean 0.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
enabled_only | "true" | "false" | no | true returns only enabled accounts. Default: all accounts. |
Response fields
| Name | Type |
|---|---|
cash_accounts[].cash_account_id | string |
cash_accounts[].ledger_account | string |
cash_accounts[].name | string | null |
cash_accounts[].currency | string |
cash_accounts[].iban | string | null |
cash_accounts[].is_primary | boolean |
cash_accounts[].enabled | boolean |
cash_accounts[].source | "enable_banking" | "manual" | "sie_import" |
cash_accounts[].balance | number | null |
cash_accounts[].available_balance | number | null |
cash_accounts[].balance_updated_at | string | null |
Example response
{
"data": {
"cash_accounts": [
{
"cash_account_id": "ca_…",
"ledger_account": "1930",
"name": "Företagskonto",
"currency": "SEK",
"iban": "SE4550000000058398257466",
"is_primary": true,
"enabled": true,
"source": "enable_banking",
"balance": 125430.5,
"available_balance": 123930.5,
"balance_updated_at": "2026-09-01T05:12:44.000Z"
}
]
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/bank-connections/:connectionId/sync
bank-connections.sync · scope transactions:write
Sync one bank connection now instead of waiting for the nightly run.
Fetches new transactions and balances for one PSD2 bank connection right away. The window is chosen server-side: the last 7 days, widened to cover any gap since last_synced_at, capped at 90 days. Returns how many transactions were imported and the new last_synced_at. A connection synced within the last 15 minutes is refused with 429 BANK_SYNC_COOLDOWN and next_allowed_at: the data is already fresh. Not dry-runnable: the bank call itself is the side effect.
Use when: GET /bank-connections shows a stale last_synced_at on an active connection and you need current bank data before building on it (liquidity, reconciliation, a report), or the user asks for the latest transactions now.
Don't use for: Polling. Connections sync every night on their own; call this once when freshness matters, then read /transactions. Fixing a dead connection: status=expired needs BankID in a browser, not a sync.
Pitfalls
- Idempotency-Key is optional here. If you send one, use a fresh key per attempt: a cooldown answer is never cached, but a completed sync is, and replaying it fetches nothing new.
- 429 BANK_SYNC_COOLDOWN follows a recent successful sync OR a recent attempt that failed (the 15-minute lease is taken before the bank is called, on every instance). Compare last_synced_at from GET /bank-connections: if it is fresh, use the data you have; if it is still stale, the previous attempt failed, so retry once after next_allowed_at (Retry-After is set).
- 409 BANK_SESSION_EXPIRED means the bank reported the consent dead during the sync; the connection is now status=expired. Hand the user the connect link; no API call revives it.
- imported: 0 is normal on a quiet account. Banks report with up to 48 hours of delay, so today's transactions often arrive tomorrow.
- Costs one Enable Banking call per enabled account: 403 CAPABILITY_BLOCKED when the company has no bank_sync entitlement.
Risk: low · Idempotent: no · Reversible: no · Dry-run supported: no
Response fields
| Name | Type |
|---|---|
connection_id | string |
bank | string | null |
imported | number |
duplicates | number |
from_date | string |
to_date | string |
last_synced_at | string |
Example response
{
"data": {
"connection_id": "4f6c…",
"bank": "Swedbank",
"imported": 3,
"duplicates": 12,
"from_date": "2026-08-26",
"to_date": "2026-09-02",
"last_synced_at": "2026-09-02T09:14:03Z"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}