Verify AI agents
Recognize AI agents that act for real people, and choose which ones may claim each benefit.
AI agents now sign up, start trials and claim credits on behalf of real people. AbuseZero recognizes agents that can prove who they are, so you can let the ones you trust through while all other automation stays under your benefit's rules. Your server verifies the incoming request, and AbuseZero takes that result into account inside the evaluation. For the bigger picture, see Bots and AI agents.
Before you begin
- A server that already evaluates claims. See Protect a benefit end to end.
- A server key with the scopes
agents:read,benefits:read,benefits:write,policies:readandevaluations:create. - The AbuseZero Node.js SDK in your server code. Verification runs in your backend, where the agent's request arrives.
List recognized agents
Read the list with GET /v1/agents, abusezero agents list in the AbuseZero CLI, or abusezero_list_agents on the AbuseZero MCP server.
import { abusezero } from "./lib/abusezero";
const agents = await abusezero.agents();The response is a list of agent objects:
| Field | Type | Description |
|---|---|---|
object | "agent" | The object type. |
id | string | The value you use in a benefit's allowed_agents. |
name | string | The agent's display name. |
AbuseZero maintains this list.
Allow the agent on a benefit
Add the agent's id to the benefit's allowed_agents setting, on the Benefits page of the dashboard, with PATCH /v1/benefits/{id} in the AbuseZero REST API, with abusezero benefits update in the AbuseZero CLI, or with abusezero_update_benefit on the AbuseZero MCP server. The helper below reads the benefit and saves it with its current revision. It's the same helper as in Add a bot check.
import { abusezero } from "./abusezero";
type Benefit = {
name: string;
policy: string;
mode: "protect" | "observe";
revision: number;
config: Record<string, unknown>;
};
export async function updateBenefitConfig(benefitId: string, changes: Record<string, unknown>) {
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,
policy_key: policy.key,
protection_mode: benefit.mode,
config: { ...config, ...changes },
revision: benefit.revision,
}, `benefit:${benefitId}:revision:${benefit.revision}`);
}await updateBenefitConfig("ben_6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c", {
allowed_agents: [agentId],
});agentId is the id of the agent you chose from the list.
On this benefit, an allowed agent isn't denied or scored as automation, and it doesn't need a bot check. Recognized agents you don't allow are treated like any other automation, under the benefit's automation setting.
Verify the incoming request
In the route that receives the claim, pass the incoming request to verifyAgentRequest with the claim's ID and your app's public origin. It returns an opaque AgentEvidence token for that request, which AbuseZero verifies during the evaluation.
import { verifyAgentRequest } from "abusezero/server";
const PUBLIC_ORIGIN = "https://app.example.com";
export async function POST(request: Request) {
const body = await request.clone().json() as { claimId: string; email: string };
const agentEvidence = await verifyAgentRequest(request, body.claimId, PUBLIC_ORIGIN);
}The next step completes this route. Three rules keep verification accurate:
- Pass the original request. Call it before any proxy, middleware or framework code rewrites the request's headers or body.
- Use a configured origin.
PUBLIC_ORIGINis your app's public HTTPS origin, set in your configuration. Never build it from a forwarded header. - Use the evaluation's claim ID. The evidence is bound to that
claim_id.
verifyAgentRequest takes a standard Fetch API Request. Route handlers in many web frameworks receive one directly. If your framework gives you a Node.js request object instead, build a Request from the original method, full URL and headers.
Send the evidence with the evaluation
Pass the result unchanged as agent_evidence in evaluate() or POST /v1/evaluations, in the same request handler, right after verification.
import { verifyAgentRequest } from "abusezero/server";
import { abusezero } from "@/lib/abusezero";
const PUBLIC_ORIGIN = "https://app.example.com";
export async function POST(request: Request) {
const body = await request.clone().json() as { claimId: string; email: string };
const agentEvidence = await verifyAgentRequest(request, body.claimId, PUBLIC_ORIGIN);
const evaluation = await abusezero.evaluate({
benefit: "pro_trial",
claim_id: body.claimId,
subject: { email: body.email },
agent_evidence: agentEvidence,
}, `evaluate:${body.claimId}`);
return Response.json({ evaluation });
}Send the evidence for every claim, whether or not an agent made the request. Your route keeps one code path. Evidence for a different claim_id returns AGENT_EVIDENCE_SCOPE, and evidence from a Node.js SDK before 0.11.0 returns AGENT_EVIDENCE_OUTDATED (both invalid request).
Verify your integration
Send a claim through the route from an ordinary browser. The evaluation runs exactly as before.
Then send a claim from an allowed agent. On a benefit with automation set to deny or bot_evidence set to required, the agent's claim is evaluated on your policy like a person's claim: it isn't denied with the reason automation, and it doesn't fail with BOT_EVIDENCE_REQUIRED.
How is this guide?
Last updated on