# Data API — QuantumProxies.io API

> Data API endpoints of the QuantumProxies.io API with parameters, request and response examples in cURL, Python, Node.js and PHP. Part of https://quantumproxies.io/docs/index.md.

Base URL `https://api.quantumproxies.io/v1` · Auth `Authorization: Bearer qp_live_…` · HTML: https://quantumproxies.io/docs/ · OpenAPI: https://quantumproxies.io/docs/openapi.json · All endpoints: https://quantumproxies.io/docs/llms.txt · This file: https://quantumproxies.io/docs/data-api.md

_Generated from openapi.json v2026-10-07 on 2026-10-07; prices and limits as of 2026-10-07_

## Scrape

One URL in, Markdown/HTML/text out, with optional structured and AI extraction.

### Scrape one URL

`POST /scraper/extract`

**Price:** `extract` $0.0002 per call + $3/GB, max $0.02 per call · `extract_render` $0.001 per call + $4/GB, max $0.05 per call · `ai_extract` $0.001 per call, max $1.50 per call. `extract` when the TLS/fetch tier served the page, `extract_render` when the browser did; both carry a perGb meter and a capUsd ceiling (pricing.json → meters). `ai_extract` + LLM tokens × ai_token_markup when ai_prompt/ai_schema is used. The pre-check reserves the worst case (render price unless engine is pinned to tls/fetch without render). _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `POST https://api.quantumproxies.io/v1/scrape` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Fetches one page through the residential pool and returns it as Markdown, HTML or text. Starts on a TLS-fingerprint tier (fast, cheap) and, with `engine: auto`, escalates to a stealth headless browser only when the target blocks; `render: true` (or `engine: render`, `screenshot`, `xhr`, `reveal_hidden`) forces the browser. Binary documents (PDF, DOCX, XLSX, CSV) are converted to text. Optional structured extraction (`extract` CSS schema or a stored `presetId`) and AI extraction (`ai_prompt`/`ai_schema`) run on the same fetch. Pass `html` instead of `url` to convert markup you already have (no fetch, no proxy bandwidth). What it does not do: it does not solve interactive captchas, and `mode: summary` returns no page content (so it cannot be combined with AI extraction). Billed per successful page by the engine that served it, plus a per-GB bandwidth component with a per-call cap; AI extraction adds the `ai_extract` fee plus marked-up LLM tokens. A page flagged as blocked or a target status ≥ 400 is not charged.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | no | http/https URL to scrape. Required unless `html` is given. |
| `html` | string | no | Markup to convert instead of fetching (max 5 MB). |
| `format` | string: `markdown`, `html`, `text`, `raw` | no | `raw` is an alias of `html`. Defaults to markdown (also when AI extraction is requested). Default `"markdown"`. |
| `formats` | array of string: `markdown`, `html`, `text` | no | Extra formats returned together under `payload.formats`. |
| `data_format` | string: `markdown`, `screenshot` | no | Compatibility alias: `markdown` sets format, `screenshot` sets `screenshot: fullPage`. |
| `engine` | string: `auto`, `tls`, `fetch`, `render` | no | `auto` starts on the TLS tier and escalates to the browser on a block; `tls`/`fetch` never escalate; `render` forces the browser. Default `"auto"`. |
| `autoEscalate` | boolean | no | Allow/forbid the auto engine's escalation to the browser (ignored once the plan's render budget is spent). |
| `tlsProfile` | string | no | TLS fingerprint profile for the TLS tier (opaque to the API; see /scraper/unlock for the known names). |
| `render` | boolean | no | Force the stealth headless browser (JS execution). Bills the render rate. Default `false`. |
| `mobile` | boolean | no | Mobile viewport and user agent. Default `false`. |
| `waitMs` | number | no | Extra wait after load before capture, render tier (clamped by the service, max 15000). |
| `waitForSelector` | string | no | Wait until this CSS selector appears (render tier). |
| `scrollToBottom` | boolean | no | Auto-scroll to trigger lazy-loaded content (render tier). Default `false`. |
| `actions` | array of object (PageAction) | no |  |
| `screenshot` | boolean \\| string: `fullPage` | no | `true` = viewport PNG, `fullPage` = whole page; forces render. Returned base64 under `screenshot`. |
| `xhr` | boolean | no | Record the page's XHR/fetch traffic under `xhr` (forces render). Use it to discover which API to target with a `fetchResource` action. |
| `expect` | object | no | Assertion that the page is the real one: `element` (CSS selector, ≤512 chars) and/or `text` (≤512 chars) must be present, otherwise the fetch is treated as blocked and retried. |
| `expect.element` | string | no |  |
| `expect.text` | string | no |  |
| `extract` | object (ExtractSchema) | no | Structured-extraction schema: field name → CSS selector, or an object with `selector`, optional `attr` (attribute to read instead of text) and `all` (true = every match as an array). |
| `presetId` | string | no | Run a stored parser preset instead of an inline `extract` schema; the run is scored per field for self-healing. 404 if the preset is not yours. |
| `contentMode` | string: `smart`, `article`, `full` | no | `smart` = whole page minus nav/footer/cookie chrome; `article` = Readability main article; `full` = entire body. Alias `content_mode`. Default `"smart"`. |
| `content_modes` | array of string: `smart`, `article`, `full` | no | Return several content modes at once under `contents`. Alias `contentModes`. |
| `fullPage` | boolean | no | Legacy: `true` = contentMode full. |
| `mode` | string: `full`, `summary` | no | `summary` drops the content and returns only metadata (title, description, canonical, contentLength, engine, bytes) — the light view for audits over many pages. Default `"full"`. |
| `include_links` | boolean | no | Also return de-duplicated absolute links under `links`. Alias `includeLinks`. Default `false`. |
| `app_state` | boolean \\| string: `auto`, `raw` | no | Mine the page's own hydration state (Next/Nuxt/JSON islands) into `metadata.appState`. `true`/`auto` = pruned to the informative parts; `raw` = full blobs. Alias `appState`. |
| `parser` | object | no | Caller CSS rules: `include` (scope the content), `exclude` (drop site-specific chrome), `keep` (protect sections from smart stripping). Each an array of ≤25 selectors, ≤200 chars each. |
| `parser.include` | array of string | no |  |
| `parser.exclude` | array of string | no |  |
| `parser.keep` | array of string | no |  |
| `reveal_hidden` | boolean | no | Render tier: open `<details>`/accordions and click through tabs, capturing every panel. Alias `revealHidden`. |
| `output` | object | no | Markdown shaping; every key also accepted flat (snake_case) at the top level, flat wins. |
| `output.frontmatter` | boolean | no |  |
| `output.toc` | boolean | no |  |
| `output.linksMode` | string: `inline`, `footnote`, `strip` | no |  |
| `output.maxTokens` | number | no |  |
| `output.imagesMode` | string: `inline`, `alt`, `strip` | no |  |
| `output.query` | string | no | Relevance filter for LLM-oriented output. |
| `output.highlights` | integer | no | Most query-relevant passages to return (requires `query`). |
| `output.summarySections` | boolean | no |  |
| `output.chunk` | object | no |  |
| `frontmatter` | boolean | no |  |
| `links_mode` | string: `inline`, `footnote`, `strip` | no |  |
| `toc` | boolean | no |  |
| `max_tokens` | number | no |  |
| `images_mode` | string: `inline`, `alt`, `strip` | no |  |
| `query` | string | no |  |
| `highlights` | integer | no |  |
| `chunk` | object | no |  |
| `summary_sections` | boolean | no |  |
| `ai_prompt` | string | no | Natural-language instruction: the LLM turns the page into structured JSON under `payload.ai.data`. |
| `ai_schema` | object | no | JSON Schema the AI output must follow (deterministic shape). Either `ai_prompt` or `ai_schema` (or both) enables AI extraction. |
| `headers` | object \\| string | no | Extra headers replayed to the target. Either a JSON object (name → value) or a raw `Header: Value` block, one per line (lines starting with `//` or `#` are ignored). Max 32 headers, names must be RFC 7230 tokens, values ≤ 4096 characters; hop-by-hop and framing headers (host, content-length, transfer-encoding, connection, …) are rejected. |
| `cookies` | object | no | name → value cookies sent to the target. |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `state` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `city` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `rotation` | string: `rotating`, `sticky` | no | New exit IP per request, or keep one IP for the session. Any other value is treated as `rotating`. Default `"rotating"`. |
| `sessionId` | string | no | Sticky session id (generated if omitted). |
| `sessionDuration` | number | no | Sticky session lifetime in minutes (clamped by the proxy layer, 3–1440, default 10). |

Example:

```json
{
  "url": "https://example.com/pricing",
  "format": "markdown",
  "country": "us",
  "extract": {
    "title": "h1",
    "price": ".price"
  }
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/extract' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/pricing","format":"markdown","country":"us","extract":{"title":"h1","price":".price"}}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/extract',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "url": "https://example.com/pricing",
        "format": "markdown",
        "country": "us",
        "extract": {
            "title": "h1",
            "price": ".price"
        }
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/extract", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "url": "https://example.com/pricing",
    "format": "markdown",
    "country": "us",
    "extract": {
      "title": "h1",
      "price": ".price"
    }
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/extract');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'url' => 'https://example.com/pricing',
    'format' => 'markdown',
    'country' => 'us',
    'extract' => [
      'title' => 'h1',
      'price' => '.price'
    ]
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Page fetched. `engine` says which tier served it and therefore which price applied.
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The free tier plus wallet balance cannot cover the worst-case estimate of this call, or the account's monthly spend cap is reached. Nothing was run or charged.
- `403` — No proxy pool is available to serve the call (no house plan and no residential/datacenter/IPv6 plan on the account).
- `404` — `presetId` does not resolve to one of your presets.
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Extraction failed on our side or timed out (70 s budget). Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Extraction successful",
  "payload": {
    "url": "https://example.com/pricing",
    "finalUrl": "https://example.com/pricing",
    "status": 200,
    "contentType": "text/html; charset=utf-8",
    "format": "markdown",
    "title": "Pricing — Example",
    "metadata": {
      "description": "Simple, transparent pricing.",
      "canonical": "https://example.com/pricing",
      "language": "en"
    },
    "content": "# Pricing\n\nStarter — $9/month …",
    "data": {
      "title": "Pricing",
      "price": "$9"
    },
    "engine": "tls",
    "attempts": 1,
    "escalated": false,
    "bytes": 48213,
    "durationMs": 1240,
    "geo": {
      "country": "us",
      "state": null,
      "city": null,
      "rotation": "rotating"
    },
    "usage": {
      "cost_usd": 0.0002,
      "free_usd": 0.0002,
      "paid_usd": 0
    }
  },
  "pagination": {}
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Your free tier for this month is used up and your balance can't cover this call (estimated $0.002). Add pay-as-you-go credit or pick a plan — your API key stays the same: https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
  "payload": {
    "code": "INSUFFICIENT_FUNDS",
    "balance": 0,
    "free_remaining": 0,
    "estimated_cost": 0.002,
    "plans_url": "https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
    "billing_mode": "free_first"
  }
}
```

Example `403`:

```json
{
  "type": "error",
  "message": "Scraper API is not available: no proxy credentials found. Contact support or purchase a residential, datacenter or IPv6 plan."
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Preset not found",
  "payload": {
    "url": "https://example.com/"
  }
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Extraction timed out",
  "payload": {
    "url": "https://example.com/"
  }
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

## Search

Structured search-engine results (Google, Bing, DuckDuckGo) and async multi-page jobs.

### Structured search results

`POST /scraper/serp`

**Price:** `serp` $0.0005 per call · `serp_render` $0.002 per call. Google (except autocomplete/reviews): `serp` when search_metadata.path is `http`, `serp_render` otherwise; Bing/DuckDuckGo: always `serp`. The pre-check reserves `serp_render` for Google. _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `POST https://api.quantumproxies.io/v1/serp` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

