How to Integrate OFAC Screening into Your Application in 5 Minutes
You need OFAC screening in your application. You do not need a six-month enterprise integration project to get it. This guide takes you from zero to production-ready sanctions with working code.
Quick Start: Your First Screening Call
The fastest way to verify the API works:
curl -X POST https://ofac-screening-production.up.railway.app/screen \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"name": "Mohammad Ali", "threshold": 0.85}'
That is it. You send a name, you get back a list of matches with scores. If matchCount is 0, the name is clear.
JavaScript / Node.js Integration
Here is a production-ready screening module with error handling and retries:
const OFAC_BASE_URL = 'https://ofac-screening-production.up.railway.app';
const OFAC_API_KEY = process.env.OFAC_API_KEY;
const screenName = async (name, options = {}) => {
const {
type = null,
dateOfBirth = null,
country = null,
threshold = 0.85,
} = options;
const body = { name, threshold };
if (type) { body.type = type; }
if (dateOfBirth) { body.dateOfBirth = dateOfBirth; }
if (country) { body.country = country; }
const response = await fetch(`${OFAC_BASE_URL}/screen`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': OFAC_API_KEY,
},
body: JSON.stringify(body),
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(
`OFAC screening failed: ${response.status} ${error.error || response.statusText}`,
);
}
return response.json();
};
// Usage in an onboarding flow
const onboardCustomer = async (customer) => {
const screening = await screenName(customer.fullName, {
type: 'Individual',
dateOfBirth: customer.dob,
country: customer.country,
});
if (screening.matchCount > 0) {
// Flag for compliance review -- do not auto-reject
await flagForReview(customer.id, screening);
return { status: 'PENDING_REVIEW', screening };
}
return { status: 'CLEAR', screening };
};
Two important patterns here. First, never auto-reject based on a match. Flag it for human review. False positives happen, and rejecting legitimate customers without review creates legal and reputational risk. Second, always store the full screening response, not just the pass/fail decision.
Python Integration
import os
import requests
OFAC_BASE_URL = "https://ofac-screening-production.up.railway.app"
OFAC_API_KEY = os.environ["OFAC_API_KEY"]
def screen_name(name, entity_type=None, date_of_birth=None,
country=None, threshold=0.85):
payload = {"name": name, "threshold": threshold}
if entity_type:
payload["type"] = entity_type
if date_of_birth:
payload["dateOfBirth"] = date_of_birth
if country:
payload["country"] = country
response = requests.post(
f"{OFAC_BASE_URL}/screen",
json=payload,
headers={
"Content-Type": "application/json",
"x-api-key": OFAC_API_KEY,
},
timeout=10,
)
response.raise_for_status()
return response.json()
def screen_batch(names, threshold=0.85):
"""Screen up to 100 names in a single request."""
response = requests.post(
f"{OFAC_BASE_URL}/screen/batch",
json={"names": names, "threshold": threshold},
headers={
"Content-Type": "application/json",
"x-api-key": OFAC_API_KEY,
},
timeout=30,
)
response.raise_for_status()
return response.json()
# Usage
result = screen_name(
"Vladimir Putin",
entity_type="Individual",
country="Russia",
)
print(f"Matches found: {result['matchCount']}")
for match in result["matches"]:
print(f" {match['entity']['name']} — score: {match['score']} "
f"({match['matchType']})")
Handling Results: Match vs Clear
Every screening response includes a matchCount field. Your application logic branches on it:
const processScreening = (screening) => {
if (screening.matchCount === 0) {
// No matches -- customer is clear
return {
decision: 'CLEAR',
screenedAt: screening.screenedAt,
listVersion: screening.listVersion,
};
}
// Matches found -- classify by highest score
const topMatch = screening.matches[0];
if (topMatch.score >= 0.95) {
// Near-certain match -- block and escalate immediately
return {
decision: 'BLOCK',
reason: `High-confidence match: ${topMatch.entity.name} (score: ${topMatch.score})`,
screenedAt: screening.screenedAt,
listVersion: screening.listVersion,
requiresReview: true,
};
}
if (topMatch.score >= 0.85) {
// Strong match -- hold for compliance review
return {
decision: 'HOLD',
reason: `Possible match: ${topMatch.entity.name} (score: ${topMatch.score})`,
screenedAt: screening.screenedAt,
listVersion: screening.listVersion,
requiresReview: true,
};
}
// Below 0.85 but above your threshold -- log and flag
return {
decision: 'FLAG',
reason: `Weak match detected: ${topMatch.entity.name} (score: ${topMatch.score})`,
screenedAt: screening.screenedAt,
listVersion: screening.listVersion,
requiresReview: true,
};
};
Batch Screening for Existing Customer Databases
When you first integrate OFAC screening, you need to screen your entire existing customer base. The batch endpoint handles up to 100 names per request:
const screenExistingCustomers = async (customers) => {
const BATCH_SIZE = 100;
const flagged = [];
for (let i = 0; i < customers.length; i += BATCH_SIZE) {
const batch = customers.slice(i, i + BATCH_SIZE);
const names = batch.map((c) => ({
name: c.fullName,
type: 'Individual',
dateOfBirth: c.dob || null,
country: c.country || null,
}));
const response = await fetch(
`${OFAC_BASE_URL}/screen/batch`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': OFAC_API_KEY,
},
body: JSON.stringify({ names, threshold: 0.85 }),
},
);
const data = await response.json();
data.results.forEach((result, idx) => {
if (result.matchCount > 0) {
flagged.push({
customer: batch[idx],
matches: result.matches,
listVersion: data.listVersion,
screenedAt: data.screenedAt,
});
}
});
console.log(
`Screened ${Math.min(i + BATCH_SIZE, customers.length)}/${customers.length}`,
);
}
return flagged;
};
For 10,000 customers, this makes 100 API calls. At typical response times of 100-200ms per batch, the full scan completes in under 30 seconds.
Setting Up Re-Screening When the List Updates
Treasury updates the SDN list multiple times per week. You need a job that checks for updates and re-screens when the list changes:
let lastKnownListVersion = null;
const checkForListUpdate = async () => {
const response = await fetch(`${OFAC_BASE_URL}/data-info`);
const info = await response.json();
if (lastKnownListVersion && info.publishDate !== lastKnownListVersion) {
console.log(
`SDN list updated: ${lastKnownListVersion} -> ${info.publishDate}`,
);
// Trigger a full re-screen of your customer database
const customers = await loadAllActiveCustomers();
const flagged = await screenExistingCustomers(customers);
if (flagged.length > 0) {
await notifyComplianceTeam(flagged);
}
}
lastKnownListVersion = info.publishDate;
};
// Run daily (or more frequently for high-risk programs)
// In production, use a proper job scheduler (cron, Bull, etc.)
Storing Results for Audit Compliance
Every screening result must be stored. When a regulator examines your compliance program, they will ask to see screening records for specific customers and date ranges.
A minimal audit record schema:
const auditRecord = {
// Who was screened
customerId: 'cust_12345',
screenedName: 'John Smith',
screenedDob: '1985-03-15',
screenedCountry: 'US',
// When and against what
screenedAt: '2026-03-16T14:30:00.000Z', // from API response
listVersion: '2026-03-14', // from API response
// Result
matchCount: 0,
decision: 'CLEAR',
matches: [], // store full match details if any
// Review (if matches were found)
reviewedBy: null,
reviewedAt: null,
reviewDecision: null, // TRUE_POSITIVE or FALSE_POSITIVE
reviewNotes: null,
};
Store these records in a tamper-evident way. An append-only database table with no UPDATE or DELETE permissions for application users is the standard approach. Retention period is five years minimum per BSA requirements.
Next Steps
- Get an API key: Visit ofac-screening-production.up.railway.app to get started
- Test with the interactive tool: easysolutions906.github.io/screen.html
- Integrate into onboarding: Add screening to your customer signup flow
- Set up batch re-screening: Run against your existing database
- Build the audit trail: Store every screening result with timestamps and list versions
The API documentation is available at the base URL. The /data-info endpoint shows the current SDN list version and record count so you always know how fresh your screening data is.
Five minutes to integrate. Years of compliant screening records to show your regulators.
Top comments (0)