Back to README · See also: Architecture · Compliance
Base URL: http://localhost:8000 (dev) / https://fraud.yourdomain.example (prod)
Auth: Bearer token in Authorization header
Protocol: HTTPS TLS 1.2+ in production
Timeout: Hard 200ms server-side cutoff
All endpoints (except /health/*) require a Bearer token:
Authorization: Bearer <api_key>Keys configured via API_KEYS environment variable (comma-separated). Invalid key → 401 Unauthorized immediately, before any business logic runs.
Score a transaction for fraud risk. Returns decision, risk score, and SHAP explanations.
Note on verb-in-path:
/scorefollows ML inference API convention. SageMaker uses/invocations, Azure ML uses/score, Vertex AI uses/predict. The action is the resource for inference APIs — a noun path (/transactions/{id}) would imply a persisted entity that doesn't exist until after scoring.
Latency SLA: P50 < 40ms · P95 < 100ms · Hard timeout 200ms
{
"transaction_id": "txn_2026_001",
"timestamp_utc": "2026-07-30T10:00:00Z",
"rail": "upi",
"channel": "upi_push",
"amount_inr": "5000.00",
"currency_code": "INR",
"customer_id_hash": "sha256_hash_64_chars",
"client_id": "tenant_fintech_001",
"merchant_id": "merch_001",
"merchant_country": "IN",
"vpa_sender_hash": "sha256_of_sender_vpa",
"vpa_receiver_hash": "sha256_of_receiver_vpa",
"upi_app": "gpay",
"ip_address_hash": "sha256_of_ip",
"device_id_hash": "sha256_of_device_fingerprint",
"txn_lat": 19.0760,
"txn_lon": 72.8777
}| Field | Type | Required | Description |
|---|---|---|---|
transaction_id |
string | Yes | Unique ID (UUIDv4 recommended) |
timestamp_utc |
ISO 8601 datetime | Yes | Transaction time in UTC |
rail |
enum | Yes | upi · aeps · dmt · cards · loans |
channel |
enum | Yes | See channel list below |
amount_inr |
decimal string | Yes | Amount in Indian Rupees |
currency_code |
string | Yes | ISO 4217, usually INR |
customer_id_hash |
hex string (64 chars) | Yes | SHA-256 of customer ID |
client_id |
string (≤ 64 chars) | No | BaaS downstream tenant ID; stored in audit trail for per-client reporting |
merchant_id |
string | Yes | Merchant identifier |
merchant_country |
string | Yes | ISO 3166-1 alpha-2 |
card_token |
string | Cards | Tokenized card (no raw PAN) |
bin_8 |
string (8 chars) | Cards | First 8 digits of card |
pan_last4 |
string (4 chars) | Cards | Last 4 digits only |
card_type |
enum | Cards | debit · credit · prepaid · corporate |
cvv_result |
enum | Cards | match · no_match · not_provided |
three_ds_result |
enum | Cards | authenticated · not_authenticated · not_attempted |
vpa_sender_hash |
hex string | UPI | Required for UPI |
vpa_receiver_hash |
hex string | UPI | Required for UPI |
upi_app |
string | UPI | App identifier |
aadhaar_last4 |
string (4 chars) | AEPS | Last 4 of Aadhaar — no more |
agent_id |
string | AEPS | Required for AEPS |
biometric_quality_score |
float 0–100 | AEPS | UIDAI quality score (NOT raw biometric) |
biometric_attempt_count |
int | AEPS | Auth attempt count |
session_duration_seconds |
float | AEPS | Session wall-clock time |
beneficiary_account_hash |
hex string | DMT | SHA-256 of beneficiary account |
beneficiary_is_new |
bool | DMT | First transfer to this beneficiary |
ip_address_hash |
hex string | Optional | SHA-256 of IP (for MaxMind lookup) |
device_id_hash |
hex string | Optional | SHA-256 of device fingerprint |
txn_lat |
float | Optional | Transaction latitude |
txn_lon |
float | Optional | Transaction longitude |
Channel values: upi_collect · upi_push · aeps_withdrawal · aeps_balance · dmt_transfer · card_present · card_not_present · ecommerce · atm · recurring · loan_disbursement · loan_repayment · utility_payment
{
"transaction_id": "txn_2026_001",
"rail": "upi",
"decision": "ALLOW",
"risk_score": 0.042153,
"risk_score_int": 42,
"model_scores": {
"lightgbm": 0.038,
"xgboost": 0.041,
"catboost": 0.035,
"lstm": 0.051,
"autoencoder": 0.029,
"isoforest": 0.061
},
"rules_triggered": [],
"top_reasons": [
{
"feature": "card_txn_count_1h",
"shap_value": 0.12,
"direction": "increases_risk"
},
{
"feature": "user_amount_z_score",
"shap_value": -0.08,
"direction": "decreases_risk"
}
],
"latency_ms": 34.7,
"model_version": "1.4.2",
"confidence": "HIGH"
}| Field | Type | Description |
|---|---|---|
decision |
enum | ALLOW · REVIEW · BLOCK |
risk_score |
float 0–1 | Calibrated fraud probability |
risk_score_int |
int 0–1000 | round(risk_score × 1000) |
model_scores |
object | Raw L2 model probabilities; xgboost/catboost/isoforest are null when those optional models are not loaded |
rules_triggered |
array | Rules that fired (with reason codes) |
top_reasons |
array | Top-5 TreeSHAP feature contributions |
latency_ms |
float | Server-side processing time |
model_version |
string | Semantic version of scoring model |
confidence |
enum | HIGH (all features available) · LOW (Redis miss) |
risk_score_int |
decision |
Recommended action |
|---|---|---|
| 0 – 299 | ALLOW |
Process transaction |
| 300 – 699 | REVIEW |
Queue for manual review; process with extra auth |
| 700 – 1000 | BLOCK |
Decline; return reason to customer |
| Status | Code | Cause |
|---|---|---|
| 401 | unauthorized |
Missing or invalid Bearer token |
| 422 | validation_error |
Invalid request body (Pydantic V2 errors) |
| 429 | rate_limited |
Too many requests for this API key |
| 503 | service_unavailable |
Models not yet loaded (startup) |
| 504 | gateway_timeout |
Processing exceeded 200ms hard timeout |
Liveness probe. Returns 200 if the process is alive (even if models are still loading).
{"status": "ok"}Readiness probe. Returns 200 only when all models are loaded and Redis is reachable.
{
"status": "ready",
"checks": {
"models_loaded": true,
"rules_loaded": true,
"redis": true
}
}Returns 503 with "status": "not_ready" if any check fails.
Prometheus metrics scrape endpoint. Returns text/plain in Prometheus exposition format.
Key metrics exposed:
| Metric | Type | Description |
|---|---|---|
fraud_request_latency_seconds |
Histogram | End-to-end request latency |
fraud_decision_total |
Counter | Decisions by {rail, decision} |
fraud_redis_miss_total |
Counter | Redis timeout / fail-open events |
fraud_feature_psi |
Gauge | PSI drift per feature |
fraud_score_psi |
Gauge | Output score PSI drift |
fraud_rules_triggered_total |
Counter | Rule fires by {rule_id, action} |
Fixed-window counter per API key via Redis Lua script (atomic INCR + EXPIRE). Default: 1000 req/min per key (override with RATE_LIMIT_PER_MINUTE env var).
Rate-limited response:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{"detail": "Rate limit exceeded"}Every response includes X-Request-ID header (UUID4 injected by middleware). Use this for log correlation and support tickets.
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000- PCI-DSS: Never send raw PAN in
transaction_id, logs, or any field. Usecard_token(tokenized).bin_8andpan_last4only. - Aadhaar Act §29:
aadhaar_last4only. Never send full Aadhaar number. - SHAP explanations: Stored for 3 years (RBI customer dispute window). Retrievable by support teams within 1 business day.
- Audit trail: Every scoring decision logged to
model_decisionswith all 6 model scores (lgbm,lstm,ae,xgb,catboost,isoforest),client_idtenant field, rules, latency, andrecord_hashSHA-256 tamper seal. Run migration002_add_xgb_catboost_client_id.sqlbefore deploying v0.6.0+.
See Compliance for full regulatory requirements.
Next: Operations · Compliance