Benefits
Create and manage the benefits AbuseZero protects, and how each one is evaluated.
A benefit is something you give users a limited number of times: a trial, a free plan, sign-up credits, a promotion. Each benefit has a key you send with every evaluation, a policy that decides its claims, and settings for eligibility, outcomes, automation and agents.
| Endpoint | Scope |
|---|---|
POST /v1/benefits | benefits:write |
GET /v1/benefits | benefits:read |
GET /v1/benefits/{id} | benefits:read |
PATCH /v1/benefits/{id} | benefits:write |
DELETE /v1/benefits/{id} | benefits:write |
With an OAuth access token, benefits:write also needs the member's Publish policies and benefits permission.
The benefit object
| Field | Type | Description |
|---|---|---|
object | string | Always benefit. |
id | string | The benefit's ID, starting with ben_. |
key | string | The key you send as benefit in evaluations. Unique in the workspace and fixed after creation. |
name | string | The benefit's display name. |
policy | string | The ID of the policy that decides its claims, starting with pol_. See Policies and rules. |
mode | string | protect or observe. See Protection mode. |
config | object | The benefit's settings. See Benefit configuration. |
revision | integer | Increases with every update. Send it back when you update the benefit. |
created_at | string | When the benefit was created. |
Protection mode
| Mode | Behavior |
|---|---|
protect | Evaluations return the result your policy reaches. |
observe | Evaluations always return allow. Each evaluation records the result your policy would have reached as observed_result, so you can measure a policy on real traffic before it affects anyone. |
Benefit configuration
Every field is optional when you create a benefit. A field you leave out takes its default.
| Field | Type | Default | Description |
|---|---|---|---|
eligibility | object | { "kind": "once" } | How often one user may receive the benefit. Fixed after creation. See Eligibility. |
outcome_tracking | string | assume_granted | assume_granted: an allowed claim counts as granted at once. report_outcome: you report the outcome of each allowed claim by its due_at. |
missing_outcome_behavior | string | hold_eligibility | For report_outcome benefits. What happens when no report arrives by due_at. hold_eligibility: the claim keeps counting. release_eligibility: the claim stops counting, and the user can claim again. |
automation | string | assess | How automated activity is treated. See Automation. |
bot_evidence | string | optional | optional or required. With required, every evaluation must carry a bot check from the browser SDK, or it returns BOT_EVIDENCE_REQUIRED. Allowed recognized agents are exempt. |
allowed_agents | array of strings | [] | The recognized AI agents allowed to claim this benefit. Use IDs from GET /v1/agents. At most 1 item. |
valuation | object | { "currency": "USD", "face_value": 0, "delivery_cost": 0 } | What one award is worth and costs you. See Valuation. |
Eligibility
| Value | Meaning |
|---|---|
{ "kind": "once" } | One award per user, ever. |
{ "kind": "calendar", "period": "day", "timezone": "Europe/Berlin" } | One award per calendar day, week or month, in the IANA time zone you set (up to 80 characters). Weeks start on Monday. |
{ "kind": "rolling", "days": 30 } | One award per rolling window of 1 to 365 days. |
To change a benefit's eligibility, create a new benefit. The existing benefit keeps its protection and history.
Automation
| Value | Behavior |
|---|---|
assess | AbuseZero Automation Defense's assessment counts toward the AbuseZero Risk Score. |
deny | Detected automation denies the claim, with the reason automation. |
allow | Detected automation doesn't count against the claim. |
Recognized agents in allowed_agents are never treated as unwanted automation for this benefit. See Bots and AI agents.
Valuation
| Field | Type | Description |
|---|---|---|
currency | string | A three-letter ISO 4217 code in capitals, such as USD. |
face_value | number | What one award is worth to the user, from 0 to 10,000,000,000, with up to two decimals. |
delivery_cost | number | What one award costs you to deliver, in the same range. |
Each outcome keeps the valuation in force when it was recorded.
Create a benefit
POST /v1/benefits · Scope benefits:write · Requires Idempotency-Key
Also available as abusezero benefits create in the AbuseZero CLI, abusezero_create_benefit on the AbuseZero MCP server and createBenefit() in the AbuseZero Node.js SDK.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name, 1 to 80 characters. |
key | string | Yes | The key you'll send in evaluations. Lowercase letters, numbers and underscores, starting with a letter; up to 64 characters. Unique in the workspace: a key that's already used returns BENEFIT_KEY_TAKEN. |
policy_key | string | Yes | The policy that decides claims: a protection level (lenient, balanced or strict) or one of your custom policies. |
protection_mode | string | Yes | protect or observe. |
config | object | Yes | The benefit configuration. Send {} to use every default. |
Example
import { randomUUID } from "node:crypto";
const benefit = await abusezero.createBenefit(
{
name: "Pro trial",
key: "pro_trial",
policy_key: "balanced",
protection_mode: "protect",
config: {
eligibility: { kind: "once" },
outcome_tracking: "report_outcome",
missing_outcome_behavior: "hold_eligibility",
valuation: { currency: "USD", face_value: 49, delivery_cost: 6.5 },
},
},
randomUUID(),
);Response
Returns 201.
{
"object": "benefit",
"id": "ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24",
"revision": 1
}Errors
| Status | Code | When |
|---|---|---|
| 422 | IDEMPOTENCY_KEY_REQUIRED | The Idempotency-Key header is missing or invalid. |
| 422 | INVALID_FIELD | A field is missing or invalid. error.field names it. |
| 422 | POLICY_NOT_FOUND | No policy has this policy_key. |
| 422 | INVALID_TIMEZONE | The calendar eligibility's timezone isn't a recognized IANA time zone. field is config.eligibility.timezone. |
| 409 | BENEFIT_KEY_TAKEN | Another benefit already uses this key. field is key. |
| 409 | IDEMPOTENCY_CONFLICT | The key was already used with a different body. |
List benefits
GET /v1/benefits · Scope benefits:read
Also available as abusezero benefits list in the AbuseZero CLI, abusezero_list_benefits on the AbuseZero MCP server and benefits() in the AbuseZero Node.js SDK.
Lists benefits, 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.benefits({ limit: 50 });Response
A list of benefit objects.
Errors
| Status | Code | When |
|---|---|---|
| 422 | CURSOR_INVALID | The cursor was changed or belongs to another list. |
Retrieve a benefit
GET /v1/benefits/{id} · Scope benefits:read
Also available as abusezero benefits get in the AbuseZero CLI, abusezero_get_benefit on the AbuseZero MCP server and getBenefit() in the AbuseZero Node.js SDK.
Example
const benefit = await abusezero.getBenefit("ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24");Response
{
"object": "benefit",
"id": "ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24",
"key": "pro_trial",
"name": "Pro trial",
"policy": "pol_7a1e3c95-4d2b-4e68-9f0a-5c8d2b6e4a17",
"mode": "protect",
"config": {
"eligibility": { "kind": "once" },
"outcome_tracking": "report_outcome",
"missing_outcome_behavior": "hold_eligibility",
"automation": "assess",
"bot_evidence": "optional",
"allowed_agents": [],
"valuation": { "currency": "USD", "face_value": 49, "delivery_cost": 6.5 }
},
"revision": 1,
"created_at": "2026-10-29T13:40:00.000Z"
}Errors
| Status | Code | When |
|---|---|---|
| 404 | RESOURCE_NOT_FOUND | No benefit with this ID exists in the workspace. |
| 422 | INVALID_FIELD | The ID doesn't start with ben_. |
Update a benefit
PATCH /v1/benefits/{id} · Scope benefits:write · Requires Idempotency-Key
Also available as abusezero benefits update in the AbuseZero CLI, abusezero_update_benefit on the AbuseZero MCP server and updateBenefit() in the AbuseZero Node.js SDK.
Replaces the benefit's name, policy, protection mode and configuration. Send every field, including the configuration fields you aren't changing: a configuration field you leave out returns to its default. The key and eligibility can't change.
The benefit object gives its policy as an ID. To keep the current policy, retrieve the policy and send its key as policy_key.
Changes apply to evaluations from the moment the update succeeds. Earlier evaluations keep the settings they ran under.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name, 1 to 80 characters. |
policy_key | string | Yes | The policy that decides claims. |
protection_mode | string | Yes | protect or observe. |
config | object | Yes | The complete benefit configuration. eligibility must match the current value. |
revision | integer | Yes | The benefit's current revision. |
Example
import { randomUUID } from "node:crypto";
const current = await abusezero.getBenefit("ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24");
await abusezero.updateBenefit(
"ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24",
{
name: current.name,
policy_key: "strict",
protection_mode: "observe",
config: current.config,
revision: current.revision,
},
randomUUID(),
);Response
Returns 200.
{
"object": "benefit",
"id": "ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24",
"revision": 2
}Errors
| Status | Code | When |
|---|---|---|
| 404 | BENEFIT_NOT_FOUND | No benefit with this ID exists in the workspace. |
| 409 | BENEFIT_CONFLICT | revision isn't the current revision. Read the benefit again. |
| 409 | BENEFIT_SCOPE_FIXED | The update changes eligibility. Create a new benefit instead. |
| 409 | IDEMPOTENCY_CONFLICT | The key was already used with a different body. |
| 422 | POLICY_NOT_FOUND | No policy has this policy_key. |
| 422 | INVALID_TIMEZONE | The calendar eligibility's timezone isn't a recognized IANA time zone. field is config.eligibility.timezone. |
| 422 | INVALID_FIELD | A field is missing or invalid. error.field names it. |
| 422 | IDEMPOTENCY_KEY_REQUIRED | The Idempotency-Key header is missing or invalid. |
Delete a benefit
DELETE /v1/benefits/{id} · Scope benefits:write · Requires Idempotency-Key
Also available as abusezero benefits delete in the AbuseZero CLI, abusezero_delete_benefit on the AbuseZero MCP server and deleteBenefit() in the AbuseZero Node.js SDK.
Deleting a benefit is permanent. It also deletes the allow and block list entries that apply only to this benefit. Evaluations that send its key fail with BENEFIT_NOT_FOUND from then on.
Example
import { randomUUID } from "node:crypto";
await abusezero.deleteBenefit("ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24", randomUUID());Response
Returns 200.
{
"object": "deleted",
"id": "ben_5b2e9c41-7d3a-4f68-a1c0-9e8d7b6a5f24",
"deleted": true
}Errors
| Status | Code | When |
|---|---|---|
| 404 | BENEFIT_NOT_FOUND | No benefit with this ID exists in the workspace. |
| 422 | IDEMPOTENCY_KEY_REQUIRED | The Idempotency-Key header is missing or invalid. |
How is this guide?
Last updated on