Skip to content

How scoring works

Every call to POST /v1/score is evaluated against your active policy: a set of rules with weights, thresholds and actions. Scoring is deterministic: the same inputs and the same policy version always give the same result.

Field Meaning
score 0–100: how anomalous the transaction looks
risk_level low, medium, high or critical, from your policy’s thresholds
confidence 0–100: how much history backs the score. A new customer can score high with low confidence
recommended_action Your policy’s action for this risk level: approve, step_up, manual_review or block
reasons The rules that raised the score, and the points each added
policy The exact policy version used

Only the policy’s enabled rules count:

score = round(100 × Σ(signal_i × weight_i) / Σ weight_i)
confidence = round(100 × Σ(coverage_i × weight_i) / Σ weight_i)

Each rule returns a signal (how anomalous, 0 to 1) and a coverage (how much reliable data it had, 0 to 1). A reason’s contribution is that rule’s share of the score in points. Rounding means contributions can differ from the score by up to 0.5.

If you leave out optional data, such as the device or beneficiary, the rules that need it contribute nothing and confidence goes down.

GET /v1/rules returns the library with defaults and validated parameters.

Rule Default Parameters
transaction_velocity on, weight 25 window_minutes 10, soft_threshold 3, threshold 5
amount_anomaly on, weight 25 baseline_multiplier 3, min_history 5
new_device on, weight 20 lookback_days 180, min_history 3
new_beneficiary on, weight 15 lookback_days 180, min_amount 0, min_history 3
rapid_activity on, weight 15 interval_seconds 60
device_change off, weight 10 lookback_days 30
geo_anomaly off, weight 10 lookback_days 90, min_history 5
unusual_time off, weight 5 timezone Africa/Lagos, min_history 10, min_share 0.05

The last three are off by default, so existing policies score as before. To use them, enable them in a new policy version, backtest it on your history, then activate it.

  • device_change compares the device with the one used in the customer’s previous device transaction, if that was within lookback_days. A different platform, such as Android to iOS, gives a signal of 1.0. A switch to another device the customer has used before, on the same platform, gives 0.5. A device never seen before is left to new_device. It needs device.id_hash, and device.platform if you want platform changes detected.
  • geo_anomaly compares location.country and location.region with where the customer has transacted. A country not seen within lookback_days gives 1.0, and a new region of a known country gives 0.5. A customer with no location history yet is not flagged. Regions are compared ignoring case and extra spaces.
  • unusual_time looks at the hour of day, in timezone, of the customer’s earlier transactions. If the share of them within an hour either side of this transaction’s time is below min_share, the signal rises, reaching 1.0 when there are none. It stays at 0 until the customer has min_history transactions.

All three build on history, so confidence stays low until a customer has transacted a few times.

Fetch GET /v1/decisions/{decision_id} for every enabled rule’s evaluation (signal, coverage, weight, contribution, message, details), the stored transaction (hashes only), the features snapshot and the policy version. Add ?expand=policy_version to embed the exact configuration used.

Scoring is idempotent on transaction.id. Sending the same ID again returns the original decision with 200 and Idempotent-Replayed: true, and the transaction is not counted twice in behavioural history.