enable-banking-api
npx machina-cli add skill peerjakobsen/smartspender/enable-banking-api --openclawEnable Banking API
Purpose
Provides domain knowledge about the Enable Banking Open Banking API — endpoints, authentication, session lifecycle, transaction structure, rate limits, and supported Danish banks. Reference this when orchestrating tools/eb-api.py for API-based transaction sync.
API Overview
Enable Banking provides PSD2-compliant access to bank account data across 2,500+ European banks. SmartSpender uses it as an alternative to browser automation for faster, more reliable transaction sync.
- API Base URL:
https://api.enablebanking.com - Authentication: JWT signed with RSA private key (PS256 algorithm)
- Data Format: JSON, ISO 20022 inspired field names
- Rate Limit: Max 4 account information requests per day per account (PSD2 regulation)
Authentication
JWT Structure
The eb-api.py helper handles JWT generation. Conceptually:
- Issuer:
enablebanking.com - Audience:
api.enablebanking.com - Algorithm: PS256 (RSA-PSS with SHA-256)
- Expiry: 1 hour from creation
- Payload: includes
app_idfrom config
Config File
Stored at ~/.config/smartspender/eb-config.json:
{
"app_id": "your-application-id",
"key_path": "~/.config/smartspender/private.pem"
}
Session Lifecycle
Consent Flow
- Initiate:
eb-api.py auth --bank <name>sends POST to/authwith ASPSP details and redirect URL (https://smartspender.mentilead.com/callback.html) - MitID: User authenticates in browser (auto-opened)
- HTTPS Relay: Bank redirects to
callback.htmlwhich forwardscodetohttp://localhost:19876/callback - Capture: Localhost listener on port 19876 catches the authorization code
- Session: Code exchanged for session_id via POST to
/sessions - Active: Session provides access to account data for 90-180 days
Session States
| State | Meaning | Action |
|---|---|---|
active | Consent valid, API accessible | Proceed with sync |
expired | Consent period ended | Re-run auth for new consent |
revoked | Bank-side revocation | Re-run auth |
no_session | No session file exists | Run setup + auth |
Session File
Stored at ~/.config/smartspender/eb-session.json:
{
"session_id": "uuid-string",
"accounts": [
{
"uid": "account-uid-string",
"account_id": {"iban": "DK1234567890123456"},
"name": "Lønkonto",
"currency": "DKK",
"product": "Checking Account"
}
],
"consent_expires": "2026-05-02T12:00:00.000Z",
"created_at": "2026-02-02T12:00:00+00:00",
"fetch_log": {}
}
API Endpoints
POST /auth
Initiate consent. Returns url for user to visit.
POST /sessions
Exchange authorization code for session. Returns session_id and accounts.
GET /sessions/{session_id}/accounts
List accounts linked to the session. Returns account UIDs, IBANs, names, currencies.
GET /accounts/{uid}/transactions
Fetch transactions. Parameters:
date_from(required): YYYY-MM-DDdate_to(optional): YYYY-MM-DD (defaults to today)continuation_key(optional): for pagination
GET /accounts/{uid}/balances
Fetch current balances. Returns balance type, amount, currency, and date.
Response structure:
{
"balances": [
{
"balance_amount": {"amount": "12543.25", "currency": "DKK"},
"balance_type": "CLBD",
"reference_date": "2026-02-03"
},
{
"balance_amount": {"amount": "12100.00", "currency": "DKK"},
"balance_type": "ITAV",
"reference_date": "2026-02-03"
}
]
}
Balance type codes:
| Type | Description | Use Case |
|---|---|---|
CLBD | Closing booked balance | End of day settled — most accurate, prefer this |
ITAV | Intraday available | Current available funds — fallback if CLBD missing |
XPCD | Expected balance | Projected balance including pending |
Parsing priority: Prefer CLBD over ITAV over XPCD. Extract balance_amount.amount as the balance value.
Transaction Structure
Enable Banking returns transactions in this JSON format:
{
"bank_transaction_code": {"code": "12", "description": "Kortbetaling"},
"booking_date": "2026-01-15",
"booking_date_time": "2026-01-15T14:30:00Z",
"credit_debit_indicator": "DBIT",
"creditor": {"name": "FØTEX"},
"debtor": {"name": "Peer Jakobsen"},
"entry_reference": "5561990681",
"remittance_information": ["Dankort-køb", "FØTEX 4123"],
"status": "BOOK",
"transaction_amount": {"amount": "847.50", "currency": "DKK"},
"balance_after_transaction": {"amount": "12543.25", "currency": "DKK", "credit_debit_indicator": "CRDT"},
"value_date": "2026-01-15"
}
Key Fields
| Field | Description |
|---|---|
booking_date | Transaction date (already YYYY-MM-DD) |
credit_debit_indicator | DBIT = debit (expense), CRDT = credit (income) |
transaction_amount.amount | Positive number as string |
transaction_amount.currency | ISO currency code |
creditor.name | Recipient (present on debits) |
debtor.name | Sender (present on credits) |
remittance_information | Array of description strings |
balance_after_transaction | Running balance (for tx_hash) |
status | BOOK (booked), PDNG (pending), INFO (informational) |
entry_reference | Bank's reference number |
bank_transaction_code | Transaction type code and description |
Transaction Status Values
- BOOK: Booked, final — include in sync
- PDNG: Pending, not yet final — skip (may change or disappear)
- INFO: Informational, not a real transaction — skip
Only sync transactions with status: BOOK.
PSD2 Rate Limits
Regulation
PSD2 (Payment Services Directive 2) limits account information requests to 4 per day per account. This is a European banking regulation, not an Enable Banking or SmartSpender limitation.
Rate Limit Strategy
eb-api.pytracks fetches per account per day in the session file- Before each fetch, check the count. Warn at 3/4, block at 4/4
- A single
eb-api.py transactionscall counts as 1 fetch regardless of pagination - Balances and account list queries are also rate-limited
Practical Impact
- Sync once per day during normal use
- If testing, be mindful of the 4/day limit
- Rate resets at midnight UTC
- Error when exceeded:
rate_limitwith message explaining the constraint
Supported Danish Banks
Enable Banking supports these Danish banks (non-exhaustive):
Major Banks
- Nykredit
- Danske Bank
- Nordea
- Jyske Bank
- Sydbank
- Spar Nord
Digital Banks
- Lunar
- Bank Norwegian
Cooperative Banks (via BEC/Bankdata)
- Arbejdernes Landsbank
- Vestjysk Bank
- Ringkjøbing Landbobank
- Sparekassen Danmark
- Plus 50+ smaller banks
ASPSP Names
When running eb-api.py auth --bank <name>, use the bank's name as registered with Enable Banking. Common Danish bank names:
Nykredit(not "nykredit" — use proper case)Danske BankNordeaJyske BankSydbankLunar
Error Codes
| Error | Meaning | Resolution |
|---|---|---|
401 Unauthorized | JWT invalid or expired | Check RSA key and app_id |
403 Forbidden | Consent missing or expired | Re-run auth for new consent |
404 Not Found | Invalid session or account UID | Verify session is active |
429 Too Many Requests | PSD2 rate limit exceeded | Wait until tomorrow |
503 Service Unavailable | Bank API temporarily down | Retry later |
Related Skills
skills/data-schemas/SKILL.md— Common transaction schema and CSV file structures including accounts.csv with EB fields
Known Limitations
- PSD2 rate limit (4/day/account) means no real-time balance checking
- Transaction descriptions from API may differ from browser export CSV (banks format them differently)
balance_after_transactionis optional — some banks don't provide it for all transaction types- Session can be revoked bank-side without notice (user changed consent settings)
- ASPSP names must match Enable Banking's registry (case-sensitive)
Source
git clone https://github.com/peerjakobsen/smartspender/blob/main/skills/enable-banking-api/SKILL.mdView on GitHub Overview
Enable Banking provides PSD2 compliant access to bank data across 2,500+ European banks. SmartSpender uses it as an API alternative to browser automation for faster, more reliable transaction sync. This skill covers endpoints, authentication, session lifecycle, and rate limits for API-based bank sync.
How This Skill Works
The eb-api.py helper handles JWT generation for authentication using PS256. After consent is granted, the flow exchanges an authorization code for a session_id, which then allows account data access via /sessions/{session_id}/accounts and subsequent endpoints to fetch transactions and balances. Data is returned in JSON with ISO style field names and a daily rate limit of four account info requests per account.
When to Use It
- When you need fast, API-based bank synchronization instead of browser automation.
- When you want PSD2 compliant access to accounts from 2,500+ European banks via Enable Banking.
- When implementing the consent flow and MitID-based authentication via the eb-api.py helper.
- When you must enforce the API rate limit of up to four account information requests per day per account.
- When you need to manage and reuse a session for 90–180 days of access to account data.
Quick Start
- Step 1: Create eb-config.json at ~/.config/smartspender with your app_id and the path to private.pem.
- Step 2: Run eb-api.py auth --bank <name> to initiate consent and complete the MitID flow.
- Step 3: Exchange the authorization code for a session_id, then call GET /sessions/{session_id}/accounts and GET /accounts/{uid}/transactions as needed.
Best Practices
- Use the eb-api.py helper to generate JWTs and keep a secure eb-config.json at ~/.config/smartspender/eb-config.json with app_id and key_path.
- Protect the RSA private key (private.pem) and rotate keys when needed; never expose keys in logs or public repos.
- Respect the rate limit by batching requests and implementing backoff for /accounts related calls.
- When fetching transactions, use date_from and optional date_to, and leverage continuation_key for pagination.
- Parse responses against the documented structure and normalize balance types, especially CLBD and ITAV, for downstream use.
Example Use Cases
- Initiate consent with POST /auth, complete MitID in the browser, exchange code for a session, then list accounts with GET /sessions/{session_id}/accounts and fetch transactions with GET /accounts/{uid}/transactions.
- Use GET /sessions/{session_id}/accounts to retrieve account UIDs and then call GET /accounts/{uid}/balances to obtain current balances including CLBD and ITAV.
- Sync a Danish user's transactions for the last 30 days by passing date_from in YYYY-MM-DD format and handle pagination with continuation_key.
- Display user friendly account names by reading the accounts array from the /sessions/{session_id}/accounts endpoint.
- On session expiry, re-run the auth flow to obtain a new session_id and continue syncing without starting from scratch.