> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.portreeve.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.portreeve.com/_mcp/server.

# Screen an event and get a verdict

POST https://api.portreeve.com/v1/verdict
Content-Type: application/json

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, by design. An optional `recommended_action` (`challenge` / `hold`) may ride alongside the verdict as advice — ignore it and nothing breaks.

Reference: https://docs.portreeve.com/api-reference/verdicts/create-verdict

## Authentication

- `Authorization` header (bearer token, required) — `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.

## Request

### Headers

- `Idempotency-Key` (string, optional) — 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.

### Body (application/json)

This endpoint expects a VerdictRequest.

- `event_type` (enum, required) — Which moment in your funnel this is. Determines which rules run and whether `ip` is required.
  - Allowed values: `signup`, `trial_start`, `trial_convert`, `checkout_attempt`, `login`
- `external_user_id` (string, optional) — 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.
- `email` (string, optional) — 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.
- `ip` (string, optional) — 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_agent` (string, optional) — The end user's User-Agent header, verbatim.
- `device_token` (string, optional) — 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.
- `identity` (map from string to string, optional) — 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` (Payment, optional) — Payment details for `checkout_attempt`. Ignored on other event types.
- `context` (map from string to any, optional) — 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

### 200

The verdict. Also returned for an idempotent replay (same `Idempotency-Key`, same body), byte-for-byte identical to the original.

- `id` (string, required) — 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}`.
- `verdict` (enum, required) — `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.
  - Allowed values: `allow`, `review`, `block`
- `reasons` (list of Reason, required) — Why. Ordering is not significant and an empty array is normal on `allow`. The reasons explain the decision; they are not the decision.
- `policy_version` (string, required) — 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_action` (VerdictResponseRecommendedAction, optional) — 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. `challenge` 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

The body failed contract validation. `details.issues[]` carries `{ path, message }` per failure. The two most common causes are an unknown top-level key (the request object is closed — see `VerdictRequest`) and a missing `ip` on an event type that requires one.

- `error` (string, required) — **This is an open set.** New values ship without a new SDK release and without a breaking change, so this is documented as a plain string rather than a closed enum. Treat an unrecognised value as informational: log it, display it, never crash on it, and never map "unknown" to a punitive action. Publishing a closed enum here would make our next additive release break every generated client that validates responses. The values defined today (also available machine-readably in `x-portreeve-known-values`) are: `invalid_request`, `unauthorized`, `rate_limited`, `idempotency_conflict`, `conflict`, `not_found`, `internal`.
- `message` (string, required) — Human-readable, for your logs. Never show it to an end user — on a `block` it can describe what tripped.
- `details` (map from string to any, optional) — Optional structured context. On a 400 it carries `issues[]`, an array of `{ path, message }` — one entry per validation failure.

### 401 Unauthorized Error

Missing, malformed, unknown, revoked, or wrong-kind API key. This endpoint requires a secret key (`sk_live_…` / `sk_test_…`); a publishable key is rejected here.

- `error` (string, required) — **This is an open set.** New values ship without a new SDK release and without a breaking change, so this is documented as a plain string rather than a closed enum. Treat an unrecognised value as informational: log it, display it, never crash on it, and never map "unknown" to a punitive action. Publishing a closed enum here would make our next additive release break every generated client that validates responses. The values defined today (also available machine-readably in `x-portreeve-known-values`) are: `invalid_request`, `unauthorized`, `rate_limited`, `idempotency_conflict`, `conflict`, `not_found`, `internal`.
- `message` (string, required) — Human-readable, for your logs. Never show it to an end user — on a `block` it can describe what tripped.
- `details` (map from string to any, optional) — Optional structured context. On a 400 it carries `issues[]`, an array of `{ path, message }` — one entry per validation failure.

### 409 Conflict Error

The `Idempotency-Key` was already used with a different request body. Change the key or send the original body.

- `error` (string, required) — **This is an open set.** New values ship without a new SDK release and without a breaking change, so this is documented as a plain string rather than a closed enum. Treat an unrecognised value as informational: log it, display it, never crash on it, and never map "unknown" to a punitive action. Publishing a closed enum here would make our next additive release break every generated client that validates responses. The values defined today (also available machine-readably in `x-portreeve-known-values`) are: `invalid_request`, `unauthorized`, `rate_limited`, `idempotency_conflict`, `conflict`, `not_found`, `internal`.
- `message` (string, required) — Human-readable, for your logs. Never show it to an end user — on a `block` it can describe what tripped.
- `details` (map from string to any, optional) — Optional structured context. On a 400 it carries `issues[]`, an array of `{ path, message }` — one entry per validation failure.

