> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfsky.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Chromedp

> Connect chromedp to a Surfsky browser, open a page, and stop the session.

chromedp attaches to a Surfsky browser through a remote allocator with the session's `ws_url`. The browser runs in Surfsky's cloud; no local Chrome is needed.

<span id="using-surfsky-with-chromedp" />

## Prerequisites

Go 1.22 or newer. Set your [API token and base URL](/quickstart#before-you-start), then create a project:

```bash theme={null}
mkdir surfsky-chromedp && cd surfsky-chromedp
go mod init example.com/surfsky-chromedp
go get github.com/chromedp/chromedp
```

The example starts a browser with an empty request body, which uses your account's default proxy pool. Proxy, fingerprint, and other start options are in the [API reference](/api-reference/profiles/start-one-time-session).

<span id="code-example" />

## Run an example

The example starts a one-time browser, connects, opens a page, prints its title, and stops the session through the API. To keep cookies and login state between runs, start a [persistent profile](/sessions#persistent-profiles) instead; the connection code is the same.

Save as `main.go`:

```go theme={null}
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/chromedp/chromedp"
)

func main() {
	baseURL := strings.TrimRight(os.Getenv("SURFSKY_API_BASE_URL"), "/")
	req, _ := http.NewRequest(http.MethodPost, baseURL+"/profiles/one_time", strings.NewReader("{}"))
	req.Header.Set("X-Cloud-Api-Token", os.Getenv("SURFSKY_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := (&http.Client{Timeout: 120 * time.Second}).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("start: HTTP %d", resp.StatusCode)
	}
	var session struct {
		InternalUUID string `json:"internal_uuid"`
		WSURL        string `json:"ws_url"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
		log.Fatal(err)
	}
	defer stopSession(baseURL, session.InternalUUID)

	allocator, cancelAllocator := chromedp.NewRemoteAllocator(context.Background(), session.WSURL, chromedp.NoModifyURL)
	defer cancelAllocator()
	ctx, cancel := chromedp.NewContext(allocator)
	defer cancel()

	var title string
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://example.com"),
		chromedp.Title(&title),
	); err != nil {
		log.Println(err)
		return
	}
	fmt.Println(title)
}

func stopSession(baseURL, internalUUID string) {
	req, _ := http.NewRequest(http.MethodPost, baseURL+"/profiles/"+internalUUID+"/stop", nil)
	req.Header.Set("X-Cloud-Api-Token", os.Getenv("SURFSKY_API_TOKEN"))
	resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
	if err != nil {
		log.Println("stop:", err)
		return
	}
	resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		log.Printf("stop: HTTP %d", resp.StatusCode)
	}
}
```

```bash theme={null}
go run .
```

Expected output:

```text theme={null}
Example Domain
```

`chromedp.NoModifyURL` keeps Surfsky's WebSocket address as returned. Without it chromedp rewrites the URL and the connection fails.

<span id="important-notes" />

## Stop the session

Stop the session through the API with the `internal_uuid` from the start response, like the deferred `stopSession` above. `chromedp.Cancel(ctx)` and the `cancel` functions only disconnect. chromedp sends `Browser.close` only for browsers it launched itself, so the remote browser keeps running until you stop it or the inactivity timeout expires. Register the stop before you connect, so error paths hit it too, and avoid `log.Fatal`, which skips deferred calls.

The same request from the shell:

```bash theme={null}
curl --fail-with-body -X POST \
  "$SURFSKY_API_BASE_URL/profiles/INTERNAL_UUID/stop" \
  -H "X-Cloud-Api-Token: $SURFSKY_API_TOKEN"
```

Otherwise the browser stops after the inactivity timeout, 30 seconds by default.
