Evaluations
One call that decides whether a user should receive a benefit, with a result, a risk assessment and a reason.
An evaluation is one decision on one claim: should this user receive this benefit, right now? Your server calls POST /v1/evaluations before it grants anything, and AbuseZero returns allow or deny.
Why it matters
Everything AbuseZero knows about a claim comes together in this one call: the user's identifiers, the benefit's full claim history, your policy, and the device, network and automation assessments. Your code stays simple. It asks one question and acts on one answer. The decision logic lives in AbuseZero, where you can change it without shipping code.
Key capabilities
- One final result. Every successful evaluation returns
allowordeny, within the evaluation's time limit. A technical problem is always an explicit error, never a disguised result. - Safe retries. Every evaluation requires an
Idempotency-Keyheader. A retry with the same key returns the original result instead of creating a second evaluation. - Stable claim IDs.
claim_ididentifies the claim in your system. If a claim was already approved, evaluating it again returns the same approval with the reasonpreviously_authorized, instead of counting a second claim. - Five signal families. Each evaluation assesses five signal families (identity, device, network, automation and benefit history) and checks every identifier against the benefit's full claim history.
- Evaluation time limit. If a source of evidence is slow or unavailable, the evaluation finishes on time with what it has. The evaluation reports exactly which inputs it received.
- Saved before success. The evaluation, the claim and its idempotency record are saved before AbuseZero returns a successful response.
- Recorded policy version. Each evaluation records the policy version it ran under, so you can explain any past decision, even after your rules change.
What you send
| Field | Required | What it is |
|---|---|---|
benefit | Yes | The benefit's key, such as pro_trial. |
claim_id | Yes | Your ID for this claim. 1 to 128 printable characters, no spaces. Use the same value for retries of the same claim. |
subject.email | Yes | The user's email address. |
subject.external_id | No | Your user ID. Recommended. Without it, AbuseZero identifies the user by email address. |
subject.email_verified | No | true when your app has verified the email address. |
context.ip | No | The user's public IP address, not your server's. |
context.user_agent, context.session_id | No | The browser's user agent and your session ID. |
identifiers.device_token | No | The value the browser SDK returned. Send it whenever you have it. |
identifiers.device_id | No | Your own device ID, such as a mobile installation ID. |
identifiers.browser_cookie_id | No | Your own first-party browser cookie ID. |
identifiers.payment | No | The payment method, as provider, scope and fingerprint from your payment processor. |
identifiers.phone_hash, identifiers.phone_verified | No | A keyed hash of the phone number, made with createPhoneHash, and whether your app verified it. Send both or neither. |
identifiers.custom | No | Up to 10 custom identifiers. |
agent_evidence | No | The result of verifyAgentRequest for this claim. |
The more identifiers you send, the more history each evaluation can connect. Send identifiers explains each one.
import { AbuseZero } from "abusezero/server";
const abusezero = new AbuseZero({ key: process.env.ABUSEZERO_SECRET_KEY!, outage: "throw" });
const evaluation = await abusezero.evaluate(
{
benefit: "pro_trial",
claim_id: "signup_8f2d41",
subject: { external_id: "user_4821", email: "jordan@example.com", email_verified: true },
context: { ip: "203.0.113.42" },
identifiers: { device_token: deviceToken },
},
"claim:signup_8f2d41",
);
if ("status" in evaluation) {
// Skipped: no result was decided. Handle it as its own branch.
} else if (evaluation.result === "allow") {
// Grant the trial.
}203.0.113.42 comes from a range reserved for documentation. In real calls, send your visitor's public address: documentation, private and other non-routable addresses return IP_NOT_ROUTABLE.
Response data
{
"object": "evaluation",
"id": "eval_9a41c7e2-5b3d-4e8f-a216-0c7d3b9e5f81",
"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_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"result": "deny",
"risk": { "score": 80, "band": "high" },
"reason": { "code": "risk_threshold", "message": "The AbuseZero Risk Score reached this policy's threshold." },
"outcome": null
}| Field | Meaning |
|---|---|
result | allow or deny. Act on this field. |
risk | The AbuseZero Risk Score and its band. |
reason | A stable code and a readable message. Every code is listed in Reason codes. |
outcome | For allowed claims, the outcome AbuseZero is tracking, and when it's due. |
Skipped evaluations
When a workspace can't run an evaluation, the response has no result:
{ "object": "evaluation", "status": "skipped", "reason": "insufficient_credits" }No decision was made, so handle a skip as its own branch in your flow. The reason is insufficient_credits, billing_overdue, service_inactive or benefit_paused. New reasons can be added within v1, so treat an unfamiliar reason like any other skip.
Find an evaluation
GET /v1/evaluations/{id} returns the full record:
{
"object": "evaluation",
"id": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"benefit": "pro_trial",
"claim_id": "signup_8f2d41",
"result": "deny",
"reason": { "code": "risk_threshold", "message": "The AbuseZero Risk Score reached this policy's threshold." },
"outcome": { "state": null, "due_at": null },
"policy_version": 4,
"created_at": "2026-10-28T16:02:11.000Z",
"mode": "protect",
"risk": { "score": 80, "band": "high" },
"inputs": { "email": "received", "ip": "received", "user_id": "received", "phone": "missing", "payment": "missing", "device": "received", "browser": "received" },
"browser_data": "collected",
"policy": { "id": "pol_51b9e2a7-0c4d-4f6e-8a13-7d2c9e5f0b14", "version": 4 }
}inputsshows what the evaluation received from you.modeisprotectorobserve. In observe mode,resultis alwaysallow, andobserved_resultshows what the policy decided.GET /v1/evaluations/{id}/graphshows the earlier claims this one connects to.GET /v1/evaluations/{id}/evidencereturns the request exactly as AbuseZero received it, with the assessment for each signal family. It needs theevaluation_evidence:readscope.
Every evaluation is billed
Evaluations, previews and Test console runs all use credits from your workspace. Workspace owners see balances and plans in Workspace settings → Billing & Subscriptions.
Programmatic access
| Interface | Use |
|---|---|
| AbuseZero REST API | POST /v1/evaluations, POST /v1/evaluation-previews, GET /v1/evaluations, GET /v1/evaluations/{id}, GET /v1/evaluations/{id}/evidence |
| AbuseZero Node.js SDK | evaluate(), previewEvaluation(), evaluations(), getEvaluation(), evaluationEvidence() |
| AbuseZero CLI | abusezero evaluations create, preview, list, get, evidence |
| AbuseZero MCP server | abusezero_create_evaluation, abusezero_preview_evaluation, abusezero_list_evaluations, abusezero_get_evaluation, abusezero_get_evaluation_evidence |
| Dashboard | Evaluations, and Developers → Test console for single test requests |
Next steps
How is this guide?
Last updated on