Documentation

Complete reference for the SwarmBrowser API. All endpoints return results within 30 seconds. Batch jobs are asynchronous.

Contents

  1. Quick start
  2. Try it live
  3. Authentication
  4. POST /v1/screenshot
  5. POST /v1/pdf
  6. POST /v1/scrape
  7. POST /v1/batch + GET /v1/batch/:id
  8. POST /v1/compare
  9. POST /v1/diff
  10. Regions
  11. Rate limits
  12. Error codes
  13. Webhooks
  14. dApp rendering
  15. Code examples

Quick start

Get a free key on the home page, then take your first screenshot:

# Screenshot any URL and save it
curl -X POST https://api.swarmbrowser.net/v1/screenshot \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}' \
  --output screenshot.png

You'll get a PNG file back immediately. No setup, no SDK required.

Try it live

Click a button to hit the real API. These run against a shared free-tier demo key (throttled).

Results appear here.

Authentication

Pass your key in the Authorization header or as a query parameter:

# Header (preferred)
Authorization: Bearer rpk_YOUR_KEY

# Query param (for webhooks and testing)
https://api.swarmbrowser.net/v1/screenshot?key=rpk_YOUR_KEY

Keys begin with rpk_ followed by 32 hex characters. Generate one on the home page — no account needed.

Keys are hashed before storage. SwarmBrowser never stores your raw key. If you lose it, generate a new one — your old key cannot be recovered.

POST /v1/screenshot

Renders a URL with a real Chromium browser and returns the screenshot as binary PNG or JPEG.

Request body

FieldTypeDefaultDescription
urlstringrequiredThe URL to render. Must include protocol (https://).
widthinteger1280Viewport width in pixels.
heightinteger720Viewport height in pixels.
formatstringpngpng or jpeg
qualityinteger85JPEG quality 1–100. Ignored for PNG.
fullPagebooleanfalseCapture full scrollable page height.
regionstringmontrealRendering node. See Regions.
waitUntilstringnetworkidle2load, domcontentloaded, networkidle0, networkidle2
blockResourcesstring[][]Resource types to block: image, stylesheet, font, script
userAgentstringChromium defaultOverride the browser user agent string.
timeoutinteger30000Navigation timeout in milliseconds.

Response

Binary image with headers:

Content-Type: image/png
X-Load-Time: 1243       # ms to load the page
X-Location: frankfurt   # region that rendered it
X-Resource-Count: 47    # number of resources loaded

Example

curl -X POST https://api.swarmbrowser.net/v1/screenshot \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -d '{"url":"https://news.ycombinator.com","width":1440,"height":900,"fullPage":true,"region":"singapore"}' \
  --output hn-singapore.png

POST /v1/pdf

Renders a URL and returns a PDF. Useful for generating reports, invoices, and archiving pages.

Request body

FieldTypeDefaultDescription
urlstringrequiredURL to render.
formatstringA4Paper size: A4, A3, Letter, Legal, Tabloid
landscapebooleanfalseLandscape orientation.
printBackgroundbooleantrueInclude background colors and images.
marginobject1cm all{"top":"1cm","bottom":"1cm","left":"1cm","right":"1cm"}
regionstringmontrealRendering node.
waitUntilstringnetworkidle2Navigation wait strategy.
timeoutinteger30000Timeout in ms.

Response

Content-Type: application/pdf — binary PDF with Content-Disposition: attachment; filename="page.pdf".

Example

curl -X POST https://api.swarmbrowser.net/v1/pdf \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -d '{"url":"https://example.com/invoice","format":"A4","landscape":false}' \
  --output invoice.pdf

POST /v1/scrape

Renders a URL and extracts structured data. Works on JavaScript-heavy SPAs, React apps, and any dynamic page.

Request body

FieldTypeDefaultDescription
urlstringrequiredURL to render.
extractstring[]["title","meta","text","headings"]Built-in fields to extract. Options: title, meta, links, text, html, headings, images
selectorsobject{}CSS selectors. Key = field name, value = CSS selector string. Returns array if multiple matches.
waitForstringCSS selector to wait for before extracting.
regionstringmontrealRendering node.
waitUntilstringnetworkidle2Navigation wait strategy.
timeoutinteger30000Timeout in ms.

Response

{
  "url": "https://example.com",
  "location": "frankfurt",
  "metrics": {
    "loadTime": 1243,
    "domContentLoaded": 890,
    "firstPaint": 342,
    "resourceCount": 47,
    "totalBytes": 1284032
  },
  "content": {
    "title": "Example Domain",
    "meta": { "description": "..." },
    "headings": [{ "level": 1, "text": "Example Domain" }],
    "text": "Example Domain\nThis domain is for use..."
  },
  "selectors": {
    "price": "$29.99",
    "stock": "In stock"
  }
}

Example

curl -X POST https://api.swarmbrowser.net/v1/scrape \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -d '{
    "url": "https://store.example.com/product/123",
    "extract": ["title", "meta"],
    "selectors": {
      "price": ".product-price",
      "rating": ".star-rating",
      "stock": "[data-availability]"
    },
    "waitFor": ".product-price"
  }'

POST /v1/batch & GET /v1/batch/:id

Submit up to 10,000 URLs in a single call. Jobs are queued and processed in parallel across the swarm. Poll for completion or receive a webhook.

POST /v1/batch — Submit job

FieldTypeDefaultDescription
urlsstring[]requiredArray of URLs. Max 10,000 on Business, 1,000 on Pro.
actionstringscrapescreenshot, pdf, or scrape
optionsobject{}Same options as the single-URL endpoint for the chosen action.
webhookstringURL to POST results to when complete.
regionstringmontrealRendering region.

Response (submit)

{
  "batchId": "bch_a1b2c3d4e5f6",
  "total": 5000,
  "estimatedSeconds": 180,
  "pollUrl": "https://api.swarmbrowser.net/v1/batch/bch_a1b2c3d4e5f6"
}

GET /v1/batch/:id — Poll status

{
  "batchId": "bch_a1b2c3d4e5f6",
  "status": "processing",  // "queued" | "processing" | "complete" | "failed"
  "total": 5000,
  "completed": 2341,
  "failed": 12,
  "results": [ ... ]  // populated when status = "complete"
}

POST /v1/compare

Renders the same URL from multiple regions simultaneously. Returns a screenshot and performance metrics per region. Ideal for spotting geo-blocks, CDN differences, and regional content changes.

Request body

FieldTypeDefaultDescription
urlstringrequiredURL to compare.
regionsstring[]all regionsSubset of regions to compare. Omit for all 8.
formatstringpngScreenshot format per region.
widthinteger1280Viewport width.
heightinteger720Viewport height.
waitUntilstringnetworkidle2Navigation wait strategy.

Response

{
  "url": "https://example.com",
  "regions": {
    "montreal": {
      "loadTime": 312,
      "resourceCount": 47,
      "screenshot": "base64..."  // base64 PNG
    },
    "frankfurt": {
      "loadTime": 287,
      "resourceCount": 51,
      "screenshot": "base64..."
    },
    "singapore": {
      "loadTime": 891,
      "error": "Navigation timeout"  // per-region errors don't fail the call
    }
  }
}

Example

curl -X POST https://api.swarmbrowser.net/v1/compare \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -d '{"url":"https://yoursite.com","regions":["montreal","frankfurt","singapore","sydney"]}'

POST /v1/diff

Like /v1/compare but also performs pixel-by-pixel comparison between regions and returns a diff image highlighting visual differences. Useful for detecting geo-blocked content, CDN staleness, and A/B test leaks.

Request body

Same as /v1/compare, plus:

FieldTypeDefaultDescription
baselinestringfirst regionRegion to use as the baseline for comparison.
thresholdnumber0.1Pixel difference threshold (0–1). Lower = more sensitive.

Response

{
  "url": "https://example.com",
  "baseline": "montreal",
  "regions": {
    "frankfurt": {
      "screenshot": "base64...",
      "diff": "base64...",       // highlighted diff image
      "diffPixels": 1284,          // number of different pixels
      "diffPercent": 0.83,          // percentage of screen changed
      "loadTime": 287
    }
  }
}