### 429 Too Many Requests Error

Per-key rate limit exceeded (default 600 requests/minute per API key; requests with an unparseable key are bucketed per source IP). `retry-after` and `x-ratelimit-*` headers accompany the response. Retry after the window; do not treat this as a verdict.

- `error` (string, required) — **This is an open set.** New values ship without a new SDK release and without a breaking change, so this is documented as a plain string rather than a closed enum. Treat an unrecognised value as informational: log it, display it, never crash on it, and never map "unknown" to a punitive action. Publishing a closed enum here would make our next additive release break every generated client that validates responses. The values defined today (also available machine-readably in `x-portreeve-known-values`) are: `invalid_request`, `unauthorized`, `rate_limited`, `idempotency_conflict`, `conflict`, `not_found`, `internal`.
- `message` (string, required) — Human-readable, for your logs. Never show it to an end user — on a `block` it can describe what tripped.
- `details` (map from string to any, optional) — Optional structured context. On a 400 it carries `issues[]`, an array of `{ path, message }` — one entry per validation failure.

### 500 Internal Server Error

Unhandled server error. The verdict engine itself fails open rather than 500ing — a rules bug returns `allow` with no reasons — so a 500 here means the request never reached a decision. Treat as retryable.

- `error` (string, required) — **This is an open set.** New values ship without a new SDK release and without a breaking change, so this is documented as a plain string rather than a closed enum. Treat an unrecognised value as informational: log it, display it, never crash on it, and never map "unknown" to a punitive action. Publishing a closed enum here would make our next additive release break every generated client that validates responses. The values defined today (also available machine-readably in `x-portreeve-known-values`) are: `invalid_request`, `unauthorized`, `rate_limited`, `idempotency_conflict`, `conflict`, `not_found`, `internal`.
- `message` (string, required) — Human-readable, for your logs. Never show it to an end user — on a `block` it can describe what tripped.
- `details` (map from string to any, optional) — Optional structured context. On a 400 it carries `issues[]`, an array of `{ path, message }` — one entry per validation failure.

## Types

### Payment

Card details, passed straight through from your payment provider. **Deliberately open**, unlike the enclosing request: unknown keys here are accepted and ignored rather than rejected. That asymmetry is load-bearing. It means an API that predates a field your SDK has started sending degrades to "signal absent" instead of returning 400 on every verdict call — silent signal loss rather than silent loss of enforcement.

- `card_fingerprint` (string, optional) — Stripe: `payment_method.card.fingerprint`. A stable per-card identifier; we never see a card number.
- `card_funding` (enum, optional) — Stripe: `payment_method.card.funding`. Absent means unknown, and unknown never fires a rule — there is no `unknown` sentinel to prefer over omitting the field.
  - Allowed values: `credit`, `debit`, `prepaid`, `unknown`
- `card_country` (string, optional) — The card's **issuing** country (Stripe: `payment_method.card.country`), ISO-3166-1 alpha-2. Case-insensitive on the wire; normalised to lowercase server-side. Absent means unknown, and unknown never fires a rule.
- `amount` (integer, optional) — Amount in minor units (cents), integer, non-negative.
- `currency` (string, optional) — ISO-4217 code, three ASCII letters. Case-insensitive on the wire; normalised to lowercase server-side.

### Reason

One signal behind a verdict.

- `code` (string, required) — **This is an open set.** New values ship without a new SDK release and without a breaking change, so this is documented as a plain string rather than a closed enum. Treat an unrecognised value as informational: log it, display it, never crash on it, and never map "unknown" to a punitive action. Publishing a closed enum here would make our next additive release break every generated client that validates responses. The values defined today (also available machine-readably in `x-portreeve-known-values`) are: `disposable_email`, `invalid_mx`, `young_domain`, `malformed_email`, `proxy_ip`, `tor_exit`, `datacenter_ip`, `velocity_ip_signups`, `velocity_device_signups`, `velocity_domain_signups`, `velocity_card_attempts`, `velocity_ip_card_attempts`, `velocity_cluster_trials`, `velocity_distinct_cards`, `velocity_card_countries`, `ato_new_device`, `ato_account_device_burst`, `identity_cluster`, `cluster_confirmed_abuse`, `cluster_trusted`, `prepaid_card`, `card_probe_pattern`, `allowlisted`, `blocklisted`, `timeout_failopen`, `partial_enrichment`.
- `detail` (map from string to ReasonDetail, optional) — A flat map of scalars specific to the code, e.g. `{ "linked_accounts": 4 }` on `identity_cluster`. Present on some codes, absent on others; treat it as informational.

### VerdictResponseRecommendedAction

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. `challenge` means ask for a second factor rather than refuse, which is the right answer to a sign-in from an unrecognised device.

### ReasonDetail

## Examples

### allow — nothing fired

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "allow",
  "reasons": [],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python allow — nothing fired
import requests

url = "https://api.portreeve.com/v1/verdict"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript allow — nothing fired
const url = 'https://api.portreeve.com/v1/verdict';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go allow — nothing fired
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby allow — nothing fired
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java allow — nothing fired
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php allow — nothing fired
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp allow — nothing fired
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift allow — nothing fired
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### review — proceed anyway

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "review",
  "reasons": [
    {
      "code": "identity_cluster",
      "detail": {
        "linked_accounts": 4
      }
    },
    {
      "code": "disposable_email"
    }
  ],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python review — proceed anyway
import requests

url = "https://api.portreeve.com/v1/verdict"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript review — proceed anyway
const url = 'https://api.portreeve.com/v1/verdict';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go review — proceed anyway
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby review — proceed anyway
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java review — proceed anyway
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php review — proceed anyway
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp review — proceed anyway
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift review — proceed anyway
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### block — stop the flow

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "block",
  "reasons": [
    {
      "code": "card_probe_pattern",
      "detail": {
        "attempts_10m": 4
      }
    }
  ],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python block — stop the flow
import requests

url = "https://api.portreeve.com/v1/verdict"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript block — stop the flow
const url = 'https://api.portreeve.com/v1/verdict';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go block — stop the flow
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby block — stop the flow
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java block — stop the flow
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php block — stop the flow
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp block — stop the flow
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift block — stop the flow
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### a reason code your SDK has never seen

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "review",
  "reasons": [
    {
      "code": "some_signal_shipped_after_your_sdk"
    }
  ],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python a reason code your SDK has never seen
import requests

url = "https://api.portreeve.com/v1/verdict"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript a reason code your SDK has never seen
const url = 'https://api.portreeve.com/v1/verdict';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go a reason code your SDK has never seen
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby a reason code your SDK has never seen
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java a reason code your SDK has never seen
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php a reason code your SDK has never seen
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp a reason code your SDK has never seen
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift a reason code your SDK has never seen
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### signup — the fullest payload

**Request**

```json
{
  "event_type": "signup",
  "external_user_id": "usr_9f2c",
  "email": "jane@example.com",
  "ip": "203.0.113.7",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
  "device_token": "dtk_2ZqX7Yk1",
  "identity": {
    "phone": "+15550100"
  }
}
```

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "allow",
  "reasons": [],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python signup — the fullest payload
import requests

url = "https://api.portreeve.com/v1/verdict"

