CLI for the SpareBank 1 personal banking API (accounts, transactions, transfers). BankID login. Designed for AI agent consumption.
SpareBank 1 CLI
Your finances, agent-ready. sb1 is a CLI for the SpareBank 1 personal banking API, built for AI agents (and humans).
List accounts, browse and export transactions, and make transfers, all authenticated with BankID over OAuth 2.0. Output is human-readable tables by default and machine-readable JSON with --json. Ships agent skills so AI agents can drive it safely.
Written in Rust. Personal and unofficial; not affiliated with SpareBank 1. Please read TERMS.md: usage of the underlying API is governed by the bank's terms (personal use, confidential credentials, enforced rate limits).
What it's for
Treat your bank as data, not a dashboard. The bank's app is great for glancing at your money; sb1 is for everything the app can't do, scripting, automation, and especially AI agents that reason over your finances.
Ask an agent about your money
Natural-language Q&A over your accounts and transactions, no fixed dashboard required. The agent chains the commands and reasons over the JSON:
- "Where did my money go last month?"
- "What subscriptions am I paying for?"
- "Did my paycheck arrive?"
- "Help me optimize my finances: where am I wasting money, what can I cut, and where is my spending inconsistent?"
sb1 --json summary
sb1 --json transactions --classified
Automate the boring parts
Scheduled balance checks, monthly reports, paycheck-arrival checks, "is the balance below X?". Pair --json with cron or any task runner:
sb1 --json accounts
sb1 --json transactions --days 30
Own your financial data
One-command export into your own spreadsheets, budgeting app, or notes, so you keep a longitudinal history instead of the bank's fixed, current-only view:
sb1 export -a Brukskonto -o booked.csv
For everyday balance-glancing on the go, the bank's own app is still the better tool; reach for sb1 when you want your money as something you can query, script, and pipe.
For AI agents
sb1 is designed for AI agent consumption: every command speaks --json, errors are explicit, and money-moving commands confirm first. The repo ships agent skills under skills/ that teach an agent how to drive it safely:
sparebank1-shared: runtime contract (auth, storage, output, command map)sparebank1-accounts: read accounts, balances, transactions, exportssparebank1-transfers: money movement with confirmation safeguardssparebank1-context: a short interview that builds a private, git-ignored
Install them into your agent:
npx skills add magnusrodseth/sparebank1-cli
Install
Prebuilt binary (macOS and Linux, no toolchain required). Installs to ~/.local/bin:
curl -fsSL https://raw.githubusercontent.com/magnusrodseth/sparebank1-cli/main/install.sh | bash
Pin a version with SB1_VERSION=v1.1.0, or change the location with SB1INSTALLDIR. Make sure the install dir is on your PATH, then run sb1 --help.
From source (requires a Rust toolchain):
cargo install --path . # installs the sb1 binary
or run from the repo:
cargo build --release # ./target/release/sb1
Setup
- Go to developer.sparebank1.no and register
clientid and clientsecret.
- Set the app's redirect URI to exactly:
http://localhost:12345/callback
- Provide the credentials. Easiest is a
.envfile (git-ignored):
cp .env.example .env # then fill in CLIENTID / CLIENTSECRET
- Log in with BankID (opens your browser):
sb1 login
The token is stored in your chosen secret store; you can delete .env afterwards.
Secret storage
Your OAuth token is effectively a standing key to your bank (the CLI can refresh access without a fresh BankID login), so where it lives matters. The store is chosen with the SB1_STORE environment variable, and **the default is your OS keychain**, so the easy path is also the secure one. sb1 login and sb1 status both print the active backend and all alternatives, so you always see your options.
| SB1_STORE | Backend | Notes | | --- | --- | --- | | unset / keychain | macOS Keychain / Linux kernel keyutils / Windows Credential Manager | Default. Secure at rest, gated by your OS. On macOS an unsigned binary re-prompts for your login password after each rebuild. | | op / 1password | 1Password via the op CLI | Secure, no per-rebuild prompt (the op session caches your unlock). Needs op installed + signed in. Vault via SB1OPVAULT (default Private); account via SB1OPACCOUNT if you have more than one. | | file | ~/.config/sparebank1-cli/*.json (0600) | Opt-in. Plaintext on disk. The most reliable backend for headless automation (servers, cron, CI, Docker) since it never prompts. Keep this dir out of git and cloud backups. |
# 1Password, no machine-password prompt; uses your 1Password unlock (Touch ID)
export SB1_STORE=op
export SB1OPACCOUNT=my.1password.eu # only needed with multiple op accounts
export SB1OPVAULT=Private # default
Plaintext files — only for headless automation where no keychain/op is available
export SB1_STORE=file
Note on keychain prompts: an unsigned binary that you rebuild gets a new
signature each time, so the keychain re-prompts for your login password on
every run ("Always Allow" can't stick). 1Password (op) avoids this because
the op CLI caches your unlock for the session. For a prompt-free keychain,
code-sign the binary with a stable self-signed certificate.
⚠ The file backend writes your token and credentials as plaintext secrets
under ~/.config/sparebank1-cli/. Anything running as your user can read them,
and a synced home directory or cloud backup will capture them. Prefer keychain
orop; reach forfileonly on a headless box where neither is available.
Usage
sb1 status # auth status, token expiry, storage backend
sb1 hello # verify auth against the Hello World endpoint
Accounts
sb1 accounts # list (add --all to include cards/BSU/ASK/pension/currency)
sb1 account Brukskonto # one account (by name, key, or number)
sb1 account Brukskonto --details # extended details
sb1 account Brukskonto --roles # roles
sb1 balance 1234.56.78903 # balance by account number
Transactions (account is positional, or use -a/--account; omit for all accounts)
sb1 transactions Brukskonto --days 30
sb1 transactions Brukskonto Sparekonto --days 30 # multiple accounts
sb1 transactions -a Brukskonto --days 30 # -a/--account works too
sb1 transactions Brukskonto --from 2026-01-01 --to 2026-03-31
sb1 transactions Brukskonto --classified
sb1 transaction <id> # details for one transaction
sb1 transactions Brukskonto --csv -o out.csv # local CSV
sb1 export -a Brukskonto -o booked.csv # server-side CSV export
Transfers (always confirms first; -y to skip)
sb1 transfer debit --from Brukskonto --to Sparekonto --amount 250
sb1 transfer debit --from Brukskonto --to Sparekonto --amount 250 --message "Sparing"
sb1 transfer creditcard --from Brukskonto --credit-card-id 1034222 --amount 500
sb1 transfer pension --from Brukskonto --policy-number 1034222 --amount 500
Machine-readable output
sb1 --json accounts
Financial overview: net worth, monthly cash flow, categories, subscriptions
sb1 summary --months 6
sb1 --json summary
Mask sensitive values for screenshots (amounts, account numbers, names)
sb1 summary --months 6 --mask
sb1 accounts --mask
--classified enriches transactions with the bank's own category, recurring, and subscription flags. summary builds on the same classification, so its categories work for any account setup (no hardcoded merchants); internal transfers between your own accounts are excluded automatically.
Most commands take an account as a positional argument (sb1 account Brukskonto, sb1 transactions Brukskonto). transactions also accepts the older -a/--account flag and lets you pass several accounts (positionally or with repeated -a); omit the account entirely to query all of them.
--mask is a global flag for sharing screenshots: it replaces sensitive values (amounts, account numbers, owner names, transaction descriptions, counterparties, savings rate) with * in table output while keeping categories, dates, and labels visible. Masked amounts keep their sign and kr shape (-kr *) so the output still looks real. It has no effect on --json or CSV output, which stay unredacted for automation.
debit transfers are restricted to your own accounts: both --from and --to must resolve to accounts in your account list. Amounts accept 250, 250.50, or 250,50.
The two CSV paths differ. transactions --csv produces a comma-delimited UTF-8 file from the client. export streams the bank's own server-side CSV, which is semicolon-delimited with a UTF-8 BOM and Norwegian headers (Dato, Beskrivelse, Rentedato, Inn, Ut, ...), the format Excel expects in a Norwegian locale.
Commands
| Command | API | | --- | --- | | login / logout / refresh / status | OAuth 2.0 (/oauth/authorize, /oauth/token) | | hello | GET /common/helloworld | | accounts | GET /accounts | | account <ref> | GET /accounts/{key} (--details, --roles) | | balance <number> | POST /accounts/balance | | transactions | GET /transactions (--classified → /transactions/classified) | | transaction <id> | GET /transactions/{id}/details | | export | GET /transactions/export | | transfer debit | POST /transfer/debit | | transfer creditcard | POST /transfer/creditcard/transferTo | | transfer pension | POST /transfer/pension | | summary | derived from /accounts + /transactions/classified |
The OpenAPI specs this client was built against are saved under docs/api/.
Development
cargo build
cargo test
cargo clippy
cargo fmt --check
Releasing
- crates.io:
cargo publish(aftercargo publish --dry-run). Users then
cargo install sparebank1-cli.
- Prebuilt binaries: tag
vX.Y.Z; CI (.github/workflows/release.yml)
See the repository's release workflow for details.