Education

Real-Time AML Sanctions Screening in Python: A Complete Integration Guide

Learn how to integrate the SanctionShield AI API to screen customers, entities, and transactions against OFAC, UN, and EU sanctions lists in real time using Python.

Real-Time AML Sanctions Screening in Python: A Complete Integration Guide

Why Real-Time Sanctions Screening Is Non-Negotiable in 2026

The cost of a single sanctions violation has never been higher. OFAC's average civil monetary penalty reached $4.2 million in 2025, and enforcement actions have accelerated in 2026 with over 1,300 new designations in the first quarter alone. For financial institutions, fintechs, and any business handling cross-border payments, sanctions screening is a legal requirement — not a nice-to-have.

The challenge is speed. Batch screening runs nightly or weekly and misses the 28% of violations that occur between screening cycles when new designations are published. Real-time screening — triggered at customer onboarding, payment initiation, and periodic re-screening — closes this gap.

This guide shows you how to build a production-ready sanctions screening integration with the SanctionShield AI API in Python, covering:

  • Customer onboarding screening
  • Payment transaction screening
  • Batch entity screening
  • Handling fuzzy name matches
  • Webhook integration for async workflows

Understanding Sanctions Lists

Before writing code, it helps to understand what you're screening against:

ListJurisdictionCoverage
OFAC SDNUnited States~12,000 individuals and entities
OFAC Non-SDNUnited StatesConsolidated, CAPTA, NS-MBS lists
UN ConsolidatedInternational~800 entries across 12 committees
EU ConsolidatedEuropean Union~2,000 entries
UK HMTUnited KingdomPost-Brexit independent list
Canada OSFICanadaFACFOA and SEMA lists

SanctionShield AI screens against all major lists simultaneously in a single API call — no need to maintain and update separate list files.


Setup

pip install requests python-dotenv pydantic
# .env
SANCTIONSHIELD_API_KEY=YOUR_API_KEY
import os
import requests
from dotenv import load_dotenv
 
load_dotenv()
 
API_KEY = os.getenv("SANCTIONSHIELD_API_KEY")
BASE_URL = "https://apivult.com/sanctionshield/v1"
 
HEADERS = {
    "X-RapidAPI-Key": API_KEY,
    "Content-Type": "application/json",
}

Step 1: Screen an Individual at Onboarding

from pydantic import BaseModel
from typing import Optional
 
class IndividualScreenRequest(BaseModel):
    full_name: str
    date_of_birth: Optional[str] = None  # ISO 8601: YYYY-MM-DD
    nationality: Optional[str] = None     # ISO 3166-1 alpha-2
    id_number: Optional[str] = None
    fuzzy_threshold: float = 0.85         # 0.0–1.0, higher = stricter matching
 
def screen_individual(person: IndividualScreenRequest) -> dict:
    """Screen an individual against all sanctions lists."""
    response = requests.post(
        f"{BASE_URL}/screen/individual",
        headers=HEADERS,
        json=person.dict(exclude_none=True),
        timeout=10,
    )
    response.raise_for_status()
    return response.json()
 
# Example: Screen a new customer at signup
result = screen_individual(IndividualScreenRequest(
    full_name="John Smith",
    date_of_birth="1985-04-12",
    nationality="US",
    fuzzy_threshold=0.88,
))
 
print(result)

Sample response:

{
  "screening_id": "scr_7x9k2m3p",
  "status": "clear",
  "match_count": 0,
  "lists_checked": ["OFAC_SDN", "OFAC_NON_SDN", "UN_CONSOLIDATED", "EU_CONSOLIDATED", "UK_HMT"],
  "screened_at": "2026-03-31T09:14:22Z",
  "processing_ms": 87
}

Step 2: Screen an Entity (Business)

class EntityScreenRequest(BaseModel):
    entity_name: str
    country: Optional[str] = None
    registration_number: Optional[str] = None
    aliases: Optional[list[str]] = None
    fuzzy_threshold: float = 0.85
 
def screen_entity(entity: EntityScreenRequest) -> dict:
    """Screen a business entity against sanctions lists."""
    response = requests.post(
        f"{BASE_URL}/screen/entity",
        headers=HEADERS,
        json=entity.dict(exclude_none=True),
        timeout=10,
    )
    response.raise_for_status()
    return response.json()
 
# Example: Screen a counterparty before onboarding
result = screen_entity(EntityScreenRequest(
    entity_name="Acme Trading LLC",
    country="AE",
    aliases=["Acme Trade", "ACME LLC"],
    fuzzy_threshold=0.90,  # stricter for entities
))

Sample response with a fuzzy hit:

{
  "screening_id": "scr_3p8n1q4r",
  "status": "review_required",
  "match_count": 1,
  "matches": [
    {
      "list": "OFAC_SDN",
      "matched_name": "ACME TRADING COMPANY",
      "similarity_score": 0.91,
      "entry_id": "SDN-17842",
      "designation_date": "2024-11-03",
      "program": "IRAN",
      "additional_info": "Vessel operator linked to petroleum shipments"
    }
  ],
  "recommendation": "manual_review",
  "screened_at": "2026-03-31T09:15:44Z"
}

Step 3: Handle Match Statuses

from enum import Enum
 
class ScreeningStatus(str, Enum):
    CLEAR = "clear"
    REVIEW_REQUIRED = "review_required"
    CONFIRMED_MATCH = "confirmed_match"
 
