Solari

Browsers

github.com/solari-sdk/solari-browser-go, package solari. The REST control plane plus a Connect helper that attaches chromedp to a session’s CDP endpoint. See the Go SDK hub for install and configuration.

go get github.com/solari-sdk/solari-browser-go
There is no Launch(), deliberately
The TypeScript SDK’s launch() returns a live Playwright Browser. Playwright has no Go client, so it is not ported. Sessions.Create hands back the raw CDP endpoint and you drive it with chromedp via Connect. Everything else matches the TypeScript SDK on the wire.

Contents

Client

The browser client. Talks the gateway REST API and is safe for concurrent use.

Fields

  • Sessions *Sessions: session create/release/replay calls.
  • Profiles *Profiles: stored browser profile CRUD.
  • Proxy *Proxy: managed-egress metadata.

Constructor

NewClient()

func NewClient(opts ClientOptions) (*Client, error)

Constructs a client. Opens no connection.

Parameters:

Returns: (*Client, error)

Errors: *SolariError if APIKey is empty or Region is unknown.

Example:

import solari "github.com/solari-sdk/solari-browser-go"

client, err := solari.NewClient(solari.ClientOptions{
    APIKey: os.Getenv("SOLARI_API_KEY"),
})

Methods

BaseURL()

func (c *Client) BaseURL() string

The resolved API origin, without a trailing slash. Useful to confirm which gateway a Region resolved to.

Returns: string, e.g. https://api.getsolari.com.

Example:

fmt.Println(client.BaseURL())

Sessions

Remote-browser session lifecycle. Reach it at client.Sessions.

Methods

Create()

func (s *Sessions) Create(ctx context.Context, opts CreateSessionOptions) (*Session, error)

Acquires a browser session (POST /sessions). The zero CreateSessionOptions requests a plain session and sends no body at all.

Parameters:

  • ctx context.Context: bounds the call.
  • opts CreateSessionOptions: ProfileID, Recording, Stealth, Captcha, WebBotAuth, Proxy.

Returns: (*Session, error), carrying the upstream WSEndpoint and CDPEndpoint.

Errors: *SolariError carrying Status and the gateway Code (e.g. CodeFeatureRequiresPlan, CodeConcurrencyLimitExceeded).

Example:

sess, err := client.Sessions.Create(ctx, solari.CreateSessionOptions{
    Stealth: true,
    Proxy:   solari.ProxyCountry("gb"),
})
if err != nil {
    log.Fatal(err)
}
defer client.Sessions.Release(context.Background(), sess.ID)
Captcha and proxy require Stealth
Captcha: true and any Proxy request are rejected unless Stealth: true is also set.

Get()

func (s *Sessions) Get(ctx context.Context, id string) (*SessionView, error)

Fetches the gateway’s view of a session (GET /sessions/:id).

Parameters:

  • ctx context.Context.
  • id string: session id.

Returns: (*SessionView, error), carrying lifted Status/ExpiresAt plus the full body in Raw.

Errors: *SolariError with Status: 404 today. See below.

Example:

view, err := client.Sessions.Get(ctx, sess.ID)  // 404s today
Get() is dead upstream: do not build on it
The pool serves no GET /sessions/:id route, so this call 404s against every gateway today. The method is wired and will work if the route lands, but nothing should depend on it. Track a session with the Session value you already hold from Create.

Release()

func (s *Sessions) Release(ctx context.Context, id string) error

Ends a session (DELETE /sessions/:id) and waits for the gateway to acknowledge. Idempotent. An already-gone session (404) is not an error.

Parameters:

  • ctx context.Context: use context.Background() in a defer so a cancelled request context still releases.
  • id string: session id.

Returns: error

Errors: *SolariError on any non-404 failure.

Example:

defer client.Sessions.Release(context.Background(), sess.ID)
// the replay is available ~1-3s later

ReplayURL()

func (s *Sessions) ReplayURL(ctx context.Context, id string) (*ReplayURL, error)

Returns a presigned link to a session’s replay. Requires Recording: true at create time, and is available ~1 to 3 seconds after Release.

Parameters:

  • ctx context.Context.
  • id string: session id.

Returns: (*ReplayURL, error), carrying URL, ExpiresInSeconds, ContentEncoding (defaults to "gzip").

