📘 Public beta · Endpoints are stable; OpenAPI specs and SDKs ship monthly. See changelog →
Products
AML Platform
Transaction monitoring

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.

POST/api/transactions/evaluate
Auth · API keyScope · writeRate limit · 600/min default · custom up to 20k/min

When to call it

Inline in your transaction-processing path, after KYC/anti-fraud and before commit. The decision tells you whether to:

  • pass — proceed
  • review — 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

customerIdstringRequired
Customer initiating the transaction (or the subject if KYC tier wasn't completed yet).
directionenumRequired
"credit" (incoming) · "debit" (outgoing).
amountIdrinteger/stringRequired
Amount in IDR. Use string for amounts > 9 quadrillion.
currencystring
ISO-4217. Default "IDR".
channelstringRequired
"transfer" · "atm" · "purchase" · "topup" · "withdrawal" · "qris" · "card" — controls which rule pack applies.
counterpartyNamestring
Other side's name. Used for counterparty screening if enabled.
counterpartyNikstring
16-digit NIK of counterparty if available.
counterpartyAccountstring
Bank account or wallet ID of counterparty.
isCrossBorderboolean
Triggers cross-border rules (FATF higher-risk corridors).
citystring
City of origin.
countrystring
ISO-3166-1 alpha-2.
externalIdstring
Your transaction ID for idempotency.
persistboolean
Set false for dry-run (no persistence, no alert created). Default true.

Response

{
  "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 draft

Adjust 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 codeTrigger
HIGH_AMT_IDRSingle transaction ≥ org's cash threshold (default IDR 500M)
CASH_STRUCT_D7Multiple cash transactions just below threshold in 7-day window
CROSS_BORDERCross-border transfer + customer KYC tier not premium
HIGH_RISK_GEOCounterparty country on the FATF high-risk list
VELOCITY_24HMore than N transactions in 24h above org-configured ceiling
PEP_CPTYCounterparty matched a PEP watchlist entry
SANCTIONS_CPTYCounterparty matched a sanctions watchlist entry
NEW_CUSTOMERFirst 90 days of customer life — slightly elevated scrutiny

Customize and add your own via Rules →.