def process_screening_result(result: dict, entity_id: str) -> str:
    """
    Apply business rules based on screening result.
    Returns the action taken: 'approved', 'flagged', 'blocked'
    """
    status = result.get("status")
 
    if status == ScreeningStatus.CLEAR:
        log_screening(entity_id, result, action="approved")
        return "approved"
 
    elif status == ScreeningStatus.REVIEW_REQUIRED:
        # Queue for compliance officer review
        create_review_task(entity_id, result)
        log_screening(entity_id, result, action="flagged")
        return "flagged"
 
    elif status == ScreeningStatus.CONFIRMED_MATCH:
        # Auto-block — no human override possible for confirmed hits
        block_entity(entity_id, result)
        notify_compliance_team(entity_id, result)
        log_screening(entity_id, result, action="blocked")
        return "blocked"
 
    else:
        raise ValueError(f"Unknown screening status: {status}")
 
def log_screening(entity_id: str, result: dict, action: str):
    """Persist screening record for audit trail."""
    record = {
        "entity_id": entity_id,
        "screening_id": result["screening_id"],
        "action": action,
        "screened_at": result["screened_at"],
        "lists_checked": result["lists_checked"],
        "match_count": result["match_count"],
    }
    # Save to your database / audit log system
    print(f"[AUDIT] {record}")

Step 4: Screen a Payment Transaction

def screen_payment(payment: dict) -> dict:
    """
    Screen all parties in a payment transaction simultaneously.
    payment: { sender, recipient, amount, currency, reference }
    """
    # Screen both sender and recipient in parallel
    import concurrent.futures
 
    parties = [
        {"role": "sender", "data": payment["sender"]},
        {"role": "recipient", "data": payment["recipient"]},
    ]
 
    results = {}
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
        futures = {
            executor.submit(
                screen_individual,
                IndividualScreenRequest(**party["data"])
            ): party["role"]
            for party in parties
        }
        for future in concurrent.futures.as_completed(futures):
            role = futures[future]
            results[role] = future.result()
 
    # Payment is clear only if ALL parties are clear
    all_clear = all(r["status"] == "clear" for r in results.values())
 
    return {
        "payment_id": payment.get("id"),
        "overall_status": "clear" if all_clear else "blocked",
        "party_results": results,
    }
 
# Example usage
payment = {
    "id": "txn_abc123",
    "sender": {"full_name": "Alice Johnson", "nationality": "GB"},
    "recipient": {"full_name": "Bob Martinez", "nationality": "MX"},
    "amount": 15000,
    "currency": "USD",
}
 
result = screen_payment(payment)
print(result)

Step 5: Batch Screening for Periodic Re-Screening

Regulations require periodic re-screening of existing customers. With SanctionShield AI, you can batch-screen your entire customer base:

def batch_screen_customers(customer_list: list[dict]) -> list[dict]:
    """
    Batch screen a list of customers.
    Handles rate limiting with exponential backoff.
    """
    import time
    results = []
 
    for i, customer in enumerate(customer_list):
        try:
            result = screen_individual(IndividualScreenRequest(**customer))
            results.append({
                "customer_id": customer.get("id"),
                **result
            })
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 429:  # rate limit
                retry_after = int(e.response.headers.get("Retry-After", 1))
                time.sleep(retry_after)
                # retry once
                result = screen_individual(IndividualScreenRequest(**customer))
                results.append({"customer_id": customer.get("id"), **result})
            else:
                raise
 
        # Progress logging
        if (i + 1) % 100 == 0:
            print(f"Screened {i + 1}/{len(customer_list)} customers")
 
    return results
 
# Generate compliance report
def generate_screening_report(results: list[dict]) -> dict:
    total = len(results)
    clear = sum(1 for r in results if r["status"] == "clear")
    review = sum(1 for r in results if r["status"] == "review_required")
    blocked = sum(1 for r in results if r["status"] == "confirmed_match")
 
    return {
        "total_screened": total,
        "clear": clear,
        "review_required": review,
        "confirmed_matches": blocked,
        "clear_rate": f"{(clear / total * 100):.1f}%",
        "generated_at": datetime.utcnow().isoformat() + "Z",
    }

Compliance Best Practices

1. Always Persist Screening Records

Every screening result must be stored for audit purposes. OFAC expects firms to maintain screening records for at least 5 years.

2. Document Your Fuzzy Threshold

Set your threshold in configuration, not in code, and document why it was chosen. A threshold of 0.85 is standard for individual screening; 0.90 is recommended for entity screening.

3. Re-Screen on List Updates

Subscribe to OFAC's SDN list update notifications (published at regulations.gov) and trigger a re-screen of high-risk customers whenever new designations are added.

4. Implement a Manual Review Workflow

review_required matches should never be auto-approved or auto-blocked. Build a compliance officer queue where trained staff review fuzzy matches before making a decision.


Integration Checklist

Before going live:

  • Screening records persisted to audit log
  • Manual review workflow for fuzzy matches
  • Re-screening triggered on list updates
  • Rate limiting and retry logic implemented
  • Compliance officer alerts for confirmed matches
  • Test with known SDN entries (OFAC publishes test cases)

Next Steps

Real-time sanctions screening is one layer of a broader AML compliance stack. Pair SanctionShield AI with FinAudit AI for invoice verification and GlobalShield for PII detection to build a comprehensive compliance pipeline.

Start with the SanctionShield AI API on APIVult and run your first screening in under 2 minutes.