[cdpfleet](https://cdpfleet.com/) / Threads

How billing works

# You buy threads, *not machines*.

A thread is a slot for a running browser. You never size servers, count CPUs or keep a pool warm: buy threads — dedicated or shared — and launch browsers into them. Time is metered to the second.

[See prices](https://cdpfleet.com/pricing)[Read the details](https://cdpfleet.com/docs/sessions)

-   **1**thread · headless
-   **2**threads · headful
-   **3–4**threads · phones
-   **1 s**billing resolution

## Heavier browsers take more threads

| Session type | Threads | What it is |
| --- | --- | --- |
| Linux headless | 1 | Any desktop engine without a display. |
| Linux headful | 2 | Full rendering on a virtual display — the default for most browsers. |
| Mobile WebView | 3 | Android WebView on hardware phones, iOS WebView ([phones](https://cdpfleet.com/mobile), early access). |
| Mobile Chrome | 4 | Chrome and other full browsers on hardware Android phones (early access). |

A launch that would exceed your threads gets `429 threads_exceeded` until a session closes. The launch response tells you the session's `weight`, and the dashboard shows what is running.

## Metered in thread-seconds

Usage is threads × seconds, nothing rounded up. Ten seconds of a headful browser is 20 thread-seconds; a minute of a headless one is 60.

On [shared threads](https://cdpfleet.com/shared-threads) the thread-seconds come out of the day's pool; on [dedicated threads](https://cdpfleet.com/dedicated-threads) they are only reported — dedicated minutes are unlimited.

## The clock stops with the socket

-   Billing starts when your browser is ready — the launch response — and stops the moment either side closes the WebSocket.
-   `browser.close()` ends the session at once.
-   A session you never connect to ends after 60 seconds — unless it was launched with `start_url` for [Launch interactive](https://cdpfleet.com/launch-interactive), which runs without a client until its idle timeout.
-   If our server doesn't accept your connection, the session isn't billed.

Launch, use, close — the whole billed lifetime of a session

 

```node
// npm install playwright@1.60.0
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:pass@proxy.example.com:8080',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing
```

```python
# pip install playwright==1.60.0 requests
# Browsers run on cdpfleet, so no `playwright install` is needed.
import os
import requests
from playwright.sync_api import sync_playwright

KEY = os.environ["CDPFLEET_API_KEY"]

# 1. Launch the browser
res = requests.post(
    "https://starter.cdpfleet.com/chromium/session",
    headers={"x-api-key": KEY},
    json={
        "proxy": "http://user:pass@proxy.example.com:8080",
        "headless": "new",
    },
    timeout=60,
)
res.raise_for_status()
ws_url = res.json()["wsUrl"]

# 2. Connect and drive it
with sync_playwright() as p:
    browser = p.chromium.connect(ws_url, headers={"x-api-key": KEY})
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.title())
    browser.close()  # ends the session and stops billing
```

```java
// Maven: com.microsoft.playwright:playwright:1.60.0  and  com.google.code.gson:gson:2.11.0
// Run with PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1: browsers run on cdpfleet.
import com.google.gson.JsonParser;
import com.microsoft.playwright.*;
import java.net.URI;
import java.net.http.*;
import java.util.Map;

public class Example {
  public static void main(String[] args) throws Exception {
    String key = System.getenv("CDPFLEET_API_KEY");

    // 1. Launch the browser
    String body = """
        {
          "proxy": "http://user:pass@proxy.example.com:8080",
          "headless": "new"
        }
        """;
    HttpResponse<String> res = HttpClient.newHttpClient().send(
        HttpRequest.newBuilder(URI.create("https://starter.cdpfleet.com/chromium/session"))
            .header("x-api-key", key)
            .header("content-type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build(),
        HttpResponse.BodyHandlers.ofString());
    if (res.statusCode() != 200) {
      throw new RuntimeException("launch failed: " + res.statusCode() + " " + res.body());
    }
    String wsUrl = JsonParser.parseString(res.body()).getAsJsonObject().get("wsUrl").getAsString();

    // 2. Connect and drive it
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().connect(wsUrl,
          new BrowserType.ConnectOptions().setHeaders(Map.of("x-api-key", key)));
      Page page = browser.newPage();
      page.navigate("https://example.com");
      System.out.println(page.title());
      browser.close(); // ends the session and stops billing
    }
  }
}
```

```csharp
// dotnet add package Microsoft.Playwright --version 1.60.0
// Browsers run on cdpfleet, so no `playwright install` is needed.
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Playwright;

var key = Environment.GetEnvironmentVariable("CDPFLEET_API_KEY")!;

// 1. Launch the browser
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", key);
var body = """
    {
      "proxy": "http://user:pass@proxy.example.com:8080",
      "headless": "new"
    }
    """;
var res = await http.PostAsync("https://starter.cdpfleet.com/chromium/session",
    new StringContent(body, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
    throw new Exception($"launch failed: {(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");
var wsUrl = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("wsUrl").GetString()!;

// 2. Connect and drive it
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectAsync(wsUrl, new BrowserTypeConnectOptions
{
    Headers = new Dictionary<string, string> { ["x-api-key"] = key },
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://example.com");
Console.WriteLine(await page.TitleAsync());
await browser.CloseAsync(); // ends the session and stops billing
```

```go
// go get github.com/playwright-community/playwright-go@v0.6000.0
//
// One-time driver setup (needs Node.js 18+): playwright-go can no longer download the
// 1.60.0 driver itself, so build it from npm and point PLAYWRIGHT_DRIVER_PATH at it:
//   mkdir -p ~/.cache/pw-driver-1.60.0 && cd ~/.cache/pw-driver-1.60.0 &&
//     curl -sL https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz | tar xz &&
//     ln -sf "$(command -v node)" node
//   export PLAYWRIGHT_DRIVER_PATH=~/.cache/pw-driver-1.60.0
// Browsers run on cdpfleet, so no browsers are downloaded (SkipInstallBrowsers).
package main

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

	"github.com/playwright-community/playwright-go"
)

func main() {
	key := os.Getenv("CDPFLEET_API_KEY")

	// 1. Launch the browser
	body := `{
	  "proxy": "http://user:pass@proxy.example.com:8080",
	  "headless": "new"
	}`
	req, _ := http.NewRequest("POST", "https://starter.cdpfleet.com/chromium/session", strings.NewReader(body))
	req.Header.Set("x-api-key", key)
	req.Header.Set("content-type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		msg, _ := io.ReadAll(res.Body)
		log.Fatalf("launch failed: %s %s", res.Status, msg)
	}
	var session struct {
		WsURL string `json:"wsUrl"`
	}
	if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
		log.Fatal(err)
	}

	// 2. Connect and drive it (Install fetches only the Playwright driver, once)
	opts := &playwright.RunOptions{SkipInstallBrowsers: true}
	if err := playwright.Install(opts); err != nil {
		log.Fatal(err)
	}
	pw, err := playwright.Run(opts)
	if err != nil {
		log.Fatal(err)
	}
	defer pw.Stop()
	browser, err := pw.Chromium.Connect(session.WsURL, playwright.BrowserTypeConnectOptions{
		Headers: map[string]string{"x-api-key": key},
	})
	if err != nil {
		log.Fatal(err)
	}
	page, err := browser.NewPage()
	if err != nil {
		log.Fatal(err)
	}
	if _, err := page.Goto("https://example.com"); err != nil {
		log.Fatal(err)
	}
	title, _ := page.Title()
	fmt.Println(title)
	browser.Close() // ends the session and stops billing
}
```

## Two ways to buy threads

### Shared threads

Pooled browser-minutes for bursty and scheduled work: 60/120/240/480 minutes per thread per day, from $1.50 per thread per month.

[Learn more →](https://cdpfleet.com/shared-threads)

### Dedicated threads

Reserved capacity for always-on work: unlimited minutes, 24-hour sessions and keep\_alive at $8.50 per thread per month.

[Learn more →](https://cdpfleet.com/dedicated-threads)

### Community

Free: 2 threads with 60 minutes each per day and a proxy included.

[Learn more →](https://cdpfleet.com/community)

## Related

Cases, recipes, posts and comparisons about this — updated as new ones are published.

### Reference docs

The details, options and errors.

-   [Sessions & billing](https://cdpfleet.com/docs/sessions)Threads, metering and timeouts.
-   [Errors & limits](https://cdpfleet.com/docs/errors)`threads_exceeded` and other limits.
-   [Account API](https://cdpfleet.com/docs/api)Usage, sessions and live threads over the API.

### Cases 9

Real problems solved end to end, run on production in five languages.

-   [Case 80: How many tabs fit in one session, by plan, browser and "memory"](https://cdpfleet.com/docs/cases/tabs-and-memory-by-plan)A long Wikipedia article opened in one new tab after another until the session ends: on Community (2 GB), a shared plan (4 GB) and a dedicated thread (6 GB), in…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/80-tabs-and-memory-by-plan)
-   [Case 78: keep\_alive: an agent run that survives its client going away](https://cdpfleet.com/docs/cases/keep-alive-reconnect)An agent works through a catalogue in a headless Chrome session with `keep_alive`, then its client disconnects. A new client reconnects to the same session and…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/78-keep-alive-reconnect)
-   [Case 76: The SDK vs plain Playwright: the same scrape, and what happens at the thread cap](https://cdpfleet.com/docs/cases/sdk-vs-plain-playwright)One small scrape written three ways in each language: a launch request of your own plus Playwright, the cdpfleet SDK, and the SDK's connect URL. The scrape is the…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/76-sdk-vs-plain-playwright)
-   [Case 72: Stay under the tab and memory caps: reuse a few pages, close what you're done with](https://cdpfleet.com/docs/cases/stay-under-the-caps)Every session has a tab cap and a memory cap. A new tab per URL runs into the first; one page that grows runs into the second. A small pool of reused tabs works…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/72-stay-under-the-caps)
-   [Case 58: Do it by hand in live view, then let your code finish in the same browser](https://cdpfleet.com/docs/cases/launch-interactive-to-code)Launch a browser with `start_url` and no client, drive it through a control-mode live link — address bar, Tab, typing — then connect your code: it finds the person's…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/58-launch-interactive-to-code)
-   [Case 50: A price watcher in one program: two snapshots minutes apart, diffed, billed only while reading](https://cdpfleet.com/docs/cases/price-watch)Take a snapshot of a catalogue price and a live market price, wait two minutes, take another and diff them field by field. Each snapshot is its own short session, so…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/50-price-watch)
-   [Case 30: Five pages at once: parallel tabs in one browser session](https://cdpfleet.com/docs/cases/parallel-pages)Open five unrelated pages concurrently in one session and compare with loading them one by one — same thread, same proxy, a fraction of the wall time.[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/30-parallel-pages)
-   [Case 20: Three visitors, one thread: browser contexts instead of sessions](https://cdpfleet.com/docs/cases/contexts-not-sessions)Cookies, storage, locale and clock isolated per persona inside a single browser session — what a context gives you for free, what it shares, and the proxy surprise…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/cases/20-contexts-not-sessions)

[All cases →](https://cdpfleet.com/docs/cases) 9 match this topic

### Recipes 6

Complete programs for one setup each.

-   [Short-lived sessions for burst jobs](https://cdpfleet.com/docs/examples#burst-timeouts)For many small jobs, make each session end itself: 15 seconds idle or 2 minutes total, whichever comes first, so a hung script never bills for long. Values take `s`,…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/examples/recipes/burst-timeouts)
-   [Chrome you can disconnect from and come back to](https://cdpfleet.com/docs/examples#chrome-keep-alive)`keep_alive: true` (dedicated threads) keeps the browser when your code disconnects: reconnect to the same `wsUrl` within `inactivity_timeout` and your contexts,…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/examples/recipes/chrome-keep-alive)
-   [Microsoft Edge over CDP that survives a disconnect](https://cdpfleet.com/docs/examples#edge-keep-alive)`keep_alive: true` (dedicated threads) with `"cdp": true`: disconnect from `cdpUrl`, reconnect within `inactivity_timeout` and your pages are where you left them.…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/examples/recipes/edge-keep-alive)
-   [A hard time cap for batch jobs](https://cdpfleet.com/docs/examples#overall-timeout-cap)`overall_timeout` ends the session at a fixed time after launch, busy or not. `inactivity_timeout` is different: it only ends a quiet session. Use the cap so a stuck…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/examples/recipes/overall-timeout-cap)
-   [Headful Opera over CDP that survives a disconnect](https://cdpfleet.com/docs/examples#opera-keep-alive)`keep_alive: true` (dedicated threads) with `"cdp": true` on headful Opera: disconnect, reconnect to the same `cdpUrl` within `inactivity_timeout`, and the default…[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/examples/recipes/opera-keep-alive)
-   [Long-running session](https://cdpfleet.com/docs/examples#long-session)Keep a session alive for up to two hours and tolerate five idle minutes. Both are capped by your plan.[Code on GitHub](https://github.com/cdpfleet/cdpfleet/tree/main/examples/recipes/long-session)

### Updates 1

Product news and browser releases.

-   [50 tabs per session, memory as the ceiling, and the memory option](https://cdpfleet.com/docs/updates/tabs-and-memory)Every plan now allows 50 tabs in one session; what actually limits a session is its memory — 2, 4 or 6 GB by plan — and the new "memory" launch option buys 2–4 times…

## Read next

[Shared threads →](https://cdpfleet.com/shared-threads)[Dedicated threads →](https://cdpfleet.com/dedicated-threads)[How it works →](https://cdpfleet.com/how-it-works)[Android & iOS →](https://cdpfleet.com/mobile)

---
Source: https://cdpfleet.com/threads · Markdown: https://cdpfleet.com/threads.md · All docs for AI tools: https://cdpfleet.com/llms.txt