Errors: *SolariError if the replay is not ready, does not exist, or the response carries no URL.

Example:

link, err := client.Sessions.ReplayURL(ctx, sess.ID)
fmt.Println(link.URL, link.ExpiresInSeconds)

DownloadReplay()

func (s *Sessions) DownloadReplay(ctx context.Context, id string) ([]byte, error)

Resolves the replay URL and downloads the bytes in one call. The bytes come back exactly as stored. Gzipped NDJSON by default, not decompressed.

Parameters:

  • ctx context.Context.
  • id string: session id.

Returns: ([]byte, error)

Errors: *SolariError if the replay lookup or the download fails.

Example:

raw, err := client.Sessions.DownloadReplay(ctx, sess.ID)
if err != nil {
    log.Fatal(err)
}
os.WriteFile("replay.ndjson.gz", raw, 0o644)

Profiles

Stored browser profiles are the cookies + localStorage a session attaches with CreateSessionOptions.ProfileID. Reach it at client.Profiles.

Methods

List()

func (p *Profiles) List(ctx context.Context) ([]Profile, error)

Returns every profile owned by the org.

Parameters:

  • ctx context.Context.

Returns: ([]Profile, error)

Errors: *SolariError on a non-2xx response.

Example:

profiles, _ := client.Profiles.List(ctx)
for _, p := range profiles {
    fmt.Println(p.ID, p.Name)
}

Create()

func (p *Profiles) Create(ctx context.Context, name string) (*Profile, error)

Makes an empty profile. Attach it via ProfileID to persist cookies and localStorage across sessions.

Parameters:

  • ctx context.Context.
  • name string: human-facing name.

Returns: (*Profile, error)

Errors: *SolariError with the gateway Code on failure.

Example:

profile, _ := client.Profiles.Create(ctx, "logged-in")

sess, _ := client.Sessions.Create(ctx, solari.CreateSessionOptions{
    ProfileID: profile.ID,
})

Save()

func (p *Profiles) Save(ctx context.Context, id string, storageState StorageState) (*SaveResult, error)

Overwrites a profile’s contents, returning the new version and stored size.

Parameters:

  • ctx context.Context.
  • id string: profile id.
  • storageState StorageState: cookies + origins. Unknown keys held in Extra are written back verbatim.

Returns: (*SaveResult, error), carrying Version and SizeBytes.

Errors: *SolariError on a non-2xx response.

Example:

// Round-trip a session's state back into its profile. StorageState is nil
// both when no profile was attached AND when an attached profile is still
// empty, so guard the dereference rather than assuming non-nil.
state := sess.StorageState
if state == nil {
    state = &solari.StorageState{}
}
res, _ := client.Profiles.Save(ctx, profile.ID, *state)
fmt.Println(res.Version, res.SizeBytes)

Delete()

func (p *Profiles) Delete(ctx context.Context, id string) error

Removes a profile. Idempotent. An already-gone profile (404) is not an error.

Parameters:

  • ctx context.Context.
  • id string: profile id.

Returns: error

Errors: *SolariError on any non-404 failure.

Example:

err := client.Profiles.Delete(ctx, profile.ID)

Proxy

Managed-egress metadata. Reach it at client.Proxy.

Methods

Countries()

func (p *Proxy) Countries(ctx context.Context) (*ProxyCountries, error)

Lists the egress countries this gateway supports, and whether managed proxying is configured at all. Check it before sending CreateSessionOptions.Proxy.

Parameters:

  • ctx context.Context.

Returns: (*ProxyCountries, error), carrying Enabled and Countries.

Errors: *SolariError on a non-2xx response.

Example:

pc, _ := client.Proxy.Countries(ctx)
if !pc.Enabled {
    log.Fatal("managed proxy is unavailable")
}
fmt.Println(pc.Countries)  // ["us" "gb" ...]

Package functions

Connect()

func Connect(ctx context.Context, session *Session, opts ...chromedp.ContextOption) (context.Context, context.CancelFunc, error)

Attaches chromedp to a session’s remote browser over raw CDP. The returned context is a chromedp browser context. Pass it to chromedp.Run.

