Install
openclaw skills install openfin-enable-bankingPSD2 Open Banking integration via Enable Banking API. Connect DACH bank accounts (Sparkasse, Volksbank, Deutsche Bank, Commerzbank, DKB, ING, Postbank + Aust...
openclaw skills install openfin-enable-bankingPSD2 Open Banking integration via Enable Banking API. Multi-mandant architecture for onboarding bank connections, fetching balances/transactions, and session renewal.
# 1. Onboard a new mandant
python scripts/onboard.py --bank "Sparkasse Karlsruhe" --country DE --mandant-id mueller
# 2. Fetch balances + transactions
python scripts/fetch.py --mandant-id mueller
# 3. Renew an expired session
python scripts/renew.py --mandant-id mueller
┌─────────────┐ POST /auth ┌──────────────────────┐
│ onboard.py │ ──────────────────→ │ Enable Banking API │
│ renew.py │ ←── redirect_url ── │ api.enablebanking.com│
└──────┬──────┘ └──────────┬───────────┘
│ │
│ poll pending_callbacks/ │ User authorizes
│ │ at bank portal
▼ ▼
┌──────────────────┐ GET /callback ┌─────────────┐
│ callback_server │ ←───────────────── │ Bank OAuth │
│ (port 8443 HTTPS)│ │ Redirect │
└──────────────────┘ └─────────────┘
│
│ saves code → pending_callbacks/{state}.json
│
▼
┌─────────────┐ POST /sessions ┌──────────────────────┐
│ onboard.py │ ──────────────────→ │ Enable Banking API │
│ renew.py │ ←── session + accs ── │ │
└──────┬──────┘ └──────────────────────┘
│
│ saves mandanten/{id}.json
▼
┌─────────────┐ GET /accounts/{uid}/ ┌──────────────────────┐
│ fetch.py │ ──────────────────→ │ Enable Banking API │
│ │ ←── balances + txns ──│ │
└──────┬──────┘ └──────────────────────┘
│
│ saves data/{id}/{date}.json
▼
stdout: JSON
enable-banking/
├── SKILL.md # This file
├── config.json # App credentials (DO NOT COMMIT)
├── .keys/ # Private key + callback certs (DO NOT COMMIT)
├── .gitignore
├── scripts/
│ ├── lib/
│ │ ├── __init__.py
│ │ └── auth.py # Shared JWT + API helpers + mandant I/O
│ ├── callback_server.py # HTTPS callback daemon
│ ├── onboard.py # New mandant connection
│ ├── fetch.py # Autonomous data fetch
│ └── renew.py # Session renewal
├── references/
│ └── api-reference.md
├── mandanten/ # Per-mandant JSON files (DO NOT COMMIT)
├── data/ # Fetched data (DO NOT COMMIT)
└── pending_callbacks/ # Temporary callback codes (DO NOT COMMIT)
scripts/lib/auth.py — Shared Module| Function | Description |
|---|---|
load_config() | Load config.json |
generate_jwt(config) | RS256 JWT for API auth |
api_request(method, endpoint, token, **kwargs) | Authenticated API call with retry (429, timeout) |
load_mandant(mandant_id) | Load mandanten/{id}.json |
save_mandant(mandant_id, data) | Save mandant file (chmod 600) |
list_mandanten() | List all mandant IDs |
scripts/callback_server.py — HTTPS Callback Serverpython scripts/callback_server.py # Port 8443 (HTTPS)
python scripts/callback_server.py --port 9443 # Custom port
python scripts/callback_server.py --no-ssl # HTTP only (dev)
.keys/ on first runGET /callback?code=...&state=... → saves to pending_callbacks/{state}.jsonGET /health → 200 OKexec background:true command:"python scripts/callback_server.py"scripts/onboard.py — Mandant Onboarding# Connect a new mandant
python scripts/onboard.py --bank "Sparkasse Karlsruhe" --mandant-id mueller
# With manual auth code (no callback server needed)
python scripts/onboard.py --bank "Sparkasse Karlsruhe" --mandant-id mueller --code ABC123
# List available banks
python scripts/onboard.py --list-banks --country DE
# Business account
python scripts/onboard.py --bank "DKB" --mandant-id firma --psu-type business
Flow (without --code):
pending_callbacks/{state}.json (needs callback_server running)mandanten/{id}.jsonscripts/fetch.py — Autonomous Data Fetch# Single mandant
python scripts/fetch.py --mandant-id mandant-1
# All mandanten
python scripts/fetch.py --all
# Date range
python scripts/fetch.py --mandant-id mandant-1 --date-from 2026-03-01 --date-to 2026-03-10
# Only balances or transactions
python scripts/fetch.py --mandant-id mandant-1 --balances-only
python scripts/fetch.py --mandant-id mandant-1 --transactions-only
data/{mandant-id}/{YYYY-MM-DD}.jsonlastFetch in mandant filescripts/renew.py — Session Renewalpython scripts/renew.py --mandant-id mueller
python scripts/renew.py --mandant-id mueller --code ABC123
mandanten/{id}.json:
{
"mandantId": "mueller",
"bank": "Sparkasse Karlsruhe",
"country": "DE",
"psuType": "personal",
"sessionId": "cbe3cd33-...",
"accounts": [
{
"uid": "2818be38-...",
"iban": "DE17...",
"name": "Max Mueller",
"currency": "EUR",
"product": "Sichteinlagen"
}
],
"validUntil": "2026-09-06T07:18:35Z",
"createdAt": "2026-03-10T08:18:00Z",
"lastFetch": null
}
data/{id}/{date}.json and stdout:
{
"mandantId": "mueller",
"fetchedAt": "2026-03-10T08:00:00Z",
"accounts": [
{
"uid": "...",
"iban": "DE17...",
"name": "Max Mueller",
"balances": [
{"type": "CLBD", "amount": "-993.13", "currency": "EUR", "date": "2026-03-10"}
],
"transactions": [
{
"date": "2026-03-02",
"amount": "-171.00",
"currency": "EUR",
"creditDebit": "DBIT",
"counterparty": "...",
"description": "...",
"bookingDate": "2026-03-02",
"valueDate": "2026-03-02"
}
]
}
]
}
pip install PyJWT)pip install cryptography)pip install requests)Local (dev/test):
--code flag (manual code entry, no server needed)VPS (production):
redirectUrl in config.json to your domainfetch.py --all runs| Issue | Solution |
|---|---|
401 Unauthorized | Check applicationId + key match in Enable Banking portal |
403 Forbidden | Session expired → python renew.py --mandant-id <id> |
| Exit code 2 | Session expired → renew |
Port already in use | Kill existing: lsof -i :8443 | grep LISTEN | awk '{print $2}' | xargs kill |
| No callback received | Check callback_server is running, firewall allows port |
| Rate limited (429) | Auto-retried; if persistent, wait and retry later |