Evaluations
Evaluate a benefit claim, and read past evaluations, their evidence and their association graphs.
An evaluation decides one benefit claim. You send what you know about the user and the claim. AbuseZero checks it against your policy, your workspace's full claim history and every signal family, and returns one final result, allow or deny, within the evaluation's time limit.
Every evaluation is saved before its response is sent. It keeps the exact policy version it ran under, so you can always explain a past decision, even after your rules change.
| Endpoint | Scope |
|---|---|
POST /v1/evaluations | evaluations:create |
GET /v1/evaluations | evaluations:read |
GET /v1/evaluations/{id} | evaluations:read |
GET /v1/evaluations/{id}/evidence | evaluation_evidence:read |
GET /v1/evaluations/{id}/graph | evaluations:read |
To report what happened after an evaluation, see Outcomes.
The evaluation object
GET /v1/evaluations/{id} returns the full evaluation object.
| Field | Type | Description |
|---|---|---|
object | string | Always evaluation. |
id | string | The evaluation's ID, starting with eval_. |
benefit | string or null | The benefit's key. See Benefits. |
claim_id | string or null | The claim ID you sent. |
result | string | allow or deny. In observe mode this is always allow. |
reason | object or null | Why the result was reached. See Reason. |
outcome | object | The claim's outcome: state and due_at. See Outcomes. |
policy_version | integer or null | The version of the policy the evaluation ran under. |
created_at | string | When the evaluation ran. |
mode | string | protect or observe: the benefit's protection mode when the evaluation ran. |
observed_result | string | Observe mode only. The result your policy reached: allow or deny. |
risk | object | The AbuseZero Risk Score. See Risk. |
signals | array | Evidence only. The assessment of each signal family, returned by Retrieve an evaluation's evidence. See Signals. |
inputs | object | Which inputs the evaluation received. See Inputs. |
browser_data | string or null | What the evaluation received from the browser SDK. See Browser data. |
policy | object | The policy the evaluation ran under: id (starting with pol_) and version. |
Reason
| Field | Type | Description |
|---|---|---|
code | string | A stable reason code, such as prior_claim_email or custom_rule. See Reason codes. |
message | string | A readable explanation you can show your team. |
rule | string | Present when one of your own rules decided the result. The ID of that rule. |
Risk
| Field | Type | Description |
|---|---|---|
score | number or null | The AbuseZero Risk Score, from 0 to 100. null when the result was decided without a score, for example by one of your rules, a list entry or a protection level. |
band | string | low, elevated, high or not_assessed. not_assessed means no score was produced. The result still stands. |
Signals
An array with one entry for each signal family. It appears only in an evaluation's evidence, which needs the evaluation_evidence:read scope, and on the evaluation in the dashboard. Evaluation and preview responses don't include it.
| Field | Type | Description |
|---|---|---|
family | string | identity, device, network, automation or benefit_history. |
assessment | string | clear, elevated, high or unavailable. unavailable means the evaluation had no evidence for this family. |
Inputs
Each key is received or missing.
| Key | Received when you send |
|---|---|
email | subject.email |
ip | context.ip |
user_id | subject.external_id. missing when the evaluation sent none. Without it, the email address still identifies the user. Evaluations recorded before 29 October 2026 read received. |
phone | identifiers.phone_hash |
payment | identifiers.payment |
device | identifiers.device_id, or a device token with browser data |
browser | identifiers.browser_cookie_id, or a device token with browser data |
missing inputs are the first thing to check when an integration behaves unexpectedly.
Browser data
| Value | Meaning |
|---|---|
collected | Browser data arrived with the evaluation. |
consent_declined | The visitor declined consent, so nothing was collected. |
not_loaded | Your consent tool kept the browser script from loading. |
blocked | The visitor's browser or an extension blocked collection. |
timed_out | Collection didn't finish in time. |
failed | Collection failed in the browser. |
site_not_listed | The page's site isn't in your site list. Add it in Developers → Browser setup. |
sdk_outdated | The page uses an outdated browser SDK. Update it. |
expired | The device token reached AbuseZero after it expired. Send it sooner. |
not_passed_on | Browser data was collected for this claim, but the evaluation didn't include its device token. Pass deviceToken to your server and send it. |
not_sent | The evaluation carried no device token. |
You can fix site_not_listed, sdk_outdated and not_passed_on. The others are expected for some visitors, and evaluations still run without browser data.
Create an evaluation
POST /v1/evaluations · Scope evaluations:create · Requires Idempotency-Key
Also available as abusezero evaluations create in the AbuseZero CLI, abusezero_create_evaluation on the AbuseZero MCP server and evaluate() in the AbuseZero Node.js SDK.
Evaluates one benefit claim and returns allow or deny. Call it from your server before you grant the benefit. Each evaluation uses workspace credits.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
benefit | string | Yes | The benefit's key. Lowercase letters, numbers and underscores, starting with a letter; up to 64 characters. |
claim_id | string | Yes | Your ID for this claim, 1 to 128 printable characters with no spaces. Use one claim ID per award. See Evaluating the same claim again. |
subject | object | Yes | The user making the claim. |
subject.email | string | Yes | The user's email address, up to 254 characters. |
subject.external_id | string | No | Your stable ID for the user, 1 to 128 characters. Recommended. Without it, AbuseZero identifies the user by email. |
subject.email_verified | boolean | No | true when your app has verified the email address. |
context | object or null | No | Details of the request your user made. |
context.ip | string or null | No | The user's public IP address, IPv4 or IPv6. Send the user's address, not your server's. Private, loopback and documentation addresses return IP_NOT_ROUTABLE. |
context.user_agent | string | No | The user's User-Agent, up to 512 characters. |
context.session_id | string | No | Your session ID for the user, 1 to 128 characters. |
identifiers | object | No | Further identifiers for the user and device. |
identifiers.device_token | string | No | The deviceToken the browser SDK returned, up to 4,096 characters. Send it unchanged, whatever its status. Send unavailable:not_loaded when your consent tool kept the script from loading. |
identifiers.device_id | string | No | Your own device ID, such as a mobile installation ID, 1 to 128 characters. |
identifiers.browser_cookie_id | string | No | Your own first-party browser cookie ID, 1 to 128 characters. |
identifiers.payment | object | No | The user's payment method. All three fields are required when you send it. |
identifiers.payment.provider | string | Yes, in payment | The name of your payment processor, 1 to 128 printable characters. |
identifiers.payment.scope | string | Yes, in payment | The account at that processor in which the fingerprint is unique, 1 to 128 printable characters. |
identifiers.payment.fingerprint | string | Yes, in payment | The processor's fingerprint for the payment method, 1 to 128 characters. |
identifiers.phone_hash | object | No | The user's phone number as a keyed hash. Create it with createPhoneHash from abusezero/server. Requires phone_verified. |
identifiers.phone_verified | boolean | With phone_hash | true when your app has verified the phone number. Send it together with phone_hash. |
identifiers.custom | array | No | Up to 10 custom identifiers, each { "key", "value_hash" }. Create each with createCustomIdentifier from abusezero/server. The key and its key version must be registered first. |
agent_evidence | string | No | The opaque AgentEvidence token verifyAgentRequest returned for this claim. Send it unchanged. See Verify AI agents. |
Phone numbers and custom identifiers travel as keyed hashes, so AbuseZero matches them without ever receiving the raw value. See Send identifiers.
Example
import { randomUUID } from "node:crypto";
import { AbuseZero } from "abusezero/server";
const abusezero = new AbuseZero({
key: process.env.ABUSEZERO_SECRET_KEY!,
outage: "throw",
});
// deviceToken is the value the browser SDK returned on your page.
export async function evaluateTrial(deviceToken: string) {
// Reuse this key if you retry this attempt.
const idempotencyKey = randomUUID();
const evaluation = await abusezero.evaluate(
{
benefit: "pro_trial",
claim_id: "trial_user_4821",
subject: {
external_id: "user_4821",
email: "jordan@example.com",
email_verified: true,
},
context: { ip: "203.0.113.42" },
identifiers: { device_token: deviceToken },
},
idempotencyKey,
);
if ("status" in evaluation) {
// Skipped: no allow or deny was decided.
} else if (evaluation.result === "allow") {
// Grant the trial.
}
return evaluation;
}Replace the example IP address with the user's public address. 203.0.113.42 comes from a range reserved for documentation, and AbuseZero rejects documentation, private and other non-routable addresses with IP_NOT_ROUTABLE.
Response
Returns 200 with the evaluation result.
{
"object": "evaluation",
"id": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"result": "allow",
"risk": { "score": 10, "band": "low" },
"reason": {
"code": "default_allow",
"message": "No rule matched, so the policy's default allowed this claim."
},
"outcome": { "state": "assumed_granted", "due_at": null }
}{
"object": "evaluation",
"id": "eval_8a1d4f20-6c3b-4e57-b9d2-0f7e6a5c3b18",
"result": "deny",
"risk": { "score": null, "band": "not_assessed" },
"reason": {
"code": "prior_claim_email",
"message": "An earlier claim used this email address."
},
"outcome": null
}| Field | Type | Description |
|---|---|---|
object | string | Always evaluation. |
id | string | The evaluation's ID. Store it with the claim: you need it to report the outcome. |
result | string | allow or deny. Grant the benefit only on allow. |
risk | object | The AbuseZero Risk Score. See Risk. |
reason | object or null | Why the result was reached. See Reason. |
outcome | object or null | For an allowed claim, its outcome state and due_at. null for a denied claim. |
When the claim's outcome is awaiting_outcome, report it by outcome.due_at. See Outcomes.
An allowed claim's response also carries its outcome in two headers, AbuseZero-Outcome-State and, when there is a due time, AbuseZero-Outcome-Due-At.
Re-evaluating a claim_id
A claim ID stands for one award of a benefit. AbuseZero keeps every claim consistent, however often you evaluate it:
- The same
Idempotency-Keyand body returns the original response. Nothing is evaluated twice. - A new key for a claim that was allowed returns
allowwith the reasonpreviously_authorizedand the original evaluation'sid. - A claim whose outcome you reported as
not_grantedis evaluated again from the start. - A claim ID already used by a different user returns
CLAIM_CONFLICT. Use a new claim ID for each award. - A claim ID from an earlier period of a calendar-based benefit returns
CLAIM_PERIOD_ENDED. Use a new claim ID for each period. - A claim whose outcome report is overdue returns
OUTCOME_CONFLICT. Report its outcome before evaluating it again.
Skipped evaluations
When the workspace can't run an evaluation, the response is 200 with status: "skipped" and no result. No allow or deny was decided, so handle skips as their own branch.
{
"object": "evaluation",
"status": "skipped",
"reason": "insufficient_credits"
}reason | Meaning |
|---|---|
insufficient_credits | The workspace doesn't have enough credits for the evaluation. |
billing_overdue | The workspace doesn't have enough credits while a payment is overdue. |
service_inactive | The workspace can't run evaluations, for example while it's being deleted. |
benefit_paused | The benefit is paused because the workspace is over its plan's limits. |
New skip reasons can be added within v1. Handle an unknown reason like any other skip. The workspace owner manages credits, payments and plan limits in Workspace settings → Billing & Subscriptions.
Errors
| Status | Code | When |
|---|---|---|
| 422 | IDEMPOTENCY_KEY_REQUIRED | The Idempotency-Key header is missing or invalid. |
| 422 | INVALID_FIELD | A field is missing or invalid. error.field names it. |
| 422 | INVALID_EMAIL | subject.email isn't in a supported form. Quoted and international local parts aren't supported. |
| 422 | INVALID_IP | context.ip isn't a valid IPv4 or IPv6 address. |
| 422 | IP_NOT_ROUTABLE | context.ip is a private, loopback, documentation or other non-routable address. Send the user's public address, or leave it out. |
| 422 | UNSUPPORTED_FIELD | The body has an unknown field, or a custom identifier and key version aren't registered in Developers → Custom identifiers. |
| 422 | DEVICE_TOKEN_INVALID | identifiers.device_token isn't a value the browser SDK returned. |
| 422 | DEVICE_TOKEN_SCOPE | The device token was collected for a different claim ID. |
| 422 | BROWSER_SDK_UPGRADE_REQUIRED | The device token came from an outdated browser SDK. |
| 422 | BOT_EVIDENCE_REQUIRED | The benefit requires a bot check and the evaluation has none that can be used. |
| 422 | AGENT_EVIDENCE_SCOPE | agent_evidence was produced for a different claim. |
| 422 | AGENT_EVIDENCE_OUTDATED | agent_evidence came from a Node.js SDK before 0.11.0. Update the SDK. |
| 404 | BENEFIT_NOT_FOUND | No benefit has this key. |
| 409 | IDEMPOTENCY_CONFLICT | The key was already used with a different body. |
| 409 | EVALUATION_PENDING | An evaluation with this key is still running. Retry with the same key. |
| 409 | CLAIM_CONFLICT | The claim ID belongs to a different user. |
| 409 | CLAIM_PERIOD_ENDED | The claim ID belongs to an earlier eligibility period. |
| 409 | OUTCOME_CONFLICT | The claim's earlier approval expired before its outcome was reported. |
| 409 | OUTCOME_ALREADY_RECORDED | An imported outcome already exists for this claim. |
| 409 | DEVICE_TOKEN_USED | The device token was already used for another claim. |
| 409 | DELETION_IN_PROGRESS | The user's history or the workspace is being deleted. |
| 409 | POLICY_UNAVAILABLE | The benefit's policy isn't available. |
| 413 | PAYLOAD_TOO_LARGE | The body is over 16 KiB. |
| 429 | RATE_LIMITED | The workspace is over a traffic limit. Wait for Retry-After. |
| 503 | BOT_EVIDENCE_UNAVAILABLE | The bot check couldn't be completed. Collect new browser data and retry. |
| 503 | TEMPORARILY_UNAVAILABLE | AbuseZero couldn't finish the request. Retry with the same key. |
| 504 | DEADLINE_EXCEEDED | The evaluation reached its time limit. Retry with the same key. |
List evaluations
GET /v1/evaluations · Scope evaluations:read
Also available as abusezero evaluations list in the AbuseZero CLI, abusezero_list_evaluations on the AbuseZero MCP server and evaluations() in the AbuseZero Node.js SDK.
Lists evaluations, newest first.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Items per page, 1 to 100. Defaults to 50. |
cursor | string | No | The next_cursor from the previous page. |
result | string | No | Only allow or only deny evaluations. |
benefit | string | No | Only evaluations for this benefit key. |
query | string | No | Only evaluations with this claim ID, user ID (subject.external_id) or email address. Up to 254 characters. |
days | integer | No | How many days back to look, 1 to 90. Defaults to 7. |
Example
const page = await abusezero.evaluations({ result: "deny", days: 30, limit: 20 });Response
{
"object": "list",
"data": [
{
"object": "evaluation",
"id": "eval_8a1d4f20-6c3b-4e57-b9d2-0f7e6a5c3b18",
"benefit": "pro_trial",
"claim_id": "trial_user_5307",
"result": "deny",
"reason": {
"code": "prior_claim_email",
"message": "An earlier claim used this email address."
},
"outcome": { "state": null },
"policy_version": 3,
"created_at": "2026-10-29T14:05:12.000Z"
}
],
"next_cursor": null
}Each item is a summary. Retrieve an evaluation for its full detail.
Errors
| Status | Code | When |
|---|---|---|
| 422 | CURSOR_INVALID | The cursor was changed or belongs to another list. |
| 422 | INVALID_FIELD | A query parameter is invalid. |
Retrieve an evaluation
GET /v1/evaluations/{id} · Scope evaluations:read
Also available as abusezero evaluations get in the AbuseZero CLI, abusezero_get_evaluation on the AbuseZero MCP server and getEvaluation() in the AbuseZero Node.js SDK.
Returns the full evaluation object.
Example
const evaluation = await abusezero.getEvaluation("eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93");Response
{
"object": "evaluation",
"id": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"benefit": "pro_trial",
"claim_id": "trial_user_4821",
"result": "allow",
"reason": {
"code": "default_allow",
"message": "No rule matched, so the policy's default allowed this claim."
},
"outcome": { "state": "granted", "due_at": null },
"policy_version": 3,
"created_at": "2026-10-29T14:02:47.000Z",
"mode": "protect",
"risk": { "score": 10, "band": "low" },
"inputs": {
"email": "received",
"ip": "received",
"user_id": "received",
"phone": "missing",
"payment": "missing",
"device": "received",
"browser": "received"
},
"browser_data": "collected",
"policy": {
"id": "pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35",
"version": 3
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | EVALUATION_NOT_FOUND | No evaluation with this ID exists in the workspace. |
| 422 | INVALID_FIELD | The ID doesn't start with eval_. |
Retrieve an evaluation's evidence
GET /v1/evaluations/{id}/evidence · Scope evaluation_evidence:read
Also available as abusezero evaluations evidence in the AbuseZero CLI, abusezero_get_evaluation_evidence on the AbuseZero MCP server and evaluationEvidence() in the AbuseZero Node.js SDK.
Returns the full evaluation object with its signal family assessments, plus the request details AbuseZero retained for it. With an OAuth access token, the signed-in member also needs permission to reveal evidence.
Example
const evaluation = await abusezero.evaluationEvidence("eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93");Response
The evaluation object, with signals and one more field:
{
"object": "evaluation",
"id": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"result": "allow",
"signals": [
{ "family": "identity", "assessment": "clear" },
{ "family": "device", "assessment": "clear" },
{ "family": "network", "assessment": "clear" },
{ "family": "automation", "assessment": "clear" },
{ "family": "benefit_history", "assessment": "clear" }
],
"evidence": {
"request": {
"benefit": "pro_trial",
"claim_id": "trial_user_4821",
"subject": {
"external_id": "user_4821",
"email": "jordan@example.com",
"email_verified": true
},
"context": { "ip": "203.0.113.42" }
},
"device_profile": {
"status": "captured",
"expires_at": "2026-11-28T14:02:47.000Z"
}
}
}The example shows only some fields of the evaluation object.
| Field | Type | Description |
|---|---|---|
evidence.request | object or null | The request body AbuseZero received. null once your workspace's evidence retention has removed it. |
evidence.device_profile.status | string | captured when browser data from the evaluation is still retained, not_retained when it isn't. |
evidence.device_profile.expires_at | string or null | When the retained browser data will be removed. |
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_REQUIRED | The signed-in member can't reveal evidence. |
| 404 | EVALUATION_NOT_FOUND | No evaluation with this ID exists in the workspace. |
Retrieve an evaluation's association graph
GET /v1/evaluations/{id}/graph · Scope evaluations:read
Also available as abusezero evaluations graph in the AbuseZero CLI, abusezero_get_evaluation_graph on the AbuseZero MCP server and evaluationGraph() in the AbuseZero Node.js SDK.
Returns the other outcomes for the same benefit that are linked to this evaluation's claim through shared identifiers. See Association graph.
An allowed claim's graph starts from its outcome. A claim with no outcome, such as a denied claim, starts from the identifiers its evaluation recorded, so a prior-claim denial shows the earlier outcomes behind it.
Example
const graph = await abusezero.evaluationGraph("eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93");Response
{
"object": "evaluation_links",
"evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"links": [
{ "outcome": "out_9e7c5a31-2d4b-4f86-b0a9-6c1e8d3f5a72", "family": "identity" },
{ "outcome": "out_9e7c5a31-2d4b-4f86-b0a9-6c1e8d3f5a72", "family": "device" },
{ "outcome": "out_1f3a5c7e-9b2d-4e6f-8a0c-3d5e7f9a1b24", "family": "network" }
],
"linked_outcomes": 2,
"complete": true
}| Field | Type | Description |
|---|---|---|
object | string | Always evaluation_links. |
evaluation | string | The evaluation's ID. |
links | array | One entry for each link: the linked outcome ID and the signal family that links them, identity, device, network or benefit_history. Device and browser identifiers both report device. One outcome can be linked in more than one way. |
linked_outcomes | integer | How many distinct outcomes are linked. |
complete | boolean | false when the graph is too large to return in full. |
A link shows an observed association. A shared device or network doesn't by itself mean two claims came from one person.
Errors
| Status | Code | When |
|---|---|---|
| 404 | EVALUATION_NOT_FOUND | No evaluation with this ID exists in the workspace. |
How is this guide?
Last updated on