FINMA guide · 3 of 3
Automating FINMA checks
on your counterparties.
Checking three names by hand takes a few minutes. For hundreds of counterparties reviewed periodically, you need a repeatable method: match by identifier first, treat names as leads, and keep a record of the version used. This guide describes that method with the OpenSwissData FINMA file and tested examples.
Contents
The principle
- Your counterpartiesName, UID and LEI when you have them.
- NormaliseUID reduced to “CHE” and nine digits, LEI in upper case.
- Match by identifierExact UID, then exact LEI: reliable matches.
- Match by nameCandidates to review, never verdicts.
- Review and recordA person decides; the file version and the decision are kept.
Matching by identifier gives reliable matches. Matching by name only produces candidates: it guides the human review, it does not conclude.
The files to match
The OpenSwissData FINMA archive contains, among other files:
Scroll the table to see all columns
| File | Contents | Good to know |
|---|---|---|
finma_registry.csv, .json, .sql, .parquet | One row per authorisation in the UID list published by FINMA. | A company holding several authorisations appears on several rows. |
finma_<category>.csv | The same rows, one file per non-empty category. | Categories grouped by OpenSwissData (field entity_type). |
finma_warnings.csv, .json, .sql, .parquet | The warning list, separate from the registry. | Name, date, commercial register filter and detail link only. |
finma.xlsx | Registry and warnings in two sheets. | A third sheet recalls the reading rules. |
changelog_90d.csv, .json | Changes observed between the available versions. | Observation dates, not FINMA decision dates. |
quality.json, checksums.sha256, provenance.json | Field coverage, collection gaps, checksums and signature. | Keep them with the result of the check. |
Registry fields used for matching
Scroll the table to see all columns
| Field | Contents |
|---|---|
uid | The UID as published by FINMA, formatted CHE-123.456.789; empty for some rows. |
name, city | The name and city published by FINMA. |
licence_type, licence_type_fr, licence_type_de, licence_type_it | The authorisation type, labelled by FINMA in English, French, German and Italian. |
entity_type | A grouping made by OpenSwissData; an unmapped label falls into other. For a precise filter, use licence_type. |
lei, lei_registration_status | The LEI, only when the UID matches exactly one LEI at GLEIF, and the status of that LEI registration. |
address, canton | Taken from GLEIF when an LEI has been attached; they may stay empty, even with an LEI. |
licence_date, status | Empty: FINMA’s list does not provide them. |
is_warning_listed | Empty: no link to the warning list is inferred from similar names. |
In the CSV and SQL files, a missing value is an empty string; in the JSON, the key is missing or null; in Parquet, the value is null. In SQL, test uid <> '' rather than uid IS NOT NULL.
Update frequency
On FINMA’s side, the lists are “regularly updated”, and each list shows its date of last modification.1 The warning list is neither exhaustive nor updated on a daily basis.2
On the OpenSwissData side, FINMA collection is scheduled daily. A collection can be blocked if its checks fail; gaps are reported in quality.json, and the version actually available is shown on the product page.
How often you run your own check is a matter for your risk policy: for example before each new relationship, then at regular intervals. The changelog_90d file helps you target the counterparties affected by an observed change.
Matching in the right order
1. The UID first
The FSO allows two ways of writing the UID, with or without hyphens and full stops, and the HR or MWST suffix is not part of the number.3 Reduce both sides to a compact form, “CHE” followed by the nine digits: an exact match on that form is a reliable match.
2. Then the LEI
If your counterparty has an LEI, compare it in upper case with the lei field. The file only attaches an LEI when the UID matches exactly one LEI at GLEIF: an LEI missing from the file does not mean the entity has none.
3. The name last, with care
For counterparties without an identifier, and for the whole warning list, which carries no UID, the name is the only common ground. Normalise both sides (case, accents, legal forms), look for names contained in one another, and put every candidate to a human review.
A name match is not an identity
For the warning list in particular, a candidate establishes nothing about your counterparty: compare domicile, address and website on FINMA’s detail page before drawing any conclusion. See the guide to the warning list.
Example: SQLite on the command line
The archive’s .sql files each create a table with text columns and fill it. Put a counterparties.csv file in the folder of the unzipped archive:
reference,name,uid
C-001,Exemple Banque SA,CHE-123.456.789
C-002,Exemple Gestion Sàrl,CHE987654321 TVA
C-003,Exemple Conseil AG,The names and UIDs in this example are fictitious. Then load both tables and run the checks in checks.sql; the --csv option of .import requires sqlite3 3.32 or later:
sqlite3 check.db < finma_registry.sql
sqlite3 check.db < finma_warnings.sql
sqlite3 -header -column check.db < checks.sql-- Your counterparties: a CSV file with the columns reference, name and uid
DROP TABLE IF EXISTS counterparties;
.import --csv counterparties.csv counterparties
-- UID reduced to "CHE" followed by nine digits, on both sides
CREATE TEMP VIEW counterparty_uid AS
SELECT *, substr(upper(replace(replace(replace(uid, '-', ''), '.', ''), ' ', '')), 1, 12) AS uid_key
FROM counterparties WHERE trim(uid) <> '';
CREATE TEMP VIEW registry_uid AS
SELECT *, substr(upper(replace(replace(replace(uid, '-', ''), '.', ''), ' ', '')), 1, 12) AS uid_key
FROM finma_registry WHERE uid <> '';
-- 1. Matches by UID, authorisations grouped
SELECT c.reference, c.name, min(r.name) AS finma_name,
group_concat(r.licence_type, ' ; ') AS authorisations
FROM counterparty_uid c
JOIN registry_uid r ON r.uid_key = c.uid_key
GROUP BY c.reference, c.name;
-- 2. To review: no UID, or UID not in the list
SELECT reference, name FROM counterparties
WHERE reference NOT IN (
SELECT c.reference FROM counterparty_uid c
JOIN registry_uid r ON r.uid_key = c.uid_key
);
-- 3. Warning list candidates to review: one name contained in the other
SELECT c.reference, c.name, w.name AS warning_name, w.date_added, w.source_url
FROM counterparties c
JOIN finma_warnings w
ON instr(lower(w.name), lower(c.name)) > 0
OR instr(lower(c.name), lower(w.name)) > 0;The first query groups the authorisations of one company. The second lists the counterparties to review, because they have no UID or no match: they are not “unauthorised” counterparties. The third only produces candidates; SQLite’s lower() function only handles ASCII letters, so accents remain distinctive.
With the JSON
To find every authorisation of a UID with jq:
jq '.[] | select(.uid == "CHE-123.456.789") | {name, licence_type, lei}' finma_registry.jsonExample: preparing names in Python
To compare names regardless of accents and legal forms, a simple normalisation is enough to produce sturdier candidates:
import csv
import re
import unicodedata
# Common legal forms, removed before comparing (illustrative list)
LEGAL_FORMS = {"ag", "sa", "sarl", "gmbh", "ltd", "sagl", "kg", "snc", "scs"}
def fold(name: str) -> str:
text = unicodedata.normalize("NFKD", name).encode("ascii", "ignore").decode().lower()
return " ".join(w for w in re.findall(r"[a-z0-9]+", text) if w not in LEGAL_FORMS)
with open("finma_warnings.csv", encoding="utf-8-sig", newline="") as f:
warnings = [(fold(row["name"]), row) for row in csv.DictReader(f)]
with open("counterparties.csv", encoding="utf-8-sig", newline="") as f:
for row in csv.DictReader(f):
key = fold(row["name"])
if len(key) < 4:
continue # name too short for a useful search
for folded, warning in warnings:
if len(folded) >= 4 and (key in folded or folded in key):
print(row["reference"], row["name"], "->", warning["name"], warning["source_url"])The list of legal forms is illustrative: adapt it to your counterparties. Every line printed remains a candidate to review.
Keeping a record of every check
To be able to explain a result later, keep with it:
- the file version, which appears in the archive name and in
quality.json; - the
checksums.sha256file and the result of its verification; - the date of the check, the matches accepted and the decisions taken on candidates.
shasum -a 256 -c checksums.sha256 # macOS
sha256sum -c checksums.sha256 # LinuxThe dates in changelog_90d are observation dates between two versions, not FINMA decision dates.
For a one-off question: kyc_check
The OpenSwissData MCP server offers the kyc_check tool without an account or key, within 100 calls per hour per IP address. It searches a name in the registry and in the warning list, ignoring case and accents, and returns the closest results with their total count. An empty answer is not a compliance certificate.
claude mcp add --transport http openswissdata https://mcp.openswissdata.com/jsonrpc
curl -s https://mcp.openswissdata.com/jsonrpc \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"kyc_check","arguments":{"name":"Exemple Banque"}}}'Buying the file does not open any paid MCP access, and new Pro subscriptions are closed for now. Details and other clients: MCP server page.
Limits
- The file follows FINMA’s list of UIDs for authorised companies. It covers neither the register for insurance intermediaries, nor SRO members, nor collective investment schemes: consult those sources directly.1
- Authorisation dates and detailed statuses are not in FINMA’s list: the corresponding fields stay empty.
- Matching by name produces false positives and can miss a variant: it remains a triage before review.
- A row in the file reflects FINMA’s list on the collection date; for any decision, the official source prevails.
Working with the data
The FINMA registry as files.
The list of UIDs of authorised companies published by FINMA, as Excel, CSV, JSON, Parquet and SQL. An LEI is added when the UID matches exactly one LEI at GLEIF, the warning list stays in separate files, and every version is signed. One-time payment of CHF 299.
Official sources
The quotations and facts in this guide come from these pages, consulted on 29 September 2026. Only these sources are authoritative, and they may have changed since.
- FINMA · Authorised or registered institutions, persons and productshttps://www.finma.ch/en/finma-public/authorised-institutions-individuals-and-products/
- FINMA · Warning listhttps://www.finma.ch/en/finma-public/warnungen/warning-list/
- Federal Statistical Office (FSO) · The UID in generalhttps://www.bfs.admin.ch/bfs/en/home/registers/enterprise-register/enterprise-identification/uid-general.html