Guide

Errors

Most failures are a typed BrowserError. Which class means fix the call and which means wait and which means try another provider

Failures from a vendor go through normalizeError, so a 401 from Steel and a 401 from Cloudflare are the same AuthError, and nearly everything else the package throws extends BrowserError too. Catch the class, not the message. Nearly is the honest word: a few guards still throw a plain Error, like a Playwright screenshot without a session or No configured provider supports … from the resolver, so keep a last branch that rethrows.

ts
import { AuthError, RateLimitError, create } from "@agntn/browsers";

try {
  await (await create("steel")).scrape("https://example.com");
} catch (error) {
  if (error instanceof RateLimitError) {
    await new Promise((resolve) => setTimeout(resolve, error.retryAfter * 1000));
  }
  else if (error instanceof AuthError) console.error(`fix the key for ${error.provider}`);
  else throw error;
}

Fix the call

ClassWhen
UnknownProviderErrorThe name isn't in the registry. Unknown provider: nope
InvalidInputErrorThe arguments contradict each other, like selector with fullPage: true
UnsupportedOperationErrorThe provider can't do it. Anchor asked to scrape

Fix the setup

ClassWhen
AuthErrorKey missing, or the vendor said 401. Missing API key for steel. Set STEEL_API_KEY
PaymentErrorThe vendor said 402 or 403. Usually a plan or a balance
NoProviderConfiguredErrorNo key set for anything and nothing registered to fall back on

Wait, or try elsewhere

ClassWhen
RateLimitErrorA 429. retryAfter holds the seconds, and the message names which provider refused and why
TimeoutErrorNo answer in time. Says how long it waited and from where
HTTPErrorAny other status. statusCode, url and the vendor's reason
SessionNotFoundErrorThe session is gone, released or expired on the vendor's side

The package also exports EmptyUrlError, ScrapeNotSupportedError, SessionLimitError and NoProviderAvailableError. Nothing built in throws them today, so don't write a catch for one. A provider of your own can still use them.

A rate limit and a timeout are different problems, and they come back as different classes. One says slow down, the other says nobody answered. Mixing them up is how an agent ends up hammering a vendor that already said please stop.

Keys never leak into a message

Errors carry the request URL, so you can see which vendor failed. Query params like token and api_key, and a username or password in the URL, are replaced with [REDACTED] first:

text
HTTP 400 from https://chrome.browserless.io/screenshot?token=%5BREDACTED%5D: Element not found on page! (requestId: …)

That line is real, from a Browserless element screenshot of a selector the page didn't have. Only the request ID is cut.