Parameters:

  • ctx context.Context: parent of the allocator.
  • session *Session: from Create. Its CDPEndpoint is used.
  • opts ...chromedp.ContextOption: passed to chromedp.NewContext.

Returns: (context.Context, context.CancelFunc, error). The cancel func detaches chromedp but does not release the session.

Errors: *SolariError if session is nil or has no CDPEndpoint. Nothing is dialed here. A connection failure surfaces on the first chromedp.Run.

Example:

browserCtx, cancel, err := solari.Connect(ctx, sess)
if err != nil {
    log.Fatal(err)
}
defer cancel()  // detaches only; Release() still ends the session

var title string
err = chromedp.Run(browserCtx,
    chromedp.Navigate("https://example.com"),
    chromedp.Title(&title))
Raw CDP skips the pool's input humanization
The pool humanizes mouse and keyboard input on the Playwright wire path only. chromedp drives raw CDP, so its clicks are single-shot teleport-and-press, the exact tell interaction-triggered anti-bot vendors score. Stealth-sensitive automation needs its own input pacing on this path.

ConnectCDP()

func ConnectCDP(ctx context.Context, cdpEndpoint string, opts ...chromedp.ContextOption) (context.Context, context.CancelFunc, error)

Connect for a bare endpoint string (for callers that persisted the endpoint rather than the whole Session).

Parameters:

  • ctx context.Context.
  • cdpEndpoint string: a complete browser websocket URL.
  • opts ...chromedp.ContextOption.

Returns: (context.Context, context.CancelFunc, error)

Errors: *SolariError if cdpEndpoint is empty.

Example:

browserCtx, cancel, err := solari.ConnectCDP(ctx, savedEndpoint)
defer cancel()

IsCode()

func IsCode(err error, code string) bool

Reports whether err (or anything it wraps) is a *SolariError carrying the given gateway error code.

Parameters:

  • err error: any error.
  • code string: e.g. CodeConcurrencyLimitExceeded.

Returns: bool

Example:

_, err := client.Sessions.Create(ctx, solari.CreateSessionOptions{Stealth: true})
if solari.IsCode(err, solari.CodeConcurrencyLimitExceeded) {
    // back off and retry later
}

Types

ClientOptions

  • APIKey string: required. Format slr_live_<id>_<secret>.
  • Region Region: RegionUSWest (the default and only value). Ignored when BaseURL is set.
  • BaseURL string: overrides the region’s origin (staging / self-hosted).
  • HTTPClient *http.Client: when set, TimeoutMs is ignored.
  • MaxAttempts int: default 2 (one retry).
  • BackoffMs *int: default 500, fixed. A pointer so 0 is distinguishable from unset.
  • TimeoutMs int: default 90000, per attempt.
429 needs caller-side backoff
The transport retries only 502, 503, 504 and transport errors, with a fixed pause, not exponential backoff. Every other non-2xx returns immediately. ConcurrencyLimitExceeded (429) is not retried: catch it with IsCode and back off yourself.

CreateSessionOptions

The zero value is valid and sends no body.

  • ProfileID string: attach a stored profile; makes the Session carry its StorageState.
  • Recording bool: record the session. Off by default.
  • Stealth bool: enable the runtime stealth shim. Off by default.
  • Captcha bool: managed captcha solving. Requires Stealth.
  • WebBotAuth bool: sign outbound requests for Cloudflare Web Bot Auth. Independent of Stealth; silently inert unless Web Bot Auth is enabled for your account.
  • Proxy ProxySpec: managed egress. Requires Stealth.

Session

  • ID string: the composite session id used by every other Sessions method.
  • WSEndpoint string: the Playwright wire-protocol endpoint. Unusable from Go. See CDPEndpoint.
  • CDPEndpoint string: the raw CDP endpoint. Pass it to Connect.
  • ExpiresAt string: ISO 8601 UTC deadline; the session auto-releases then.
  • StorageState *StorageState: the attached profile’s contents. See the tri-state note below.
  • StorageStateAttached bool: whether a profile was attached at all.
  • Proxy *ResolvedProxyConfig: non-nil only when a managed proxy was requested.
