---
title: "Matching API quickstart tutorial"
summary: "Make and interpret your first screening call using the `/match` endpoint."
url: "https://www.opensanctions.org/docs/api/quickstart/"
date_updated: "2026-10-01"
---

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](/advancedsearch/).

{% alert variant="info" heading="Before you start: get an API key" %}
Requests are authenticated with an API key. [Sign up](/account/) to create one — if you sign up with a **business email address**, you start with a free trial.
We also issue [free API keys for public-interest work](/docs/commercial/exemption/): 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](/docs/api/authentication/) for details.
{% /alert %}

## Your first request

```python
# /// 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.

{% alert variant="info" heading="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`](https://yenteclient.followthemoney.tech/) 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](https://yenteclient.followthemoney.tech/mcp/) that exposes screening to LLM agents.
{% /alert %}

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:

{% linkcard title="Read more" %}
[The `/match` request](/docs/api/request/)
{% /linkcard %}

## Reading the response

This response has been condensed for readability:

```json
{
  "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](/docs/identifiers/) (e.g. `NK-…`). Use this to [fetch full details](/docs/api/entities/) via `/entities/<id>`.
- **`properties`** — a full description of the entity, aggregated from all source `datasets`.
- **`datasets`** — the [data sources](/datasets/sources/) this entity appears in.
- **`score`** — the confidence that this is a true match according to the [algorithm](/matcher/) used. The `explanations` explain how the algorithm arrived at the match score.

{% linkcard title="Read more" %}
[The `/match` response](/docs/api/response/)
{% /linkcard %}

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

```json
  "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](/docs/topics/) 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)](https://followthemoney.tech/docs/faq/#why-are-all-properties-multi-valued) 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 {% #entities %}

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

{% alert variant="info" %}
See an [example of an entity](/entities/NK-aU5ybkbRFJucf8YMwsJvDw) on the OpenSanctions website. It includes all the `properties` from all the source `datasets`, as well as all related entities under [Relationships](/entities/NK-aU5ybkbRFJucf8YMwsJvDw/#links).
{% /alert %}

A `/match` result and an `/entities` response contain the same core fields, but `/entities` includes [related entities](/docs/nested-entities/) inline by default, like [`Sanction`](/reference/#schema.Sanction), [`Ownership`](/reference/#schema.Ownership), [`Directorship`](/reference/#schema.Directorship), and [`Family`](/reference/#schema.Family).

The below follows from your first request above:

```python
...
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.
