AbuseZero

Test changes safely

Preview single claims, replay a policy change against recent traffic, and observe before you protect.

A preview runs the same evaluation as live traffic without recording a claim. Replays and simulations reuse the evidence your past evaluations retained, so a changed result comes from your policy change. This guide takes one policy change from draft to live: preview a claim, replay the change against recent traffic, simulate it in sequence, observe it on real claims, then switch it on. For how policies work, see Policies and rules.

Before you begin

Preview a single claim

A preview runs a claim through the full evaluation and returns the result, without creating a claim, an evaluation or an outcome. It takes the same input as an evaluation and adds the inputs it received to the response. Use the SDK's previewEvaluation() or POST /v1/evaluation-previews. The AbuseZero CLI command is abusezero evaluations preview, and the AbuseZero MCP server tool is abusezero_preview_evaluation.

import { abusezero } from "./lib/abusezero";

const preview = await abusezero.previewEvaluation({
  benefit: "pro_trial",
  claim_id: "preview_0001",
  subject: { external_id: "user_4821", email: "jordan@example.com", email_verified: true },
}, "preview_0001");
Response
{
  "object": "evaluation_preview",
  "preview": true,
  "result": "allow",
  "risk": { "score": 20, "band": "low" },
  "reason": {
    "code": "default_allow",
    "message": "No rule matched, so the policy's default allowed this claim."
  },
  "inputs": {
    "email": "received",
    "ip": "missing",
    "user_id": "received",
    "phone": "missing",
    "payment": "missing",
    "device": "missing",
    "browser": "missing"
  }
}

Previews use credits like evaluations, and so do runs in the Test console under Developers, which previews claims from the dashboard. Both have their own rate limit, separate from evaluations; over it, they return 429 RATE_LIMITED with a Retry-After header.

Draft the policy change

Create the change as a new policy with POST /v1/policies, abusezero policies create in the AbuseZero CLI, or abusezero_create_policy on the AbuseZero MCP server. A policy affects no claims until a benefit uses it, so you can test it freely. This one builds on the Balanced protection level and adds one rule of your own.

await abusezero.createPolicy({
  name: "Balanced with device limit",
  key: "balanced_device_limit",
  definition: {
    base: "balanced",
    default_result: "allow",
    rules: [
      {
        id: "repeat_device",
        name: "Repeat device",
        result: "deny",
        when: { signal: "device.prior_claims", operator: "gte", value: 2 },
      },
    ],
  },
}, "create-policy-balanced-device-limit");

GET /v1/policy-signals lists every condition you can use in a rule. To start from a policy you already have, duplicate it instead with POST /v1/policies/{id}/duplicate.

Test against past traffic

A replay runs your benefit's recent evaluations through the draft policy and shows which results would change. On the dashboard, it's Test against past traffic on the Policies page. The REST API call is POST /v1/replays; the AbuseZero CLI command is abusezero simulations replay, and the AbuseZero MCP server tool is abusezero_replay_evaluations.

const replay = await abusezero.replay({
  benefit_id: "ben_6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c",
  policy_key: "balanced_device_limit",
});
Response (abridged)
{
  "object": "replay",
  "limit": 500,
  "data": [
    {
      "evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
      "original": "allow",
      "replayed": "deny",
      "changed": true
    }
  ]
}

limit is how many recent evaluations the replay covers. Count the entries where changed is true, then open a few of those evaluations to check that the new results are the ones you want.

Simulate the change in sequence

A replay checks each evaluation on its own. A simulation runs your recent claims in order, starting from an empty history, as if they had arrived under the draft policy. Each claim it allows counts toward the ones after it. The REST API call is POST /v1/simulations; the AbuseZero CLI command is abusezero simulations run, and the AbuseZero MCP server tool is abusezero_simulate_sequence.

const simulation = await abusezero.simulate({
  benefit_id: "ben_6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c",
  policy_key: "balanced_device_limit",
});
Response (abridged)
{
  "object": "simulation",
  "data": [
    {
      "step": 1,
      "evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
      "result": "allow",
      "reason": {
        "code": "default_allow",
        "message": "No rule matched, so the policy's default allowed this claim."
      }
    }
  ]
}

Replays and simulations reuse evaluations you already have. They don't create new evaluations and don't use credits.

Run in observe mode

To see the change on live traffic before it decides anything, move the benefit to the draft policy in observe mode, with PATCH /v1/benefits/{id}. In observe mode, every evaluation returns allow, and each evaluation records what the policy would have decided as observed_result.

lib/save-benefit.ts
import { abusezero } from "./abusezero";

type Benefit = {
  name: string;
  policy: string;
  mode: "protect" | "observe";
  revision: number;
  config: Record<string, unknown>;
};

export async function saveBenefit(
  benefitId: string,
  changes: { policy_key?: string; protection_mode?: "protect" | "observe" },
) {
  const benefit = await abusezero.getBenefit(benefitId) as Benefit;
  const policy = await abusezero.getPolicy(benefit.policy) as { key: string };
  const config = Object.fromEntries(Object.entries(benefit.config).filter(([, value]) => value !== null));

  return abusezero.updateBenefit(benefitId, {
    name: benefit.name,
    config,
    policy_key: changes.policy_key ?? policy.key,
    protection_mode: changes.protection_mode ?? benefit.mode,
    revision: benefit.revision,
  }, `benefit:${benefitId}:revision:${benefit.revision}`);
}
await saveBenefit("ben_6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c", {
  policy_key: "balanced_device_limit",
  protection_mode: "observe",
});

In observe mode, every claim is allowed. Keep the benefit in observe mode only as long as you need to compare results. Evaluations in observe mode are real evaluations and use credits.

Switch to protect mode

When the observed results match what you expect, move the benefit back to protect mode.

await saveBenefit("ben_6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c", {
  protection_mode: "protect",
});

Verify your integration

Open an evaluation created after the switch with GET /v1/evaluations/{id}.

curl https://api.abusezero.com/v1/evaluations/eval_7c2e9b4a-1d3f-4e5a-9b6c-8d7e6f5a4b3c \
  -H "Authorization: Bearer $ABUSEZERO_SECRET_KEY"
  • mode is protect.
  • policy.id is the draft policy's ID, and policy.version is its current version.

Every evaluation keeps the policy version it ran under, so evaluations from before the switch still show the earlier policy.

How is this guide?

Last updated on

On this page