Swiss Open Banking CLI via SIX bLink API and OpenWealth API — optimized for LLM agents
Project description
rbcli
Swiss Open Banking from your terminal. Payments, accounts, statements, product comparison, infrastructure monitoring, cryptographic signing, an independent git-backed ledger, and LLM safety guardrails — powered by SIX bLink, designed for autonomous LLM agents.
$ rbcli pay send --from CH9300762011623852957 --to CH6589144414396878645 \
--amount 250.00 --currency CHF --reference "Invoice 2026-003" \
--force --sign --assert-min-balance 10000
✓ Payment executed (SCA bypassed) [signed]
Payment Id: pay-348264a1
Uetr: a3c8f921-71b4-4e2a-9d05-83bf2e1c6a47
Status: EXECUTED
Amount: CHF 250.00
From Balance: 12450.00 → 12200.00
Signed: True
Signature: d4f89cc1786de61f1aa5bc29...
Why?
Swiss banks have had bLink since 2020. Top managers are conservative — they limit what's possible through APIs because they fear losing the bank–client relationship. rbcli demonstrates what's already technically feasible.
┌──────────────────────────────────────────────────────────────┐
│ What banks fear │
│ │
│ Today: Client ──── Bank App ──── Bank │
│ (lock-in) │
│ │
│ rbcli: Client ──── Terminal/LLM ──── bLink ──── Bank │
│ (open) (exists!) │
│ │
│ The app becomes optional. The client becomes sovereign. │
└──────────────────────────────────────────────────────────────┘
The --force flag is the politically interesting part: it shows that SCA via a proprietary mobile app is a business decision, not a technical limitation. Add --sign and you have stronger authentication than any banking app offers.
The Twint proof
The same Swiss banks operate both bLink and Twint. One is open, the other is closed. This isn't an accident — it's a choice:
bLink (SIX) Twint
──────────────────────────── ────────────────────────────
Open standard Closed ecosystem
Any third party can connect Merchant contract required
Documented REST APIs No public API for P2P
Client chooses the interface Twint IS the interface
rbcli works ✓ rbcli can't work ✗
Same banks. Same country. Opposite philosophy.
Twint P2P payments (person-to-person via phone number) are completely locked inside the Twint app. There is no programmatic way to send a Twint P2P payment — no URL scheme, no open API, no webhook. The banks chose this. They could have built Twint on open rails. They didn't, because lock-in was the point.
rbcli exists because bLink exists. The infrastructure for open banking is already there. What's missing is the will to use it.
Quick start
# Install
brew install pipx && pipx ensurepath
pipx install .
# Start the simulator (3 demo banks, pre-seeded accounts)
rbcli sim start
# Get the full picture in one call
rbcli wealth summary
# Make a signed payment with safety guardrails
rbcli keys init
rbcli pay send --from CH9300762011623852957 --to CH6589144414396878645 \
--amount 100.00 --force --sign --assert-min-balance 10000
# Keep your own records
rbcli ledger init
rbcli ledger sync
# Stop
rbcli sim stop
Architecture
┌───────────────────────────────────────────────────────────┐
│ LLM Agent / Terminal │
│ Claude Code · Cowork · OpenClaw · bash │
│ │
│ "Show me everything I own. Optimize for deposit │
│ protection and yield across cash and securities." │
└───────────────────────┬───────────────────────────────────┘
│ shell (structured JSON)
┌───────────────────────▼───────────────────────────────────┐
│ rbcli │
│ │
│ accounts · statements · pay · transfer · products │
│ banks · standing-orders · monitor · notifications │
│ keys · ledger · wealth · portfolio · customers │
│ │
│ ┌───────────┐ ┌──────────┐ ┌────────────────────────┐ │
│ │ Signing │ │ Ledger │ │ Channel Tracking │ │
│ │ Ed25519 │ │ Git repo│ │ blink/ebanking/... │ │
│ └───────────┘ └──────────┘ └────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐│
│ │ Safety: --dry-run · --idempotency-key · --assert-min ││
│ └───────────────────────────────────────────────────────┘│
└───────────────────────┬───────────────────────────────────┘
│ HTTP REST
┌───────────────────────▼───────────────────────────────────┐
│ rbcli sim (Flask) / bLink / OpenWealth │
│ │
│ ┌─ AIS + PSS ────────────────────────────────────────┐ │
│ │ Big Bank AG · KaBe Bank · Small Bank AG │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌─ OpenWealth Custody ───────────────────────────────┐ │
│ │ Positions · Transactions · Portfolios · Customers │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
Client sovereignty stack
Three features that shift the power from the bank to the client:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────────┐ You see which channel initiated each │
│ │ 1. Channel │ transaction. "Show me debits I didn't │
│ │ Visibility │ initiate through rbcli" = fraud │
│ └────────┬─────────┘ detection with one command. │
│ │ │
│ ┌────────▼─────────┐ Every statement, payment, and balance │
│ │ 2. Git Ledger │ is committed to a local git repo. │
│ │ (independent │ Cryptographic hashes. Immutable. │
│ │ record) │ "Verify my records against the bank." │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ Ed25519 signatures on payment │
│ │ 3. Cryptographic │ instructions. Non-repudiable proof. │
│ │ Signing │ Stronger than any mobile app OTP. │
│ └──────────────────┘ │
│ │
│ The bank is no longer the sole record-keeper. │
│ The client becomes a peer, not a dependent. │
└─────────────────────────────────────────────────────────────────┘
LLM safety guardrails
Three features that make autonomous LLM agents safe for banking:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ --dry-run Check consequences without executing. │
│ "What happens if I move CHF 100k?" │
│ Shows before/after balances for both │
│ sides. No money moves. │
│ │
│ --assert-min-balance Hard guardrail. Payment is rejected if │
│ remaining balance would fall below N. │
│ Prevents an LLM from draining an account. │
│ │
│ --idempotency-key Same key = same result on retry. │
│ If the LLM retries a failed call, the │
│ payment isn't executed twice. │
│ Response includes deduplicated: true. │
│ │
│ Safety first. Efficiency second. Optimization third. │
└─────────────────────────────────────────────────────────────────┘
# Check consequences before acting
$ rbcli pay send --from CH93... --to CH65... --amount 100000 --force --dry-run
✓ Dry run — no payment executed
From Balance Before: 500000.0
From Balance After: 400000.0
To Balance Before: 12450.0
To Balance After: 112450.0
# Guardrail: abort if balance would drop too low
$ rbcli pay send --from CH93... --to CH65... --amount 5000 --force --assert-min-balance 10000
✗ Payment would leave balance at CHF 5450.00, below required minimum of CHF 10000.00.
# Prevent double payments on LLM retry
$ rbcli pay send --from CH93... --to CH65... --amount 100 --force --idempotency-key "rent-2026-03"
✓ Payment executed (SCA bypassed)
$ rbcli pay send --from CH93... --to CH65... --amount 100 --force --idempotency-key "rent-2026-03"
✓ Payment executed (SCA bypassed) (deduplicated)
Deduplicated: True ← no money moved
Wealth summary: cash + securities
One command for the full picture — cash across all banks plus securities across all custody accounts:
$ rbcli wealth summary
Wealth Summary
Total Wealth: CHF 1,784,900.25
Cash 34.8% CHF 621,750.25 █████████████
Securities 65.2% CHF 1,163,150.00 ██████████████████████████
Cash Positions
Cash Total: CHF 621,750.25
Weighted Avg Yield: 0.112%
Est. Annual Interest: CHF 672.84
esisuisse Covered: CHF 203,130.25 (33%)
esisuisse Uncovered: CHF 418,620.00
✓ Big Bank AG CHF 100,650.00 AA
⚠ KaBe Bank CHF 515,420.00 A
↳ CHF 400,000.00 exceeds esisuisse limit
✓ Small Bank AG CHF 5,680.25 BBB+
Securities Portfolio
Market Value: CHF 1,163,150.00
Unrealized P&L: CHF +81,400.00 (+7.52%)
Positions: 5
fund 44.1% CHF 512,400.00 █████████████████
equity 38.8% CHF 450,750.00 ███████████████
bond 17.2% CHF 200,000.00 ██████
UBS ETF SMI (CHF) CHF 491,000.00 +31,000.00 kabe-bank
Nestlé SA CHF 270,750.00 +28,500.00 big-bank
Conf. Bond 1.5% 2028 CHF 200,000.00 +2,000.00 big-bank
Swiss Re AG CHF 180,000.00 +18,000.00 big-bank
iShares Core MSCI World CHF 21,400.00 +1,900.00 kabe-bank
Warnings
⚠ exceeds_deposit_protection — KaBe Bank +CHF 400k
⚠ concentration_risk — 83% of cash at KaBe Bank
⚠ total_bank_exposure — CHF 1M at KaBe Bank (58% of wealth)
⚠ low_credit_rating — Small Bank BBB+
ℹ zero_yield — CHF 12,450 earning 0%
In JSON mode, the LLM gets a portfolio object nested within the response — total market value, cost basis, unrealized P&L, positions with ISINs, asset class breakdown, and per-bank distribution. One call, complete picture.
Demo banks
┌──────────────────────────────────────────────────────────────────┐
│ Big Bank AG KaBe Bank Small Bank AG │
│ (big-bank) (kabe-bank) (small-bank) │
│ │
│ Rating: AA Rating: A Rating: BBB+ │
│ Savings: 0.20% Savings: 0.50% Savings: 0.20% │
│ esisuisse: 100k esisuisse: 100k esisuisse: 100k │
│ │
│ Accounts: Accounts: Accounts: │
│ · CHF current 12.4k · CHF current 500k · CHF current 5.7k │
│ · CHF savings 85k · CHF savings 0 · CHF savings 0 │
│ · EUR current 3.2k · USD trading 15.4k │
└──────────────────────────────────────────────────────────────────┘
KaBe Bank holds CHF 500'000 — set up for the Einlagensicherung demo scenario.
Commands
Core banking
| Command | Description |
|---|---|
accounts list |
List accounts at a bank |
accounts balance <IBAN> |
Current balance (auto-committed to ledger) |
statements fetch <IBAN> |
Transaction history with --channel filter (auto-committed) |
statements export <IBAN> |
Export as CSV, JSON, or MT940 |
pay send --from --to --amount |
Payment with --force, --sign, --dry-run, --assert-min-balance, --idempotency-key |
pay confirm <id> --otp <code> |
SCA confirmation |
pay verify <id> |
Verify cryptographic signature |
pay list |
Recent payments with UETR and channel |
transfer send --from --to --amount |
Internal transfer with --dry-run, --assert-min-balance, --idempotency-key |
standing-orders list/create/cancel |
Recurring payments |
Discovery, comparison, and aggregation
| Command | Description |
|---|---|
banks list |
All banks with credit ratings, rates, deposit protection |
banks info <alias> |
Full detail: rates, esisuisse, capabilities |
products list |
Products across all banks |
products compare --type savings |
Ranked comparison, best rate first |
wealth summary |
Unified view: cash + securities + warnings (one call) |
Securities portfolio (OpenWealth Custody)
| Command | Description |
|---|---|
portfolio positions |
All securities across all custody accounts |
portfolio positions --portfolio-id <id> |
Positions for one portfolio |
portfolio transactions |
Security transaction history (buys, sells, dividends) |
portfolio transactions --isin <ISIN> |
Transactions for one security |
portfolio summary |
Asset allocation, currency exposure, P&L, warnings |
Customer management (OpenWealth)
| Command | Description |
|---|---|
customers list |
All managed customers with linked accounts |
customers info <id> |
Customer detail: profile, risk, linked custody accounts |
Signing and ledger
| Command | Description |
|---|---|
keys init |
Generate Ed25519 keypair |
keys show |
Display public key |
keys export |
Export public key (PEM or hex) for bank registration |
ledger init |
Initialize git-backed ledger |
ledger sync |
Fetch & commit all accounts across all banks |
ledger verify <IBAN> |
Diff local records against bank's current data |
ledger status |
What's tracked, last sync dates |
ledger log |
Git history of all interactions |
Monitoring (Koivu-style)
| Command | Description |
|---|---|
monitor ping --from --to |
Pennytest: send CHF 0.01, measure latency |
monitor run --config routes.toml |
Continuous monitoring loop |
monitor status |
Recent ping results |
monitor report |
Uptime %, avg latency, failures |
Infrastructure
| Command | Description |
|---|---|
sim start/stop/reset/status |
Manage the mock bLink server |
config init/show/list-banks/set-default |
Configuration |
notifications list |
Payment notifications (camt.054) with channel |
--help-llm |
Structured JSON manifest for LLM agents |
LLM agent usage
rbcli is designed so LLM agents can use it autonomously:
- Auto-detection: JSON when piped, tables when interactive
- ANSI colors: semantic coloring in TTY mode (amounts, channels, ratings, statuses), zero ANSI in JSON/CSV
--help-llm: complete capability manifest with parameter schemas- Exit codes: 0=success, 2=auth, 4=insufficient funds, 5=pending SCA, 6=unreachable, 7=timeout, 8=rejected
- Error suggestions: every error tells the agent how to fix it
- Safety:
--dry-run,--assert-min-balance,--idempotency-keyprevent catastrophic mistakes --diff: automatic before/after balances on every executed payment
What Claude Code does with rbcli
You say: "I have CHF 500'000 at KaBe Bank. Distribute it so everything is covered by Einlagensicherung, and maximize yield."
Claude Code reasoning:
1. rbcli wealth summary
→ Sees CHF 500k at KaBe, 33% esisuisse coverage, 3 warnings
2. Thinks: "3 banks × 100k = 300k max coverage. Keep 300k at KaBe
(best rate 0.50%), move 100k to Big Bank (AA), 100k to Small Bank.
Trade-off: CHF 600/year less interest for 200k more protection."
3. rbcli pay send --from CH65... --to CH93... --amount 100000 \
--force --sign --assert-min-balance 200000 --dry-run
→ Dry run OK. From: 500k → 400k. To: 12.4k → 112.4k.
4. rbcli pay send --from CH65... --to CH93... --amount 100000 \
--force --sign --assert-min-balance 200000 \
--idempotency-key "esisuisse-bigbank-2026-03-25"
→ Executed. Signed. Diff: 500k → 400k.
5. rbcli pay send --from CH65... --to CH36... --amount 100000 \
--force --sign --assert-min-balance 200000 \
--idempotency-key "esisuisse-smallbank-2026-03-25"
→ Executed. Signed. Diff: 400k → 300k.
6. rbcli wealth summary
→ Confirms: 3 banks, esisuisse coverage now 49%, warnings reduced.
Two calls for the full picture (wealth summary), two dry-run-validated + signed + idempotent payments, one verification. No banking app does this.
Channel visibility
Every transaction shows which channel initiated it:
$ rbcli statements fetch CH9300762011623852957
DATE AMOUNT COUNTERPARTY REFERENCE CHANNEL
---------- ------- -------------------- ---------------------- ---------
2026-03-24 -1500 Immobilien Müller AG Miete März 2026 ebanking
2026-03-22 8500 Arbeitgeber GmbH Lohn Februar 2026 ebics
2026-03-20 -89.9 Swisscom AG Mobile Abo Februar mobile
2026-03-18 -245.5 Migros Zürich Einkauf KK-1234 card
2026-03-13 -350 CSS Versicherung Krankenkasse März 2026 ebanking
2026-03-10 -42 SBB AG GA Monatsrate mobile
Filter by channel: rbcli statements fetch CH93... --channel ebanking
Channels: blink, ebanking, mobile, ebics, card, system
Cryptographic signing
# Generate keypair
rbcli keys init
rbcli keys export > public.pem # Register with bank
# Sign a payment
rbcli pay send --from CH93... --to CH65... --amount 100 --force --sign
# Verify later
rbcli pay verify pay-348264a1
✓ Signature VALID for pay-348264a1
algorithm: Ed25519
local_key_match: True
What gets signed: a canonical JSON of from_iban, to_iban, amount, currency, reference, uetr, and timestamp. The signature is stored in the payment record and auto-committed to the git ledger.
The argument: if a payment is signed with Ed25519, the bank has stronger proof of authorization than a mobile app OTP. The proprietary app becomes not just unnecessary but inferior.
Git-backed ledger
# Initialize
rbcli ledger init
# Sync all banks (fetches statements + balances, commits to git)
rbcli ledger sync
# Your audit trail
rbcli ledger log
COMMIT TIMESTAMP MESSAGE
53500392c6e2 2026-03-25 11:52:37 +0000 rbcli: transfer xfr-3e553101
2df774f15993 2026-03-25 11:52:37 +0000 rbcli: payment pay-a0d6cd19 EXECUTED
f0e9401d2699 2026-03-25 11:52:36 +0000 rbcli: balance CH3608387... at small-bank
d8b269acbbf4 2026-03-25 11:52:36 +0000 rbcli: statements CH3608387... (2 txns)
# Audit the bank
rbcli ledger verify CH9300762011623852957
✓ Records match. No discrepancies found.
Every interaction is a git commit. If the bank ever changes or removes a transaction, ledger verify shows the discrepancy — because your local repo has the cryptographic proof of what they told you before.
~/.config/rbcli/ledger/ ← git repo
├── big-bank/
│ └── CH9300762011623852957/
│ ├── statements/2026-03-25.json
│ ├── balances/2026-03-25T103000.json
│ ├── payments/pay-348264a1.json ← includes signature
│ └── transfers/xfr-3e553101.json
├── kabe-bank/
│ └── ...
└── small-bank/
└── ...
ISO 20022 messages
| Message | Purpose | CLI command |
|---|---|---|
| pain.001 | Payment initiation | pay send |
| camt.052 | Intraday balance/transactions | statements fetch --intraday |
| camt.053 | End-of-day statements | statements fetch |
| camt.054 | Payment notifications | notifications list |
All payments include a UETR (Unique End-to-End Transaction Reference) — UUID v4, same format as SWIFT gpi.
How rbcli compares
Comparis.ch Banking App Fintech/bLink Twint rbcli + LLM
─────────── ─────────── ───────────── ───────── ──────────────
Compare products ✓ static own only pre-built UI ✗ ✓ structured
See all balances ✗ one bank ✓ multibank ✗ ✓ one command
Full wealth view ✗ ✗ ✗ ✗ ✓ wealth summary
Execute payments ✗ ✓ with app ✓ redirect ✓ app only ✓ --force
Custom reasoning ✗ ✗ pre-coded ✗ ✓ any question
Sign payments ✗ ✗ ✗ ✗ ✓ Ed25519
Independent record ✗ ✗ ✗ ✗ ✓ git ledger
Channel audit ✗ ✗ partial ✗ ✓ full
Dry-run / preview ✗ ✗ ✗ ✗ ✓ --dry-run
Idempotency ✗ ✗ ✗ ✗ ✓ --idempotency-key
Balance guardrail ✗ ✗ ✗ ✗ ✓ --assert-min-balance
Risk warnings ✗ ✗ some ✗ ✓ structured
Open API n/a ✗ ✓ bLink ✗ closed ✓ bLink
New feature speed weeks months weeks months seconds
Lock-in none bank app fintech app Twint app none
Configuration
~/.config/rbcli/
├── config.toml # Bank connections and defaults
├── signing.key # Ed25519 private key (chmod 600)
├── signing.pub # Ed25519 public key
├── ledger/ # Git repo with banking records
└── sim.pid # Simulator PID file
Monitoring (Koivu-style)
Inspired by Koivu Network — monitoring banking infrastructure uptime using real pennytest payments:
rbcli monitor ping --from CH93... --to CH65... --timeout 10
✓ Ping OK: 6ms (big-bank→big-bank)
Continuous monitoring with a routes config:
# routes.toml
[[routes]]
name = "intra-bank-bigbank"
from = "CH9300762011623852957"
to = "CH2800762011623852958"
bank = "big-bank"
amount = 0.01
currency = "CHF"
[[routes]]
name = "cross-bank-to-kabe"
from = "CH9300762011623852957"
to = "CH6589144414396878645"
bank = "big-bank"
amount = 0.01
currency = "CHF"
rbcli monitor run --config routes.toml --interval 60
Roadmap
- bankcli — thin orchestrator routing to protocol-specific CLIs
- fixcli — FIX protocol for trading and securities
- ebicscli — EBICS for corporate banking
- OpenWealth — portfolio management, custody accounts
- MCP server — expose as Model Context Protocol server for direct LLM tool use
- Real bLink — drop-in replacement for the simulator
- Alpensign / SwissSign — qualified electronic signatures (ZertES)
Man page
man ./man/rbcli.1
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rbcli-0.1.0.tar.gz.
File metadata
- Download URL: rbcli-0.1.0.tar.gz
- Upload date:
- Size: 75.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
092e57ff63fe4ca78bdff219831163db1d4514db7941bd957ea1bc5b48deb60a
|
|
| MD5 |
20853412b8d96350e32ea570d5985403
|
|
| BLAKE2b-256 |
dc012641c7d1b03f2304e3e2231382c3a63d4c54ee345351dfa9ee9a6a6bd205
|
Provenance
The following attestation bundles were made for rbcli-0.1.0.tar.gz:
Publisher:
publish.yml on gbits-io/rbcli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rbcli-0.1.0.tar.gz -
Subject digest:
092e57ff63fe4ca78bdff219831163db1d4514db7941bd957ea1bc5b48deb60a - Sigstore transparency entry: 1191458237
- Sigstore integration time:
-
Permalink:
gbits-io/rbcli@2f2b37be0cb0451a0c564e1c118caabd65c902e8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gbits-io
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2f2b37be0cb0451a0c564e1c118caabd65c902e8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rbcli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rbcli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 80.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5524067008e19c842b0c8848cde6fcc72c23097ed68f4be7b22de2907f659813
|
|
| MD5 |
246d655f65b07453dd1b41721ed27f9b
|
|
| BLAKE2b-256 |
a3eee366a558c039d6cafea71405eec2a928426583558678cd9aca9503f51b56
|
Provenance
The following attestation bundles were made for rbcli-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on gbits-io/rbcli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rbcli-0.1.0-py3-none-any.whl -
Subject digest:
5524067008e19c842b0c8848cde6fcc72c23097ed68f4be7b22de2907f659813 - Sigstore transparency entry: 1191458238
- Sigstore integration time:
-
Permalink:
gbits-io/rbcli@2f2b37be0cb0451a0c564e1c118caabd65c902e8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gbits-io
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2f2b37be0cb0451a0c564e1c118caabd65c902e8 -
Trigger Event:
push
-
Statement type: