Statement
Send a customer's bank statements, in any layout, and get the AML and fraud risk of the whole history: the band, the signals that fired with the rows they cite, the counterparties screened, and the typologies they point to.
Type: statement · Endpoint: POST /api/v1/statements/analyze · File (1M tokens per call)
Request
curl https://api.mandatelabs.ai/api/v1/decide \
-H "X-API-Key: $WARD_API_KEY" -H "Content-Type: application/json" \
-d '{"type": "statement", "subject_ref": "cust_1042", "subject_consent": true, "input": {
"files": [{"file_base64": "...", "file_name": "2026-08.pdf"}, {"file_base64": "...", "file_name": "2026-09.pdf"}],
"profile": {"monthly_income": 3200, "occupation": "nurse", "customer_type": "individual"},
"focus": "aml"}}'
d = client.statement(["2026-08.pdf", "2026-09.pdf"], profile={"monthly_income": 3200, "occupation": "nurse"},
focus="aml", subject_ref="cust_1042", subject_consent=True)
const d = await client.statement(["2026-08.pdf", "2026-09.pdf"], { profile: { monthly_income: 3200, occupation: "nurse" },
focus: "aml", subject_ref: "cust_1042", subject_consent: true });
The SDK helpers read the files and base64-encode them for you.
Input
| Field | Description |
|---|---|
file_base64 + file_name, or files[] |
One statement, or several statements of the same account (up to 200 files, 10 MB each, 60 MB total) merged into one history |
profile |
Declared monthly_income, expected_monthly_inflow, occupation, customer_type: movements are read against it |
focus |
all (default), aml or fraud: what the score is for |
screen_counterparties |
Screen counterparty names (default true) |
column_map |
A confirmed column reading for a layout Ward could not read alone (Files) |
thresholds |
Thresholds for this analysis only |
subject_ref |
Your customer id |
Formats: CSV, TSV, XLSX, PDF with a text layer; English, Spanish and other headers; date order and decimal separator inferred per file. Card network exports (DE fields) and compliance alert exports are recognised too.
Main signals
| Code | Family | Weight | Fires when |
|---|---|---|---|
STM_STRUCTURING |
AML | 0.55 | 3+ cash or check deposits at 80–100% of the reporting threshold within 30 days |
STM_PASS_THROUGH |
AML | 0.50 | 2+ credits of 1,000+ with 80%+ paid out within 3 days |
STM_FAN_IN |
AML | 0.40 | 8+ distinct payers within 30 days |
STM_VOLUME_SPIKE |
AML | 0.30 | A month at 3x+ the median of the others |
STM_CASH_INTENSIVE |
AML | 0.30 | Cash is 50%+ of volume |
STM_GAMBLING |
AML | 0.20 | Gambling merchants or transfers |
STM_NEW_PAYEE_LARGE |
Fraud | 0.35 | First-time transfer of 3x+ the median debit and 1,000+ |
STM_PEP_COUNTERPARTY, STM_QMAP_MERCHANT, STM_ADVERSE_MEDIA |
Lists | — | A counterparty matched a list: lifts APPROVE to STEP_UP |
The full matrix (version in components.statement_matrix) is in the result's signals, each with its weight, explanation, the rows it cites and its typologies.
Result
| Field | Meaning |
|---|---|
risk.band, risk.score, risk.score_kind |
low < 0.30 ≤ medium < 0.60 ≤ high |
signals[] |
Code, family, weight, detail, cited rows, typologies |
flagged[] |
The flagged transactions, highest score first |
summary |
Period, transactions, money in and out, top counterparties |
checks |
Sanctions, PEP, adverse media, WMID, enforcement on counterparties: status, names checked, matches |
coverage |
How much of the history Ward could read |
notes |
Every assumption (date order, currency, guessed columns) |
analysis_id |
Stored result: GET /api/v1/statements/{id} |
Decision: low → APPROVE, medium → STEP_UP, high → DECLINE.
Unreadable files answer 422 with a stable code: unsupported_format, scanned_pdf, encrypted, no_header, not_transactions, needs_mapping, … See Files in any format.