payload = {
    "event_type": "signup",
    "external_user_id": "usr_9f2c",
    "email": "jane@example.com",
    "ip": "203.0.113.7",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
    "device_token": "dtk_2ZqX7Yk1",
    "identity": { "phone": "+15550100" }
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript signup — the fullest payload
const url = 'https://api.portreeve.com/v1/verdict';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"event_type":"signup","external_user_id":"usr_9f2c","email":"jane@example.com","ip":"203.0.113.7","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36","device_token":"dtk_2ZqX7Yk1","identity":{"phone":"+15550100"}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go signup — the fullest payload
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	payload := strings.NewReader("{\n  \"event_type\": \"signup\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\",\n  \"ip\": \"203.0.113.7\",\n  \"user_agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"identity\": {\n    \"phone\": \"+15550100\"\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby signup — the fullest payload
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"event_type\": \"signup\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\",\n  \"ip\": \"203.0.113.7\",\n  \"user_agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"identity\": {\n    \"phone\": \"+15550100\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java signup — the fullest payload
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"event_type\": \"signup\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\",\n  \"ip\": \"203.0.113.7\",\n  \"user_agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"identity\": {\n    \"phone\": \"+15550100\"\n  }\n}")
  .asString();
```

```php signup — the fullest payload
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'body' => '{
  "event_type": "signup",
  "external_user_id": "usr_9f2c",
  "email": "jane@example.com",
  "ip": "203.0.113.7",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
  "device_token": "dtk_2ZqX7Yk1",
  "identity": {
    "phone": "+15550100"
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp signup — the fullest payload
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"event_type\": \"signup\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\",\n  \"ip\": \"203.0.113.7\",\n  \"user_agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"identity\": {\n    \"phone\": \"+15550100\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift signup — the fullest payload
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "event_type": "signup",
  "external_user_id": "usr_9f2c",
  "email": "jane@example.com",
  "ip": "203.0.113.7",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
  "device_token": "dtk_2ZqX7Yk1",
  "identity": ["phone": "+15550100"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### trial_start

**Request**

```json
{
  "event_type": "trial_start",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1"
}
```

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "allow",
  "reasons": [],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python trial_start
import requests

url = "https://api.portreeve.com/v1/verdict"

payload = {
    "event_type": "trial_start",
    "external_user_id": "usr_9f2c",
    "ip": "203.0.113.7",
    "device_token": "dtk_2ZqX7Yk1"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript trial_start
const url = 'https://api.portreeve.com/v1/verdict';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"event_type":"trial_start","external_user_id":"usr_9f2c","ip":"203.0.113.7","device_token":"dtk_2ZqX7Yk1"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go trial_start
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	payload := strings.NewReader("{\n  \"event_type\": \"trial_start\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby trial_start
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"event_type\": \"trial_start\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}"

response = http.request(request)
puts response.read_body
```

```java trial_start
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"event_type\": \"trial_start\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}")
  .asString();
```

```php trial_start
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'body' => '{
  "event_type": "trial_start",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp trial_start
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"event_type\": \"trial_start\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift trial_start
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "event_type": "trial_start",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### trial_convert — the one event type that may omit ip

**Request**

```json
{
  "event_type": "trial_convert",
  "external_user_id": "usr_9f2c",
  "email": "jane@example.com"
}
```

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "allow",
  "reasons": [],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python trial_convert — the one event type that may omit ip
import requests

url = "https://api.portreeve.com/v1/verdict"

payload = {
    "event_type": "trial_convert",
    "external_user_id": "usr_9f2c",
    "email": "jane@example.com"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript trial_convert — the one event type that may omit ip
const url = 'https://api.portreeve.com/v1/verdict';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"event_type":"trial_convert","external_user_id":"usr_9f2c","email":"jane@example.com"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go trial_convert — the one event type that may omit ip
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	payload := strings.NewReader("{\n  \"event_type\": \"trial_convert\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby trial_convert — the one event type that may omit ip
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"event_type\": \"trial_convert\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\"\n}"

response = http.request(request)
puts response.read_body
```

```java trial_convert — the one event type that may omit ip
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"event_type\": \"trial_convert\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\"\n}")
  .asString();
```

```php trial_convert — the one event type that may omit ip
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'body' => '{
  "event_type": "trial_convert",
  "external_user_id": "usr_9f2c",
  "email": "jane@example.com"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp trial_convert — the one event type that may omit ip
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"event_type\": \"trial_convert\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"email\": \"jane@example.com\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift trial_convert — the one event type that may omit ip
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "event_type": "trial_convert",
  "external_user_id": "usr_9f2c",
  "email": "jane@example.com"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### checkout_attempt — with Stripe card details

**Request**

```json
{
  "event_type": "checkout_attempt",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1",
  "payment": {
    "card_fingerprint": "Xt5EWLLDS7FJjR1c",
    "card_funding": "prepaid",
    "card_country": "US",
    "amount": 2900,
    "currency": "usd"
  }
}
```

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "allow",
  "reasons": [],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python checkout_attempt — with Stripe card details
import requests

url = "https://api.portreeve.com/v1/verdict"

payload = {
    "event_type": "checkout_attempt",
    "external_user_id": "usr_9f2c",
    "ip": "203.0.113.7",
    "device_token": "dtk_2ZqX7Yk1",
    "payment": {
        "card_fingerprint": "Xt5EWLLDS7FJjR1c",
        "card_funding": "prepaid",
        "card_country": "US",
        "amount": 2900,
        "currency": "usd"
    }
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript checkout_attempt — with Stripe card details
const url = 'https://api.portreeve.com/v1/verdict';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"event_type":"checkout_attempt","external_user_id":"usr_9f2c","ip":"203.0.113.7","device_token":"dtk_2ZqX7Yk1","payment":{"card_fingerprint":"Xt5EWLLDS7FJjR1c","card_funding":"prepaid","card_country":"US","amount":2900,"currency":"usd"}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go checkout_attempt — with Stripe card details
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	payload := strings.NewReader("{\n  \"event_type\": \"checkout_attempt\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"payment\": {\n    \"card_fingerprint\": \"Xt5EWLLDS7FJjR1c\",\n    \"card_funding\": \"prepaid\",\n    \"card_country\": \"US\",\n    \"amount\": 2900,\n    \"currency\": \"usd\"\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby checkout_attempt — with Stripe card details
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"event_type\": \"checkout_attempt\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"payment\": {\n    \"card_fingerprint\": \"Xt5EWLLDS7FJjR1c\",\n    \"card_funding\": \"prepaid\",\n    \"card_country\": \"US\",\n    \"amount\": 2900,\n    \"currency\": \"usd\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java checkout_attempt — with Stripe card details
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"event_type\": \"checkout_attempt\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"payment\": {\n    \"card_fingerprint\": \"Xt5EWLLDS7FJjR1c\",\n    \"card_funding\": \"prepaid\",\n    \"card_country\": \"US\",\n    \"amount\": 2900,\n    \"currency\": \"usd\"\n  }\n}")
  .asString();
```

```php checkout_attempt — with Stripe card details
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'body' => '{
  "event_type": "checkout_attempt",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1",
  "payment": {
    "card_fingerprint": "Xt5EWLLDS7FJjR1c",
    "card_funding": "prepaid",
    "card_country": "US",
    "amount": 2900,
    "currency": "usd"
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp checkout_attempt — with Stripe card details
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"event_type\": \"checkout_attempt\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\",\n  \"payment\": {\n    \"card_fingerprint\": \"Xt5EWLLDS7FJjR1c\",\n    \"card_funding\": \"prepaid\",\n    \"card_country\": \"US\",\n    \"amount\": 2900,\n    \"currency\": \"usd\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift checkout_attempt — with Stripe card details
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "event_type": "checkout_attempt",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1",
  "payment": [
    "card_fingerprint": "Xt5EWLLDS7FJjR1c",
    "card_funding": "prepaid",
    "card_country": "US",
    "amount": 2900,
    "currency": "usd"
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### login — account takeover screening

**Request**

```json
{
  "event_type": "login",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1"
}
```

**Response**

```json
{
  "id": "evt_01J8Z3K9QP4M2N",
  "verdict": "allow",
  "reasons": [],
  "policy_version": "2026-08-18.0"
}
```

**SDK Code**

```python login — account takeover screening
import requests

url = "https://api.portreeve.com/v1/verdict"

payload = {
    "event_type": "login",
    "external_user_id": "usr_9f2c",
    "ip": "203.0.113.7",
    "device_token": "dtk_2ZqX7Yk1"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript login — account takeover screening
const url = 'https://api.portreeve.com/v1/verdict';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"event_type":"login","external_user_id":"usr_9f2c","ip":"203.0.113.7","device_token":"dtk_2ZqX7Yk1"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go login — account takeover screening
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.portreeve.com/v1/verdict"

	payload := strings.NewReader("{\n  \"event_type\": \"login\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby login — account takeover screening
require 'uri'
require 'net/http'

url = URI("https://api.portreeve.com/v1/verdict")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"event_type\": \"login\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}"

response = http.request(request)
puts response.read_body
```

```java login — account takeover screening
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.portreeve.com/v1/verdict")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"event_type\": \"login\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}")
  .asString();
```

```php login — account takeover screening
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.portreeve.com/v1/verdict', [
  'body' => '{
  "event_type": "login",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp login — account takeover screening
using RestSharp;

var client = new RestClient("https://api.portreeve.com/v1/verdict");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"event_type\": \"login\",\n  \"external_user_id\": \"usr_9f2c\",\n  \"ip\": \"203.0.113.7\",\n  \"device_token\": \"dtk_2ZqX7Yk1\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift login — account takeover screening
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "event_type": "login",
  "external_user_id": "usr_9f2c",
  "ip": "203.0.113.7",
  "device_token": "dtk_2ZqX7Yk1"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.portreeve.com/v1/verdict")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```