AbuseZero

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 allow or deny, 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-Key header. A retry with the same key returns the original result instead of creating a second evaluation.
  • Stable claim IDs. claim_id identifies the claim in your system. If a claim was already approved, evaluating it again returns the same approval with the reason previously_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.
POST /v1/evaluationsallow or deny, risk, reasonGrant or refuse the benefitYour serverAbuseZeroYour user
POST /v1/evaluationsallow or deny, risk, reasonGrant or refuse the benefitYour serverAbuseZeroYour user

What you send

FieldRequiredWhat it is
benefitYesThe benefit's key, such as pro_trial.
claim_idYesYour ID for this claim. 1 to 128 printable characters, no spaces. Use the same value for retries of the same claim.
subject.emailYesThe user's email address.
subject.external_idNoYour user ID. Recommended. Without it, AbuseZero identifies the user by email address.
subject.email_verifiedNotrue when your app has verified the email address.
context.ipNoThe user's public IP address, not your server's.
context.user_agent, context.session_idNoThe browser's user agent and your session ID.
identifiers.device_tokenNoThe value the browser SDK returned. Send it whenever you have it.
identifiers.device_idNoYour own device ID, such as a mobile installation ID.
identifiers.browser_cookie_idNoYour own first-party browser cookie ID.
identifiers.paymentNoThe payment method, as provider, scope and fingerprint from your payment processor.
identifiers.phone_hash, identifiers.phone_verifiedNoA keyed hash of the phone number, made with createPhoneHash, and whether your app verified it. Send both or neither.
identifiers.customNoUp to 10 custom identifiers.
agent_evidenceNoThe result of verifyAgentRequest for this claim.

The more identifiers you send, the more history each evaluation can connect. Send identifiers explains each one.

Node.js
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

Allowed
{
  "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 }
}
Denied
{
  "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
}
FieldMeaning
resultallow or deny. Act on this field.
riskThe AbuseZero Risk Score and its band.
reasonA stable code and a readable message. Every code is listed in Reason codes.
outcomeFor 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 }
}
  • inputs shows what the evaluation received from you.
  • mode is protect or observe. In observe mode, result is always allow, and observed_result shows what the policy decided.
  • GET /v1/evaluations/{id}/graph shows the earlier claims this one connects to.
  • GET /v1/evaluations/{id}/evidence returns the request exactly as AbuseZero received it, with the assessment for each signal family. It needs the evaluation_evidence:read scope.

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

Next steps

How is this guide?

Last updated on

On this page