Decisions API · v2

One call. A decision and a way to yes.

Send a payment, get back allow, step up, hold or block. In the analyst view you also get the calibrated fraud probability, the reasons, and the cheapest changes that would get it approved, each with the PayPal calls that would carry it out. Interactive schema: /api/docs.

Quickstart

curl -X POST "https://yes-if-silk.vercel.app/api/decide?view=analyst" \
  -H "Content-Type: application/json" \
  -d '{"TransactionAmt": 834, "TransactionDT": 13910400, "card6": "credit",
       "prior_cnt": 30, "prior_mean_amt": 95, "prev_dt": 13800000,
       "card_device_prior": 12, "DeviceInfo_distinct_uids": 1, "dist1": 8}'

POST /api/decide

POST /api/decide?view=user|analyst

  • view=user (default): {"action", "guidance"} only. No scores, thresholds or exact counterfactuals, so the decision boundary cannot be mapped from the buyer side.
  • view=analyst: everything below. Rate-limited to 60 requests a minute per client.

Transaction fields

Only TransactionAmt and TransactionDT are required. History and graph fields would come from your feature store; anything you leave out is filled from training-set defaults (per email domain where known).

FieldMeaning
TransactionAmtAmount in USD (required)
TransactionDTSeconds from a reference point; hour of day is (DT // 3600) % 24 (required)
card6debit or credit; actionable
card4, ProductCD, DeviceType, P_emaildomainCard network, product type, mobile/desktop, buyer email domain
dist1Distance between billing address and the payment's location (km)
prior_cnt, prior_mean_amt, prev_dtEarlier payments on this card, their mean amount, and the previous payment's timestamp
card_device_priorHow many times this card has used this device before
DeviceInfo_distinct_uids, DeviceInfo_prior_cntDistinct cards and payments seen on this device so far (ring signal)
P_emaildomain_*, addr1_*The same counts for the email domain and billing region

Analyst response

{
  "action": "HOLD",                     // ALLOW | STEP_UP | HOLD | BLOCK
  "guidance": "This payment is being reviewed. You'll hear back shortly.",
  "risk": 0.1316,                       // calibrated fraud probability
  "thresholds": {"tau_allow": 0.0286, "tau_hold": 1.0, "tau_block": 1.0},
  "top_reasons": [{"reason": "Time of day", "impact": 0.833, "direction": "raises"},
                  {"reason": "Amount vs. this card's usual spend", "impact": 0.814, "direction": "raises"}, …],
  "counterfactuals": [{
    "label": "yes, if it goes through 8 hours later",
    "change": {"amount_mult": 1.0, "delay_h": 8, "card6": "credit"},
    "risk": 0.018, "cost": 0.167, "plausible": true, "amount": 834.0,
    "paypal_plan": [{"kind": "wait", "minutes": 480, …}, {"kind": "capture", "paypal_call": true, …}]
  }, …],
  "paypal_plan": [{"kind": "review", …}, {"kind": "capture", "amount": 834.0, "paypal_call": true, …}],
  "transaction": { …the payment with defaults filled in… },
  "latency_ms": 4.1
}

How the action is chosen. Below tau_allow the payment is allowed. Above it, a learned policy picks the cheapest friction (step-up, hold or block) from the calibrated risk, the amount, and what it has learned about how often each friction catches fraud or loses a good customer. tau_block forces a block.

Counterfactuals come from a grid of 83 changes: pay 10–90% now, delay 1–24 hours, switch debit/credit. Valid options fall under tau_allow; they are ranked by change cost (|log amount ratio| + 0.5 × days delayed + 0.3 × card switch) and kept diverse. They describe the model, not causal truth.

PayPal plans

Every plan assumes a PayPal order created with intent AUTHORIZE. Steps marked paypal_call map to the Payments API; the others are handled by your app.

StepMeaning
capturePOST /v2/payments/authorizations/{id}/capture; partial captures send final_capture: false
voidPOST /v2/payments/authorizations/{id}/void
verifyOne-time code or app confirmation, then continue
reviewKeep the authorization; an analyst approves (capture) or rejects (void)
waitKeep the authorization and capture after minutes
switch_fundingAsk the buyer to approve again with a different funding source

Checkout endpoints

Used by the sandbox checkout. Amounts always come from the server-side catalog, never from the browser.

EndpointDoes
GET /api/checkout/configPublic client id, catalog and demo buyers
POST /api/checkout/quote{product, persona} → what the buyer sees and what the merchant sees
POST /api/checkout/orders{product, persona, offer?} → creates a PayPal AUTHORIZE order (403 if blocked with no offer)
POST …/orders/{id}/authorizeChecks approval and amount, authorizes, runs the plan until it needs the buyer, a reviewer or time
POST …/orders/{id}/continue{code} for verification, {review: approve|reject} for holds, {} to skip a wait in the sandbox
GET …/orders/{id}Current status and captures

Other endpoints

GET /api/healthModel version, metrics, thresholds and policy
GET /api/samplesSix labelled test-set payments (clean, large, borderline, takeover, ring, card testing)
GET /api/resultsEvery experiment result (E1–E7, fairness) behind this site

Limits & safety

  • Analyst view: 60 requests per minute per client. Checkout orders: 20 per minute.
  • PayPal sandbox only: the client refuses any non-sandbox host.
  • The model is trained on synthetic data and friction outcomes are simulated. Do not use it for real payment decisions.
  • No LLM is involved in any decision.