Solari

Network Access

Restrict a stealth session to a fixed list of hosts. Requests to anything outside the list are refused at the proxy layer; direct-IP connections are not currently covered.

How to turn it on

Pass allowedDomains to sessions.create(). It requires stealth: true — the same precondition as proxies and captcha solving.

Team or Enterprise plan required
Egress allowlisting is available on the Team and Enterprise plans. A request with a non-empty allowedDomains on any other plan is refused — see What a refusal looks like below.
await client.sessions.create({
  stealth: true,
  allowedDomains: ["example.com", "api.example.com"],
})

A domain on the list also covers its subdomains: example.com matches example.com and any *.example.com, but not otherexample.com. The match is case-insensitive. The create response echoes the normalised list back under the same field so you can confirm what actually took effect.

Omitting, nulling, or emptying the list

These three inputs are NOT interchangeable:

ValueResult
field omitted entirelyNo restriction — unrestricted egress.
nullSame as omitted — no restriction. Treated identically to the field being absent, not to an empty list.
[] (empty array)Rejected with 400, not treated as unrestricted:
{ "error": "Invalid `allowedDomains` field — an empty array is rejected, not treated as unrestricted. Omit the field entirely for no allowlist, or provide at least one host." }

This is deliberate, not a strictness choice applied uniformly across the API: the dangerous case is a list built dynamically from configuration that collapses to empty — a bad environment variable, an over-aggressive filter, a parsing slip — which would otherwise mean a caller who asked to restrict the browser to nothing silently got everything, with no error and no signal. A 400 surfaces that mistake immediately instead of failing open. If you actually mean “no restriction,” either omit the field or pass null explicitly — both work, and are clearer about the intent than passing an array that happens to be empty.

What a refusal looks like

A request to a host that isn't on the list is refused at the point the browser tries to connect — the navigation or subresource fetch fails, the same way it would against a network that's simply unreachable. There are two codes depending on where the refusal happens, and both mean the same thing: a plain page.goto to a blocked host — the common case — surfaces as Chromium's ERR_BLOCKED_BY_CLIENT; a redirect to a blocked host, or a request on the managed-proxy path, surfaces as ERR_TUNNEL_CONNECTION_FAILED instead. Either one is indistinguishable from the destination genuinely being unreachable — there is no separate, policy-specific error code.

Creating a session with allowedDomains but without stealth: true is refused up front:

{ "error": "allowedDomains requires stealth: true" }

Creating a session with allowedDomains on a plan below Team is refused the same way, after the stealth check above passes:

{
  "error": "Egress allowlisting requires a Team or Enterprise plan",
  "code": "FeatureRequiresPlan",
  "feature": "allowedDomains",
  "plan": "professional"
}

With a managed proxy

Allowlist enforcement runs on the host your browser is trying to reach, checked before any connection is made — it works the same way whether or not you've also turned on a managed proxy. With a proxy, that check is the only thing standing between your browser and the open internet; without one, there's a second, independent layer behind it. Either way, a host outside your list is refused at the proxy layer — direct-IP connections are not currently covered, same as above.

DNS lookups are not blocked
allowedDomains stops the browser from connecting to a host outside your list. It does not stop a DNS lookup for that host's name from resolving — a lookup can succeed and return an address without the browser ever being allowed to use it. If your own threat model depends on third parties being unable to tell whether a given hostname exists, treat that as outside what this feature covers.