AbuseZero

Report outcomes

Tell AbuseZero what you actually granted, so every later evaluation checks against what really happened.

An outcome records what happened after an allow: whether you granted the benefit, didn't, or took it back. Outcomes become the history that every later evaluation is checked against, so later decisions rest on what really happened. For the full model, see Outcomes.

Before you begin

Choose how the benefit tracks outcomes

Two benefit settings decide how outcomes are recorded. Set them when you create the benefit, or change them on the Benefits page or with PATCH /v1/benefits/{id}. Both are part of the benefit's configuration.

SettingValueEffect
outcome_trackingassume_granted (default)Every allowed claim counts as granted straight away. Its outcome starts as assumed_granted (see outcome states). You report only exceptions.
report_outcomeEvery allowed claim waits for your report. Its outcome starts as awaiting_outcome, with a due_at time.
missing_outcome_behaviorhold_eligibility (default)If no report arrives by due_at, the outcome becomes outcome_unknown and still counts as claimed.
release_eligibilityIf no report arrives by due_at, the outcome becomes outcome_unknown and stops counting, so the user can claim again.

Use report_outcome when granting can fail after the evaluation, for example when a trial needs a card check. Use assume_granted when an allow always leads to a grant.

Report a granted benefit

After you grant the benefit, report granted before outcome.due_at, with the SDK's outcome() or POST /v1/evaluations/{id}/outcome. The AbuseZero CLI command is abusezero outcomes report, and the AbuseZero MCP server tool is abusezero_report_outcome. Add external_outcome_id if you want to link the outcome to your own record.

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

const report = await abusezero.outcome(evaluationId, {
  outcome: "granted",
  external_outcome_id: "trial_8f2d41",
}, `outcome:${evaluationId}:granted`);
Response
{
  "object": "outcome_report",
  "evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
  "state": "granted",
  "conflicts": []
}

Reporting the same outcome again changes nothing, so a retry with the same Idempotency-Key is always safe.

Report a benefit you didn't grant

If the claim was allowed but you didn't grant the benefit, report not_granted with a reason of 3 to 500 characters. The claim stops counting, so the user can claim the benefit again.

await abusezero.outcome(evaluationId, {
  outcome: "not_granted",
  reason: "Card check failed before the trial started.",
}, `outcome:${evaluationId}:not_granted`);

Report a reversal

If you granted the benefit and later took it back, for example with a refund, report reversed with a reason. A reversed benefit still counts as received.

await abusezero.outcome(evaluationId, {
  outcome: "reversed",
  reason: "Refunded at the customer's request.",
}, `outcome:${evaluationId}:reversed`);

Each report moves the outcome from its current state:

ReportAccepted when the outcome is
grantedawaiting_outcome, outcome_unknown or assumed_granted
not_grantedawaiting_outcome, outcome_unknown or assumed_granted
reversedgranted or assumed_granted

Any other change returns OUTCOME_CONFLICT. Only allowed evaluations have outcomes, so a report for a denied evaluation also returns OUTCOME_CONFLICT. Allowed changes lists every transition.

Check for conflicts

A granted report can arrive after due_at on a benefit set to release_eligibility. By then, the same user may have claimed again. When that happens, conflicts lists the outcomes (out_…) recorded meanwhile for the same identifiers.

if (report.conflicts.length > 0) {
  await flagForReview(evaluationId, report.conflicts);
}

flagForReview stands for your own review process. Review each listed outcome and report reversed or not_granted where the benefit shouldn't stand.

Verify your integration

Read the outcomes for your workspace with GET /v1/outcomes, abusezero outcomes list in the AbuseZero CLI, or abusezero_list_outcomes on the AbuseZero MCP server. Outcomes still in awaiting_outcome near their due_at, or already in outcome_unknown, mean reports aren't arriving.

const outcomes = await abusezero.outcomes({ limit: 20 });
Response (abridged)
{
  "object": "list",
  "data": [
    {
      "object": "outcome",
      "id": "out_9a7b6c5d-4e3f-4a2b-8c1d-0e9f8a7b6c5d",
      "evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
      "state": "granted",
      "source": "evaluation",
      "occurred_at": "2026-10-29T15:01:12.000Z",
      "due_at": "2026-10-29T15:10:00.000Z"
    }
  ],
  "next_cursor": null
}

How is this guide?

Last updated on

On this page