Warddocs
Capabilities

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.