export const ERROR_CODES = [ "INVALID_API_KEY", "EMAIL_NOT_VERIFIED", "RATE_LIMITED", "CONCURRENCY_LIMIT", "INVALID_REQUEST", "URL_NOT_ALLOWED", "TARGET_TIMEOUT", "TARGET_BLOCKED", "TARGET_UNAVAILABLE", "PROVIDER_UNAVAILABLE", "NETWORK_UNAVAILABLE", "BROWSER_UNAVAILABLE", "BROWSER_TIMEOUT", "RESPONSE_TOO_LARGE", "TOO_MANY_REDIRECTS", "INSUFFICIENT_CREDITS", "USAGE_LIMIT_REACHED", "SESSION_NOT_FOUND", "SESSION_EXPIRED", "CRAWL_NOT_FOUND", "CRAWL_LIMIT_REACHED", "NOT_FOUND", "FORBIDDEN", "INTERNAL_ERROR", ] as const; export type ErrorCode = (typeof ERROR_CODES)[number]; export const ERROR_HTTP_STATUS: Record = { INVALID_API_KEY: 401, EMAIL_NOT_VERIFIED: 403, RATE_LIMITED: 429, CONCURRENCY_LIMIT: 429, INVALID_REQUEST: 400, URL_NOT_ALLOWED: 400, TARGET_TIMEOUT: 504, TARGET_BLOCKED: 502, TARGET_UNAVAILABLE: 502, PROVIDER_UNAVAILABLE: 503, NETWORK_UNAVAILABLE: 400, BROWSER_UNAVAILABLE: 400, BROWSER_TIMEOUT: 504, RESPONSE_TOO_LARGE: 502, TOO_MANY_REDIRECTS: 502, INSUFFICIENT_CREDITS: 402, USAGE_LIMIT_REACHED: 402, SESSION_NOT_FOUND: 404, SESSION_EXPIRED: 410, CRAWL_NOT_FOUND: 404, CRAWL_LIMIT_REACHED: 429, NOT_FOUND: 404, FORBIDDEN: 403, INTERNAL_ERROR: 500, }; export const ERROR_MESSAGES: Record = { INVALID_API_KEY: "The API key is missing, malformed, revoked or expired.", EMAIL_NOT_VERIFIED: "Verify your email address before using the production API.", RATE_LIMITED: "Too many requests. Slow down and retry after the indicated delay.", CONCURRENCY_LIMIT: "Concurrent request limit reached for your organization.", INVALID_REQUEST: "The request body is invalid.", URL_NOT_ALLOWED: "The target URL is not allowed.", TARGET_TIMEOUT: "The target did not respond in time.", TARGET_BLOCKED: "The target blocked every route we tried.", TARGET_UNAVAILABLE: "The target could not be reached.", PROVIDER_UNAVAILABLE: "No network route is currently available for this request.", NETWORK_UNAVAILABLE: "The requested network class is not available right now.", BROWSER_UNAVAILABLE: "The managed browser is disabled or temporarily unavailable.", BROWSER_TIMEOUT: "The browser did not finish in time.", RESPONSE_TOO_LARGE: "The response exceeded the maximum allowed size.", TOO_MANY_REDIRECTS: "The target redirected too many times.", INSUFFICIENT_CREDITS: "Your account has no remaining credits.", USAGE_LIMIT_REACHED: "A spending or usage limit configured on this project was reached.", SESSION_NOT_FOUND: "The session does not exist.", SESSION_EXPIRED: "The session has expired.", CRAWL_NOT_FOUND: "The crawl job does not exist.", CRAWL_LIMIT_REACHED: "Too many crawl jobs are running for your organization.", NOT_FOUND: "Resource not found.", FORBIDDEN: "You do not have access to this resource.", INTERNAL_ERROR: "An internal error occurred.", }; export class FetchaError extends Error { readonly code: ErrorCode; readonly status: number; readonly details?: Record; readonly requestId?: string; constructor( code: ErrorCode, message?: string, opts: { details?: Record; requestId?: string; cause?: unknown } = {}, ) { super(message ?? ERROR_MESSAGES[code], { cause: opts.cause }); this.name = "FetchaError"; this.code = code; this.status = ERROR_HTTP_STATUS[code]; this.details = opts.details; this.requestId = opts.requestId; } toJSON(requestId?: string) { return { error: { code: this.code, message: this.message, request_id: requestId ?? this.requestId ?? null, ...(this.details ? { details: this.details } : {}), }, }; } } export function isFetchaError(e: unknown): e is FetchaError { return e instanceof FetchaError || (typeof e === "object" && e !== null && (e as { name?: string }).name === "FetchaError"); }