Handle outages and retries
Choose what your app does if AbuseZero can't be reached, retry safely, and handle skipped evaluations.
Every evaluation returns one final result within the evaluation's time limit, and every write can be retried safely with the same Idempotency-Key. This guide covers the rare cases around that: AbuseZero unreachable from your server, a retryable error, and an evaluation that is skipped instead of decided. Plan for each case once, and every claim gets a defined answer.
Before you begin
- A server that evaluates claims. See Protect a benefit end to end.
Choose your outage behavior
The AbuseZero Node.js SDK's outage option decides what evaluate() returns when AbuseZero can't be reached or returns a retryable error after the SDK's own retries.
outage | What evaluate() does |
|---|---|
"throw" | Throws an AbuseZeroError with retryable: true. Your code decides. |
"allow" | Returns { source: "local_outage", result: "allow", claim_id, error }. Your users keep getting the benefit. |
"deny" | Returns { source: "local_outage", result: "deny", claim_id, error }. Nothing is granted until AbuseZero answers. |
import { AbuseZero } from "abusezero/server";
export const abusezero = new AbuseZero({
key: process.env.ABUSEZERO_SECRET_KEY!,
outage: "allow",
});Three cases never become a local decision, whatever you choose:
- Rate limits. A
429always throws, so request volume can never switch protection off. See Rate limits. - Bot check errors. Codes starting with
BOT_EVIDENCE_always throw. See Add a bot check. - Skipped evaluations. These are answers from AbuseZero, covered below and in Skipped evaluations are not errors.
Reuse the idempotency key on every retry
Every write needs an Idempotency-Key of 1 to 128 printable characters with no spaces. Store the key with the claim, and send the same key and the same request body on every retry. A retry then returns the original response instead of repeating the write: no second evaluation, outcome, import or delete.
const idempotencyKey = `evaluate:${claim.claimId}`;
await saveClaimAttempt(claim.claimId, idempotencyKey);
const evaluation = await abusezero.evaluate(input, idempotencyKey);saveClaimAttempt stands for your own storage. A changed request body needs a new key: the same key with a different body returns IDEMPOTENCY_CONFLICT.
Retry retryable errors
The Node.js SDK already retries briefly within its own request budget and honors Retry-After. If it still fails, check retryable before you retry.
import { AbuseZeroError } from "abusezero/server";
try {
return await abusezero.evaluate(input, idempotencyKey);
} catch (error) {
if (error instanceof AbuseZeroError && error.retryable) {
await scheduleRetry(claim.claimId, idempotencyKey, error.requestId);
return { status: "pending" as const };
}
throw error;
}scheduleRetry stands for your own job queue. Retry with the same key.
| Code | Status | Meaning |
|---|---|---|
EVALUATION_PENDING | 409 | The same evaluation is still processing. Retry with the same key. See Troubleshooting. |
RATE_LIMITED | 429 | Too many requests. Wait for Retry-After seconds. See Troubleshooting. |
TEMPORARILY_UNAVAILABLE | 503 | AbuseZero couldn't finish the request. Retry with the same key. |
DEADLINE_EXCEEDED | 504 | The request ran past its time limit. Retry with the same key. |
If you call the REST API directly, apply the same rules: retry 429 and 5xx responses, and responses whose error has "retryable": true, with the same Idempotency-Key, after Retry-After when it is present. Bot check errors are the exception: they need a fresh device token and a new key. Log the X-Request-ID header of every failed response. REST API errors lists every code.
Handle skipped evaluations
A skipped evaluation means the workspace couldn't run it, so no allow or deny was decided. Your outage setting doesn't apply.
{
"object": "evaluation",
"status": "skipped",
"reason": "insufficient_credits"
}reason | What it means |
|---|---|
insufficient_credits | The workspace doesn't have enough credits. |
billing_overdue | The workspace doesn't have enough credits while a payment is overdue. |
service_inactive | The workspace can't run evaluations, for example while it's being deleted. |
benefit_paused | The benefit is paused because the workspace is over its plan's limits. |
Handle a skip as its own branch, and treat a reason you don't recognize like any other skip. New reasons can be added within API v1 (REST API versioning). Troubleshooting covers each reason. The workspace owner resolves skips in Workspace settings → Billing & Subscriptions.
if ("status" in evaluation) {
await alertWorkspaceOwner(evaluation.reason);
return applyFallback(claim);
}alertWorkspaceOwner and applyFallback stand for your own alerting and your chosen fallback.
Reconcile local decisions
When evaluate() returns source: "local_outage", AbuseZero didn't decide the claim. Keep the claim ID and its idempotency key. When AbuseZero is reachable again, evaluate the claim again with the same key and body. If the first request did reach AbuseZero, you get its original result. Otherwise, the claim is evaluated now.
const evaluation = await abusezero.evaluate(input, storedIdempotencyKey);
if (!("status" in evaluation) && evaluation.source === "abusezero" && evaluation.result === "deny") {
await reviewGrantedDuringOutage(input.claim_id, evaluation);
}reviewGrantedDuringOutage stands for your own follow-up, such as a review queue.
Verify your integration
Simulate an outage with a client whose fetch always fails, then check what your code receives.
import { AbuseZero } from "abusezero/server";
const offline = new AbuseZero({
key: process.env.ABUSEZERO_SECRET_KEY!,
outage: "allow",
fetch: async () => { throw new Error("offline"); },
});
const result = await offline.evaluate(input, "outage-check-0001");
console.log(result);With outage: "allow", result.source is local_outage and result.result is allow. With outage: "throw", the call throws an AbuseZeroError whose retryable is true.
How is this guide?
Last updated on