Skip to main content
All errors extend the base ReaderError class and carry a typed code, a retryable flag, and a toJSON() method for structured logging.
import { ReaderError } from "@vakra-dev/reader";

try {
  await reader.scrape({ urls: ["https://example.com"] });
} catch (err) {
  if (err instanceof ReaderError) {
    console.error({
      code: err.code,
      message: err.message,
      retryable: err.retryable,
      url: err.url,
    });
  }
}

Base class

class ReaderError extends Error {
  code: ReaderErrorCode;
  url?: string;
  retryable: boolean;
  cause?: Error;
  toJSON(): SerializedError;
}
Every error in the table below extends ReaderError.

Error reference

Error classCodeRetryableWhen it’s thrown
NetworkErrorNETWORK_ERRORConnection reset, socket error, network unreachable
TimeoutErrorTIMEOUTRequest or page load exceeded timeoutMs
DNSErrorDNS_ERRORCannot resolve hostname
TLSErrorTLS_ERRORSSL/certificate handshake failed
CloudflareErrorCLOUDFLARE_CHALLENGECloudflare challenge didn’t resolve in time
BotDetectedErrorBOT_DETECTEDBot detection page detected in response
AccessDeniedErrorACCESS_DENIED401/403 returned by the origin
ProxyConnectionErrorPROXY_CONNECTION_ERRORProxy unreachable or auth failed
ProxyExhaustedErrorPROXY_EXHAUSTEDAll proxy tiers tried and failed
ContentExtractionErrorCONTENT_EXTRACTION_FAILEDHTML parsing failed - likely corrupt response
EmptyContentErrorEMPTY_CONTENTContent below minimum length (50 chars) - could be rate limit
ContentTooLargeErrorCONTENT_TOO_LARGEHTML exceeds maximum size limit
MarkdownConversionErrorMARKDOWN_CONVERSION_FAILEDsupermarkdown couldn’t convert the HTML
InvalidUrlErrorINVALID_URLURL parsing failed
ValidationErrorINVALID_OPTIONSInvalid options passed to scrape/crawl
RobotsBlockedErrorROBOTS_BLOCKEDURL blocked by robots.txt
BrowserPoolErrorBROWSER_ERRORPool initialization or instance failure
ClientClosedErrorCLIENT_CLOSEDClient has already been closed
NotInitializedErrorNOT_INITIALIZEDInternal - component not initialized (bug report this)
RetryBudgetExhaustedErrorRETRY_BUDGET_EXHAUSTEDAll retries (engine switch, proxy escalation, general) exhausted

Importing specific error classes

import {
  ReaderError,
  NetworkError,
  TimeoutError,
  CloudflareError,
  BotDetectedError,
  AccessDeniedError,
  ProxyConnectionError,
  ProxyExhaustedError,
  RobotsBlockedError,
  ValidationError,
  InvalidUrlError,
  RetryBudgetExhaustedError,
} from "@vakra-dev/reader";

Serialized error format

Every ReaderError instance has a toJSON() method for structured logging:
interface SerializedError {
  name: string;
  code: ReaderErrorCode;
  message: string;
  url?: string;
  timestamp: string;    // ISO timestamp
  retryable: boolean;
  cause?: string;       // Original error message if wrapped
  stack?: string;
  // ... error-specific fields (timeoutMs, challengeType, etc.)
}
Use it to ship errors to Datadog, Sentry, or whatever observability stack you’re running:
try {
  await reader.scrape({ urls: [...] });
} catch (err) {
  if (err instanceof ReaderError) {
    logger.error(err.toJSON());
  } else {
    logger.error({ message: err.message, stack: err.stack });
  }
}

Retry pattern using the flag

async function scrapeWithRetry(url, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await reader.scrape({ urls: [url] });
    } catch (err) {
      if (!(err instanceof ReaderError) || !err.retryable || attempt === maxAttempts - 1) {
        throw err;
      }
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
    }
  }
}

Where to go next

Error Handling concept

Retry budgets and how errors propagate through the pipeline.