Transaction monitoring
The high-volume real-time endpoint. Evaluates a single transaction against the configured rules and returns a decision in under 100 ms p95.
/api/transactions/evaluateWhen to call it
Inline in your transaction-processing path, after KYC/anti-fraud and before commit. The decision tells you whether to:
pass— proceedreview— proceed but flag for analyst review (an alert is created)escalate— block and route to a case immediately
You decide how to map decisions to gates in your transaction state machine. The endpoint returns; persistence + alert creation happen synchronously.
Request
customerIdstringRequireddirectionenumRequiredamountIdrinteger/stringRequiredcurrencystringchannelstringRequiredcounterpartyNamestringcounterpartyNikstringcounterpartyAccountstringisCrossBorderbooleancitystringcountrystringexternalIdstringpersistbooleanResponse
{
"ok": true,
"data": {
"decision": "pass | review | escalate",
"riskScore": 65,
"alertId": "alt_01HXY...",
"hits": [
{
"ruleId": "rul_01HXY...",
"ruleName": "High Transaction Amount",
"severity": "low | medium | high | critical",
"scoreContribution": 35
}
]
}
}alertId is present only when decision !== "pass".
Decision math
riskScore = sum of all hit.scoreContribution
review = riskScore >= decisionPolicy.txReviewScore (default 40)
escalate = riskScore >= decisionPolicy.txEscalateScore (default 70)
sar = riskScore >= decisionPolicy.txSarScore (default 90) ← automatic SAR draftAdjust thresholds with PATCH /api/decision-policy.
At-scale ingestion
For high-volume banks, the default 600 req/min is too low. We provision custom limits per-org up to 20,000 req/min with predictable p99 latency. Talk to us before launch with:
- Expected steady-state RPS and burst-window RPS
- Geographic distribution (Indonesia-only or multi-region)
- Whether you can tolerate queue mode (1-2s latency tail with backpressure) for cost reduction
Dry-run mode
Pass persist: false to score the transaction without writing anything:
{
"customerId": "cus_01HXY...",
"direction": "credit",
"amountIdr": 100000000,
"channel": "transfer",
"persist": false
}Returns the same shape with alertId: null. Use for what-if analysis, rule backtesting, or running a transaction through multiple rule configurations.
Idempotency
Pass externalId (your transaction ID). Within a 24-hour dedup window, replaying the same externalId returns the original decision:
{
"ok": true,
"data": { ..., "idempotent": true }
}This is critical for retries: if you crash after the API responds but before you record the alert ID, retrying with the same externalId won't create duplicates.
`externalId` is not a transaction ID lookup
We don't expose a GET /api/transactions?externalId=... endpoint. The externalId is only used for idempotency dedup, not as a primary key. Use the response's alertId (when present) to track the alert; store both the alert ID and your externalId in your DB for cross-reference.
Batch evaluation (bulk POST)
For end-of-day batch re-evaluation of historical transactions:
POST /api/transactions/evaluate
{
"batch": [
{ /* tx 1 */ },
{ /* tx 2 */ }
]
}Max 1,000 per request. Response is a per-item array. Batch mode is rate-limited as one request regardless of batch size, but each item still hits the rules engine; we recommend batches of 100–500 for predictable latency.
Common rules out of the box
| Rule code | Trigger |
|---|---|
HIGH_AMT_IDR | Single transaction ≥ org's cash threshold (default IDR 500M) |
CASH_STRUCT_D7 | Multiple cash transactions just below threshold in 7-day window |
CROSS_BORDER | Cross-border transfer + customer KYC tier not premium |
HIGH_RISK_GEO | Counterparty country on the FATF high-risk list |
VELOCITY_24H | More than N transactions in 24h above org-configured ceiling |
PEP_CPTY | Counterparty matched a PEP watchlist entry |
SANCTIONS_CPTY | Counterparty matched a sanctions watchlist entry |
NEW_CUSTOMER | First 90 days of customer life — slightly elevated scrutiny |
Customize and add your own via Rules →.