Screen an event and get a verdict

Call this inline, before you commit the action, at one of four moments: `signup`, `trial_start`, `trial_convert`, `checkout_attempt`. The response is one of `allow` / `review` / `block` plus the reason codes behind it. **`review` never holds the user's flow** — it means "proceed, we are looking at it"; a later denial arrives on the `review.resolved` webhook and you revoke then. Enforcement is always yours; Portreeve never contacts your users. There is no risk score and no `recommended_action` field, by design.

Authentication

AuthorizationBearer

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

Idempotency-KeystringOptional<=255 characters

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

This endpoint expects an object.
event_typeenumRequired

Which moment in your funnel this is. Determines which rules run and whether ip is required.

external_user_idstringOptional<=256 characters

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.

emailstringOptional3-320 characters

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.

ipstringOptional

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.

user_agentstringOptional<=2048 characters

The end user’s User-Agent header, verbatim.

device_tokenstringOptional<=512 characters

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.

identitymap from strings to stringsOptional

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.

paymentobjectOptional

Payment details for checkout_attempt. Ignored on other event types.

contextmap from strings to anyOptional

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.

idstring

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}.

verdictenum

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.

reasonslist of objects

Why. Ordering is not significant and an empty array is normal on allow. The reasons explain the decision; they are not the decision.

policy_versionstring

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.

recommended_actionenum or stringOptional

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.

Errors

400
Bad Request Error
401
Unauthorized Error
409
Conflict Error
429
Too Many Requests Error
500
Internal Server Error