Menu

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

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

NameType
bank_connections[].connection_idstring
bank_connections[].bankstring | null
bank_connections[].status"pending" | "pending_selection" | "active" | "expired" | "error"
bank_connections[].sincestring
bank_connections[].last_synced_atstring | null
bank_connections[].consent_expiresstring | null
bank_connections[].error_messagestring | 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

NameTypeRequiredDescription
enabled_only"true" | "false"notrue returns only enabled accounts. Default: all accounts.

Response fields

NameType
cash_accounts[].cash_account_idstring
cash_accounts[].ledger_accountstring
cash_accounts[].namestring | null
cash_accounts[].currencystring
cash_accounts[].ibanstring | null
cash_accounts[].is_primaryboolean
cash_accounts[].enabledboolean
cash_accounts[].source"enable_banking" | "manual" | "sie_import"
cash_accounts[].balancenumber | null
cash_accounts[].available_balancenumber | null
cash_accounts[].balance_updated_atstring | 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).
  • 429 BANK_RATE_LIMITED is the BANK limiting the consent (PSD2 banks allow only a few unattended fetches per day), not this API. The connection stays valid: do not renew it. Wait until next_allowed_at (Retry-After is set); it is our cooldown, not a reset time confirmed by the bank.
  • 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

NameType
connection_idstring
bankstring | null
importednumber
duplicatesnumber
from_datestring
to_datestring
last_synced_atstring

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"
  }
}

POST /api/v1/companies/:companyId/cash-accounts

cash-accounts.create · scope companies:write

Create a bank account by hand (no bank connection), with the payee details invoices print.

Adds a manual bank account (cash_accounts, source manual) in a currency, on the next free BAS 19xx ledger account for that currency unless ledger_account (1920-1999) is given, and adds that account to the chart if missing. payee holds what customer invoices print (bankgiro, IBAN, ...); invoice_payee defaults to true. A later bank connection with the same IBAN takes this row over in place. Owner/admin only. Idempotent. Dry-runnable.

Use when: The company has a bank account that is not connected through the bank integration (a savings account, a currency account, a bank without PSD2) and it should appear in Konton, the booking flows or on invoices.

Don't use for: Connecting a bank (the bank connection flow creates its own accounts), changing an existing account (PATCH /cash-accounts/{id}) or choosing which account invoices print by default (PUT /cash-accounts/payee-defaults).

Pitfalls

  • An IBAN another account of the company already carries returns 409 CASH_ACCOUNT_IBAN_DUPLICATE: one physical account must exist once.
  • A ledger_account another cash account holds returns 409 CASH_ACCOUNT_LEDGER_TAKEN; omit it to get the next free one.
  • ledger_account is a STRING in 1920-1999 ("1931"), never a number, and never a till (1910-1919) or a PSP clearing account.
  • Owner or admin only: a member key gets 403 FORBIDDEN.
  • Creating an account does not make it the default payee: set that with PUT /cash-accounts/payee-defaults.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequired
namestringyes
currency"SEK" | "EUR" | "USD" | "GBP" | "NOK" | "DKK" | "CHF"yes
ledger_accountstringno
invoice_payeebooleanno
payeeobjectno

Response fields

NameTypeDescription
cash_account_idstring
ledger_accountstringBAS 19xx account the bank account books on, as a string.
namestring | null
currencystring
ibanstring | nullThe bank identity IBAN (written by the bank sync).
source"enable_banking" | "manual" | "sie_import"
bank_connectedbooleanTrue when a bank connection holds the account.
enabledboolean
is_primaryboolean
voucher_seriesstring | nullVerifikationsserie override; null follows the per-source default.
invoice_payeebooleanWhether the account may be printed as payee on customer invoices.
payeeobject

Example request

{
  "name": "Sparkonto",
  "currency": "SEK",
  "payee": {
    "bank_name": "SEB",
    "bankgiro": "5050-1234"
  }
}

Example response

