PlatformXeDocs
Get API Key

Decide

Render a synchronous fraud verdict for a subject + action + resource.

Render a synchronous risk verdict. Always returns within the engine's p99 budget; the durable audit write is dispatched asynchronously so it never blocks the response.

Endpoint

POST /api/v1/fraud/decide

Scope: fraud:decide Plan gate: Detection Pack addon (Pro or Enterprise) Rate limit: 10,000 requests / hour per API key

Request body

FieldTypeRequiredDescription
subject.idstringYesThe actor performing the action (e.g. user id).
subject.kindstringYesTenant-defined kind, e.g. user, merchant, device.
subject.attributesobjectNoFreeform attributes evaluated by ABAC rules in Phase 6B.
actionstringYesThe action being attempted, e.g. transfer, withdraw, login.
resource.idstringNoThe resource the action targets, e.g. transaction id.
resource.kindstringYesThe resource type, e.g. transaction, withdrawal, account.
resource.attributesobjectNoFreeform resource attributes (amount, recipient, etc.).
context.ipstringNoClient IP. Redacted before persistence — present in audit trail as [REDACTED].
context.deviceFingerprintstringNoSHA-256 hash of a tenant-computed device fingerprint.
context.userAgentstringNoClient user-agent. Redacted before persistence.
context.geoHintstringNoTenant-supplied geo hint (country code).
context.amount{ value: number, currency: string }NoTransactional amount, evaluated by amount-based rules.
context.externalScore{ value: number, source: string }NoOptional behavioural-ML score the tenant ran upstream. 0–100.
idempotencyKeystringNoRecorded on the decision's audit row for correlation. Replay is driven by the x-idempotency-key header, not by this field — see Idempotency.

Idempotency

Send an x-idempotency-key header to make a decide call safe to retry. Decide uses the same platform idempotency guard as every other Detection Pack mutation:

  • A repeated key from the same API key's organization within 24 hours returns the first response (same decisionId and verdict), with the header X-Idempotency-Cache: HIT. The engine does not run again and no second decision is recorded.
  • While the first request is still being processed, a concurrent request with the same key gets 409 CONFLICT.
  • Without the header, every call renders and records a new decision.

Use a key that identifies the business action, such as the transaction id.

Response

{
  "success": true,
  "data": {
    "decisionId": "dec_2j7yhq8wcs9...",
    "verdict": "allow",
    "score": 0,
    "reasons": [
      {
        "kind": "default",
        "code": "phase_6a_default_allow",
        "weight": 0,
        "detail": "No rules evaluated — Phase 6B wires the rule engine. Default allow."
      }
    ],
    "ttlSeconds": 30,
    "notice": "This verdict is informational and is provided for risk assessment. The tenant remains the decision authority for any action taken. See https://docs.platformxe.com/legal/fraud-detection-terms for full terms.",
    "decidedAt": "2026-05-03T09:14:21.412Z",
    "shadow": false,
    "latencyMs": 4
  }
}
FieldTypeDescription
decisionIdstringUnique id for this verdict; surface it to your reviewers and support tooling.
verdict"allow" | "review" | "step_up" | "block"The recommended outcome. The tenant remains the decision authority.
scorenumber (0–100)Aggregated risk score. Always 0 in Phase 6A; rule-driven in Phase 6B.
reasonsArray<{ kind, code, weight, detail }>Structured chain explaining the verdict. Safe to surface to ops/support. No PII.
ttlSecondsnumberHow long, in seconds, the verdict should be treated as current by the caller. This is not the idempotency window; replay is 24 hours via x-idempotency-key.
noticestringMandatory liability disclaimer surfaced on every response.
decidedAtstring (ISO-8601)When the engine rendered the verdict.
shadowbooleantrue only for /shadow-decide responses.
latencyMsnumberEngine latency budget for this call (excludes network).

Verdict semantics

VerdictMeaningRecommended caller behaviour
allowNo risk signals triggered.Proceed with the action.
reviewSuspicion score above the review threshold but below step-up.Queue for human review or run a soft challenge.
step_upStronger signal — escalate authentication.Trigger an OTP / passwordless / MFA challenge before proceeding.
blockHigh-confidence risk or hard rule (sanctions hit).Reject the action and (optionally) open a case.

Error responses

HTTPCodeCause
400BAD_REQUESTMissing required fields or malformed body.
401UNAUTHORIZEDMissing or invalid API key.
402DETECTION_PACK_REQUIREDThe org has not enabled the Detection Pack addon.
403FORBIDDENAPI key has no organization context, or wrong scope.
409CONFLICTA request with the same x-idempotency-key is still being processed.
429RATE_LIMITED10,000/hr ceiling exceeded.
500INTERNAL_ERRORUnexpected engine failure.

Examples

curl

curl -X POST https://api.platformxe.com/api/v1/fraud/decide \
  -H "Content-Type: application/json" \
  -H "x-api-key: pxk_live_your_api_key_here" \
  -H "x-idempotency-key: txn_8a91" \
  -d '{
    "subject": { "id": "usr_001", "kind": "user" },
    "action": "transfer",
    "resource": { "id": "txn_8a91", "kind": "transaction" },
    "context": {
      "amount": { "value": 50000, "currency": "NGN" }
    }
  }'

TypeScript SDK

import { PlatformXeClient } from '@caldera/platformxe-sdk';

const px = new PlatformXeClient({ apiKey: process.env.PLATFORMX_API_KEY! });

const verdict = await px.fraud.decide({
  subject: { id: 'usr_001', kind: 'user' },
  action: 'transfer',
  resource: { id: 'txn_8a91', kind: 'transaction' },
  context: { amount: { value: 50_000, currency: 'NGN' } },
  idempotencyKey: 'txn_8a91',
});

if (verdict.data.verdict === 'block') {
  // Tenant decides what blocking means in their system
  console.warn('blocked:', verdict.data.decisionId, verdict.data.reasons);
}

Performance

PercentileTarget
p5015ms
p9560ms
p99120ms

The hot path reads velocity counters from the shared cache only. Recording the decision in the velocity counters happens after the response is returned, as does durable persistence to fraud_decisions (fanned out via Inngest).