One search on Google, Bing or DuckDuckGo through the residential pool, returned as JSON in the common SERP-API shape: organic results, ads, People Also Ask, related searches, knowledge panels, AI Overview and 17 Google verticals (`search_type`). Google renders by default so JS-only blocks are populated; pass `render: false` for the cheaper HTTP tier. Identical requests are served from a 24h cache for free (`search_metadata.from_cache`). It does not paginate on your behalf: request one `page` at a time and read `pagination.available_pages`/`has_next`, or use `/scraper/serp/bulk`. Billed per successful search by the tier that served it (`search_metadata.path`); blocked searches answer 429 and cost nothing; `autocomplete` and `reviews` always bill the request price.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | string | no | Search query. Required except for ID-addressed verticals: `place_details` with `place_id`/`data_id`, `product` with `product_id`, `flights`, `lens`, `trends` (no query = trending searches), `reviews` with `data_id`. |
| `engine` | string: `google`, `bing`, `duckduckgo` | no |  Default `"google"`. |
| `search_type` | string: `search`, `shopping`, `images`, `news`, `places`, `maps`, `videos`, `scholar`, `jobs`, `autocomplete`, `place_details`, `hotels`, `flights`, `events`, `product`, `lens`, `reviews`, `trends` | no | Vertical. Alias `type`; Google aliases `tbm` (shop/isch/nws/lcl/vid) and `udm` (28/2/12/1/7) are mapped too. Default `"search"`. |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `lang` | string | no | UI language, e.g. `en`, `it`. Alias `hl`. |
| `num` | number | no | Results to request (clamped to 1–100 by the service; Google serves ~10 per page and merges pages — see `search_metadata.paging`). |
| `page` | number | no | Result page, 1-based. |
| `start` | number | no | Result offset (alternative to `page`). |
| `device` | string: `desktop`, `mobile` | no | Alias `brd_mobile: 1`. Default `"desktop"`. |
| `render` | boolean | no | Google: renders by default; `false` pins the cheaper HTTP tier. Bing/DuckDuckGo never render by default. |
| `browser` | string: `chrome`, `firefox`, `safari` | no | Browser profile for the render tier. Alias `brd_browser`. |
| `safe` | string: `active`, `off` | no | SafeSearch. |
| `nfpr` | boolean \\| integer: `1` | no | Disable auto-corrected results. |
| `location` | string | no | Human-readable search location ("Milan, Italy"), encoded to uule server-side. |
| `uule` | string | no | Encoded uule token OR raw `lat,lon[,radius]`. |
| `google_params` | object | no | Escape hatch: extra Google URL params (keys ≤40 chars of letters, digits, `.`, `-`, `_`; values strings ≤512 chars or numbers). |
| `jobs` | boolean | no | Jobs box on the main SERP. Alias `ibp: "htl;jobs"`. |
| `place_id` | string | no | `place_details`. |
| `data_id` | string | no | `place_details` / `reviews` (feature id 0x…:0x…). |
| `product_id` | string \\| number | no | `product`: the seller list of one shopping result. |
| `departure_id` | string | no | `flights`: airport/city code. |
| `arrival_id` | string | no |  |
| `outbound_date` | string | no | YYYY-MM-DD. |
| `return_date` | string | no |  |
| `check_in_date` | string | no | `hotels`. |
| `check_out_date` | string | no |  |
| `adults` | number | no | `hotels`, clamped 1–30. |
| `children_ages` | array of number | no |  |
| `free_cancellation` | boolean | no |  |
| `accommodation_type` | string: `hotels`, `vacation_rentals` | no |  |
| `currency` | string | no | `hotels`/`flights` price currency (USD, EUR…). |
| `gps_coordinates` | string | no | `maps`: `lat,lon[,zoom]`. |
| `image_url` | string | no | `lens`: http(s) URL of the image to search by. |
| `exact_matches` | boolean | no | `lens`. |
| `sort_by` | string: `relevance`, `newest`, `highest_rating`, `lowest_rating` | no | `reviews`. |
| `filter` | string | no | `reviews`: keyword filter. |
| `next_page_token` | string | no | `reviews`: continuation token from `serpapi_pagination`. |
| `wait_for` | string | no | Render tier: CSS selector to wait for before capture. |
| `include_html` | boolean | no | Also return the page HTML under `html` (scripts stripped). |
| `product_ids` | boolean | no | `shopping`: resolve product ids for each result (slower). |

Example:

```json
{
  "query": "best coffee grinder",
  "engine": "google",
  "country": "us",
  "lang": "en",
  "num": 10
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/serp' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"query":"best coffee grinder","engine":"google","country":"us","lang":"en","num":10}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/serp',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "query": "best coffee grinder",
        "engine": "google",
        "country": "us",
        "lang": "en",
        "num": 10
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/serp", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "query": "best coffee grinder",
    "engine": "google",
    "country": "us",
    "lang": "en",
    "num": 10
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/serp');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'query' => 'best coffee grinder',
    'engine' => 'google',
    'country' => 'us',
    'lang' => 'en',
    'num' => 10
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Search succeeded. The envelope's `pagination` mirrors `payload.pagination`.
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The free tier plus wallet balance cannot cover the worst-case estimate of this call, or the account's monthly spend cap is reached. Nothing was run or charged.
- `403` — No proxy pool is available to serve the call (no house plan and no residential/datacenter/IPv6 plan on the account).
- `429` — Key/plan rate limit, or the engine blocked every attempt (retryable, never billed).
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "SERP successful",
  "payload": {
    "search_metadata": {
      "status": "Success",
      "engine": "google",
      "search_url": "https://www.google.com/search?q=best+coffee+grinder&gl=us&hl=en",
      "created_at": "2026-10-07T09:12:44.000Z",
      "total_time_taken": 2.8,
      "attempts": 1,
      "bytes": 212544,
      "path": "http"
    },
    "search_parameters": {
      "engine": "google",
      "q": "best coffee grinder",
      "search_type": "search",
      "device": "desktop",
      "country": "us",
      "language": "en",
      "page": 1
    },
    "organic": [
      {
        "rank": 1,
        "title": "The 6 Best Coffee Grinders of 2026",
        "link": "https://example.com/reviews/coffee-grinders",
        "display_link": "example.com › reviews",
        "source": "example.com",
        "description": "We tested 24 burr grinders…",
        "date": "12 Sep 2026"
      }
    ],
    "ads": [],
    "people_also_ask": [
      {
        "question": "Is a burr grinder worth it?"
      }
    ],
    "related_searches": [
      {
        "query": "best coffee grinder under 100"
      }
    ],
    "ai_overview": null,
    "knowledge_graph": null,
    "pagination": {
      "current": 1,
      "next": 2,
      "total_pages": null,
      "other_pages": {
        "2": "https://www.google.com/search?q=best+coffee+grinder&start=10"
      },
      "available_pages": [
        1,
        2,
        3,
        4,
        5
      ],
      "has_next": true
    },
    "usage": {
      "cost_usd": 0.0005,
      "free_usd": 0.0005,
      "paid_usd": 0
    }
  },
  "pagination": {
    "current": 1,
    "next": 2,
    "total_pages": null,
    "available_pages": [
      1,
      2,
      3,
      4,
      5
    ],
    "has_next": true
  }
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Your free tier for this month is used up and your balance can't cover this call (estimated $0.002). Add pay-as-you-go credit or pick a plan — your API key stays the same: https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
  "payload": {
    "code": "INSUFFICIENT_FUNDS",
    "balance": 0,
    "free_remaining": 0,
    "estimated_cost": 0.002,
    "plans_url": "https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
    "billing_mode": "free_first"
  }
}
```

Example `403`:

```json
{
  "type": "error",
  "message": "Scraper API is not available: no proxy credentials found. Contact support or purchase a residential, datacenter or IPv6 plan."
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Start a multi-page search job

`POST /scraper/serp/bulk`

**Price:** `serp_render` $0.002 per call · `serp` $0.0005 per call. Google: `serp_render` × max_pages up front; Bing/DuckDuckGo: `serp` × max_pages. Unfetched pages refunded at settlement. _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `POST https://api.quantumproxies.io/v1/serp/bulk` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Paginates one query across up to 10 result pages asynchronously, riding real pagination on one session. Returns a job id immediately; poll `GET /scraper/serp/bulk/{jobId}` for the merged, de-duplicated organic results as pages land, or pass a `webhook` to receive the finished job. Only web-type verticals (search, news, videos, images, shopping) are supported here. Charged up front for `max_pages`; pages the query does not have are refunded when the job settles.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | string | yes |  |
| `engine` | string: `google`, `bing`, `duckduckgo` | no |  Default `"google"`. |
| `search_type` | string: `search`, `news`, `videos`, `images`, `shopping` | no | Alias `type`. Default `"search"`. |
| `max_pages` | integer | no |  Default `5`. |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `lang` | string | no | Alias `hl`. |
| `device` | string: `desktop`, `mobile` | no | Alias `brd_mobile: 1`. |
| `render` | boolean | no |  |
| `wait_for` | string | no |  |
| `browser` | string: `chrome`, `firefox`, `safari` | no |  |
| `safe` | string: `active`, `off` | no |  |
| `nfpr` | boolean \\| integer: `1` | no |  |
| `uule` | string | no |  |
| `location` | string | no |  |
| `google_params` | object | no |  |
| `webhook` | string | no | Public http(s) URL that receives the finished job by POST. |

Example:

```json
{
  "query": "best coffee grinder",
  "engine": "google",
  "country": "us",
  "max_pages": 3
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/serp/bulk' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"query":"best coffee grinder","engine":"google","country":"us","max_pages":3}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/serp/bulk',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "query": "best coffee grinder",
        "engine": "google",
        "country": "us",
        "max_pages": 3
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/serp/bulk", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "query": "best coffee grinder",
    "engine": "google",
    "country": "us",
    "max_pages": 3
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/serp/bulk');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'query' => 'best coffee grinder',
    'engine' => 'google',
    'country' => 'us',
    'max_pages' => 3
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Job accepted.
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The free tier plus wallet balance cannot cover the worst-case estimate of this call, or the account's monthly spend cap is reached. Nothing was run or charged.
- `403` — No proxy pool is available to serve the call (no house plan and no residential/datacenter/IPv6 plan on the account).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Bulk SERP started",
  "payload": {
    "id": "sb_4d1e9c",
    "status": "running",
    "query": "best coffee grinder",
    "total": 3,
    "completed": 0,
    "statusUrl": "/api/v1/scraper/serp/bulk/sb_4d1e9c",
    "usage": {
      "cost_usd": 0.006,
      "free_usd": 0.006,
      "paid_usd": 0
    }
  },
  "pagination": {}
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Your free tier for this month is used up and your balance can't cover this call (estimated $0.002). Add pay-as-you-go credit or pick a plan — your API key stays the same: https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
  "payload": {
    "code": "INSUFFICIENT_FUNDS",
    "balance": 0,
    "free_remaining": 0,
    "estimated_cost": 0.002,
    "plans_url": "https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
    "billing_mode": "free_first"
  }
}
```

Example `403`:

```json
{
  "type": "error",
  "message": "Scraper API is not available: no proxy credentials found. Contact support or purchase a residential, datacenter or IPv6 plan."
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Poll a multi-page search job

`GET /scraper/serp/bulk/{jobId}`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `GET https://api.quantumproxies.io/v1/serp/bulk/{jobId}` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Progress and merged organic results of a bulk search job you own. Pass the previous response's `nextCursor` as `since` to receive only new rows. Jobs are kept about an hour after they finish. Free to poll.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | string | yes | Job id returned by the POST that started it. |
| `since` | query | integer | no | Organic cursor from the previous poll's `nextCursor`. |

#### Examples

**curl**

```bash
curl -X GET 'https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.get(
    'https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a", {
  method: "GET",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Job view.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `404` — Unknown id — or an id that belongs to another account (ownership failures answer 404, never 403, so ids cannot be probed).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Bulk SERP status",
  "payload": {
    "id": "sb_4d1e9c",
    "status": "completed",
    "query": "best coffee grinder",
    "total": 3,
    "completed": 3,
    "failed": 0,
    "pages": [
      {
        "page": 1,
        "organic_count": 10,
        "bytes": 210331,
        "path": "http"
      },
      {
        "page": 2,
        "organic_count": 10,
        "bytes": 198002,
        "path": "http"
      },
      {
        "page": 3,
        "organic_count": 9,
        "bytes": 190114,
        "path": "http"
      }
    ],
    "organic": [
      {
        "rank": 1,
        "title": "The 6 Best Coffee Grinders of 2026",
        "link": "https://example.com/reviews/coffee-grinders",
        "display_link": "example.com",
        "description": "…"
      }
    ],
    "related_searches": [],
    "ai_overview": null,
    "nextCursor": 29,
    "createdAt": 1759828364000,
    "finishedAt": 1759828391000
  },
  "pagination": {}
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Job not found"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Cancel a multi-page search job

`DELETE /scraper/serp/bulk/{jobId}`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `DELETE https://api.quantumproxies.io/v1/serp/bulk/{jobId}` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Stops a running bulk search job you own. Pages never fetched are refunded at settlement.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | string | yes | Job id returned by the POST that started it. |

#### Examples

**curl**

```bash
curl -X DELETE 'https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.delete(
    'https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/serp/bulk/job_8f2c1a');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'DELETE',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Cancelled.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `404` — Unknown id — or an id that belongs to another account (ownership failures answer 404, never 403, so ids cannot be probed).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Bulk SERP cancelled",
  "payload": {
    "id": "sb_4d1e9c",
    "status": "cancelled"
  },
  "pagination": {}
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Job not found"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

## Map & Crawl

URL discovery for a whole site and asynchronous site crawls.

### Discover a site's URLs

`POST /scraper/map`

**Price:** `map` $0.0005 per call _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `POST https://api.quantumproxies.io/v1/map` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Merges robots.txt sitemaps, /sitemap.xml (nested indexes included) and same-domain homepage links into one de-duplicated list, synchronously. Returns up to `limit` URLs plus the site-wide `total` and a per-section `summary`; `group_by: path` returns the path tree instead of the list. It does not fetch page bodies — pair it with /scraper/batch for that. One flat price per call.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | Seed URL. |
| `limit` | number | no | Max URLs to return (service cap 5000). Default `100`. |
| `search` | string | no | Only return URLs containing this substring. |
| `includeSubdomains` | boolean | no |  Default `false`. |
| `sitemapOnly` | boolean | no | Skip the homepage link scrape. Default `false`. |
| `group_by` | string: `path` | no | Return the path tree with counts instead of the URL list. Alias `groupBy`. |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |

Example:

```json
{
  "url": "https://example.com",
  "limit": 100,
  "search": "blog"
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/map' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com","limit":100,"search":"blog"}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/map',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "url": "https://example.com",
        "limit": 100,
        "search": "blog"
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/map", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "url": "https://example.com",
    "limit": 100,
    "search": "blog"
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/map');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'url' => 'https://example.com',
    'limit' => 100,
    'search' => 'blog'
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — URLs discovered.
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The free tier plus wallet balance cannot cover the worst-case estimate of this call, or the account's monthly spend cap is reached. Nothing was run or charged.
- `403` — No proxy pool is available to serve the call (no house plan and no residential/datacenter/IPv6 plan on the account).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Map successful",
  "payload": {
    "url": "https://example.com",
    "links": [
      "https://example.com/blog/",
      "https://example.com/blog/how-to-scrape"
    ],
    "count": 100,
    "total": 2417,
    "summary": {
      "/blog": 1988,
      "/docs": 240,
      "/": 1
    },
    "sources": {
      "sitemap": 2410,
      "homepage": 42
    },
    "durationMs": 3120,
    "geo": {
      "country": null
    },
    "usage": {
      "cost_usd": 0.0005,
      "free_usd": 0.0005,
      "paid_usd": 0
    }
  },
  "pagination": {}
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Your free tier for this month is used up and your balance can't cover this call (estimated $0.002). Add pay-as-you-go credit or pick a plan — your API key stays the same: https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
  "payload": {
    "code": "INSUFFICIENT_FUNDS",
    "balance": 0,
    "free_remaining": 0,
    "estimated_cost": 0.002,
    "plans_url": "https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
    "billing_mode": "free_first"
  }
}
```

Example `403`:

```json
{
  "type": "error",
  "message": "Scraper API is not available: no proxy credentials found. Contact support or purchase a residential, datacenter or IPv6 plan."
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Start a site crawl

`POST /scraper/crawl`

**Price:** `crawl_page` $0.0003 per call · `extract_render` $0.001 per call + $4/GB, max $0.05 per call. `crawl_page` × min(limit, 500) up front, or `extract_render` × pages when render is true. Refund of the unfetched share at settlement. _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `POST https://api.quantumproxies.io/v1/crawl` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Breadth-first crawl from a seed URL through the residential pool, each page converted to the requested format. Returns a job id immediately; poll `GET /scraper/crawl/{jobId}`. Charged up front on `limit` pages (render price per page with `render: true`); unfetched or failed pages are refunded when the job settles. A render crawl takes one browser-render token at submission and its pages then queue on the plan's render concurrency.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | Seed URL. |
| `limit` | number | no | Max pages (cap 500). Billed on this up front. Default `50`. |
| `depth` | number | no | Max link depth from the seed (cap 10). Default `3`. |
| `format` | string: `markdown`, `html`, `text` | no |  Default `"markdown"`. |
| `contentMode` | string: `smart`, `article`, `full` | no | Alias `content_mode`. Default `"smart"`. |
| `render` | boolean | no | Render every page with the stealth browser (slower, rendered rate). Default `false`. |
| `sameDomain` | boolean | no |  Default `true`. |
| `allowSubdomains` | boolean | no |  Default `false`. |
| `include` | array of string | no | URL substrings/globs to include, e.g. ["/guides/*"]. |
| `exclude` | array of string | no |  |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |

Example:

```json
{
  "url": "https://docs.example.com",
  "limit": 50,
  "depth": 3,
  "include": [
    "/guides/*"
  ]
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/crawl' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://docs.example.com","limit":50,"depth":3,"include":["/guides/*"]}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/crawl',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "url": "https://docs.example.com",
        "limit": 50,
        "depth": 3,
        "include": [
            "/guides/*"
        ]
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/crawl", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "url": "https://docs.example.com",
    "limit": 50,
    "depth": 3,
    "include": [
      "/guides/*"
    ]
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/crawl');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'url' => 'https://docs.example.com',
    'limit' => 50,
    'depth' => 3,
    'include' => ['/guides/*']
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Crawl started.
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The free tier plus wallet balance cannot cover the worst-case estimate of this call, or the account's monthly spend cap is reached. Nothing was run or charged.
- `403` — No proxy pool is available to serve the call (no house plan and no residential/datacenter/IPv6 plan on the account).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Crawl started",
  "payload": {
    "id": "cr_d5b8a1",
    "status": "running",
    "seed": "https://docs.example.com/",
    "limit": 50,
    "depth": 3,
    "format": "markdown",
    "pagesCrawled": 0,
    "pagesQueued": 1,
    "statusUrl": "/api/v1/scraper/crawl/cr_d5b8a1",
    "usage": {
      "cost_usd": 0.015,
      "free_usd": 0.015,
      "paid_usd": 0
    }
  },
  "pagination": {}
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Your free tier for this month is used up and your balance can't cover this call (estimated $0.002). Add pay-as-you-go credit or pick a plan — your API key stays the same: https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
  "payload": {
    "code": "INSUFFICIENT_FUNDS",
    "balance": 0,
    "free_remaining": 0,
    "estimated_cost": 0.002,
    "plans_url": "https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
    "billing_mode": "free_first"
  }
}
```

Example `403`:

```json
{
  "type": "error",
  "message": "Scraper API is not available: no proxy credentials found. Contact support or purchase a residential, datacenter or IPv6 plan."
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Poll a crawl job

`GET /scraper/crawl/{jobId}`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `GET https://api.quantumproxies.io/v1/crawl/{jobId}` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Progress and crawled pages of a job you own. Use `since` (the previous `nextCursor`) to page a large crawl and `include_content=false` for cheap status checks. Jobs are kept about an hour after they finish. Free to poll.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | string | yes | Job id returned by the POST that started it. |
| `since` | query | integer | no | Page cursor from the previous poll's `nextCursor`. |
| `include_content` | query | string: `true`, `false` | no | Omit to get the full job; `false` strips page content. |

#### Examples

**curl**

