Skip to main content

IBANforge Python SDK

PyPI Python License

Official Python SDK for the IBANforge API — IBAN validation, BIC/SWIFT lookup, Swiss BC-Nummer, and sanctions/SEPA/VoP compliance triage.

Built for AI finance agents and fintech developers. Sync + async clients, full type hints, typed exceptions.

Every code block on this page is executed by the test suite, against recorded responses from the real API, and each # comment on a print is checked against what actually printed. A block that stops being true fails CI.

Install

pip install ibanforge

Quick start

from ibanforge import IBANforge

with IBANforge(api_key="ifk_...") as client:
    out = client.validate_iban("CH1000230000000012345")

    print(out["valid"])                            # True
    print(out["country"]["code"])                  # 'CH'
    print(out["bic"]["bank_name"])                 # 'UBS Switzerland AG'
    print(out["bic"]["code"])                      # 'UBSWCHZH'
    print(out["sepa"]["member"])                   # True
    print(out["sepa"]["schemes"])                  # ['SCT', 'SDD']
    print(out["clearing"]["iid"])                  # '00230'
    print(out["clearing"]["sic"])                  # True
    print(out["clearing"]["qr_iid"])               # '30005'
    print(out["bank_code_check"]["status"])        # 'verified'
    print(out["risk_indicators"]["country_risk"])  # 'standard'

api_key and base_url fall back to the IBANFORGE_API_KEY and IBANFORGE_API_BASE environment variables — the same names the MCP server reads, so one setting configures both.

The answer nobody else gives: is this bank code real?

An IBAN can pass its mod-97 checksum and still name a bank that does not exist. bank_code_check says which:

from ibanforge import IBANforge

with IBANforge(api_key="ifk_...") as client:
    # The IBAN the SWIFT registry uses as an illustration. Structurally
    # perfect, bank code 00762 allocated to nobody:
    ghost = client.validate_iban("CH9300762011623852957")

    print(ghost["valid"])                            # True
    print(ghost["bank_code_check"]["status"])        # 'not_in_register'
    print(ghost["bank_code_check"]["authoritative"]) # True
    print(ghost["bic"])                              # None
    print(ghost["clearing"])                         # None
    print(ghost["next_steps"][0]["code"])            # 'bank_code_not_allocated'

valid: True and not_in_register is the correct pair: the number is well-formed, the bank is not there. Do not send. (This is also why out["bic"]["bank_name"] raises TypeError on that IBAN — bic is None. Use (out.get("bic") or {}).get("bank_name") when the IBAN is untrusted.)

Need an IBAN that does resolve — for a fixture, a demo, a test suite? Ask for one, with its proof:

from ibanforge import IBANforge

with IBANforge() as client:                         # free, no key
    t = client.test_iban(country="CH")
    proof = t["test_ibans"][0]["proof"]["bank_code_check"]
    print(proof["status"])                          # 'verified'
    print(proof["authoritative"])                   # True

Get a reusable key without e-mail

from ibanforge import IBANforge

key = IBANforge.generate_api_key()
print(key["monthly_limit"])                         # 25
# key["api_key"] is shown ONCE — store it now.

No address, card or confirmation is needed. Store the key in your secret configuration, reuse it for subsequent calls, and read usage() for its actual allowance. Normally this key provides 25 REST requests per month; temporary creation protection can lower that allowance, so read monthly_limit from the response. Do not create a key per request.

After the key has served a call, POST /v1/keys/claim with Authorization: Bearer ifk_… can raise its allowance: an explicitly supplied mailbox and a six-digit code give 200 requests per month; an x402 payment made on the key gives 200 requests once. An exhausted quota does not authorize sending an address or making a payment on the user's behalf. Follow the API's error details and retry delay.

The existing email argument and code option remain supported for callers who explicitly choose that route. No email is inferred from environment variables or other accounts.

Quick start (async)

import asyncio
from ibanforge import AsyncIBANforge

IBANS = ["CH1000230000000012345", "DE89370400440532013000"]

