Policies
Protection levels, custom policies, rule format, versions and the conditions available to your rules.
A policy decides the claims of every benefit that uses it. Every workspace has three protection levels, Lenient, Balanced and Strict, which AbuseZero defines and maintains. You can use a level as it is, or build a custom policy on one with your own rules.
| Endpoint | Scope |
|---|---|
POST /v1/policies | policies:write |
GET /v1/policies | policies:read |
GET /v1/policies/{id} | policies:read |
PATCH /v1/policies/{id} | policies:write |
POST /v1/policies/{id}/duplicate | policies:write |
DELETE /v1/policies/{id} | policies:write |
GET /v1/policies/{id}/versions | policies:read |
GET /v1/policies/{id}/versions/{version} | policies:read |
GET /v1/policy-signals | policies:read |
With an OAuth access token, policies:write also needs the member's Publish policies and benefits permission.
The policy object
| Field | Type | Description |
|---|---|---|
object | string | Always policy. |
id | string | The policy's ID, starting with pol_. |
key | string | The key benefits use to select the policy. |
name | string | The policy's display name. |
kind | string | managed for a protection level, custom for your own policy. |
active_version | integer | The version evaluations use now. |
created_at | string | When the policy was created. |
updated_at | string | When the policy last changed. |
Protection levels
Protection levels appear in every workspace as managed policies with the keys lenient, balanced and strict. AbuseZero defines and maintains what each level contains. Levels are read-only: to change one, duplicate it and edit the copy.
See Protection levels for how to choose one.
Custom policy definition
A custom policy is a definition you write. It can build on a protection level.
| Field | Type | Required | Description |
|---|---|---|---|
base | string | No | The protection level to build on: lenient, balanced or strict. |
default_result | string | Yes | allow or deny: the result when nothing else decides. |
risk_threshold | integer or null | No | A multiple of 5 from 5 to 100; other values return INVALID_FIELD. A claim whose AbuseZero Risk Score reaches this value is denied when no rule decides it. Leave it out or send null to keep the setting of your base level, or AbuseZero's standard setting when there is no base. |
rules | array | Yes | Up to 50 of your own rules. Send [] for none. |
How a policy decides
- Your rules are checked in the order you list them. The first rule that matches decides the result.
- Your rules take priority over the protection level you build on.
- When no rule decides, a claim whose AbuseZero Risk Score reaches
risk_thresholdis denied. - Otherwise,
default_resultapplies.
Allow and block list entries decide a matching claim outright, whatever the policy says. Each evaluation's reason tells you what decided it. See Reason codes.
Rules
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Your ID for the rule, 1 to 64 characters, unique in the policy. The evaluation's reason.rule returns this ID when the rule decides a claim. |
name | string | Yes | A readable name, 1 to 100 characters. When you retrieve an evaluation the rule decided, its reason.message quotes this name. |
result | string | Yes | allow or deny: the result when the rule matches. |
when | object | Yes | A condition, or a group of conditions. |
Conditions and groups
A condition compares one value with your value:
{ "signal": "network.anonymous", "operator": "eq", "value": true }| Field | Type | Required | Description |
|---|---|---|---|
signal | string | Yes | A condition key from List conditions. |
operator | string | Yes | See Operators. |
value | string, number or boolean | For comparisons | The value to compare with. Its type must match the condition's type. Leave it out for status operators. |
Combine conditions with a group. all matches when every item matches. any matches when at least one item matches.
{
"all": [
{ "signal": "network.anonymous", "operator": "eq", "value": true },
{ "any": [
{ "signal": "email.verified", "operator": "eq", "value": false },
{ "signal": "email.disposable", "operator": "eq", "value": true }
] }
]
}- A group holds 1 to 10 items.
- Groups nest at most two levels deep.
- A rule has at most 10 conditions in total.
Operators
| Operator | Matches when | Condition types |
|---|---|---|
eq | The value equals value. | All |
neq | The value differs from value. | All |
gt | The value is greater than value. | Number |
gte | The value is greater than or equal to value. | Number |
lt | The value is less than value. | Number |
lte | The value is less than or equal to value. | Number |
is_missing | You didn't send the input this condition needs. | All |
is_unknown | AbuseZero received the input but couldn't determine a value for it. | All |
is_unavailable | No value was available when the evaluation ran. | All |
Comparison operators match only when the condition has a value. Use the status operators to write rules for inputs you didn't send or values that weren't available.
Create a policy
POST /v1/policies · Scope policies:write · Requires Idempotency-Key
Also available as abusezero policies create in the AbuseZero CLI, abusezero_create_policy on the AbuseZero MCP server and createPolicy() in the AbuseZero Node.js SDK.
Creates a custom policy at version 1.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name, 1 to 80 characters. |
key | string | Yes | Lowercase letters, numbers and underscores, starting with a letter; up to 64 characters. Unique in the workspace. |
definition | object | Yes | The custom policy definition. |
Example
import { randomUUID } from "node:crypto";
const policy = await abusezero.createPolicy(
{
name: "Trial protection",
key: "trial_protection",
definition: {
base: "balanced",
default_result: "allow",
rules: [
{
id: "unverified_anonymous",
name: "Unverified email on an anonymous network",
result: "deny",
when: {
all: [
{ signal: "network.anonymous", operator: "eq", value: true },
{ signal: "email.verified", operator: "eq", value: false },
],
},
},
],
},
},
randomUUID(),
);Response
Returns 201.
{
"object": "policy",
"id": "pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35",
"revision": 1,
"version": 1
}To start using the policy, set it as a benefit's policy_key. To see its effect first, run a replay.
Errors
| Status | Code | When |
|---|---|---|
| 409 | POLICY_KEY_TAKEN | Another policy uses this key. |
| 409 | IDEMPOTENCY_CONFLICT | The key was already used with a different body. |
| 422 | INVALID_FIELD | The definition is invalid, for example a duplicate rule ID, a reserved rule ID, a value of the wrong type, or groups nested too deeply. error.field gives the path to the problem, such as definition.rules.0.id. |
| 422 | UNSUPPORTED_FIELD | A rule uses a condition that isn't supported, or a custom identifier that isn't registered. |
| 422 | POLICY_NOT_FOUND | base isn't an available protection level. |
| 422 | IDEMPOTENCY_KEY_REQUIRED | The Idempotency-Key header is missing or invalid. |
List policies
GET /v1/policies · Scope policies:read
Also available as abusezero policies list in the AbuseZero CLI, abusezero_list_policies on the AbuseZero MCP server and policies() in the AbuseZero Node.js SDK.
Lists the workspace's protection levels and custom policies, 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.policies({ limit: 50 });Response
A list of policy objects.
Errors
| Status | Code | When |
|---|---|---|
| 422 | CURSOR_INVALID | The cursor was changed or belongs to another list. |
Retrieve a policy
GET /v1/policies/{id} · Scope policies:read
Also available as abusezero policies get in the AbuseZero CLI, abusezero_get_policy on the AbuseZero MCP server and getPolicy() in the AbuseZero Node.js SDK.
Example
const policy = await abusezero.getPolicy("pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35");Response
{
"object": "policy",
"id": "pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35",
"key": "trial_protection",
"name": "Trial protection",
"kind": "custom",
"active_version": 2,
"created_at": "2026-10-28T09:15:00.000Z",
"updated_at": "2026-10-29T11:20:00.000Z"
}To read the rules, retrieve the active version.
Errors
| Status | Code | When |
|---|---|---|
| 404 | RESOURCE_NOT_FOUND | No policy with this ID exists in the workspace. |
| 422 | INVALID_FIELD | The ID doesn't start with pol_. |
Update a policy
PATCH /v1/policies/{id} · Scope policies:write · Requires Idempotency-Key
Also available as abusezero policies update in the AbuseZero CLI, abusezero_update_policy on the AbuseZero MCP server and updatePolicy() in the AbuseZero Node.js SDK.
Saves a new version of a custom policy. Send the complete definition. Every benefit that uses the policy evaluates with the new version from the moment the update succeeds. Earlier evaluations keep the version they ran under.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name, 1 to 80 characters. |
definition | object | Yes | The complete custom policy definition. |
revision | integer | Yes | The policy's current active_version. |
Example
import { randomUUID } from "node:crypto";
await abusezero.updatePolicy(
"pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35",
{
name: "Trial protection",
definition: { base: "strict", default_result: "allow", rules: [] },
revision: 2,
},
randomUUID(),
);Response
Returns 200.
{
"object": "policy",
"id": "pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35",
"revision": 3,
"version": 3
}Errors
| Status | Code | When |
|---|---|---|
| 404 | POLICY_NOT_FOUND | No custom policy with this ID exists in the workspace. |
| 409 | POLICY_CONFLICT | revision isn't the current version. Read the policy again. |
| 409 | PROTECTION_LEVEL_READ_ONLY | The policy is a protection level. Duplicate it instead. |
| 422 | INVALID_FIELD | The definition is invalid. |
| 422 | UNSUPPORTED_FIELD | A rule uses an unsupported condition or an unregistered custom identifier. |
Duplicate a policy
POST /v1/policies/{id}/duplicate · Scope policies:write · Requires Idempotency-Key
Also available as abusezero policies duplicate in the AbuseZero CLI, abusezero_duplicate_policy on the AbuseZero MCP server and duplicatePolicy() in the AbuseZero Node.js SDK.
Creates a new custom policy from the active version of an existing one. Duplicating a protection level creates a custom policy built on that level, ready for your own rules.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the copy, 1 to 80 characters. |
key | string | Yes | Key for the copy. Lowercase letters, numbers and underscores, starting with a letter; up to 64 characters. |
Example
import { randomUUID } from "node:crypto";
const copy = await abusezero.duplicatePolicy(
"pol_0e6b4d82-7a3c-4f19-8b5e-1d9c7a3f5e60",
{ name: "Balanced with my rules", key: "balanced_custom" },
randomUUID(),
);Response
Returns 201.
{
"object": "policy",
"id": "pol_b3f7d1a9-2c6e-4d85-9a0b-6e4c2f8d1b39",
"revision": 1,
"version": 1
}Errors
| Status | Code | When |
|---|---|---|
| 404 | POLICY_NOT_FOUND | No policy with this ID exists in the workspace. |
| 409 | POLICY_KEY_TAKEN | Another policy uses this key. |
Delete a policy
DELETE /v1/policies/{id} · Scope policies:write · Requires Idempotency-Key
Also available as abusezero policies delete in the AbuseZero CLI, abusezero_delete_policy on the AbuseZero MCP server and deletePolicy() in the AbuseZero Node.js SDK.
Deleting a custom policy is permanent and removes all its versions. Move every benefit that uses it to another policy first. Protection levels can't be deleted.
Example
import { randomUUID } from "node:crypto";
await abusezero.deletePolicy("pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35", randomUUID());Response
Returns 200.
{
"object": "deleted",
"id": "pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35",
"deleted": true
}Errors
| Status | Code | When |
|---|---|---|
| 404 | POLICY_NOT_FOUND | No custom policy with this ID exists in the workspace. A retry with the same Idempotency-Key after a successful delete returns the original response instead. |
| 409 | POLICY_IN_USE | Benefits still use the policy. The message says how many. |
| 409 | PROTECTION_LEVEL_READ_ONLY | The policy is a protection level. |
List policy versions
GET /v1/policies/{id}/versions · Scope policies:read
Also available as abusezero policies versions in the AbuseZero CLI, abusezero_list_policy_versions on the AbuseZero MCP server and call() in the AbuseZero Node.js SDK.
Lists every version of a policy, newest first, in one page.
Example
const versions = await abusezero.call(
"/v1/policies/pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35/versions",
);Response
{
"object": "list",
"data": [
{ "object": "policy_version", "version": 2, "created_at": "2026-10-29T11:20:00.000Z" },
{ "object": "policy_version", "version": 1, "created_at": "2026-10-28T09:15:00.000Z" }
],
"next_cursor": null
}Retrieve a policy version
GET /v1/policies/{id}/versions/{version} · Scope policies:read
Also available as abusezero policies version in the AbuseZero CLI, abusezero_get_policy_version on the AbuseZero MCP server and call() in the AbuseZero Node.js SDK.
Returns one version. For a custom policy, the version includes the definition you saved. For a protection level, it names the level only: AbuseZero keeps each level's contents private.
Example
const version = await abusezero.call(
"/v1/policies/pol_c4d8e2a7-1b5f-4a93-8e6d-2f0b9c7a1e35/versions/2",
);Response
{
"object": "policy_version",
"version": 2,
"managed": false,
"definition": {
"base": "balanced",
"risk_threshold": 75,
"default_result": "allow",
"rules": [
{
"id": "unverified_anonymous",
"name": "Unverified email on an anonymous network",
"result": "deny",
"when": {
"all": [
{ "signal": "network.anonymous", "operator": "eq", "value": true },
{ "signal": "email.verified", "operator": "eq", "value": false }
]
}
}
]
},
"created_at": "2026-10-29T11:20:00.000Z"
}{
"object": "policy_version",
"version": 1,
"managed": true,
"level": "balanced",
"created_at": "2026-10-01T08:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
object | string | Always policy_version. |
version | integer | The version number. |
managed | boolean | true for a protection level. |
definition | object | Custom policies only. The definition saved with this version: base, risk_threshold, default_result and your rules. risk_threshold is null unless you set it yourself, so a definition you read can be saved back unchanged. |
level | string | Protection levels only. lenient, balanced or strict. |
created_at | string | When the version was saved. |
Errors
| Status | Code | When |
|---|---|---|
| 404 | POLICY_VERSION_NOT_FOUND | The policy has no such version. |
List conditions
GET /v1/policy-signals · Scope policies:read
Also available as abusezero policies signals in the AbuseZero CLI, abusezero_list_policy_signals on the AbuseZero MCP server and call() in the AbuseZero Node.js SDK.
Lists every condition your custom rules can use, in one page. Takes no parameters.
Example
const conditions = await abusezero.call("/v1/policy-signals");Response
{
"object": "list",
"data": [
{ "object": "condition", "key": "email.verified", "label": "Email verified by your app", "type": "boolean" },
{ "object": "condition", "key": "user.prior_claims", "label": "Earlier claims by this user", "type": "number", "min": 0 },
{ "object": "condition", "key": "network.risk", "label": "Network risk", "type": "string", "values": ["low", "elevated", "high"] }
],
"next_cursor": null
}The example shows three of the conditions.
| Field | Type | Description |
|---|---|---|
key | string | The value to use as signal in a condition. |
label | string | A readable name. |
type | string | boolean, number or string. A comparison's value must have this type. |
values | array | Present when only certain values are allowed. |
min, max | number | Present when numbers have a range. |
Available conditions
| Key | Label | Type |
|---|---|---|
email.verified | Email verified by your app | boolean |
email.disposable | Disposable email address | boolean |
phone.verified | Phone verified by your app | boolean |
user.prior_claims | Earlier claims by this user | whole number, 0 or more |
email.prior_claims | Earlier claims with this email | whole number, 0 or more |
device.prior_claims | Earlier claims from this device | whole number, 0 or more |
payment.prior_claims | Earlier claims with this payment method | whole number, 0 or more |
phone.prior_claims | Earlier claims with this phone | whole number, 0 or more |
network.risk | Network risk | low, elevated or high |
network.anonymous | Anonymous network | boolean |
network.repeat_attempts | Recent attempts from this network | whole number, 0 or more |
network.country | Network country (ISO code) | string, such as DE |
automation.detected | Automation detected | boolean |
custom.<key>.prior_outcomes | Earlier claims with your custom identifier <key> | whole number, 0 or more |
For a custom identifier condition, replace <key> with the key of a registered custom identifier, such as custom.license_id.prior_outcomes. A custom identifier that a policy uses can't be deleted. New conditions can be added within v1.
How is this guide?
Last updated on