CrewAI agents work best when tools are:
- deterministic (same input -> same output)
- fast (low latency, low glue code)
- safe (minimal data retention; avoid logging page content by default)
browser.city gives you two good “tool shapes”:
- Request API: one call to turn a URL into markdown
- Humanized REST (
/v1/do/*): interactive steps over HTTP (open, navigate, click, type, markdown) without running Playwright
1) Tool: URL -> markdown (Request API)
Define a tool that returns markdown for any URL:
tools.ts
const apiKey = process.env.BROWSERCITY_API_KEY!;const opts = { method: "POST", headers: { Authorization: `Bearer ${apiKey}` } };export async function browsercityMarkdown(url: string): Promise<string> { const res = await fetch("https://api.browser.city/v1/requests", { ...opts, body: JSON.stringify({ url, markdown: true }), }).then((r) => r.json()); return res.content as string;}import osimport requestsapi_key = os.environ["BROWSERCITY_API_KEY"]def browsercity_markdown(url: str) -> str: res = requests.post( "https://api.browser.city/v1/requests", headers={"Authorization": f"Bearer {api_key}"}, json={"url": url, "markdown": True}, ).json() return 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);async Task<string> BrowsercityMarkdown(string url){ var res = await http.PostAsJsonAsync( "https://api.browser.city/v1/requests", new { url, markdown = true }); return await res.Content.ReadAsStringAsync();}import java.net.URI;import java.net.http.*;var apiKey = System.getenv("BROWSERCITY_API_KEY");var http = HttpClient.newHttpClient();String browsercityMarkdown(String url) throws Exception { var body = "{\"url\":\"" + url + "\",\"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()); return res.body();}
Wire this function into your CrewAI agent as a tool (exact wiring depends on your CrewAI version).
2) Tool: interactive browse -> markdown (Humanized REST)
When a site needs state (cookies) or interaction (click/type), use /v1/do/* as a tool.
browse.ts
const apiKey = process.env.BROWSERCITY_API_KEY!;const opts = { method: "POST", headers: { Authorization: `Bearer ${apiKey}` } };export async function browsercityBrowseMarkdown(url: string): Promise<string> { const open = await fetch("https://api.browser.city/v1/do/open", { ...opts, body: JSON.stringify({ browser: "chromium" }), }).then((r) => r.json()); const sessionId = open.result as string; await fetch("https://api.browser.city/v1/do/navigate", { ...opts, body: JSON.stringify({ sessionId, url }), }); const md = await fetch("https://api.browser.city/v1/do/markdown", { ...opts, body: JSON.stringify({ sessionId }), }).then((r) => r.json()); return md.result as string;}import osimport requestsapi_key = os.environ["BROWSERCITY_API_KEY"]headers = {"Authorization": f"Bearer {api_key}"}def browsercity_browse_markdown(url: str) -> str: open_res = requests.post( "https://api.browser.city/v1/do/open", headers=headers, json={"browser": "chromium"}, ).json() session_id = open_res["result"] requests.post( "https://api.browser.city/v1/do/navigate", headers=headers, json={"sessionId": session_id, "url": url}, ) md = requests.post( "https://api.browser.city/v1/do/markdown", headers=headers, json={"sessionId": session_id}, ).json() return md["result"]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);async Task<string> BrowsercityBrowseMarkdown(string url){ var open = await http.PostAsJsonAsync( "https://api.browser.city/v1/do/open", new { browser = "chromium" }); var openJson = await open.Content.ReadFromJsonAsync<DoResponse>() ?? throw new Exception("bad response"); var sessionId = openJson.result; await http.PostAsJsonAsync( "https://api.browser.city/v1/do/navigate", new { sessionId, url }); var md = await http.PostAsJsonAsync( "https://api.browser.city/v1/do/markdown", new { sessionId }); var mdJson = await md.Content.ReadFromJsonAsync<DoResponse>() ?? throw new Exception("bad response"); return mdJson.result;}public record DoResponse(string result);import java.net.URI;import java.net.http.*;import java.util.regex.*;var apiKey = System.getenv("BROWSERCITY_API_KEY");var http = HttpClient.newHttpClient();String browseMarkdown(String url) throws Exception { var openRes = http.send( HttpRequest.newBuilder() .uri(URI.create("https://api.browser.city/v1/do/open")) .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString("{\"browser\":\"chromium\"}")) .build(), HttpResponse.BodyHandlers.ofString()); var sessionId = extractJsonString(openRes.body(), "result"); http.send( HttpRequest.newBuilder() .uri(URI.create("https://api.browser.city/v1/do/navigate")) .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString( "{\"sessionId\":\"" + sessionId + "\",\"url\":\"" + url + "\"}")) .build(), HttpResponse.BodyHandlers.discarding()); var mdRes = http.send( HttpRequest.newBuilder() .uri(URI.create("https://api.browser.city/v1/do/markdown")) .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString("{\"sessionId\":\"" + sessionId + "\"}")) .build(), HttpResponse.BodyHandlers.ofString()); return extractJsonString(mdRes.body(), "result");}static String extractJsonString(String json, String key) { var m = Pattern.compile("\"" + key + "\"\\s*:\\s*\"([^\"]+)\"").matcher(json); if (!m.find()) throw new RuntimeException("missing " + key); return m.group(1);}
What to use when
- Default to Request API for reading pages at scale (cheap and simple).
- Use Humanized REST when you need click/type/navigation without running Playwright.
- Use Sessions API for long-running, production-grade workflows you want to own in Playwright.