The onboarding problem
Every AML platform has the same cold-start problem: a new bank signs up, and their transaction history, months or years of it, needs to be screened before the platform is useful. The traditional answer is "integrate with our API," which means:
- The bank's engineering team writes code
- They test against a sandbox
- They deploy to production
- Weeks pass before the first real screening
For a mid-sized Nigerian bank with a small tech team, that's a non-starter. They need value on day one.
Our answer: a CSV template
GET /api/v1/transactions/import-template
Downloads a 15-column CSV (external ID, type, channel, amount, currency, sender/receiver details, timestamp) with one example row. The bank's operations team fills it with their transaction history. No engineers needed.
POST /api/v1/transactions/import (multipart/form-data, field "file")
Uploads the filled template. The response comes back in under a second:
{
"batchId": "91c25a6e-...",
"queued": 4500,
"invalid": 12,
"errors": [
{ "line": 3, "externalId": "TX-003", "error": "unknown type \"WIRE\"" },
{ "line": 7, "error": "invalid BVN \"123\" (11 digits)" }
]
}
Every invalid row is reported with its line number and the specific validation failure. The ops team fixes the 12 bad rows and re-uploads, or proceeds with the 4,500 valid ones.
What happens next
The valid rows are screened through the full pipeline: CBN mandatory rules, global sanctions (OFAC, UN, UK, BIS, plus the domestic NiGSAC list), behavioral analysis, customer risk profiles. Not a simplified version. The exact same code path as a real-time transaction.
A background worker processes them in chunks (100 rows per batch per minute) so the live screening path is never blocked. Progress is visible in the dashboard:
Import batch: COMPLETED
4,500 rows screened · 12 invalid · 0 duplicates
Verdicts: 4,389 APPROVE / 82 REVIEW / 21 ESCALATE / 8 BLOCK
Cases opened: 12
Duplicates (same external ID as an existing transaction) are counted, never re-screened. Files are capped at 5,000 rows / 2 MB. Split larger imports into multiple files.
The batch record is the evidence
Every import creates a ScreeningBatch row with the full count breakdown: total, screened, duplicates, invalid, outcome distribution (approve/review/escalate/block), and cases created.
The batch record is the onboarding evidence: "we imported 6 months of history and screened all N transactions, producing M alerts and K cases."
Retroactive re-screening: the other direction
Bulk import screens new data. Retroactive re-screening (or "look-back") re-screens existing data against current sanctions lists and rules.
POST /api/v1/transactions/rescreen
{ "fromDate": "2026-06-01", "toDate": "2026-09-01" }
Why this matters:
- Sanctions list updates: OFAC adds a new entity. A customer who's been transacting for months might now match, but their historical screenings all said "clear" because the list didn't have them yet.
- Rule changes: you tighten a threshold from ₦5M to ₦3M. Transactions that passed under the old threshold might now trigger.
- Regulatory remediation: "we identified a gap and re-screened the affected population." The sentence every examiner wants to hear after a deficiency is found.
The re-screen runs the sanctions/PEP layer against stored transaction data. Original verdicts are never rewritten (immutable evidence). Escalations open SANCTIONS_HIT cases tagged retroactive. Deltas are stored in a separate RescreenResult table with the matched rule names and case links.
The design: why not just re-run the full pipeline?
For imports, we do. Each row goes through TransactionGuardService.screenTransaction(), the exact same entry point as POST /transactions/screen. This guarantees identical behavior.
For re-screening, we run only the sanctions/PEP layer (global lists + local watchlists) because:
- It's the layer that changes over time (lists update; rules are per-tenant and already versioned)
- Re-running behavioral analysis on historical data would produce misleading results (behavioral baselines are time-sensitive)
- The sanctions look-back is the specific regulatory requirement
Engineering details worth noting
- CSV parsing is pure and unit-tested: 10 specs cover quoted fields, escaped quotes, enum validation (types, channels, KYC tiers), BVN format, amount/timestamp validation, and in-file duplicate detection
- Row errors are capped at 200 per batch to bound memory
- The worker is a GuardedCron (every minute, up to 5 active batches). A failure marks the batch FAILED without affecting others
- Every batch creation is audited (
SCREENING_BATCH_CREATED) with the source filename and row counts
See a live trace on your own data
We'll run the tracer against a scenario from your institution in a 30-minute demo: blocked transaction, full fund trace, frozen destination, packaged case.