async def main():
    async with AsyncIBANforge(api_key="ifk_...") as client:
        results = await asyncio.gather(*[client.validate_iban(i) for i in IBANS])
        print(len(results))                         # 2
        print(sum(1 for r in results if r["valid"]))  # 2

asyncio.run(main())

All endpoints

Method Cost What it does
format_iban(iban) free Pure mod-97 + structure check. Pre-filter malformed IBANs before paying.
validate_iban(iban) $0.005 Full enrichment — BIC, EMI/vIBAN flag, SEPA + VoP, bank-code register check, Swiss BC-Nummer for CH/LI
validate_batch([iban, ...]) $0.002 / IBAN Up to 100 IBANs in one call. CSV cleanup, payout list triage.
lookup_bic(code) $0.003 BIC/SWIFT → bank name, country, city, LEI, registered address. 121k+ BIC entries (39k+ LEI-enriched via GLEIF).
lookup_ch_clearing(iid) $0.003 Swiss BC-Nummer / IID → full SIX BankMaster rail participation + QR-IID, the deepest Swiss clearing data in any public API.
check_compliance(iban) $0.02 Sanctions (bank BIC) + FATF + SEPA Instant + VoP + risk score 0–100
validate_reference(reference) free QR-bill (QRR), ISO 11649 (RF/SCOR), Belgian OGM/VCS or Finnish reference, checked against the dated document that publishes the rule
check_address(scheme, address) free A structured ISO 20022 postal address measured against a scheme's rules (sps, hvps_plus, fedwire), each finding citing its guideline
iban_structures() free Every supported country and its IBAN length
iban_structure(country) free One country's BBAN template — offsets, lengths, charsets
test_iban(country=...) free Test IBANs with a REAL bank code, plus the register row proving it
credit_bundles() free Prepaid packs and their per-call price
demo() free Worked examples of every endpoint
usage() free This key's quota for the current month
health() free API version, database size
IBANforge.generate_api_key(email=None) free Reusable key without e-mail; read its returned quota

In practice:

from ibanforge import IBANforge

with IBANforge(api_key="ifk_...") as client:
    batch = client.validate_batch(["CH1000230000000012345", "DE89370400440532013000"])
    print(batch["count"])                           # 2
    print(batch["valid_count"])                     # 2

    bic = client.lookup_bic("UBSWCHZH80A")
    print(bic["institution"])                       # 'UBS Switzerland AG'
    print(bic["lei"])                               # '549300WOIFUSNYH0FL22'

    ch = client.lookup_ch_clearing("230")
    print(ch["institution"]["name"])                # 'UBS Switzerland AG'
    print(ch["payment_services"]["sic"])            # True
    print(ch["qr_iid"])                             # '30005'

    structures = client.iban_structures()
    print(structures["total"])                      # 89

    ch_structure = client.iban_structure("CH")
    print(ch_structure["iban_length"])              # 21
    print(ch_structure["bban_pattern"])             # '5!n12!c'

    packs = client.credit_bundles()
    print(packs["bundles"][0]["credits"])           # 1000
    print(packs["bundles"][0]["price_usdc"])        # 5

    d = client.demo()
    print(len(d["iban_examples"]) > 0)              # True

    print(client.usage()["limit"])                  # 200
    print(client.health()["status"])                # 'ok'

Free format check (no key needed)

Save money by pre-filtering bad IBANs before paying for enrichment — and note that a malformed IBAN is a 200 with valid: False, not an exception:

from ibanforge import IBANforge

with IBANforge() as client:                         # no api_key required
    out = client.format_iban("CH93007620116238529XX")
    print(out["valid"])                             # False
    print(out["error"])                             # 'checksum_failed'

    ok = client.format_iban("CH1000230000000012345")
    print(ok["valid"])                              # True
    print(ok["bban"]["bank_code"])                  # '00230'

Compliance triage

The score is nested under compliance. There is no top-level risk_score, and no recommended_action.

