Use REST /v1/do/* when
- Your orchestrator speaks HTTP
- You want one action per request
- You need queue-friendly browser jobs
Our /v1/do/* endpoints turn full browser automation into stateless REST calls. Open a browser, navigate, click, type, and extract DOMs directly from any orchestrator that speaks HTTP.
POST /v1/do/openPOST /v1/do/navigatePOST /v1/do/markdownPOST /v1/do/closeHumanized REST simplifies browser control without hiding its power. It runs on the exact same infrastructure as our MCP server and Playwright WebSockets, just exposed for HTTP-first agents.
Pass JSON in, get JSON out. Authenticate via Bearer token. Grab a sessionId from the open call, then include it on subsequent requests to keep your state alive.
const API_BASE = new URL('https://api.browser.city/v1/do/');const apiKey = process.env.BROWSERCITY_API_KEY;async function doAction(action: string, body: unknown) { const url = new URL(encodeURIComponent(action), API_BASE); const response = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); if (!response.ok) { throw new Error(['Humanized REST action failed:', response.status].join(' ')); } return response.json();}const session = await doAction('open', { browser: 'chromium' });try { await doAction('navigate', { sessionId: session.sessionId, url: 'https://example.com' }); const markdown = await doAction('markdown', { sessionId: session.sessionId }); console.log(markdown.result);} finally { await doAction('close', { sessionId: session.sessionId });}import osfrom urllib.parse import quoteimport requestsAPI_BASE = 'https://api.browser.city/v1/do'HEADERS = {'Authorization': f"Bearer {os.environ['BROWSERCITY_API_KEY']}"}def action_url(action: str) -> str: return f'{API_BASE}/{quote(action, safe="")}'def do_action(action: str, body: dict) -> dict: response = requests.post(action_url(action), headers=HEADERS, json=body, timeout=30) response.raise_for_status() return response.json()session = do_action('open', {'browser': 'chromium'})try: do_action('navigate', {'sessionId': session['sessionId'], 'url': 'https://example.com'}) markdown = do_action('markdown', {'sessionId': session['sessionId']}) print(markdown.get('result'))finally: do_action('close', {'sessionId': session['sessionId']})using System.Net.Http.Headers;using System.Net.Http.Json;var apiBase = new Uri("https://api.browser.city/v1/do/");using var http = new HttpClient();http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Bearer", Environment.GetEnvironmentVariable("BROWSERCITY_API_KEY"));var session = await PostActionAsync<OpenResponse>(http, apiBase, "open", new { browser = "chromium" });try{ await PostActionAsync<object>(http, apiBase, "navigate", new { sessionId = session.SessionId, url = "https://example.com" }); var markdown = await PostActionAsync<MarkdownResponse>(http, apiBase, "markdown", new { sessionId = session.SessionId }); Console.WriteLine(markdown.Result);}finally{ await PostActionAsync<object>(http, apiBase, "close", new { sessionId = session.SessionId });}static Uri ActionUri(Uri apiBase, string action) => new(apiBase, Uri.EscapeDataString(action));static async Task<T> PostActionAsync<T>(HttpClient http, Uri apiBase, string action, object body){ using var response = await http.PostAsJsonAsync(ActionUri(apiBase, action), body); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<T>() ?? throw new InvalidOperationException("BrowserCity returned an empty response.");}record OpenResponse(string SessionId);record MarkdownResponse(string? Result);import com.fasterxml.jackson.core.type.TypeReference;import com.fasterxml.jackson.databind.ObjectMapper;import java.net.URI;import java.net.URLEncoder;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.nio.charset.StandardCharsets;import java.util.Map;public class HumanizedRest { private static final URI API_BASE = URI.create("https://api.browser.city/v1/do/"); private static final HttpClient HTTP = HttpClient.newHttpClient(); private static final ObjectMapper JSON = new ObjectMapper(); private static final TypeReference<Map<String, Object>> MAP_RESPONSE = new TypeReference<>() {}; public static void main(String[] args) throws Exception { var session = post("open", Map.of("browser", "chromium")); var sessionId = (String) session.get("sessionId"); try { post("navigate", Map.of("sessionId", sessionId, "url", "https://example.com")); var markdown = post("markdown", Map.of("sessionId", sessionId)); System.out.println(markdown.get("result")); } finally { post("close", Map.of("sessionId", sessionId)); } } private static Map<String, Object> post(String action, Map<String, Object> body) throws Exception { var request = HttpRequest.newBuilder(actionUri(action)) .header("Authorization", "Bearer %s".formatted(System.getenv("BROWSERCITY_API_KEY"))) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(body))) .build(); var response = HTTP.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() / 100 != 2) { throw new RuntimeException("Humanized REST action failed: %d %s".formatted(response.statusCode(), response.body())); } return JSON.readValue(response.body(), MAP_RESPONSE); } private static URI actionUri(String action) { return API_BASE.resolve(URLEncoder.encode(action, StandardCharsets.UTF_8)); }} Our REST layer exposes the exact primitives an agent needs to explore the web, all mounted directly under /v1/do/.
POST /v1/do/openPOST /v1/do/close POST /v1/do/navigatePOST /v1/do/navigate_back POST /v1/do/markdownPOST /v1/do/htmlPOST /v1/do/snapshotPOST /v1/do/search_snapshot POST /v1/do/clickPOST /v1/do/typePOST /v1/do/fill_formPOST /v1/do/select_option
Building a dynamic agent? Call GET /v1/do/tools to fetch a JSON schema of all available actions, ready to drop directly into your LLM's tool array.
Start for free. No credit card required. Private sessions by default.