```bash
curl -X GET 'https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.get(
    'https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a", {
  method: "GET",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Job view.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `404` — Unknown id — or an id that belongs to another account (ownership failures answer 404, never 403, so ids cannot be probed).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Crawl status",
  "payload": {
    "id": "cr_d5b8a1",
    "status": "completed",
    "seed": "https://docs.example.com/",
    "limit": 50,
    "depth": 3,
    "format": "markdown",
    "pagesCrawled": 37,
    "pagesQueued": 0,
    "pages": [
      {
        "url": "https://docs.example.com/guides/getting-started",
        "status": 200,
        "title": "Getting started",
        "depth": 1,
        "content": "# Getting started\n…"
      }
    ],
    "nextCursor": 37,
    "hasMore": false,
    "createdAt": 1759828364000,
    "finishedAt": 1759828512000
  },
  "pagination": {}
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Job not found"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Cancel a crawl job

`DELETE /scraper/crawl/{jobId}`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `DELETE https://api.quantumproxies.io/v1/crawl/{jobId}` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Stops a running crawl you own; pages never fetched are refunded at settlement.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | string | yes | Job id returned by the POST that started it. |

#### Examples

**curl**

```bash
curl -X DELETE 'https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.delete(
    'https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/crawl/job_8f2c1a');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'DELETE',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Cancelled.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `404` — Unknown id — or an id that belongs to another account (ownership failures answer 404, never 403, so ids cannot be probed).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Crawl cancelled",
  "payload": {
    "id": "cr_d5b8a1",
    "status": "cancelled"
  },
  "pagination": {}
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Job not found"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

## Batch

Many known URLs scraped asynchronously.

### Scrape many URLs asynchronously

`POST /scraper/batch`

**Price:** `batch_url` $0.0002 per call · `extract_render` $0.001 per call + $4/GB, max $0.05 per call. `batch_url` × urls up front (or `extract_render` × urls when rendering). Failed URLs refunded at settlement. _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `POST https://api.quantumproxies.io/v1/batch` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Up to 5,000 URLs you already know, with shared options, run concurrently through the residential pool. Returns a job id immediately; poll `GET /scraper/batch/{jobId}` or pass a `webhook` to receive the finished job by POST. Charged up front per URL (render price with `render: true` or `engine: render`); the failed/blocked share is refunded when the job settles. A render batch takes one browser-render token at submission.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `urls` | array of string | yes |  |
| `format` | string: `markdown`, `html`, `text` | no |  Default `"markdown"`. |
| `engine` | string: `auto`, `tls`, `fetch`, `render` | no |  Default `"auto"`. |
| `render` | boolean | no | Force the headless browser for every URL. Default `false`. |
| `extract` | object (ExtractSchema) | no | Structured-extraction schema: field name → CSS selector, or an object with `selector`, optional `attr` (attribute to read instead of text) and `all` (true = every match as an array). |
| `contentMode` | string: `smart`, `article`, `full` | no | Alias `content_mode`. Default `"smart"`. |
| `fullPage` | boolean | no | Legacy: contentMode full. |
| `mode` | string: `full`, `summary` | no | `summary` stores per-URL metadata only (title, description, canonical, contentLength). Default `"full"`. |
| `concurrency` | number | no | Simultaneous fetches (clamped 1–20; your tier's batch concurrency also applies). Default `5`. |
| `webhook` | string | no | Public http(s) URL that receives the finished job by POST. |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |

Example:

```json
{
  "urls": [
    "https://example.com/a",
    "https://example.com/b"
  ],
  "format": "markdown",
  "mode": "summary"
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/batch' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"urls":["https://example.com/a","https://example.com/b"],"format":"markdown","mode":"summary"}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/batch',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "urls": [
            "https://example.com/a",
            "https://example.com/b"
        ],
        "format": "markdown",
        "mode": "summary"
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/batch", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "urls": [
      "https://example.com/a",
      "https://example.com/b"
    ],
    "format": "markdown",
    "mode": "summary"
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/batch');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'urls' => ['https://example.com/a', 'https://example.com/b'],
    'format' => 'markdown',
    'mode' => 'summary'
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Batch started.
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The free tier plus wallet balance cannot cover the worst-case estimate of this call, or the account's monthly spend cap is reached. Nothing was run or charged.
- `403` — No proxy pool is available to serve the call (no house plan and no residential/datacenter/IPv6 plan on the account).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Batch started",
  "payload": {
    "id": "bt_91ac07",
    "status": "running",
    "total": 2,
    "completed": 0,
    "failed": 0,
    "statusUrl": "/api/v1/scraper/batch/bt_91ac07",
    "usage": {
      "cost_usd": 0.0004,
      "free_usd": 0.0004,
      "paid_usd": 0
    }
  },
  "pagination": {}
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Your free tier for this month is used up and your balance can't cover this call (estimated $0.002). Add pay-as-you-go credit or pick a plan — your API key stays the same: https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
  "payload": {
    "code": "INSUFFICIENT_FUNDS",
    "balance": 0,
    "free_remaining": 0,
    "estimated_cost": 0.002,
    "plans_url": "https://app.quantumproxies.io/plans?utm_source=api&utm_medium=402&utm_campaign=free-tier",
    "billing_mode": "free_first"
  }
}
```

Example `403`:

```json
{
  "type": "error",
  "message": "Scraper API is not available: no proxy credentials found. Contact support or purchase a residential, datacenter or IPv6 plan."
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Poll a batch job

`GET /scraper/batch/{jobId}`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `GET https://api.quantumproxies.io/v1/batch/{jobId}` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Progress and per-URL results of a job you own. Polls return metadata only unless `include_content=true`; pass the previous `nextCursor` as `since` to receive only newer items. Jobs are kept about an hour after they finish. Free to poll.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | string | yes | Job id returned by the POST that started it. |
| `since` | query | integer | no | Item cursor from the previous poll's `nextCursor`. |
| `include_content` | query | string: `true`, `false` | no | `true` includes each item's page content. |

#### Examples

**curl**

```bash
curl -X GET 'https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.get(
    'https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a", {
  method: "GET",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Job view.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `404` — Unknown id — or an id that belongs to another account (ownership failures answer 404, never 403, so ids cannot be probed).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Batch status",
  "payload": {
    "id": "bt_91ac07",
    "status": "completed",
    "total": 2,
    "completed": 2,
    "failed": 0,
    "contentTruncated": false,
    "items": [
      {
        "url": "https://example.com/a",
        "status": 200,
        "title": "Page A",
        "description": "…",
        "canonical": "https://example.com/a",
        "contentLength": 5120,
        "engine": "tls",
        "bytes": 31870
      },
      {
        "url": "https://example.com/b",
        "status": 200,
        "title": "Page B",
        "description": null,
        "canonical": null,
        "contentLength": 2210,
        "engine": "tls",
        "bytes": 12003
      }
    ],
    "nextCursor": 2,
    "hasMore": false,
    "createdAt": 1759828364000,
    "finishedAt": 1759828370000
  },
  "pagination": {}
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Job not found"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Cancel a batch job

`DELETE /scraper/batch/{jobId}`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
**Short alias:** `DELETE https://api.quantumproxies.io/v1/batch/{jobId}` (same handler, primary host only)  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Stops a running batch you own; URLs never fetched are refunded at settlement.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jobId` | path | string | yes | Job id returned by the POST that started it. |

#### Examples

**curl**

```bash
curl -X DELETE 'https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.delete(
    'https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a", {
  method: "DELETE",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/batch/job_8f2c1a');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'DELETE',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — Cancelled.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `404` — Unknown id — or an id that belongs to another account (ownership failures answer 404, never 403, so ids cannot be probed).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Batch cancelled",
  "payload": {
    "id": "bt_91ac07",
    "status": "cancelled"
  },
  "pagination": {}
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `404`:

```json
{
  "type": "error",
  "message": "Job not found"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

## Web Unlocker

Replay any HTTP request through a residential exit with a real browser fingerprint; prepaid per GB.

### Replay a request through the Web Unlocker

`POST /scraper/unlock`

**Price:** `unlock_request` $2.40/GB of transferred data. Flat base is 0; the whole charge is the `unlock_request` perGb meter applied to `usage.bytes`, debited from the prepaid unlocker GB of the chosen tier. No wallet charge, no free tier. 402 when the tier's GB are exhausted or expired. _(list of 2026-10-07)_  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

Send a request the way your own HTTP client would (url, method, headers, body) and get it back replayed through a residential (or mobile) exit under a real browser TLS fingerprint. GET/HEAD/OPTIONS retry on a fresh exit IP under a different fingerprint when blocked and finally escalate to a headless browser that answers JavaScript challenges; any other method gets exactly one attempt (a response means the target saw it, and replaying a POST could double-submit). A still-blocked page is returned with `blocked: true`, `blockClass` and `vendor`, never as a silent success; `failOnBlock: true` turns that into a 502. It does not solve interactive captchas. This is the same engine as the CONNECT forward proxy, without the proxy protocol — no certificate to install. Billing is different from every other Data API call: it is metered in bytes against the tier's prepaid Web Unlocker GB (`premium` = residential exits, `mobile` = mobile exits), not the wallet, and every attempt counts — retries, a blocked page, the browser escalation's page load.

#### Request body (required)

`Content-Type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes |  |
| `method` | string | no |  Default `"GET"`. |
| `headers` | object \\| string | no | Extra headers replayed to the target. Either a JSON object (name → value) or a raw `Header: Value` block, one per line (lines starting with `//` or `#` are ignored). Max 32 headers, names must be RFC 7230 tokens, values ≤ 4096 characters; hop-by-hop and framing headers (host, content-length, transfer-encoding, connection, …) are rejected. |
| `body` | string | no | Request body as UTF-8 text (JSON, form data…). Use this OR `bodyBase64`. |
| `bodyBase64` | string | no | Request body as base64 for binary payloads. Max 8 MB decoded. |
| `tier` | string: `premium`, `mobile` | no | Which prepaid pool pays and which exits are used. Anything other than `mobile` is `premium`. Default `"premium"`. |
| `tlsProfile` | string: `chrome`, `firefox`, `safari`, `safari_ios`, `edge`, `brave`, `mobile` | no |  Default `"chrome"`. |
| `mobile` | boolean | no | Mobile Safari fingerprint (fingerprint only — the product tier is `tier`). Default `false`. |
| `render` | string: `html`, `png` \\| boolean | no | `html`/`png` (or `true` = html) runs the page in a headless browser instead of the TLS tier, GET/HEAD only, one render token. `false` pins the TLS tier: a blocked page is returned as-is, never escalated. |
| `autoRender` | boolean | no | Escalate a blocked GET to the browser. Default `true`. |
| `keepHeaders` | boolean | no |  Default `false`. |
| `successStatusCodes` | array of integer | no | Origin statuses to accept as success — never treated as a block, never retried. |
| `timeoutMs` | integer | no | Per-attempt timeout at the target (capped by the service's 90 s total budget). |
| `failOnBlock` | boolean | no | 502 instead of a 200 with `blocked: true`. GB are debited either way. Default `false`. |
| `waitForSelector` | string | no | Browser tier: CSS selector to wait for before capture. |
| `waitMs` | integer | no |  |
| `returnCookies` | boolean | no | Return the origin's cookie jar under `cookies`. Default `false`. |
| `cookies` | object | no | Cookies merged into the Cookie header sent to the target (e.g. a clearance obtained earlier). |
| `country` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `state` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `city` | string | no | Country code or place name: letters, digits, spaces, dots, hyphens, underscores; max 56 characters. `all` means no targeting. |
| `rotation` | string: `rotating`, `sticky` | no |  Default `"rotating"`. |
| `sessionId` | string | no | Sticky session id — reuse it across calls to keep one exit IP. |
| `sessionDuration` | number | no | Sticky session lifetime in minutes. |

Example:

```json
{
  "url": "https://example.com/api/search?q=shoes",
  "method": "GET",
  "headers": {
    "Accept": "application/json"
  },
  "country": "us",
  "tier": "premium"
}
```

#### Examples

**curl**

```bash
curl -X POST 'https://api.quantumproxies.io/v1/scraper/unlock' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/api/search?q=shoes","method":"GET","headers":{"Accept":"application/json"},"country":"us","tier":"premium"}'
```

**Python (requests)**

```python
import requests

r = requests.post(
    'https://api.quantumproxies.io/v1/scraper/unlock',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    json={
        "url": "https://example.com/api/search?q=shoes",
        "method": "GET",
        "headers": {
            "Accept": "application/json"
        },
        "country": "us",
        "tier": "premium"
    },
    timeout=120,
)
data = r.json()
if data["type"] != "response":
    raise SystemExit(data["message"])
print(data["payload"])
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/unlock", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "url": "https://example.com/api/search?q=shoes",
    "method": "GET",
    "headers": {
      "Accept": "application/json"
    },
    "country": "us",
    "tier": "premium"
  })
});
const data = await res.json();
if (data.type !== "response") throw new Error(data.message);
console.log(data.payload);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/unlock');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'url' => 'https://example.com/api/search?q=shoes',
    'method' => 'GET',
    'headers' => [
      'Accept' => 'application/json'
    ],
    'country' => 'us',
    'tier' => 'premium'
  ]),
]);
$raw = curl_exec($ch);
curl_close($ch);
$data = json_decode($raw, true);
if ($data['type'] !== 'response') { throw new RuntimeException($data['message']); }
print_r($data['payload']);
```

#### Responses

- `200` — The origin's response (possibly a block page, see `blocked`).
- `400` — Malformed input. `message` names the parameter and the rule it broke.
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `402` — The tier's prepaid unlocker GB are exhausted or expired.
- `403` — No exit pool for the requested tier (e.g. no mobile unlocker GB on the account).
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `500` — Our side: the scraper service is unavailable or the call timed out. Never billed.
- `502` — Only with `failOnBlock: true`: every tier came back blocked. Same diagnostics as the 200 form; the GB moved are debited.
- `503` — The Data API group is switched off for non-admin keys (early-access kill switch).

Example `200` response:

```json
{
  "type": "response",
  "message": "Unlocker request successful",
  "payload": {
    "status": 200,
    "headers": {
      "content-type": "application/json; charset=utf-8",
      "cache-control": "no-store"
    },
    "body": "{\"results\":[{\"id\":1,\"name\":\"Runner\"}]}",
    "bodyBase64": "eyJyZXN1bHRzIjpbeyJpZCI6MSwibmFtZSI6IlJ1bm5lciJ9XX0=",
    "finalUrl": "https://example.com/api/search?q=shoes",
    "contentType": "application/json; charset=utf-8",
    "profile": "chrome",
    "attempts": 1,
    "blocked": false,
    "clearance": "miss",
    "exitSessionId": "s_2f91",
    "strategy": {
      "startTier": "tls",
      "hedged": false
    },
    "rendered": false,
    "escalated": false,
    "tier": "premium",
    "geo": {
      "country": "us",
      "state": null,
      "city": null,
      "rotation": "rotating"
    },
    "usage": {
      "bytes": 18432,
      "unlock_gb_remaining": 4.98,
      "unlock_gb_purchased": 5
    }
  },
  "pagination": {}
}
```

Example `400`:

```json
{
  "type": "error",
  "message": "Invalid format. Must be one of: markdown, html, text (or data_format markdown/screenshot, format raw)"
}
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `402`:

```json
{
  "type": "error",
  "message": "Web Unlocker premium bandwidth exhausted. Buy more unlocker GB to keep using it.",
  "payload": {
    "tier": "premium",
    "unlock_gb_remaining": 0,
    "unlock_gb_purchased": 5,
    "expires_at": "2026-11-01T00:00:00.000Z",
    "expired": false
  }
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `500`:

```json
{
  "type": "error",
  "message": "Scraper service unavailable. Please try again later."
}
```

Example `502`:

```json
{
  "type": "error",
  "message": "Target blocked the request: js_challenge",
  "payload": {
    "status": 403,
    "blockReason": "js_challenge",
    "blockClass": "js_challenge",
    "vendor": "cloudflare",
    "attempts": 3,
    "rendered": true,
    "escalated": true,
    "usage": {
      "bytes": 90112,
      "unlock_gb_remaining": 4.97,
      "unlock_gb_purchased": 5
    }
  }
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "This API is temporarily unavailable while we finalize pricing. Contact support for early access."
}
```

Try it: https://app.quantumproxies.io/data-api/playground

### Download the unlocker CA certificate

`GET /scraper/unlock/ca`

**Price:** No charge.  
**Rate limit:** Per API key, by tier (see Rate limits and tiers).  
[Try it in the Playground](https://app.quantumproxies.io/data-api/playground) · [Markdown for this group](https://quantumproxies.io/docs/data-api.md)

The interception CA used by the Web Unlocker forward proxy (CONNECT mode). The proxy terminates your TLS to read and rewrite the request, so an HTTP client that keeps certificate verification on must trust this file for the target host (`curl --proxy-cacert qp-unlocker-ca.pem --cacert qp-unlocker-ca.pem …`). Not needed for POST /scraper/unlock, nor for the proxy's direct mode. Public by nature, kept behind the key so downloads are attributable. Free.

#### Examples

**curl**

```bash
curl -X GET 'https://api.quantumproxies.io/v1/scraper/unlock/ca' \
  -H 'Authorization: Bearer qp_live_YOUR_API_KEY'
```

**Python (requests)**

```python
import requests

r = requests.get(
    'https://api.quantumproxies.io/v1/scraper/unlock/ca',
    headers={"Authorization": "Bearer qp_live_YOUR_API_KEY"},
    timeout=120,
)
r.raise_for_status()
open("response.out", "wb").write(r.content)
```

**Node (fetch)**

```javascript
const res = await fetch("https://api.quantumproxies.io/v1/scraper/unlock/ca", {
  method: "GET",
  headers: {
    Authorization: "Bearer qp_live_YOUR_API_KEY"
  }
});
const bytes = Buffer.from(await res.arrayBuffer());
require("node:fs").writeFileSync("response.out", bytes);
```

**PHP (curl)**

```php
<?php
$ch = curl_init('https://api.quantumproxies.io/v1/scraper/unlock/ca');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer qp_live_YOUR_API_KEY'],
]);
$raw = curl_exec($ch);
curl_close($ch);
file_put_contents('response.out', $raw);
```

#### Responses

- `200` — PEM certificate (attachment `qp-unlocker-ca.pem`, cacheable 1h).
- `401` — Missing, malformed, unknown, disabled or expired API key; or the account is not active.
- `429` — Key rate limit (hourly window), plan per-minute budget, browser-render concurrency, or pool capacity. Back off on Retry-After.
- `503` — The certificate could not be fetched from the unlocker service right now.

Example `200` response:

```json
"-----BEGIN CERTIFICATE-----\nMIIB…\n-----END CERTIFICATE-----\n"
```

Example `401`:

```json
{
  "type": "error",
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qp_live_YOUR_API_KEY"
}
```

Example `429`:

```json
{
  "type": "error",
  "message": "Rate limit exceeded. Resets in 23 minutes.",
  "payload": {
    "rateLimit": 1200,
    "requestCount": 1200,
    "resetAt": "2026-10-07T15:00:00.000Z"
  }
}
```

Example `503`:

```json
{
  "type": "error",
  "message": "Unlocker certificate is temporarily unavailable"
}
```

Try it: https://app.quantumproxies.io/data-api/playground
