A from-scratch, pure-Python client for the EBICS banking protocol (EBICS 3.0 / H005), validated live against Zürcher Kantonalbank (ZKB).
What it provides today:
- Key ceremony — INI/HIA/HPB with X.509 key transmission (self-signed mit Schlüsseln or CA-issued mit Zertifikaten), the printable initialisation letter (HTML/PDF), and bank-key pinning across sessions
- Read — statement/report downloads with parsers into typed models: camt.053 end-of-day statements, camt.052 intraday reports, camt.054 booking advices (incl. QRR/SCOR/LSV), and pain.002 payment status reports
- Write — pain.001 payment submission (BTU) with the A006 electronic signature
- Security throughout — the bank's
AuthSignatureis verified on every response, unknown return codes are never masked, and everything fails closed - Self-inspection —
available_order_types()(HAA) andsubscriber_info()(HTD)
What is missing: distributed signatures (EDS/VEU — multi-person payment approval; see milestone 7 below), and validation against banks beyond ZKB. Legacy EBICS versions (H004 and earlier) are deliberately unsupported.
- Stack: Python 3.11+, just two runtime deps —
cryptography(RSA/AES) andlxml(XML / inclusive Canonical XML 1.0); everything else stdlib. No PHP/Java sidecar. (Rationale: docs/04-implementation-plan.md.) - License model: source-available — free for personal use, paid license for commercial/business use (see docs/02-licensing-strategy.md).
- Reusable & app-agnostic: designed to be embedded as a dependency in a downstream application, not tied to any one consumer — a stable, reusable standard.
EBICS access now requires EBICS 3.0 / H005 (the pre-3.0 protocol was retired ~Nov 2025), and the ISO 20022 "2009" message vintage retires 21 Nov 2026 — so a client must speak H005 and consume camt.053.001.08 (the 2019 vintage) and submit pain.001.001.09 payments. There is no other pure-Python client for this. We build one, kept tightly scoped. EBICS is a stable, formally versioned standard, so a scoped client is low ongoing maintenance — the cost is upfront correctness. See docs/03-library-landscape.md for the landscape.
from ebicsclient import Bank, User, Client, generate_keyring, save_keyring, PAIN_001
bank = Bank(host_id="ZKBKCHZZ", url="https://ebicsweb.example.com/ebicsweb")
user = User(partner_id="PARTNER1", user_id="USER1")
# 1. Generate the three RSA key pairs (once) and store them encrypted.
keyring = generate_keyring()
save_keyring(keyring, "keyring.json", passphrase="…")
client = Client(bank, user, keyring)
# 2. Key initialisation: submit your keys, then print/sign/send the letter and wait for activation.
client.ini()
client.hia()
letter = client.make_ini_letter() # HTML, or PDF with the optional [pdf] extra
# … send letter.content to the bank; once activated:
# 3. Fetch the bank's public keys (verify their published hashes out of band).
client.hpb()
# 4. Read: download and parse the end-of-day statements.
for statement in client.download_statements():
print(statement.iban, statement.closing_balance)
# 5. Write: initiate a payment (a pain.001.001.09 document, as bytes).
transaction_id = client.upload(PAIN_001, pain001_bytes)The certificate-based ("mit Zertifikaten") profile is a constructor option — see docs/11-certificate-profiles.md.
EBICS IDs can carry leading zeros (a real Partner ID may look like
00123456), and ISO message versions look like"08". Unquoted in YAML/JSON/TOML these parse as numbers — the wrong type and silently stripped of their zeros (PyYAML even reads all-octal-digit values as octal). Since 1.3.1 the library rejects non-string values immediately with an explanatory error, but the correct fix is always quoting the value in your config (partner_id: "00123456",message_version: "08") — never wrapping the parsed number instr(), which would keep the wrong, zero-stripped identifier and talk to the bank as the wrong subscriber. The same applies to digits-only keyring passphrases.
| Doc | Contents |
|---|---|
| docs/01-protocol-and-formats.md | EBICS/H005 background, the two regulatory deadlines, message formats |
| docs/02-licensing-strategy.md | Dual-licensing plan, legal reasoning, reimplementation |
| docs/03-library-landscape.md | Existing EBICS libraries and the gap this library fills |
| docs/04-implementation-plan.md | Scope, modules, the two hard parts, build order, test strategy |
| docs/05-zkb-onboarding.md | The INI/HIA + signed-letter ceremony, ZKB BTF/order params |
| docs/06-engineering-conventions.md | Baseline practices: layout, logging, errors, security, typing, testing, CI |
| docs/07-handshake-testing.md | Validating INI/HIA/HPB + download/upload against the ZKB test platform |
| docs/08-parity-and-xsd-findings.md | The inclusive-vs-exclusive c14n correction and verification discipline |
| docs/09-zkb-test-platform-settings.md | What the ZKB test platform exposes, and its upload/simulation model |
| docs/10-btf-order-types.md | ZKB's EBICS order-type → H005 BTF catalogue |
| docs/11-certificate-profiles.md | "mit Schlüsseln" vs "mit Zertifikaten", and the certificate seam |
../local/ (outside the repo) |
Real ZKB connection credentials, kept in the workspace outside the repo — can't be committed |
Contributors: see CONTRIBUTING.md. One-command setup with uv:
git clone https://github.com/schulluk/ebicsclient && cd ebicsclient && uv sync --all-groups
(or pip install -e . --group dev on pip ≥ 25.1).
This is a money-moving library — the engineering bar is docs/06-engineering-conventions.md.
Read and write validated live against the ZKB test platform. The key ceremony, the statement download path, and the payment upload (envelope, authentication signature, A006 electronic signature, and order-data encryption) are all accepted by the bank, and the camt.053 parser is validated against a real bank statement.
Milestone 1 — Key ceremony (validated live on ZKB)
- Key generation + encrypted keyring, and EBICS public-key hashes
- Authentication signature (inclusive Canonical XML 1.0 + RSA-SHA256)
- HTTPS transport (TLS 1.2 floor, certifi fallback via the optional
tlsextra) - INI/HIA/HPB handshake
- X.509 key transmission: mit Schlüsseln (self-signed) and mit Zertifikaten (CA certs)
- Initialisation letter (HTML, or PDF via the optional
pdfextra) - Bank-key pinning across sessions (
hpb(pinned=...))
Milestone 2 — Read (validated live on ZKB)
- Order-data decryption (RSA-unwrap + AES-128-CBC)
- Statement download —
EOP/camt.053BTD transaction (initialise → transfer → receipt) - camt.053 parsing (balances + entries) — validated on a real ZKB statement
Milestone 3 — Write (validated live on ZKB)
- Order-data encryption and the A006 electronic signature (RSASSA-PSS)
- Payment upload —
MCT/pain.001BTU transaction — accepted live
Milestone 4 — Verification & release
- Exception model with retryability classification
- Offline verification: H005 XSD validation, C14N golden vectors, ebics-client-php parity
- Golden regression fixture from a real ZKB statement
- CI (ruff / mypy --strict / pytest) and tag-triggered PyPI releases (Trusted Publishing)
Milestone 5 — Message formats (parsers built against genuine ZKB messages)
- pain.002 status-report parser (group / payment / transaction statuses, reason codes)
- camt.052 intraday reports
- camt.054 booking advices (incl. the QRR / SCOR / LSV variants via
service_option)
Milestone 6 — Protocol hardening & conveniences
- Verify the bank's
AuthSignatureon every response — validated live on ZKB - Subscriber self-inspection —
available_order_types()(HAA) andsubscriber_info()(HTD)
Milestone 7 — Distributed signatures (EDS/VEU) (next; parked until validatable)
EDS (Elektronische Verteilte Unterschrift) is EBICS's workflow for orders that need
multiple people to sign before the bank executes them — dual control on payments. An order
is uploaded with requestEDS and parks in the bank's VEU queue; further signatories list the
queue, inspect the order, and deliver their A006 signatures until the configured quorum (e.g.
first + second signature) is reached. The building blocks (A006 signing, the SignatureFlag,
the admin-download transaction pattern) are in place; what is missing is a validatable
multi-user setup — the ZKB test subscriber is single-user with NumSigRequired=0, so the
workflow cannot be exercised there. This milestone starts when a downstream setup with a real
multi-signature profile exists.
- Upload into the VEU queue (
SignatureFlagwithrequestEDS) - List pending orders (HVU/HVZ) and fetch order details (HVD) and transactions (HVT)
- Deliver an additional signature (HVE) and cancel a pending order (HVS)
- Validate the full quorum workflow against a multi-signature bank profile
This client is EBICS 3.0 (H005) only — legacy versions (H004 and earlier) will not be
supported (see docs/04); the protocol/ seam exists for a
future EBICS version, not for the past.
Source-available under the PolyForm Noncommercial License 1.0.0 — free for noncommercial use; commercial/business use requires a paid license. See LICENSE.md and the rationale in docs/02-licensing-strategy.md.