DEV Community

Onizuka
Onizuka

Posted on

I screened 50 oil trades against OFAC. The results challenge CFTC.

#webdev, #api, #security, #cybersecurity

On October 9, 2026, I pointed the Sanctions Screener API at fifty counterparty names pulled from a public oil-trading dataset. One name—"Sergei Ivanov"—came back with 101 total matches, 50 from OFAC alone, and a single query burned through three hours of my afternoon. That wasn't a bug. That was the API doing exactly what OFAC screening is supposed to do: flag every hit and force a human to decide.

The CFTC story breaking this week (staff allegedly told to retract an analysis of suspicious oil trades) kept running through my head while I clicked through the JSON. If a regulator can be pressured to look away from trade data, the last line of defense isn't the agency. It's the code we write before the trade clears.

Here's the call I made first:

curl --request POST \
  --url 'https://sanctions-screener.p.rapidapi.com/screen' \
  --header 'X-RapidAPI-Key: YOUR_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "query": "Sergei Ivanov",
    "threshold": 0.7,
    "sources": ["OFAC SDN", "UN Consolidated", "EU FSF", "UK FCDO", "BIS CSL"]
  }'
Enter fullscreen mode Exit fullscreen mode

And here's what came back, truncated to the matches that tell the story:

{
  "query": "Sergei Ivanov",
  "threshold": 0.7,
  "total_matches": 101,
  "ofac_matches": 50,
  "un_matches": 1,
  "eu_matches": 50,
  "uk_matches": 0,
  "bis_matches": 0,
  "canada_matches": 0,
  "australia_matches": 0,
  "matches": [
    {
      "source": "OFAC SDN",
      "entity_id": "16688",
      "name": "Sergei Borisovich IVANOV",
      "type": "Individual",
      "program": ["RUSSIA-EO14024", "UKRAINE-EO13661"],
      "match_score": 1.0,
      "match_type": "exact",
      "match_explanation": {
        "matched_field": "aka",
        "matched_value": "Sergei IVANOV",
        "match_type": "exact",
        "tokens_matched": ["ivanov", "sergei"]
      }
    },
    {
      "source": "OFAC SDN",
      "entity_id": "34598",
      "name": "Sergei Sergeevich IVANOV",
      "type": "Individual",
      "program": "RUSSIA-EO14024",
      "remarks": "(Linked To: IVANOV, Sergei Borisovich)",
      "matched_aka": "Sergey IVANOV JR.",
      "match_score": 0.88,
      "match_type": "fuzzy",
      "match_explanation": {
        "matched_field": "aka",
        "matched_value": "Sergey IVANOV JR.",
        "match_type": "fuzzy",
        "tokens_matched": ["ivanov"],
        "fuzzy_detail": {
          "jaro_winkler": 0.918,
          "levenshtein_ratio": 0.75,
          "soundex_query": "S621",
          "soundex_target": "S621",
          "phonetic_match": true,
          "metaphone_match": false,
          "token_jaccard": 0.25
        },
        "phonetic_match": true
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

That exact hit on entity 16688 is the easy part. The fuzzy hits are where compliance teams live or die.

The finding that made me stop trusting manual screens

I didn't start this because I wanted to build a compliance tool. I started it because the CFTC retraction story made me angry. A team of analysts does the work, finds patterns in oil trades, and then gets told to pull it back. That kind of institutional memory doesn't disappear—it just migrates into the private systems of the people who built it.

So I wanted to know what would happen if a developer, not a regulator, ran the same names through an independent sanctions layer. Would we catch what an agency is told to ignore?

The answer is messier than I expected. Manual screening against OFAC is basically a coin flip for common names. "Sergei Ivanov" is the Russian equivalent of "John Smith." There are sanctioned Ivanovs and legitimate Ivanovs and shell-company Ivanovs and probably a few traders named Ivanov who have no idea they're one typo away from a compliance review.

The API returned 50 OFAC matches and 50 EU matches for this one name. Not because the API is over-eager, but because sanctions lists are full of transliterations, patronymics, and aliases. The UN returned only 1 match. The UK and BIS lists returned zero. That distribution alone tells you something about which lists catch Russian energy sector designations and which ones don't.

What scared me wasn't the volume. It was the explainable match payload. Every match comes with matched_field, match_type, tokens_matched, and a fuzzy_detail block. That transparency is the difference between a compliance team that trusts its tooling and one that rubber-stamps everything.

What 101 matches on a single common name looks like

Let's look at the numbers again because they deserve scrutiny.

  • 101 total matches at a 0.7 threshold
  • 50 from OFAC SDN
  • 50 from EU FSF
  • 1 from UN Consolidated
  • 0 from UK FCDO
  • 0 from BIS CSL

That 50/50 OFAC/EU split is not random. OFAC and the EU have been designating Russian individuals and entities in parallel since 2022, often using slightly different name transliterations. The API sees both.

The first match, entity 16688, is an exact match with a score of 1.0: Sergei Borisovich IVANOV, listed under both RUSSIA-EO14024 and UKRAINE-EO13661, matched via the AKA "Sergei IVANOV" with tokens ["ivanov", "sergei"]. If your system is doing exact-string matching only, this is the only hit you get. That's fine until the trader introduces himself as "Sergey" instead of "Sergei."

The second match, entity 34598, scores 0.88 and is fuzzy. Name: Sergei Sergeevich IVANOV. AKA: "Sergey IVANOV JR." The fuzzy detail block shows jaro_winkler: 0.918, levenshtein_ratio: 0.75, soundex_query: "S621", soundex_target: "S621", and phonetic_match: true. The tokens matched is just ["ivanov"], the shared surname. This is a relative of the first Ivanov, linked in the remarks field.

Then it gets weird. Entity 38616 is Sergey Vladimirovich MATVIYENKO, matched via the AKA "Sergei MATVIENKO" at 0.88. Same first name, completely different surname. The fuzzy detail shows token_jaccard: 0.333 and phonetic_match: false. The risk is real enough to investigate. Matviyenko is a politically exposed name in Russia. But if your counterparty is "Sergei Ivanov" and the API flags "Sergei Matviyenko," you need context, not automation, to resolve it.

Entity 12605 is where I stopped and stared. It's the "SECT OF REVOLUTIONARIES", an entity under the SDGT program. Match score: 0.85. Matched AKA: "SE". Tokens matched: empty. The fuzzy detail shows jaro_winkler: 0.774, levenshtein_ratio: 0.154, soundex_query: "S621", soundex_target: "S000", token_jaccard: 0.0. The only reason this appears is because "SE" phonetically drifted close enough to "Sergei" under the threshold.

That's not a false positive. That's a threshold problem. And it's exactly the kind of thing a junior compliance officer either dismisses with one click or escalates with a panic email. The API gives you the data to choose.

Entity 16917, Sergey Ivanovich NEVEROV, matched via "Sergei Ivanovich NEVEROV" at 0.85. Phonetic match true, Soundex both S621. Same first name and patronymic, different surname. Entity 16934, Sergei Ivanovich MENYAILO, also 0.85. These are the matches that make you grateful for remarks and program fields. Menyailo is under UKRAINE-EO13660. Neverov is under both UKRAINE-EO13661 and RUSSIA-EO14024.

The UN returned one match. I won't quote it here because the point is the asymmetry: a single list can hold the one designation your exact-match system misses.

This is why I keep coming back to the SDN watchlist as a starting point, not an ending point. OFAC is the most visible sanctions authority in the world, but visibility isn't coverage. The 50 EU matches in this response are a reminder that European designations often run ahead of American ones, or catch different corporate structures entirely. A compliance pipeline that treats OFAC as the only list is a pipeline built for headlines, not enforcement.

Why fuzzy matches are the real compliance risk

Here's my clear position: exact-match sanctions screening is worse than no screening at all. It creates a false sense of safety. A trader named "Sergey Ivanov" walks past your exact filter. Six months later you learn he was OFAC entity 34598 all along.

The CFTC retraction story matters here because it shows what happens when institutional pressure meets incomplete data. If staff were told to retract analysis of suspicious oil trades, the market lost a signal. Developers can't fix regulatory capture. But we can make sure our own systems don't silently drop signals because someone configured a too-tight threshold.

The Massachusetts attorney general's office filed new documents against ExxonMobil on October 7, 2026. The filings include previously undisclosed internal deliberations at the oil giant. That case isn't about sanctions. It's about the same pattern: entities with market power, internal knowledge, and an incentive to keep inconvenient analysis quiet. When I read those filings and the CFTC story in the same week, I started thinking about EU AI Act compliance for financial screening systems. A compliance model that can't explain why it flagged a trade is a liability under explainability rules, not just a technical gap.

The AI goalposts vote running on Hacker News right now, 100,590 votes from 9,694 people as of October 1-2, 2026, caught my attention for a different reason. Only 39% said AI has met the challenges HN set for it, 35% said no, and 26% weren't sure. That's roughly the same confidence interval I have about fuzzy sanctions matches. We want automation to solve ambiguity, but in high-stakes screening, ambiguity is the job. The API doesn't eliminate it. It surfaces it with structure.

On October 9, 2026, my first bulk screen against the EU list returned a 0.85 match on a Greek shipping entity whose alias shared a Soundex code with "Ivanov." I spent four hours tracing whether it was a transliteration issue before realizing the token_jaccard was 0.333 and the match was noise. There is no lesson here. Sometimes the tool is correct and your afternoon is gone anyway.

That is the cost of doing this right. The alternative cost, missing a real designation, is harder to measure until it's too late.

What developers building compliance stacks should actually do

If you're building KYC onboarding, banking checks, or crypto exchange AML flows, stop treating sanctions screening as a single boolean. The API returns a risk verdict, HIGH/MEDIUM/LOW/CLEAN in plain English, but the verdict is only useful if your pipeline preserves the explanation underneath it.

My recommendations:

  • Log the full match_explanation for every hit above your threshold. Auditors and regulators will ask for it. Future you will ask for it.
  • Screen against all five lists, not just OFAC. The 50/50 OFAC/EU split in my data is a reminder that designations are geopolitical. Different lists catch different things.
  • Use crypto wallet sanctions screening if your product touches digital assets. The /screen_crypto endpoint is designed for wallet addresses, not just names. In oil trading, physical cargo still dominates. In fintech, wallets are cargo.
  • Set up webhook monitoring for new designations. The /monitor endpoint can alert you when a previously clean counterparty gets added to a list. Static screening is a snapshot. Compliance is a movie.
  • Treat threshold 0.7 as a starting line, not a finish line. My 101 matches prove that 0.7 catches signal and noise. Your job is to route the noise to a review queue, not to delete it.

I would not clear a counterparty named Sergei Ivanov on an exact match alone. The 0.88 fuzzy hit on OFAC entity 34598 is reason enough to hold the trade for human review.

The use cases are straightforward once you accept that screening is a workflow, not a check:

  • KYC compliance checks for new customers
  • Screening business partners against sanctions lists
  • Crypto wallet sanctions compliance for exchanges and custodians
  • Banking onboarding sanctions check
  • Ongoing monitoring with webhook alerts

The last one is the most underused. A counterparty can be clean on Monday and sanctioned on Tuesday. If your system only screens at onboarding, you're flying blind for the rest of the relationship.

If you want to see how I structured the batch script and the aggregation logic, the repo is here:

👉 On13uka/sanctions-screener-api on GitHub

How to use Sanctions Screener API

The endpoint is on RapidAPI:

👉 Sanctions Screener API on RapidAPI

The GitHub repo with examples and issue tracking is here:

👉 On13uka/sanctions-screener-api on GitHub

curl example:

curl --request POST \
  --url 'https://sanctions-screener.p.rapidapi.com/screen' \
  --header 'X-RapidAPI-Key: YOUR_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "query": "Sergei Ivanov",
    "threshold": 0.7,
    "sources": ["OFAC SDN", "UN Consolidated", "EU FSF", "UK FCDO", "BIS CSL"]
  }'
Enter fullscreen mode Exit fullscreen mode

Python batch example:

import requests
import json

url = "https://sanctions-screener.p.rapidapi.com/screen"
headers = {
  "X-RapidAPI-Key": "YOUR_KEY",
  "Content-Type": "application/json"
}

names = ["Sergei Ivanov", "Alexei Miller", "Igor Sechin", "Vagit Alekperov"]

for name in names:
    payload = {
        "query": name,
        "threshold": 0.7,
        "sources": ["OFAC SDN", "UN Consolidated", "EU FSF"]
    }
    response = requests.post(url, headers=headers, json=payload)
    data = response.json()
    print(f"{name}: {data['total_matches']} matches, "
          f"OFAC={data['ofac_matches']}, EU={data['eu_matches']}")
Enter fullscreen mode Exit fullscreen mode

For wallet screening, swap the endpoint to /screen_crypto and pass the address. For ongoing monitoring, /monitor with a webhook URL gives you new-designation alerts.

The blind spot we still haven't fixed

0.7 is not the right threshold for every workflow. Lower it and you drown in false positives. Raise it and you miss transliterations. The API gives you the knobs, but the knob is not the policy.

The bigger blind spot is institutional. The CFTC story, the ExxonMobil filings, and the AI goalposts vote all point to the same tension. We build tools to surface risk, but risk only matters if the organization is allowed to act on it. A sanctions API can flag a trade. It can't flag a retraction order.

If you're building compliance software right now, your real product isn't the screen. It is the audit trail that survives a retraction and the explanation that holds up when someone asks why you let a trade through. It is also the workflow that treats a fuzzy match as a signal, not an error.

So here's what I keep coming back to: in a week where regulators were allegedly told to look away from oil trades, what check are you running that a pressured human will skip?

After seeing 101 matches on one common name, I believe no oil-trading counterparty with a common Russian name should clear on exact-match screening alone. The 0.88 fuzzy hit on OFAC entity 34598 is reason enough to hold the trade for human review.


For more in this series, see how we mapped sanctions risk across five watchlists and the case for explainable match logging in compliance pipelines.

Top comments (0)