Endpoints are UPSTREAM URLs
WSEndpoint and CDPEndpoint are the gateway URLs as issued, unlike the TypeScript SDK, which wraps them in a loopback proxy. They are reachable from anywhere that can reach the gateway and carry their own auth, so you can hand them to another process or machine.
StorageState is tri-state
StorageState is nil both when no profile was attached and when the attached profile is empty. Use StorageStateAttached to tell them apart: nil + false = no profile; nil + true = the profile exists but is empty.

SessionView

The gateway proxies this straight from the pool host, so the payload is pool-versioned: common fields are lifted out and the whole body is kept.

  • ID, SessionID, Status, ExpiresAt string: each present only when the pool sent it.
  • Raw json.RawMessage: the complete response body as received.

StorageState

The same shape Playwright’s context.storageState() produces.

  • Cookies []Cookie: each with Name, Value, and optional Domain, Path, Expires, HTTPOnly, Secure, SameSite.
  • Origins []Origin: each with Origin and optional LocalStorage entries (Name, Value).
  • Extra map[string]json.RawMessage: any other top-level keys, preserved so a Create Save round-trip never silently drops fields the gateway added.

ProxySpec

A closed interface. The wire accepts only these forms. Implemented by ProxyPreset and ProxyRequest.

  • ProxyCountry(cc)ProxyPreset: egress from a country’s default (residential) pool. cc is lowercase ISO-3166-1 alpha-2, e.g. ProxyCountry("gb").
  • ProxySmart ProxyPreset: the gateway picks and escalates the egress per host.
  • ProxyOff ProxyPreset: disables managed egress.
  • ProxyRequest: the fully-specified form; see below.
Proxy: solari.ProxyCountry("gb")   // preset
Proxy: solari.ProxySmart           // per-host escalation
Proxy: solari.ProxyRequest{Country: "us", Tier: solari.TierMobile}

ProxyRequest

  • Country string: lowercase ISO-3166-1 alpha-2. Defaults to "us".
  • Tier ProxyTier: TierResidential (default, rotating), TierStatic (fixed ISP IP), TierMobile (carrier CGNAT).
  • ASN string: pin egress to an autonomous system, e.g. "20057".
  • Session string: sticky-session id (alphanumeric + dash, ≤32 chars).
  • SessionDuration int: sticky lifetime in minutes (1 to 30, default 10). Only with Session.
  • State, City string: US-only geo narrowing, e.g. "california" / "los_angeles".

ResolvedProxyConfig

  • Server string: proxy server URL.
  • Username, Password string: credentials.
  • TimezoneID string: timezone matching the egress IP.
  • Country string, Tier ProxyTier: what the gateway actually assigned.

ProxyCountries

  • Enabled bool: whether managed proxy is available to you. When false, proxy requests fail regardless of country.
  • Countries []string: supported egress countries.

Profile / SaveResult

  • Profile: ID string, Name string.
  • SaveResult: Version int, SizeBytes int.

ReplayURL

  • URL string: presigned download URL.
  • ExpiresInSeconds int: URL lifetime.
  • ContentEncoding string: encoding of the object at URL. Defaults to "gzip".

SolariError

The single error type this SDK produces. Match it with errors.As.

  • Message string: human-readable description.
  • Status int: the HTTP status, or 0 for transport/validation errors.
  • Code string: the gateway’s code value, empty when the body carried none.
  • Err error: the underlying cause, exposed via errors.Unwrap.

Codes are plain strings, not a closed enum. The gateway may add more, so prefer IsCode over an exhaustive switch.

ConstantValueMeaning
CodeFeatureRequiresPlanFeatureRequiresPlanStealth, captcha, or managed proxy is not enabled for the plan.
CodeConcurrencyLimitExceededConcurrencyLimitExceededThe org is at its live-session cap.
CodePlanLimitExceededPlanLimitExceededA plan quota (minutes, profiles, …) is spent.
CodeBrowserUnhealthyBrowserUnhealthyThe acquired browser failed its health check.
Exhausted retries keep Status and Code
When every attempt is spent the returned error still carries the last response’s Status and Code, a deliberate deviation from the TypeScript SDK, which loses them. So errors.As and IsCode keep working after a retry storm.
var serr *solari.SolariError
if errors.As(err, &serr) {
    fmt.Println(serr.Status, serr.Code, serr.Message)
}