ZeroLeaksDocs
ZeroLeaks SDK

Errors and Reliability

Handle typed SDK errors, rate limits, timeouts, cancellation, and failed scans.

The SDK exports typed errors:

import {
  ZeroLeaksAbortError,
  ZeroLeaksError,
  ZeroLeaksScanError,
  ZeroLeaksTimeoutError,
} from "@zeroleaks/sdk";

try {
  await zeroleaks.runtimeScans.run(target);
} catch (error) {
  if (error instanceof ZeroLeaksScanError) {
    console.error(error.scanId, error.details);
  } else if (error instanceof ZeroLeaksAbortError) {
    console.error("Local polling was aborted");
  } else if (error instanceof ZeroLeaksTimeoutError) {
    console.error("The request or polling operation timed out");
  } else if (error instanceof ZeroLeaksError) {
    console.error(error.status, error.code, error.requestId);
  }
}

HTTP errors

StatusMeaningRecommended behavior
400Invalid requestCorrect the named field before retrying
401Missing, invalid, or revoked API keyReplace the key; do not retry automatically
403No access to scans: no active paid plan, trial scans used up, or an inactive workspace (code: "SUBSCRIPTION_LIMIT")Change the plan or workspace; do not retry automatically
404Resource is absent or inaccessibleVerify the ID and API-key owner
410Endpoint is retired (PROMPT_SCANS_RETIRED)Use a supported scan track; do not retry
429Plan throughput cap (SCAN_CONCURRENCY_LIMIT, SCAN_HOURLY_LIMIT) or the hourly abuse ceilingWait for a running scan to finish or for the time in the message or reset field
500 / 503Service or deployment configuration errorRetry later with bounded backoff

The response body is JSON with error, code, message, hint, and docs. Existing error strings, codes, and operation-specific fields are preserved. Unknown API endpoints also return JSON with HTTP 404. The SDK copies them to error.message and error.code and keeps the full body in error.details. Scans are unlimited on paid plans; 429 only means too many scans are running or started in the last hour. See plan throughput.

The SDK does not retry any request automatically. Retrying scan creation could create duplicate scans because it is not idempotent.

Retired endpoints return 410 Gone as a ZeroLeaksError with code PROMPT_SCANS_RETIRED (for example scans.run() and scans.create()). This error is terminal; do not retry the request.

Runtime target exceptions are submitted to the active relay event and rethrown as ZeroLeaksScanError. The hosted workflow fails with the same target error and does not score the partial run.

Rate-limit headers

When the scan-creation abuse limiter runs, successful responses and errors after that check include the actual quota snapshot. The current policy is a sliding window of 120 scan creations per hour per user, shared across that user's keys. Runtime and endpoint scans share a limiter; skill scans use a separate limiter. Plan concurrency and hourly caps still apply independently. Requests that do not run a limiter do not advertise a remaining quota.

RateLimit-Policy: "default";q=120;w=3600
RateLimit: "default";r=119;t=3540
RateLimit-Limit: 120
RateLimit-Remaining: 119
RateLimit-Reset: 3540

RateLimit and RateLimit-Policy follow the IETF HTTPAPI RateLimit draft. q is the request quota, w the window in seconds, r the remaining quota, and t the seconds until reset. Compatibility RateLimit-Reset is also a delay in seconds; the existing X-RateLimit-Reset remains a Unix timestamp in milliseconds.

A 429 includes Retry-After in seconds. For the abuse limiter it reflects the reset time. When a plan cap has no precise reset header, the API recommends a 60-second retry interval; read the error message and existing scan state before starting another scan. Credential-protection limits also return quota headers on 429, and Shield keeps its own retry guidance. Quotas are per caller and responses are not cached.

Do not automatically retry scan creation. Honor Retry-After before trying again, reduce concurrency when remaining quota is low, and use bounded backoff for transient read failures.

Abort local polling

const controller = new AbortController();

const pending = zeroleaks.endpointScans.wait(scanId, {
  signal: controller.signal,
});

controller.abort();

Aborting a runtime scan or reaching its local timeout triggers best-effort cancellation of the hosted workflow. For endpoint scans, aborting only stops local polling; call endpointScans.cancel(scanId) to stop the hosted scan. runtimeScans.cancel(runtimeScanId) does the same for a runtime scan you no longer want to finish.

On this page