{
  "data": {
    "cash_account_id": "7f3a…",
    "ledger_account": "1931",
    "name": "Sparkonto",
    "currency": "SEK",
    "iban": "SE4550000000058398257466",
    "source": "manual",
    "bank_connected": false,
    "enabled": true,
    "is_primary": false,
    "voucher_series": null,
    "invoice_payee": true,
    "payee": {
      "bank_name": "SEB",
      "clearing_number": null,
      "account_number": null,
      "bankgiro": "5050-1234",
      "plusgiro": null,
      "swish": null,
      "iban": "SE4550000000058398257466",
      "bic": "ESSESESS",
      "bank_code": null,
      "foreign_account_number": null
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/cash-accounts/:id/set-primary

cash-accounts.set-primary · scope companies:write

Make a bank account the company's primary.

The primary is where bookings land when nothing else says which bank account they belong to: the skattekonto counter leg and transactions with no cash account. It must be an enabled SEK giro or bank account (BAS 1920-1999). The flag moves in one transaction and the change is logged with the acting user. Only bookings made afterwards follow the new primary; nothing posted changes. Owner/admin only. Idempotent. Dry-runnable.

Use when: The company's main business account is not the one marked primary (typically the seeded 1930).

Don't use for: Choosing which account invoices print (PUT /cash-accounts/payee-defaults) or moving transactions between accounts.

Pitfalls

  • A disabled, non-SEK or non-bank account (till, PSP clearing) returns 400 CASH_ACCOUNT_PRIMARY_INELIGIBLE with details.reason.
  • Owner or admin only: a member key gets 403 FORBIDDEN.
  • Takes no body; the account id is in the path.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameTypeDescription
cash_account_idstring
ledger_accountstringBAS 19xx account the bank account books on, as a string.
namestring | null
currencystring
ibanstring | nullThe bank identity IBAN (written by the bank sync).
source"enable_banking" | "manual" | "sie_import"
bank_connectedbooleanTrue when a bank connection holds the account.
enabledboolean
is_primaryboolean
voucher_seriesstring | nullVerifikationsserie override; null follows the per-source default.
invoice_payeebooleanWhether the account may be printed as payee on customer invoices.
payeeobject

Example response

{
  "data": {
    "cash_account_id": "7f3a…",
    "ledger_account": "1931",
    "name": "Sparkonto",
    "currency": "SEK",
    "iban": "SE4550000000058398257466",
    "source": "manual",
    "bank_connected": false,
    "enabled": true,
    "is_primary": true,
    "voucher_series": null,
    "invoice_payee": true,
    "payee": {
      "bank_name": "SEB",
      "clearing_number": null,
      "account_number": null,
      "bankgiro": "5050-1234",
      "plusgiro": null,
      "swish": null,
      "iban": "SE4550000000058398257466",
      "bic": "ESSESESS",
      "bank_code": null,
      "foreign_account_number": null
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PATCH /api/v1/companies/:companyId/cash-accounts/:id

cash-accounts.update · scope companies:write

Edit a bank account: verifikationsserie, payee details, name, or turn it on/off.

Sparse update of one cash account. voucher_series (one letter A-Z, null clears) sets the verifikationsserie for entries booked from the account. The payee fields (bank_name, clearing_number, account_number, bankgiro, plusgiro, swish, iban, bic, bank_code, foreign_account_number), name and invoice_payee decide what customer invoices print; "" or null clears a field. enabled=false hides an account no bank connection holds from Konton and the booking flows. The ledger account and the primary flag are not editable here. Idempotent. Dry-runnable.

Use when: The company changes bank details customers pay to, wants its own voucher series per bank account, or stops using a manually added account.

Don't use for: Making an account the primary (POST /cash-accounts/{id}/set-primary), choosing the default payee per currency (PUT /cash-accounts/payee-defaults) or moving a transaction to another account.

Pitfalls

  • Payee fields, name, invoice_payee and enabled are owner/admin only (403 FORBIDDEN); voucher_series alone is open to any writer.
  • Payee fields on a PSP clearing account or a till return 400 INVOICE_PAYEE_ACCOUNT_INVALID: only 1920-1999 bank accounts print on invoices.
  • enabled on an account a bank connection holds returns 409 CASH_ACCOUNT_ENABLED_BANK_MANAGED; disabling the primary returns 400 CASH_ACCOUNT_DISABLE_PRIMARY, and one with unbooked transactions 400 CASH_ACCOUNT_DISABLE_UNRESOLVED.
  • An iban another account already carries returns 409 CASH_ACCOUNT_IBAN_DUPLICATE.
  • Changing voucher_series only affects entries booked afterwards; nothing posted is renumbered.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequired
bank_namestring | nullno
clearing_numberstring | null | ""no
account_numberstring | null | ""no
bankgirostring | null | ""no
plusgirostring | null | ""no
swishstring | nullno
ibanstring | null | ""no
bicstring | null | ""no
bank_codestring | null | ""no
foreign_account_numberstring | null | ""no
voucher_seriesstring | nullno
namestring | nullno
invoice_payeebooleanno
enabledbooleanno

Response fields

NameTypeDescription
cash_account_idstring
ledger_accountstringBAS 19xx account the bank account books on, as a string.
namestring | null
currencystring
ibanstring | nullThe bank identity IBAN (written by the bank sync).
source"enable_banking" | "manual" | "sie_import"
bank_connectedbooleanTrue when a bank connection holds the account.
enabledboolean
is_primaryboolean
voucher_seriesstring | nullVerifikationsserie override; null follows the per-source default.
invoice_payeebooleanWhether the account may be printed as payee on customer invoices.
payeeobject

Example request

{
  "bankgiro": "5050-1234",
  "invoice_payee": true
}

Example response

{
  "data": {
    "cash_account_id": "7f3a…",
    "ledger_account": "1931",
    "name": "Sparkonto",
    "currency": "SEK",
    "iban": "SE4550000000058398257466",
    "source": "manual",
    "bank_connected": false,
    "enabled": true,
    "is_primary": false,
    "voucher_series": null,
    "invoice_payee": true,
    "payee": {
      "bank_name": "SEB",
      "clearing_number": null,
      "account_number": null,
      "bankgiro": "5050-1234",
      "plusgiro": null,
      "swish": null,
      "iban": "SE4550000000058398257466",
      "bic": "ESSESESS",
      "bank_code": null,
      "foreign_account_number": null
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PUT /api/v1/companies/:companyId/cash-accounts/payee-defaults

cash-accounts.set-payee-default · scope companies:write

Choose which bank account invoices in a currency tell the customer to pay to.

Sets (or clears with cash_account_id null) the default payee account for one currency: every new invoice in that currency prints this account's payment details unless the invoice picks another. The account must be a bank account (1920-1999), enabled, flagged invoice_payee, and carry what the currency needs (an IBAN for anything but SEK). Answers every per-currency default after the change. Owner/admin only. Idempotent. Dry-runnable.

Use when: The company wants EUR invoices paid to its EUR account, or changes which SEK account customers pay to.

Don't use for: Editing the bank details themselves (PATCH /cash-accounts/{id}) or the primary account (set-primary).

Pitfalls

  • An account that cannot print for the currency returns 400 INVOICE_PAYEE_ACCOUNT_INVALID with details.reason (not_bank_account, disabled, not_payee, unusable_for_currency).
  • Invoices already sent keep the payment details they were sent with.
  • Owner or admin only: a member key gets 403 FORBIDDEN.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
currency"SEK" | "EUR" | "USD" | "GBP" | "NOK" | "DKK" | "CHF"yesInvoice currency, e.g. "SEK" or "EUR".
cash_account_idstring | nullyesThe cash account id (cash_account_id from GET /cash-accounts), not its ledger account.

Response fields

NameType
defaults[].currencystring
defaults[].cash_account_idstring

Example request

{
  "currency": "EUR",
  "cash_account_id": "7f3a…"
}

Example response

{
  "data": {
    "defaults": [
      {
        "currency": "EUR",
        "cash_account_id": "7f3a…"
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}