from ibanforge import IBANforge

with IBANforge(api_key="ifk_...") as client:
    out = client.check_compliance("GB29NWBK60161331926819")

    print(out["compliance"]["risk_score"])                  # 10
    print(out["compliance"]["risk_level"])                  # 'low'
    print(out["compliance"]["sanctions"]["matched_lists"])  # []
    print(out["compliance"]["sanctions"]["fatf_status"])    # 'member'
    print(out["compliance"]["reachability"]["sct"])         # True
    print(out["compliance"]["vop"]["participant"])          # False
    print(out["meta"]["scope"])                             # 'bank_bic_only'

Sanctions screening is at the bank (BIC8) level — it does not screen the beneficiary name and is not a regulated AML/CFT product. risk_level: 'unassessable' means nothing could be screened; it is the absence of a verdict, never a favourable one, and risk_score is then None.

Error handling

The SDK raises typed exceptions — catch the specific class you care about, or the base IBANforgeError. Each carries .status, .code (the API's error slug) and the parsed .body.

from ibanforge import IBANforge, AuthError, InvalidInputError

with IBANforge(api_key="ifk_wrong") as client:
    try:
        client.usage()
    except AuthError as e:
        print(e.status)                             # 401
        print(e.code)                               # 'invalid_key'

with IBANforge() as client:
    try:
        client.lookup_bic("NOTABIC")
    except InvalidInputError as e:
        print(e.status)                             # 400
        print(e.code)                               # 'invalid_bic_format'
Class HTTP When
AuthError 401 / 403 Missing, revoked or mistyped key; mailbox verification required
PaymentRequiredError 402 No key and no credit. e.body["accepts"] carries the x402 challenge — pay and retry, no dead end
QuotaExhaustedError 429 Monthly free quota spent (the API usually answers 402 instead, so you can pay through)
RateLimitError 429 Too fast — back off
PayloadTooLargeError 413 The body is over the limit — split it, do not retry the same payload
InvalidInputError other 4xx Malformed request (a malformed IBAN is a 200, see above)
APIError 5xx Server-side failure — retry with backoff

For LLM agents (LangChain, LlamaIndex, CrewAI, AutoGen)

The IBANforge API is also available as a native MCP server (npx -y ibanforge-mcp) and via x402 micropayments — see the agent guide. For Python-first agents, the SDK above is usually enough.

Configuration

from ibanforge import IBANforge

client = IBANforge(
    api_key="ifk_...",     # or the IBANFORGE_API_KEY environment variable
    base_url=None,         # or IBANFORGE_API_BASE; defaults to api.ibanforge.com
    timeout=30.0,          # seconds, default
    user_agent="my-app/1.2",
)
print(client.health()["version"] is not None)       # True
client.close()

License

MIT.

Release files for ibanforge 1.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ibanforge 1.7.0
File Size Uploaded
ibanforge-1.7.0.tar.gz 23.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ibanforge 1.7.0
File Interpreter ABI Platform
ibanforge-1.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 45.1 kB

Release files / ibanforge-1.7.0.tar.gz

Download URL ibanforge-1.7.0.tar.gz
Size 23.4 kB
Tags Source
SHA-256 checksum
How to use checksums
44bac6c9dbca86c6cb96216a65541a2c8f8854821497175564caf7d9a37a8dd0
BLAKE2b-256 checksum
How to use checksums
e301940396791b78bf61a16ff7bb7d133a1841db62054b67ed76007a44634a6a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / ibanforge-1.7.0-py3-none-any.whl

Download URL ibanforge-1.7.0-py3-none-any.whl
Size 21.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba96c3c47bc9319e61fae5cc11101f27241b5b1b777aac79a41ebfd31c85f034
BLAKE2b-256 checksum
How to use checksums
f61ffb57bcba7bb60328409344054b56d4ae76a1a67cfa70dfbcdacb24daef5f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

This release

1.7.0 This release

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.4

2 release files

1.4.3

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page