Outcomes
Report whether an allowed claim was granted, and read the outcome of every claim.
Every allowed claim has an outcome: the record of whether the user actually received the benefit. The outcomes you report become the history that later evaluations are checked against.
| Endpoint | Scope |
|---|---|
POST /v1/evaluations/{id}/outcome | outcomes:write |
GET /v1/outcomes | outcomes:read |
GET /v1/outcomes/{id} | outcomes:read |
How outcomes start depends on the benefit's outcome_tracking setting. See Benefits.
The outcome object
| Field | Type | Description |
|---|---|---|
object | string | Always outcome. |
id | string | The outcome's ID, starting with out_. |
evaluation | string or null | The evaluation that allowed the claim. null for an imported outcome. See Evaluations. |
state | string | The outcome's state. See Outcome states. |
source | string | evaluation when an evaluation created it, or import when it came from an import. |
occurred_at | string | When the claim was allowed, or when an imported benefit was granted. |
due_at | string or null | When the outcome report is due, for benefits that use report_outcome. Otherwise null. |
Outcome states
| State | Meaning | Counts toward eligibility |
|---|---|---|
assumed_granted | The benefit uses assume_granted, so the allowed claim counts as granted at once. | Yes |
awaiting_outcome | The benefit uses report_outcome. AbuseZero is waiting for your report by due_at. | Yes |
granted | You reported or imported that the user received the benefit. | Yes |
not_granted | You reported that the user didn't receive the benefit. The user can claim it again. | No |
outcome_unknown | No report arrived by due_at. The benefit's missing_outcome_behavior decides whether it counts. | hold_eligibility: yes. release_eligibility: no. |
reversed | You reported that a granted benefit was taken back. The claim still counts. | Yes |
New states can be added within v1.
Report an outcome
POST /v1/evaluations/{id}/outcome · Scope outcomes:write · Requires Idempotency-Key
Also available as abusezero outcomes report in the AbuseZero CLI, abusezero_report_outcome on the AbuseZero MCP server and outcome() in the AbuseZero Node.js SDK.
Reports what happened to an allowed claim. {id} is the ID of the evaluation that allowed it.
Report granted when you deliver the benefit and not_granted when you don't. Report reversed when you take a granted benefit back. For a benefit that uses report_outcome, report by the due_at the evaluation returned.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
outcome | string | Yes | granted, not_granted or reversed. |
reason | string | With not_granted or reversed | Why the outcome changed, 3 to 500 characters. Required when you report not_granted or reversed and the outcome is in a different state. |
external_outcome_id | string | No | Your own ID for the grant, such as a subscription or order ID. 1 to 128 printable characters with no spaces. |
Allowed changes
| You report | Allowed when the outcome is |
|---|---|
granted | assumed_granted, awaiting_outcome or outcome_unknown |
not_granted | assumed_granted, awaiting_outcome or outcome_unknown |
reversed | granted or assumed_granted |
Reporting the state the outcome already has changes nothing, and is a safe way to attach an external_outcome_id. Every change is recorded in your audit log.
Example
import { randomUUID } from "node:crypto";
const report = await abusezero.outcome(
"eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
{ outcome: "granted", external_outcome_id: "sub_20481" },
randomUUID(),
);Response
Returns 200.
{
"object": "outcome_report",
"evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"state": "granted",
"conflicts": []
}| Field | Type | Description |
|---|---|---|
object | string | Always outcome_report. |
evaluation | string | The evaluation the outcome belongs to. |
state | string | The outcome's state after the report. |
conflicts | array | Outcome IDs that now overlap with this claim. See below. |
Conflicts
For a benefit that uses release_eligibility, a claim stops counting when its report is overdue. If you then report it granted, other claims with the same identifiers may have been allowed in the meantime. conflicts lists their outcome IDs so you can reconcile them in your application. It's empty in every other case.
Errors
| Status | Code | When |
|---|---|---|
| 422 | IDEMPOTENCY_KEY_REQUIRED | The Idempotency-Key header is missing or invalid. |
| 422 | INVALID_FIELD | outcome is missing, a field is invalid, or the ID doesn't start with eval_. |
| 422 | UNSUPPORTED_FIELD | The body has a field this endpoint doesn't define. |
| 422 | REASON_REQUIRED | A not_granted or reversed report that changes the outcome has no reason. |
| 404 | EVALUATION_NOT_FOUND | No evaluation with this ID exists in the workspace. |
| 409 | OUTCOME_CONFLICT | The evaluation was denied, a newer evaluation replaced it for this claim, the change isn't allowed from the current state, or the external_outcome_id belongs to another outcome. Read the evaluation's current outcome before reporting again. |
| 409 | IDEMPOTENCY_CONFLICT | The key was already used with a different body. |
| 429 | RATE_LIMITED | The workspace is over a traffic limit. Wait for Retry-After. |
List outcomes
GET /v1/outcomes · Scope outcomes:read
Also available as abusezero outcomes list in the AbuseZero CLI, abusezero_list_outcomes on the AbuseZero MCP server and outcomes() in the AbuseZero Node.js SDK.
Lists outcomes, 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. |
Example
const page = await abusezero.outcomes({ limit: 50 });Response
{
"object": "list",
"data": [
{
"object": "outcome",
"id": "out_9e7c5a31-2d4b-4f86-b0a9-6c1e8d3f5a72",
"evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"state": "assumed_granted",
"source": "evaluation",
"occurred_at": "2026-10-29T14:02:47.000Z",
"due_at": null
},
{
"object": "outcome",
"id": "out_1f3a5c7e-9b2d-4e6f-8a0c-3d5e7f9a1b24",
"evaluation": null,
"state": "granted",
"source": "import",
"occurred_at": "2026-09-14T08:30:00.000Z",
"due_at": null
}
],
"next_cursor": "kZ3x9QvB7mT2pLr8Wn4cYh6dFs1aJe5u"
}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 outcome
GET /v1/outcomes/{id} · Scope outcomes:read
Also available as abusezero outcomes get in the AbuseZero CLI, abusezero_get_outcome on the AbuseZero MCP server and getOutcome() in the AbuseZero Node.js SDK.
Returns one outcome object.
Example
const outcome = await abusezero.getOutcome("out_9e7c5a31-2d4b-4f86-b0a9-6c1e8d3f5a72");Response
{
"object": "outcome",
"id": "out_9e7c5a31-2d4b-4f86-b0a9-6c1e8d3f5a72",
"evaluation": "eval_3f6c2a1e-8b4d-4c7a-9e2f-5d1b7a0c4e93",
"state": "granted",
"source": "evaluation",
"occurred_at": "2026-10-29T14:02:47.000Z",
"due_at": null
}Errors
| Status | Code | When |
|---|---|---|
| 404 | RESOURCE_NOT_FOUND | No outcome with this ID exists in the workspace. |
| 422 | INVALID_FIELD | The ID doesn't start with out_. |
Next steps
How is this guide?
Last updated on