Skip to content

Commit da13339

Browse files
committed
fix: Merge branch 'main' of github.com:boundlessfi/boundless into production
2 parents 9bf7142 + dd8b3ff commit da13339

22 files changed

Lines changed: 3405 additions & 92 deletions

File tree

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
---
2+
name: didit-aml-screening
3+
description: >
4+
Integrate Didit AML Screening standalone API to screen individuals or companies against
5+
global watchlists. Use when the user wants to perform AML checks, screen against sanctions
6+
lists, check PEP status, detect adverse media, implement KYC/AML compliance, screen against
7+
OFAC/UN/EU watchlists, calculate risk scores, or perform anti-money laundering screening
8+
using Didit. Supports 1300+ databases, fuzzy name matching, configurable scoring weights,
9+
and continuous monitoring.
10+
version: 1.1.0
11+
metadata:
12+
openclaw:
13+
requires:
14+
env:
15+
- DIDIT_API_KEY
16+
primaryEnv: DIDIT_API_KEY
17+
emoji: '🛡️'
18+
homepage: https://docs.didit.me
19+
---
20+
21+
# Didit AML Screening API
22+
23+
## Overview
24+
25+
Screens individuals or companies against 1,300+ global watchlists and high-risk databases in real-time. Uses a two-score system: **Match Score** (identity confidence) and **Risk Score** (threat level).
26+
27+
**Key constraints:**
28+
29+
- `full_name` is the only **required** field
30+
- Supports `entity_type`: `"person"` (default) or `"company"`
31+
- Document number acts as a "Golden Key" for definitive matching
32+
- All weight parameters must sum to 100
33+
34+
**Coverage:** OFAC SDN, UN, EU, HM Treasury, Interpol, FBI, 170+ national sanction lists, PEP Levels 1-4, 50,000+ adverse media sources, financial crime databases.
35+
36+
**Scoring system:**
37+
38+
1. **Match Score** (0-100): Is this the same person? → classifies hits as False Positive or Unreviewed
39+
2. **Risk Score** (0-100): How risky is this entity? → determines final AML status
40+
41+
**API Reference:** https://docs.didit.me/standalone-apis/aml-screening
42+
**Feature Guide:** https://docs.didit.me/core-technology/aml-screening/overview
43+
**Risk Scoring:** https://docs.didit.me/core-technology/aml-screening/aml-risk-score
44+
45+
---
46+
47+
## Authentication
48+
49+
All requests require `x-api-key` header. Get your key from [Didit Business Console](https://business.didit.me) → API & Webhooks, or via programmatic registration (see below).
50+
51+
## Getting Started (No Account Yet?)
52+
53+
If you don't have a Didit API key, create one in 2 API calls:
54+
55+
1. **Register:** `POST https://apx.didit.me/auth/v2/programmatic/register/` with `{"email": "you@gmail.com", "password": "MyStr0ng!Pass"}`
56+
2. **Check email** for a 6-character OTP code
57+
3. **Verify:** `POST https://apx.didit.me/auth/v2/programmatic/verify-email/` with `{"email": "you@gmail.com", "code": "A3K9F2"}` → response includes `api_key`
58+
59+
**To add credits:** `GET /v3/billing/balance/` to check, `POST /v3/billing/top-up/` with `{"amount_in_dollars": 50}` for a Stripe checkout link.
60+
61+
See the **didit-verification-management** skill for full platform management (workflows, sessions, users, billing).
62+
63+
---
64+
65+
## Endpoint
66+
67+
```
68+
POST https://verification.didit.me/v3/aml/
69+
```
70+
71+
### Headers
72+
73+
| Header | Value | Required |
74+
| -------------- | ------------------ | -------- |
75+
| `x-api-key` | Your API key | **Yes** |
76+
| `Content-Type` | `application/json` | **Yes** |
77+
78+
### Body (JSON)
79+
80+
| Parameter | Type | Required | Default | Description |
81+
| --------------------------- | ------- | -------- | ---------- | --------------------------------------------- |
82+
| `full_name` | string | **Yes** || Full name of person or entity |
83+
| `date_of_birth` | string | No || DOB in `YYYY-MM-DD` format |
84+
| `nationality` | string | No || ISO country code (alpha-2 or alpha-3) |
85+
| `document_number` | string | No || ID document number ("Golden Key") |
86+
| `entity_type` | string | No | `"person"` | `"person"` or `"company"` |
87+
| `aml_name_weight` | integer | No | `60` | Name weight in match score (0-100) |
88+
| `aml_dob_weight` | integer | No | `25` | DOB weight in match score (0-100) |
89+
| `aml_country_weight` | integer | No | `15` | Country weight in match score (0-100) |
90+
| `aml_match_score_threshold` | integer | No | `93` | Below = False Positive, at/above = Unreviewed |
91+
| `save_api_request` | boolean | No | `true` | Save in Business Console |
92+
| `vendor_data` | string | No || Your identifier for session tracking |
93+
94+
### Example
95+
96+
```python
97+
import requests
98+
99+
response = requests.post(
100+
"https://verification.didit.me/v3/aml/",
101+
headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"},
102+
json={
103+
"full_name": "John Smith",
104+
"date_of_birth": "1985-03-15",
105+
"nationality": "US",
106+
"document_number": "AB1234567",
107+
"entity_type": "person",
108+
},
109+
)
110+
print(response.json())
111+
```
112+
113+
```typescript
114+
const response = await fetch('https://verification.didit.me/v3/aml/', {
115+
method: 'POST',
116+
headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
117+
body: JSON.stringify({
118+
full_name: 'John Smith',
119+
date_of_birth: '1985-03-15',
120+
nationality: 'US',
121+
}),
122+
});
123+
```
124+
125+
### Response (200 OK)
126+
127+
```json
128+
{
129+
"request_id": "a1b2c3d4-...",
130+
"aml": {
131+
"status": "Approved",
132+
"total_hits": 2,
133+
"score": 45.5,
134+
"hits": [
135+
{
136+
"id": "hit-uuid",
137+
"caption": "John Smith",
138+
"match_score": 85,
139+
"risk_score": 45.5,
140+
"review_status": "False Positive",
141+
"datasets": ["PEP"],
142+
"properties": { "name": ["John Smith"], "country": ["US"] },
143+
"score_breakdown": {
144+
"name_score": 95,
145+
"name_weight": 60,
146+
"dob_score": 100,
147+
"dob_weight": 25,
148+
"country_score": 100,
149+
"country_weight": 15
150+
},
151+
"risk_view": {
152+
"categories": { "score": 55, "risk_level": "High" },
153+
"countries": { "score": 23, "risk_level": "Low" },
154+
"crimes": { "score": 0, "risk_level": "Low" }
155+
}
156+
}
157+
],
158+
"screened_data": {
159+
"full_name": "John Smith",
160+
"date_of_birth": "1985-03-15",
161+
"nationality": "US",
162+
"document_number": "AB1234567"
163+
},
164+
"warnings": []
165+
}
166+
}
167+
```
168+
169+
---
170+
171+
## Match Score System
172+
173+
**Formula:** `(Name × W1) + (DOB × W2) + (Country × W3)`
174+
175+
| Component | Default Weight | Algorithm |
176+
| --------- | -------------- | -------------------------------------------------------------------- |
177+
| Name | 60% | RapidFuzz WRatio — handles typos, word order, middle name variations |
178+
| DOB | 25% | Exact=100%, Year-only=100%, Same year diff date=50%, Mismatch=-100% |
179+
| Country | 15% | Exact=100%, Mismatch=-50%, Missing=0%. Auto-converts ISO codes |
180+
181+
**Document Number "Golden Key":**
182+
183+
| Scenario | Effect |
184+
| ----------------------------- | ------------------------- |
185+
| Same type, same value | Override score to **100** |
186+
| Different type or one missing | Keep base score (neutral) |
187+
| Same type, different value | **-50 point penalty** |
188+
189+
**Classification:** Score < threshold (default 93) → **False Positive**. Score >= threshold → **Unreviewed**.
190+
191+
> When data is missing, remaining weights are re-normalized. E.g., name-only → name weight becomes 100%.
192+
193+
---
194+
195+
## Risk Score System
196+
197+
**Formula:** `(Country × 0.30) + (Category × 0.50) + (Criminal × 0.20)`
198+
199+
**Final AML Status (from highest risk score among non-FP hits):**
200+
201+
| Highest Risk Score | Status |
202+
| ------------------- | ------------- |
203+
| Below 80 (default) | **Approved** |
204+
| Between 80-100 | **In Review** |
205+
| Above 100 | **Declined** |
206+
| All False Positives | **Approved** |
207+
208+
**Category scores (50% weight):**
209+
210+
| Category | Score |
211+
| ---------------------------- | ----- |
212+
| Sanctions / PEP Level 1 | 100 |
213+
| Warnings & Regulatory | 95 |
214+
| PEP Level 2 / Insolvency | 80 |
215+
| Adverse Media | 60 |
216+
| PEP Level 4 / Businessperson | 55 |
217+
218+
---
219+
220+
## Status Values & Handling
221+
222+
| Status | Meaning | Action |
223+
| --------------- | --------------------------------------------- | --------------------------------- |
224+
| `"Approved"` | No significant matches or all False Positives | Safe to proceed |
225+
| `"In Review"` | Matches found with moderate risk | Manual compliance review needed |
226+
| `"Rejected"` | High-risk matches confirmed | Block or escalate per your policy |
227+
| `"Not Started"` | Screening not yet performed | Check for missing data |
228+
229+
### Error Responses
230+
231+
| Code | Meaning | Action |
232+
| ----- | -------------------- | --------------------------------------- |
233+
| `400` | Invalid request body | Check `full_name` and parameter formats |
234+
| `401` | Invalid API key | Verify `x-api-key` header |
235+
| `403` | Insufficient credits | Check credits in Business Console |
236+
237+
---
238+
239+
## Warning Tags
240+
241+
| Tag | Description |
242+
| --------------------------------- | ---------------------------------------------------------------------- |
243+
| `POSSIBLE_MATCH_FOUND` | Potential watchlist matches requiring review |
244+
| `COULD_NOT_PERFORM_AML_SCREENING` | Missing KYC data. Provide full name, DOB, nationality, document number |
245+
246+
---
247+
248+
## Response Field Reference
249+
250+
### Hit Object
251+
252+
| Field | Type | Description |
253+
| -------------------------- | ------- | ------------------------------------------------------------------------- |
254+
| `match_score` | integer | 0-100 identity confidence score |
255+
| `risk_score` | float | 0-100 threat level score |
256+
| `review_status` | string | `"False Positive"`, `"Unreviewed"`, `"Confirmed Match"`, `"Inconclusive"` |
257+
| `datasets` | array | e.g. `["Sanctions"]`, `["PEP"]`, `["Adverse Media"]` |
258+
| `pep_matches` | array | PEP match details |
259+
| `sanction_matches` | array | Sanction match details |
260+
| `adverse_media_matches` | array | `{headline, summary, source_url, sentiment_score, adverse_keywords}` |
261+
| `linked_entities` | array | Related persons/entities |
262+
| `first_seen` / `last_seen` | string | ISO 8601 timestamps |
263+
264+
**Adverse media sentiment:** `-1` = slightly negative, `-2` = moderately, `-3` = highly negative.
265+
266+
---
267+
268+
## Continuous Monitoring
269+
270+
Available on **Pro plan**. Automatically included for all AML-screened sessions.
271+
272+
- **Daily automated re-screening** against updated watchlists
273+
- New hits → session status updated to "In Review" or "Declined" based on thresholds
274+
- **Real-time webhook notifications** on status changes
275+
- Zero additional integration — uses same thresholds from workflow config
276+
277+
---
278+
279+
## Common Workflows
280+
281+
### Basic AML Check
282+
283+
```
284+
1. POST /v3/aml/ → {"full_name": "John Smith", "nationality": "US"}
285+
2. If "Approved" → no significant watchlist matches
286+
If "In Review" → review hits[].datasets, hits[].risk_view for details
287+
If "Rejected" → block user, check hits for sanctions/PEP details
288+
```
289+
290+
### Comprehensive KYC + AML
291+
292+
```
293+
1. POST /v3/id-verification/ → extract name, DOB, nationality, document number
294+
2. POST /v3/aml/ → screen extracted data with all fields populated
295+
3. More data = higher match accuracy = fewer false positives
296+
```
297+
298+
---
299+
300+
## Utility Scripts
301+
302+
**screen_aml.py**: Screen against AML watchlists from the command line.
303+
304+
```bash
305+
# Requires: pip install requests
306+
export DIDIT_API_KEY="your_api_key"
307+
python scripts/screen_aml.py --name "John Smith"
308+
python scripts/screen_aml.py --name "John Smith" --dob 1985-03-15 --nationality US
309+
python scripts/screen_aml.py --name "Acme Corp" --entity-type company
310+
```
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
"""Didit AML Screening - Screen individuals or companies against global watchlists.
3+
4+
Usage:
5+
python scripts/screen_aml.py --name "John Smith"
6+
python scripts/screen_aml.py --name "John Smith" --dob 1985-03-15 --nationality US
7+
8+
Environment:
9+
DIDIT_API_KEY - Required. Your Didit API key.
10+
11+
Examples:
12+
python scripts/screen_aml.py --name "John Smith"
13+
python scripts/screen_aml.py --name "Acme Corp" --entity-type company
14+
python scripts/screen_aml.py --name "Maria Garcia" --dob 1990-01-01 --nationality ESP --doc-number X1234567
15+
"""
16+
import argparse
17+
import json
18+
import os
19+
import sys
20+
21+
import requests
22+
23+
ENDPOINT = "https://verification.didit.me/v3/aml/"
24+
25+
26+
def get_api_key() -> str:
27+
api_key = os.environ.get("DIDIT_API_KEY")
28+
if not api_key:
29+
print("Error: DIDIT_API_KEY environment variable is not set.", file=sys.stderr)
30+
sys.exit(1)
31+
return api_key
32+
33+
34+
def screen_aml(full_name: str, date_of_birth: str = None, nationality: str = None,
35+
document_number: str = None, entity_type: str = "person",
36+
threshold: int = None, vendor_data: str = None) -> dict:
37+
api_key = get_api_key()
38+
payload = {"full_name": full_name, "entity_type": entity_type}
39+
if date_of_birth:
40+
payload["date_of_birth"] = date_of_birth
41+
if nationality:
42+
payload["nationality"] = nationality
43+
if document_number:
44+
payload["document_number"] = document_number
45+
if threshold is not None:
46+
payload["aml_match_score_threshold"] = threshold
47+
if vendor_data:
48+
payload["vendor_data"] = vendor_data
49+
r = requests.post(ENDPOINT,
50+
headers={"x-api-key": api_key, "Content-Type": "application/json"},
51+
json=payload, timeout=60)
52+
if r.status_code not in (200, 201):
53+
print(f"Error {r.status_code}: {r.text}", file=sys.stderr)
54+
sys.exit(1)
55+
return r.json()
56+
57+
58+
def main():
59+
parser = argparse.ArgumentParser(description="Screen against AML watchlists via Didit")
60+
parser.add_argument("--name", required=True, help="Full name of person or entity")
61+
parser.add_argument("--dob", help="Date of birth (YYYY-MM-DD)")
62+
parser.add_argument("--nationality", help="Country code (alpha-2 or alpha-3)")
63+
parser.add_argument("--doc-number", help="Document number")
64+
parser.add_argument("--entity-type", default="person", choices=["person", "company"],
65+
help="Entity type (default: person)")
66+
parser.add_argument("--threshold", type=int, help="Match score threshold (0-100)")
67+
parser.add_argument("--vendor-data", help="Your identifier for tracking")
68+
args = parser.parse_args()
69+
70+
result = screen_aml(args.name, args.dob, args.nationality, args.doc_number,
71+
args.entity_type, args.threshold, args.vendor_data)
72+
print(json.dumps(result, indent=2))
73+
74+
aml = result.get("aml", {})
75+
status = aml.get("status", "Unknown")
76+
total_hits = aml.get("total_hits", 0)
77+
print(f"\n--- Status: {status} | Total hits: {total_hits} ---")
78+
for hit in aml.get("hits", [])[:5]:
79+
print(f" {hit.get('match_score', '?')}% — {hit.get('name', '?')} "
80+
f"[{', '.join(hit.get('categories', []))}]")
81+
82+
83+
if __name__ == "__main__":
84+
main()

0 commit comments

Comments
 (0)