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-golaunch() 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: NewClient(), BaseURL()Sessions: Create(), Get(), Release(), ReplayURL(), DownloadReplay()Profiles: List(), Create(), Save(), Delete()Proxy: Countries()- Package functions: Connect(), ConnectCDP(), IsCode()
- Types:
ClientOptions,CreateSessionOptions,Session,SessionView,StorageState,ProxySpec,ProxyRequest,ResolvedProxyConfig,ProxyCountries,Profile,SaveResult,ReplayURL,SolariError
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:
optsClientOptions: see ClientOptions.
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() stringThe 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:
ctxcontext.Context: bounds the call.optsCreateSessionOptions: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: 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:
ctxcontext.Context.idstring: 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 todayGET /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) errorEnds a session (DELETE /sessions/:id) and waits for the gateway to acknowledge. Idempotent. An already-gone session (404) is not an error.
Parameters:
ctxcontext.Context: usecontext.Background()in adeferso a cancelled request context still releases.idstring: 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 laterReplayURL()
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:
ctxcontext.Context.idstring: 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:
ctxcontext.Context.idstring: 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:
ctxcontext.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:
ctxcontext.Context.namestring: 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:
ctxcontext.Context.idstring: profile id.storageStateStorageState: cookies + origins. Unknown keys held inExtraare 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) errorRemoves a profile. Idempotent. An already-gone profile (404) is not an error.
Parameters:
ctxcontext.Context.idstring: 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:
ctxcontext.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:
ctxcontext.Context: parent of the allocator.session*Session: from Create. ItsCDPEndpointis used.opts...chromedp.ContextOption: passed tochromedp.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))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:
ctxcontext.Context.cdpEndpointstring: 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) boolReports whether err (or anything it wraps) is a *SolariError carrying the given gateway error code.
Parameters:
errerror: any error.codestring: 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
APIKeystring: required. Formatslr_live_<id>_<secret>.RegionRegion:RegionUSWest(the default and only value). Ignored whenBaseURLis set.BaseURLstring: overrides the region’s origin (staging / self-hosted).HTTPClient*http.Client: when set,TimeoutMsis ignored.MaxAttemptsint: default2(one retry).BackoffMs*int: default500, fixed. A pointer so0is distinguishable from unset.TimeoutMsint: default90000, per attempt.
ConcurrencyLimitExceeded (429) is not retried: catch it with IsCode and back off yourself.CreateSessionOptions
The zero value is valid and sends no body.
ProfileIDstring: attach a stored profile; makes theSessioncarry itsStorageState.Recordingbool: record the session. Off by default.Stealthbool: enable the runtime stealth shim. Off by default.Captchabool: managed captcha solving. RequiresStealth.WebBotAuthbool: sign outbound requests for Cloudflare Web Bot Auth. Independent ofStealth; silently inert unless Web Bot Auth is enabled for your account.ProxyProxySpec: managed egress. RequiresStealth.
Session
IDstring: the composite session id used by every otherSessionsmethod.WSEndpointstring: the Playwright wire-protocol endpoint. Unusable from Go. SeeCDPEndpoint.CDPEndpointstring: the raw CDP endpoint. Pass it to Connect.ExpiresAtstring: ISO 8601 UTC deadline; the session auto-releases then.StorageState*StorageState: the attached profile’s contents. See the tri-state note below.StorageStateAttachedbool: whether a profile was attached at all.Proxy*ResolvedProxyConfig: non-nil only when a managed proxy was requested.
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 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,ExpiresAtstring: each present only when the pool sent it.Rawjson.RawMessage: the complete response body as received.
StorageState
The same shape Playwright’s context.storageState() produces.
Cookies[]Cookie: each withName,Value, and optionalDomain,Path,Expires,HTTPOnly,Secure,SameSite.Origins[]Origin: each withOriginand optionalLocalStorageentries (Name,Value).Extramap[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.ccis lowercase ISO-3166-1 alpha-2, e.g.ProxyCountry("gb").ProxySmartProxyPreset: the gateway picks and escalates the egress per host.ProxyOffProxyPreset: 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
Countrystring: lowercase ISO-3166-1 alpha-2. Defaults to"us".TierProxyTier:TierResidential(default, rotating),TierStatic(fixed ISP IP),TierMobile(carrier CGNAT).ASNstring: pin egress to an autonomous system, e.g."20057".Sessionstring: sticky-session id (alphanumeric + dash, ≤32 chars).SessionDurationint: sticky lifetime in minutes (1 to 30, default 10). Only withSession.State,Citystring: US-only geo narrowing, e.g."california"/"los_angeles".
ResolvedProxyConfig
Serverstring: proxy server URL.Username,Passwordstring: credentials.TimezoneIDstring: timezone matching the egress IP.Countrystring,TierProxyTier: what the gateway actually assigned.
ProxyCountries
Enabledbool: whether managed proxy is available to you. When false, proxy requests fail regardless of country.Countries[]string: supported egress countries.
Profile / SaveResult
Profile:IDstring,Namestring.SaveResult:Versionint,SizeBytesint.
ReplayURL
URLstring: presigned download URL.ExpiresInSecondsint: URL lifetime.ContentEncodingstring: encoding of the object atURL. Defaults to"gzip".
SolariError
The single error type this SDK produces. Match it with errors.As.
Messagestring: human-readable description.Statusint: the HTTP status, or0for transport/validation errors.Codestring: the gateway’scodevalue, empty when the body carried none.Errerror: the underlying cause, exposed viaerrors.Unwrap.
Codes are plain strings, not a closed enum. The gateway may add more, so prefer IsCode over an exhaustive switch.
| Constant | Value | Meaning |
|---|---|---|
CodeFeatureRequiresPlan | FeatureRequiresPlan | Stealth, captcha, or managed proxy is not enabled for the plan. |
CodeConcurrencyLimitExceeded | ConcurrencyLimitExceeded | The org is at its live-session cap. |
CodePlanLimitExceeded | PlanLimitExceeded | A plan quota (minutes, profiles, …) is spent. |
CodeBrowserUnhealthy | BrowserUnhealthy | The acquired browser failed its health check. |
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)
}