Matching API quickstart tutorial

Make and interpret your first screening call using the /match endpoint.

The /match endpoint uses query-by-example: you describe an entity in as much detail as you can, and the API returns ranked candidates from the database. You can experiment with the /match endpoint via the advanced screening search.

Before you start: get an API key

Requests are authenticated with an API key. Sign up to create one — if you sign up with a business email address, we generate a free trial key for you.

We also issue free API keys for public-interest work: journalism, civil-society advocacy, and academic research.

Keep your key in an environment variable; the examples below read it from OPENSANCTIONS_API_KEY. See Authentication for details.

Your first request 

# /// script
# dependencies = ["requests"]
# ///
# Run with: uv run quickstart.py

import os
import requests

BASE_URL = "https://api.opensanctions.org"
API_ENDPOINT = "match"
DATASET = "default"
API_KEY = os.environ.get("OPENSANCTIONS_API_KEY")

session = requests.Session()
session.headers["Authorization"] = f"ApiKey {API_KEY}"

query = {
    "schema": "Person",
    "properties": {
        "firstName": ["Aleksandr"],
        "lastName": ["Zacharov"],
        "birthDate": ["1965"],
    },
}

response = session.post(
    f"{BASE_URL}/{API_ENDPOINT}/{DATASET}",
    json={"queries": {"q": query}},
)
response.raise_for_status()

results = response.json()["responses"]["q"]["results"]
for r in results:
    print(f"\n{r['caption']}, ID: {r['id']}, Match score: {r['score']}")
    print('Available information:\n ', ', '.join(list(r.keys())))
    if 'properties' in r.keys():
        print('Available properties:\n ', ', '.join(list(r['properties'].keys())))
        if 'topics' in r['properties']:
            print('Risk topics:\n ', ', '.join(list(r['properties']['topics'])))

This is a basic request for a person. Run it and you should see output for at least one match.

Building in Python? There's an SDK

The examples in these docs use plain HTTP requests so you can see exactly how the API works — and translate the calls into any programming language. For Python integrations, our yente-client package (pip install yente-client) wraps the same endpoints in a typed SDK with entity classes and structured error handling. It also ships yente-cli, a command-line tool for running match and search queries from the shell, and an MCP server that exposes screening to LLM agents.

There's much more you can do — learn about scoping a query, how filtering works, which properties to supply for which schema types, and tuning the matching algorithm, in the deep dive on the /match request:

Reading the response 

This response has been condensed for readability:

{
  "responses": {
    "q": {
      "status": 200,
      "results": [
        {
          "id": "NK-aU5ybkbRFJucf8YMwsJvDw",
          "caption": "Alexander Vyacheslavovich ZAKHAROV",
          "schema": "Person",
          "properties": {...}, # expanded below
          "datasets": ["ua_nsdc_sanctions", "fr_tresor_gels_avoir", "us_trade_csl", "us_ofac_sdn", "eu_fsf", ...],
          "referents": ["usgsa-s4mrvqj3g", "fr-ga-7952", "gb-fcdo-rus2051", "eu-fsf-eu-12789-10", ...],
          "target": true,
          "first_seen": "2023-07-19T18:02:43",
          "last_seen": "2026-03-25T12:53:09",
          "last_change": "2026-02-16T16:44:18",
          "score": 0.9199999999999999,
          "explanations": {
            "name_match": {
              "detail": "['aleksandr'≈'aleksandr' symbolMatch [NAME:Q17501806]: 1.00, weight 1.00] ['zacharov'≈'zacharov' symbolMatch [NAME:Q4188781]: 1.00, weight 1.30] ['vjaceslavovic' extraResultPart: 0.00, weight 0.20]",
              "score": 0.9199999999999999,
              "query": "Aleksandr Zacharov",
              "candidate": "Zacharov Aleksandr Vjačeslavovič"
            },
            "vessel_imo_mmsi_match": {
              "detail": "Not a vessel",
              "score": 0.0,
              "query": None,
              "candidate": None
            }
          },
          "match": true
        },
        {...}
      ],
      "total": { "value": 2, "relation": "eq" },
      "query": {...}
    }
  },
  "limit": 5
}

The top-level response for each query (q in our example) includes a results list. The limit, set to 5 by default, is the maximum number of results returned per query. Each result describes a potentially matching entity. Some key fields on these:

  • id — the entity identifier (e.g. NK-…). Use this to fetch full details via /entities/<id>.
  • properties — a full description of the entity, aggregated from all source datasets.
  • datasets — the data sources this entity appears in.
  • score — the confidence that this is a true match according to the algorithm used. The explanations explain how the algorithm arrived at the match score.

The (trimmed) properties for the first result look like:

  "properties": {
    "lastName": ["ZAKHAROV", "Zakharov", "Zacharov", "Захаров"],
    "position": ["Owner of LLC CST", "Propriétaire de LLC CST"],
    "middleName": ["Vyacheslavovich", "Вячеславович", "Vjačeslavovič"],
    "country": ["ru"],
    "birthDate": ["1965-09-21"],
    "firstName": ["Александр", "Aleksandr Vyacheslavovich", "ALEXANDER VYACHESLAVOVICH", "Aleksandr", "Alexander"],
    "topics": ["corp.disqual", "sanction", "debarment"],
    "programId": ["EU-UKR", "SECO-UKRAINE", "GB-RUS", "UA-SA1644", "US-RUSHAR", "NZ-RSA2022"]
}

topics is a key property listing the risk tags on the entity, such as sanction, role.pep, or debarment. This is the fastest way to understand what kind of risk a match carries.

Properties are always multi-valued, since they're aggregated from all source datasets. From the FollowTheMoney (FtM) docs:

First, multi-valued properties are a common thing in the FtM domain: companies have multiple official forms of their name, people can have multiple citizenships, names, and, more often than you'd think, ambiguous birth dates.

So, if a Person entity has two birthdays, it simply means that different datasets have differing data on the real-life person — not that you should plan to attend two parties.

Taking it further: the /entities endpoint  

If you want to retrieve additional details for a matched entity, use the /entities/<id> endpoint, where <id> is the id attribute on one of the results.

See an example of an entity on the OpenSanctions website. It includes all the properties from all the source datasets, as well as all related entities under Relationships.

A /match result and an /entities response contain the same core fields, but /entities includes related entities inline by default, like Sanction, Ownership, Directorship, and Family.

The below follows from your first request above:

...
nested_entity_response = session.get(
    f"{BASE_URL}/entities/{results[0]['id']}"
)
nested_entity_json = nested_entity_response.json()
sanctions = nested_entity_json["properties"]["sanctions"]
for sanction in sanctions:
  print(f"{nested_entity_json['caption']} is sanctioned by {sanction['properties']['authority']}")

Paste it into the same terminal as before to see the issuing authority of each sanction related to the first match.