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
| Status | Meaning | Recommended behavior |
|---|---|---|
400 | Invalid request | Correct the named field before retrying |
401 | Missing, invalid, or revoked API key | Replace the key; do not retry automatically |
403 | No 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 |
404 | Resource is absent or inaccessible | Verify the ID and API-key owner |
410 | Endpoint is retired (PROMPT_SCANS_RETIRED) | Use a supported scan track; do not retry |
429 | Plan throughput cap (SCAN_CONCURRENCY_LIMIT, SCAN_HOURLY_LIMIT) or the hourly abuse ceiling | Wait for a running scan to finish or for the time in the message or reset field |
500 / 503 | Service or deployment configuration error | Retry 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: 3540RateLimit 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.