Skip to main content
openswissdata.
Menu

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.

  • 8 min read
  • 3 official sources
  • consulted on 29 September 2026
Contents
  1. The principle
  2. The files to match
  3. Update frequency
  4. Matching in the right order
  5. Example: SQLite on the command line
  6. Example: preparing names in Python
  7. Keeping a record of every check
  8. For a one-off question: kyc_check
  9. Limits
  10. Official sources

The principle

How an automated check unfolds
  1. Your counterpartiesName, UID and LEI when you have them.
  2. NormaliseUID reduced to “CHE” and nine digits, LEI in upper case.
  3. Match by identifierExact UID, then exact LEI: reliable matches.
  4. Match by nameCandidates to review, never verdicts.
  5. 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:

Useful contents of the archive

Scroll the table to see all columns

FileContentsGood to know
finma_registry.csv, .json, .sql, .parquetOne row per authorisation in the UID list published by FINMA.A company holding several authorisations appears on several rows.
finma_<category>.csvThe same rows, one file per non-empty category.Categories grouped by OpenSwissData (field entity_type).
finma_warnings.csv, .json, .sql, .parquetThe warning list, separate from the registry.Name, date, commercial register filter and detail link only.
finma.xlsxRegistry and warnings in two sheets.A third sheet recalls the reading rules.
changelog_90d.csv, .jsonChanges observed between the available versions.Observation dates, not FINMA decision dates.
quality.json, checksums.sha256, provenance.jsonField coverage, collection gaps, checksums and signature.Keep them with the result of the check.

Registry fields used for matching

Fields of finma_registry

Scroll the table to see all columns

FieldContents
uidThe UID as published by FINMA, formatted CHE-123.456.789; empty for some rows.
name, cityThe name and city published by FINMA.
licence_type, licence_type_fr, licence_type_de, licence_type_itThe authorisation type, labelled by FINMA in English, French, German and Italian.
entity_typeA grouping made by OpenSwissData; an unmapped label falls into other. For a precise filter, use licence_type.
lei, lei_registration_statusThe LEI, only when the UID matches exactly one LEI at GLEIF, and the status of that LEI registration.
address, cantonTaken from GLEIF when an LEI has been attached; they may stay empty, even with an LEI.
licence_date, statusEmpty: FINMA’s list does not provide them.
is_warning_listedEmpty: 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:

counterparties.csv
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:

Terminal: load and check
sqlite3 check.db < finma_registry.sql
sqlite3 check.db < finma_warnings.sql
sqlite3 -header -column check.db < checks.sql
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:

Terminal: jq
jq '.[] | select(.uid == "CHE-123.456.789") | {name, licence_type, lei}' finma_registry.json

Example: preparing names in Python

To compare names regardless of accents and legal forms, a simple normalisation is enough to produce sturdier candidates:

candidates.py
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.sha256 file and the result of its verification;
  • the date of the check, the matches accepted and the decisions taken on candidates.
In the folder of the unzipped archive
shasum -a 256 -c checksums.sha256    # macOS
sha256sum -c checksums.sha256        # Linux

The 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 Code, or any HTTP client
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.

View the product pageDownload the CSV sampleFree sample, no account needed: up to 20 registry rows per category.

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.

  1. FINMA · Authorised or registered institutions, persons and productshttps://www.finma.ch/en/finma-public/authorised-institutions-individuals-and-products/
  2. FINMA · Warning listhttps://www.finma.ch/en/finma-public/warnungen/warning-list/
  3. Federal Statistical Office (FSO) · The UID in generalhttps://www.bfs.admin.ch/bfs/en/home/registers/enterprise-register/enterprise-identification/uid-general.html
Back to contents ↑