Quick verdict: If your top KPI is cold start latency and you want per-second billing with no idle charge, Kernel.sh is compelling. If you want fewer cost/config surprises across mixed workloads (extraction + automation + agents) and you care about stealth-by-default + zero-logs, browser.city is the steadier default.
The real decision: “fastest browser” vs “least friction browser infra”
Kernel.sh’s story is speed: unikernel architecture and extremely fast cold starts (vendor-claimed). That matters when you spin up massive numbers of short-lived sessions.
browser.city’s story is production ergonomics:
- stealth is enabled by default
- the system is designed around a zero-logs posture
- you get multiple access modes (Request API, Playwright sessions, MCP tools)
At a glance
| Dimension | browser.city | Kernel.sh |
|---|---|---|
| Cold starts | Fast (focus on infra UX) | Speed-first (unikernels; vendor-claimed sub-150ms cold start) |
| Billing | Simple usage model | Per-second billing (headline advantage for bursty workloads) |
| Stealth | Default | “Stealth on all plans” (verify current behavior) |
| Entry points | Request API, Sessions API, Humanized REST, MCP tools | Primarily browser session workflows + integrations |
| Best for | Mixed workloads (extract + automate + agents) | High-volume short sessions where cold start dominates |
Pricing: per-second is great, but model the whole job
Per-second billing can be a huge win when:
- sessions are short
- you can avoid idle time
- retries are rare
But most real scraping/automation stacks pay for more than compute:
- proxy bandwidth
- retries on hostile sites
- long-running authenticated sessions
If you’re benchmarking cost, do it on “job cost,” not “browser time.”
API surface: extraction vs orchestration
browser.city provides a Request API for “just give me markdown,” which tends to outperform a full browser session for pure extraction jobs.
Example:
const apiKey = process.env.BROWSERCITY_API_KEY!;const opts = { method: "POST", headers: { Authorization: `Bearer ${apiKey}` } };const res = await fetch("https://api.browser.city/v1/requests", { ...opts, body: JSON.stringify({ url: "https://example.com", markdown: true }),}).then((r) => r.json());console.log(res.content);import osimport requestsapi_key = os.environ["BROWSERCITY_API_KEY"]res = requests.post( "https://api.browser.city/v1/requests", headers={"Authorization": f"Bearer {api_key}"}, json={"url": "https://example.com", "markdown": True},).json()print(res["content"])using System.Net.Http.Headers;using System.Net.Http.Json;var apiKey = Environment.GetEnvironmentVariable("BROWSERCITY_API_KEY")!;var http = new HttpClient();http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);var res = await http.PostAsJsonAsync( "https://api.browser.city/v1/requests", new { url = "https://example.com", markdown = true });Console.WriteLine(await res.Content.ReadAsStringAsync());import java.net.URI;import java.net.http.*;var apiKey = System.getenv("BROWSERCITY_API_KEY");var http = HttpClient.newHttpClient();var body = "{\"url\":\"https://example.com\",\"markdown\":true}";var req = HttpRequest.newBuilder() .uri(URI.create("https://api.browser.city/v1/requests")) .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString(body)) .build();var res = http.send(req, HttpResponse.BodyHandlers.ofString());System.out.println(res.body());
When you need orchestration, use sessions and connect with Playwright.
When to pick which
Choose Kernel.sh if:
- you need the lowest cold-start latency you can get
- your workload is lots of short-lived sessions (burst, fan-out)
- per-second billing maps well to your cost model
Choose browser.city if:
- you want a single set of primitives for extraction + automation + agents
- you want stealth enabled by default with minimal configuration
- you care about a zero-logs posture for sensitive workflows