WKN Validation in Python
The WKN has no check digit — any 6-character alphanumeric string passes format validation. Here's how to validate WKNs properly in Python with a single API call that confirms the identifier and enriches it with live instrument data.
In this guide
1. What is a WKN?
A WKN (Wertpapierkennnummer — also written WPKN or WPK) is the 6-character alphanumeric identifier used to identify securities in Germany. Issued since 1968 by WM Datenservice (formerly Wertpapier-Mitteilungen) in Frankfurt, WKNs appear in German brokerage confirmations, bank statements, portfolio reports, and financial data feeds.
While ISIN has become the standard for cross-border and regulatory reporting (MiFID II, EMIR), WKNs remain pervasive in the German retail and private banking segment. Many German brokers, neo-banks, and wealth management platforms expose WKNs alongside ISINs — and some legacy systems expose WKNs only.
Unlike ISIN, CUSIP, or SEDOL, the WKN has no check digit. It is an opaque sequence — there is no algorithm to verify structural correctness. Confirming a WKN requires a database lookup.
2. WKN anatomy
A WKN is exactly 6 characters long, using digits and uppercase letters:
| Property | Detail |
|---|---|
| Length | 6 characters |
| Allowed characters | [A-Z0-9] |
| Check digit | None |
| Issuing authority | WM Datenservice, Frankfurt |
| Scope | German securities (equities, bonds, funds, warrants, ETFs) |
Some well-known examples:
| WKN | ISIN | Security |
|---|---|---|
| 514000 | DE0005140008 | Deutsche Bank AG |
| 716460 | DE0007164600 | SAP SE |
| 723610 | DE0007236101 | Siemens AG |
| 840400 | DE0008404005 | Allianz SE |
3. WKN inside a German ISIN
German ISINs embed the WKN in a predictable way. The NSIN (National Securities Identifying Number) portion of a DE ISIN is the WKN zero-padded on the left to 9 characters:
# Extracting WKN from a German ISIN isin = "DE0005140008" country = isin[:2] # "DE" nsin = isin[2:11] # "000514000" check = isin[11] # "8" # The WKN is the NSIN with leading zeros stripped wkn = nsin.lstrip("0") # "514000" print(wkn) # → 514000
4. Why format checks are not enough
No check digit — typos are invisible
Because there is no check digit, a one-character typo (514001 instead of 514000) passes format validation silently. Without a database lookup there is no way to detect it. In a trading or portfolio management context, this kind of error can result in the wrong security being referenced in a position, order, or report.
Retired and reassigned WKNs
When a company is acquired, merged, or delisted, its WKN may be retired or superseded. A structurally valid WKN might map to a company that no longer exists, a fund that was wound up, or a warrant that expired. Format validation cannot distinguish an active security from a retired one.
No public registry
WM Datenservice does not publish a free downloadable list of all valid WKNs. The WKN database is a commercial product. An API backed by OpenFIGI is the practical alternative for confirming WKNs against real securities.
5. The right solution: one API call
The IsValid WKN API combines format validation and live OpenFIGI enrichment in a single GET /v0/wkn request.
Get your free API key at isvalid.dev.
Full parameter reference and response schema: WKN Validation API docs →
6. Python code example
Using the isvalid-sdk package or the requests library directly.
# wkn_validator.py import os from isvalid_sdk import IsValid iv = IsValid(api_key=os.environ["ISVALID_API_KEY"]) # ── Example usage ───────────────────────────────────────────────────────────── result = iv.wkn("514000") # Deutsche Bank AG if not result["valid"]: print("Invalid WKN: must be 6 alphanumeric characters") else: print(f"WKN : {result['wkn']}") if result.get("found"): print(f"Name : {result.get('name')}") print(f"ISIN : {result.get('isin')}") print(f"Ticker : {result.get('ticker')}") print(f"Exchange : {result.get('exchCode')}") print(f"FIGI : {result.get('figi')}") print(f"Sector : {result.get('marketSector')}")
Expected output for 514000:
WKN : 514000 Name : Deutsche Bank AG ISIN : DE0005140008 Ticker : DBK Exchange : GY FIGI : BBG000BC5588 Sector : Equity
In a FastAPI endpoint:
# router.py (FastAPI) from fastapi import APIRouter, HTTPException import requests, os router = APIRouter() API_KEY = os.environ["ISVALID_API_KEY"] @router.get("/securities/validate-wkn") def validate_wkn_endpoint(wkn: str): if not wkn: raise HTTPException(status_code=400, detail="Missing wkn parameter") try: resp = requests.get( "https://api.isvalid.dev/v0/wkn", params={"value": wkn}, headers={"Authorization": f"Bearer {API_KEY}"}, timeout=10, ) resp.raise_for_status() result = resp.json() except requests.RequestException: raise HTTPException(status_code=502, detail="WKN validation service unavailable") if not result["valid"]: raise HTTPException(status_code=400, detail=f"Invalid WKN: {wkn}") return { "wkn": result["wkn"], "found": result.get("found"), "isin": result.get("isin"), "name": result.get("name"), "ticker": result.get("ticker"), "exchCode": result.get("exchCode"), "figi": result.get("figi"), }
7. cURL example
Deutsche Bank AG:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/wkn?value=514000"
SAP SE:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/wkn?value=716460"
Invalid format:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/wkn?value=5140"
8. Understanding the response
Response for a valid WKN found in OpenFIGI:
{ "valid": true, "wkn": "514000", "found": true, "dataSource": "openfigi", "isin": "DE0005140008", "name": "Deutsche Bank AG", "ticker": "DBK", "exchCode": "GY", "securityType": "Common Stock", "marketSector": "Equity", "figi": "BBG000BC5588", "compositeFIGI": "BBG000BC5588" }
Response for an invalid WKN:
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Format validation result (6-char [A-Z0-9]) |
| wkn | string | Normalised (uppercased, whitespace-stripped) WKN |
| found | boolean | null | true — found in OpenFIGI; false — not found; null — OpenFIGI unavailable |
| isin | string | null | Associated ISIN (e.g. DE0005140008) |
| name | string | null | Full instrument name |
| ticker | string | null | Exchange ticker symbol |
| exchCode | string | null | Bloomberg exchange code (e.g. GY for XETRA) |
| securityType | string | null | Security type (e.g. Common Stock) |
| marketSector | string | null | Market sector (e.g. Equity) |
| figi | string | null | Financial Instrument Global Identifier |
| compositeFIGI | string | null | Composite FIGI across all exchanges |
9. Edge cases to handle
found: null vs. found: false
found: false means OpenFIGI responded but did not recognise the WKN. found: null means the lookup failed (timeout, rate limit). In the null case, treat the result as inconclusive and retry — do not reject the WKN as invalid.
10. Summary
| What | Detail |
|---|---|
| Format | 6 alphanumeric characters [A-Z0-9], no check digit |
| Check algorithm | None — purely opaque sequence |
| Issuing authority | WM Datenservice, Frankfurt |
| Coverage | German securities (equities, bonds, ETFs, warrants, structured products) |
| Enrichment source | OpenFIGI (Bloomberg) via ID_WERTPAPIER |
| API endpoint | GET /v0/wkn?value=514000 |
- ISIN validation — ISINs embed WKNs for DE-prefixed codes
- SEDOL validation — the UK equivalent
- CUSIP validation — the North American equivalent
- WKN Validation in Node.js
Python integration notes
Use Pydantic V2's @field_validator or the Annotated + AfterValidator pattern to embed WKN validation in your data models. The validator calls the IsValid API and raises a ValueError if valid is False. Because there is no check digit to compute locally, the API call is the only meaningful validation step — format-only regex checks provide little protection against real-world errors.
Async batch validation
For bulk imports — portfolio data from a custodian file, for example — use asyncio.gather() with a shared httpx.AsyncClient and an asyncio.Semaphore to cap concurrency at your API rate limit. This reduces total validation time from O(n) sequential to O(1) bounded by the pool size — important when processing hundreds of WKNs at once.
- Store
ISVALID_API_KEYin a.envfile and load withpython-dotenv - Cache results in Redis with a 24h TTL — WKN-to-instrument mappings rarely change
- Apply
.strip().upper()before validation to normalise input from different source systems - Use
NewType('Wkn', str)to distinguish validated WKNs from raw strings in type annotations - Store the
isinfield from the response — it enables downstream cross-referencing without additional API calls