Use this when a plain HTTP client returns an empty shell because the page builds its content in JavaScript. The Request API renders the page in a real stealth browser and returns the finished content as markdown, so your pipeline never sees a loading spinner.
Executable starters
scrape-page.ts
const apiKey = process.env.BROWSERCITY_API_KEY;if (!apiKey) { throw new Error('Set BROWSERCITY_API_KEY before running this script.');}const response = await fetch('https://api.browser.city/v1/requests', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com/product/123', markdown: true }),});if (!response.ok) { throw new Error(`Request failed: ${response.status} ${await response.text()}`);}const page = await response.json();console.log(page.content);import jsonimport osimport urllib.errorimport urllib.requestAPI_KEY = os.environ["BROWSERCITY_API_KEY"]request = urllib.request.Request( "https://api.browser.city/v1/requests", data=json.dumps({"url": "https://example.com/product/123", "markdown": True}).encode("utf-8"), headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }, method="POST",)try: with urllib.request.urlopen(request, timeout=120) as response: page = json.loads(response.read().decode("utf-8"))except urllib.error.HTTPError as error: detail = error.read().decode("utf-8", errors="replace") raise RuntimeError(f"Request failed: {error.code} {detail}") from errorprint(page["content"])
Production hardening checklist
- Store the markdown result immediately; re-fetching costs another render.
- Treat non-2xx responses as retryable with backoff rather than failing the whole batch.
- Move to
POST /v1/requests/batchonce you process URL lists instead of single pages. - Respect target sites’ terms and rate expectations; extraction speed is rarely the bottleneck, blocks are.
Cost and plan notes
One extraction is one short browser render plus the page’s traffic. Heavy product pages with large media weigh more than text articles, so measure a small sample first and put the real average into /pricing-calculator.