Screen an event and get a verdict
Authentication
Authorization: Bearer sk_live_… (or sk_test_…). Server-side only — never ship a secret key to a browser or a mobile app. The key determines the tenant and the mode; there is no mode parameter on any request.
Headers
Optional, max 255 characters. A replay with the same key and the same body returns the stored response and does not re-count velocity signals. The same key with a different body is a 409. Keys are scoped to your tenant and mode. Longer or empty values are ignored (treated as absent) rather than rejected.
Request
Which moment in your funnel this is. Determines which rules run and whether ip is required.
Your own id for the user. Strongly recommended on every event: it is how the review.resolved webhook tells you which user to revoke, so generate the id before you call and create the account with it.
Validated at the edge for transport sanity only — 3 to 320 characters and must contain an @. That is deliberate: the engine judges email quality and reports it in reason codes, and the schema only transports what your user typed. Do not pre-filter or normalise before sending.
The end user’s IP, IPv4 or IPv6.
Required for signup, trial_start and checkout_attempt; optional for trial_convert. trial_convert is usually fired from a payment-provider webhook handler where no client request — and therefore no honest client IP — is in scope. Never substitute your own server’s IP: omit the field rather than lie, because a wrong IP poisons velocity and geo signals for every other account that shares it.
The end user’s User-Agent header, verbatim.
A token from POST /v1/device (via @portreeve/browser). Expired, tampered, or cross-tenant/cross-mode tokens are silently dropped — the verdict still returns 200, just without device signals. Never hard-require the token on your side; collection is blockable by ordinary privacy tooling.
An open string→string map of any other identifiers you hold: phone, payer_wallet, github_handle, and so on. Keys up to 64 characters, values up to 512. Unrecognised keys are stored and used for identity linking — you do not need to ask us before sending a new one.
Payment details for checkout_attempt. Ignored on other event types.
Free-form data of your own. Stored and echoed back, never validated, never used in a decision. This is the escape hatch that lets the rest of the request object stay closed.
Response
The verdict. Also returned for an idempotent replay (same Idempotency-Key, same body), byte-for-byte identical to the original.
The event id. Persist it alongside your user or charge — it is the join key for the review.resolved webhook and for GET /v1/events/{id}.
allow: proceed. review: proceed anyway, and be ready to revoke when the review.resolved webhook says so. block: stop the flow with a generic error — do not tell the user which signal tripped.
Why. Ordering is not significant and an empty array is normal on allow. The reasons explain the decision; they are not the decision.
The rules version that produced this decision, e.g. 2026-08-18.0. Useful when correlating a change in your block rate with a change on our side.
Optional advice that rides alongside the verdict — today only on login. Absent when there is nothing to advise. It does not change what the verdict means: ignore it and nothing breaks. step_up means ask for a second factor rather than refuse, which is the right answer to a sign-in from an unrecognised device.