Regions

Use the region parameter to select where rendering happens. For /v1/compare and /v1/diff, pass an array to regions.

IDLocationContinent
montrealMontreal, CanadaNorth America
frankfurtFrankfurt, GermanyEurope
londonLondon, UKEurope
warsawWarsaw, PolandEurope
singaporeSingaporeSoutheast Asia
jakartaJakarta, IndonesiaSoutheast Asia
kualalumpurKuala Lumpur, MalaysiaSoutheast Asia
sydneySydney, AustraliaOceania
Free tier: only montreal is available. All regions are available on Pro and Business.

Rate limits

TierPages/dayConcurrentBatch maxRegions
Free1001montreal only
Pro10,000101,000 URLsAll 8
Business100,0005010,000 URLsAll 8

Overflow (Pro and Business): $0.50 per 1,000 extra pages. You are never blocked — you pay for what you use. Overflow is pulled automatically from your approved allowance.

Rate limit response headers

X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9873
X-RateLimit-Reset: 1713600000

Error codes

HTTP codeErrorMeaning
400missing_urlurl field is required.
401invalid_keyKey not found or malformed.
429rate_limitDaily page limit reached. Upgrade or wait until midnight UTC.
500render_failedBrowser error. Usually a bad URL or timeout. Check the error field.
504timeoutPage took longer than timeout ms to load.

Webhooks

For batch jobs, pass a webhook URL. We'll POST to it with Content-Type: application/json when the job is complete. The payload is identical to the poll response.

curl -X POST https://api.swarmbrowser.net/v1/batch \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -d '{
    "urls": ["https://a.com", "https://b.com"],
    "action": "screenshot",
    "webhook": "https://yourserver.com/webhooks/swarm"
  }'

We retry the webhook 3 times with exponential backoff if your server returns a non-2xx response.

dApp rendering

Capture Web3 pages and DeFi dashboards with injected wallet state. Pass injectWallet to inject a fake Ethereum provider with a configurable address and balance.

curl -X POST https://api.swarmbrowser.net/v1/screenshot \
  -H "Authorization: Bearer rpk_YOUR_KEY" \
  -d '{
    "url": "https://app.uniswap.org",
    "injectWallet": {
      "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
      "balance": "10.5",
      "chainId": 1
    },
    "waitFor": ".token-amount-input",
    "fullPage": false
  }'

This injects window.ethereum with the specified wallet state before page load, so the dApp renders as if a wallet is connected.

Code examples

Node.js

const fs = require('fs');

async function screenshot(url, region = 'frankfurt') {
  const res = await fetch('https://api.swarmbrowser.net/v1/screenshot', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SWARM_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url, region, fullPage: true }),
  });
  if (!res.ok) throw new Error(await res.text());
  const buf = Buffer.from(await res.arrayBuffer());
  fs.writeFileSync('out.png', buf);
  console.log(`Load time: ${res.headers.get('x-load-time')}ms`);
}

screenshot('https://example.com');

Python

import requests, os

def screenshot(url, region='frankfurt'):
    r = requests.post(
        'https://api.swarmbrowser.net/v1/screenshot',
        headers={'Authorization': f'Bearer {os.environ["SWARM_KEY"]}'},
        json={'url': url, 'region': region, 'fullPage': True},
        timeout=60
    )
    r.raise_for_status()
    with open('out.png', 'wb') as f:
        f.write(r.content)
    print(f"Load time: {r.headers['x-load-time']}ms")

screenshot('https://example.com')

Geographic comparison (Node.js)

const res = await fetch('https://api.swarmbrowser.net/v1/compare', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.SWARM_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://yoursite.com',
    regions: ['montreal', 'frankfurt', 'singapore', 'sydney'],
  }),
});

const data = await res.json();
for (const [region, result] of Object.entries(data.regions)) {
  if (result.error) {
    console.log(`${region}: ERROR — ${result.error}`);
  } else {
    console.log(`${region}: ${result.loadTime}ms, ${result.resourceCount} resources`);
  }
}