# How do I add a backup to ZenRows?

Keep ZenRows for the bulk of your pages, and send UpRock the requests it couldn't complete, like a page it couldn't get, a challenge it didn't solve or a full concurrency pool. It's also worth comparing for country-specific pages, because on ZenRows choosing a country requires premium proxies, so each such page costs at least 10 credits, while UpRock charges $0.50 per 1,000 pages in any of its 63 countries.

Updated 21 Oct 2026. Web page: https://uprock.ai/compare/zenrows

## On this page

- [What ZenRows does well](#what-zenrows-does-well)
- [What a page costs, with and without a country](#what-a-page-costs-with-and-without-a-country)
- [Reading a ZenRows error](#reading-a-zenrows-error)
- [ZenRows and UpRock side by side](#zenrows-and-uprock-side-by-side)
- [The fallback, in TypeScript and Python](#the-fallback-in-typescript-and-python)
- [FAQ](#faq)
- [Other use cases to explore](#explore)
- [Related articles](#related)
- [References](#references)
- [Get started](#get-started)

## What ZenRows does well

ZenRows is a scraping API built around getting pages from hard-to-reach sites; its docs say it's "designed to bypass most modern antibot solutions out-of-the-box". Its main product, Fetch (until recently the Universal Scraper API), renders JavaScript, runs scripted steps, extracts data with CSS selectors, returns markdown, plain text, PDFs or screenshots, and has an Adaptive Stealth Mode that starts cheap and escalates only when needed. Around it: Browser Sessions for full remote browsers, crawling and batch jobs, SDKs for Python, Node and Go, a CLI, LangChain and other integrations, and an official MCP server. Premium proxies cover more than 190 countries.

If your pages need scripted steps or extraction in one call, ZenRows is a solid choice. Keep it.

## What a page costs, with and without a country

ZenRows bills fixed credit weights: 1 for a basic request, 5 with JavaScript, 10 with premium proxies, 25 with both. Choosing a country (`proxy_country`) requires premium proxies, so a country-specific page never costs less than 10 credits. Here's what 1,000 pages cost on ZenRows' smallest and largest self-serve plans, billed monthly (prices checked 21 Oct 2026; B1 is $19 for 45,000 credits, S5 is $2,999 for 45 million):

| Request | Credits | B1 ($19) | S5 ($2,999) | UpRock |
|---|---|---|---|---|
| Basic, no country | 1 | $0.42 | $0.07 | $0.50 |
| JavaScript, no country | 5 | $2.11 | $0.33 | $0.50 |
| A chosen country (premium proxies) | 10 | $4.22 | $0.67 | $0.50 |
| A chosen country with JavaScript | 25 | $10.56 | $1.67 | $0.50 |

Annual billing gives two months free, which takes about a sixth off each number. Without a country, ZenRows costs less than UpRock for basic requests on both plans, and for JavaScript ones on the largest; with a country, it costs more on every plan in the table.

## Reading a ZenRows error

ZenRows answers failures with an HTTP status and an RFC 7807 body whose `code` says what happened. Its own guidance is to retry 422, 429 and 5xx and to leave 400 to 404 alone. Here's how we'd route the codes that matter (from ZenRows' error catalog, checked 21 Oct 2026):

| Code (HTTP) | ZenRows' meaning | What to do |
|---|---|---|
| `RESP001` (422) | It couldn't get the content (a target 503 also arrives this way) | Send to UpRock |
| `REQS002` (400) | The page needs JavaScript or premium proxies (not charged) | Send to UpRock, or retry on ZenRows with a pricier tier |
| `RESP006` (424) | A challenge on the page wasn't solved | Send to UpRock |
| `AUTH006`, `AUTH008` (429) | Concurrency or rate limit reached | Send to UpRock |
| `AUTH004` (402) | Usage exceeded | Send to UpRock, or top up |
| `RESP009` (503), `CTX0002` (504), `ERR0000` (500) | At capacity, timed out or an internal error | Send to UpRock |
| `REQS001` (400) | The domain is forbidden (ZenRows blocks banks, payment gateways, visa and government sites) | Respect it |
| `RESP002`, `RESP007` (404) | The page doesn't exist, or the domain doesn't resolve (both billed) | Don't retry |
| `RESP008` (400) | The site's certificate or configuration is broken | Don't retry |

## ZenRows and UpRock side by side

| | ZenRows | UpRock |
|---|---|---|
| Price per 1,000 pages | [$0.07 to $10.56](https://docs.zenrows.com/first-steps/pricing) depending on plan and options (table above) | $0.50 by card or USDC, $0.40 with UPT |
| Failed requests | [Not charged](https://docs.zenrows.com/first-steps/pricing); 404 and 410 are | Not documented yet |
| Country | [Requires premium proxies](https://docs.zenrows.com/fetch/features/proxy-country), 10 credits or more; 190+ countries | Every request, at one price: 63 countries, or a region |
| Where requests come from | Premium proxies described as residential IPs from household ISP connections; how they're sourced isn't stated | Devices whose owners opted in |
| Scripted steps, extraction, screenshots | Yes | No |
| Concurrency | 5 to 400 requests at once, by plan | Not published yet |
| MCP server | [Official](https://docs.zenrows.com/mcp/overview), hosted or local | Yes, remote (streamable HTTP) |
| Video | No (it can list video sources found on a page) | Search public video by moment, $1 per 1,000 searches |
| To try it | Free plan with 5,000 credits a month, no card | Prepaid credits |

Where ZenRows is better: control over each request, scripted steps and extraction, more countries, failed requests that cost nothing, low prices for pages without a country on big plans, and a free plan. Where UpRock fits: the requests ZenRows couldn't complete, country-specific pages at one flat price, and video.

## The fallback, in TypeScript and Python

Every version ask ZenRows for markdown with JavaScript and premium proxies in the country you pass (25 credits), read the error code from the body when it fails, send the codes worth a second try to UpRock and raise the rest. The TypeScript version calls ZenRows' HTTP API directly, because the Node SDK has no timeout (its docs say a hung request waits indefinitely) and ZenRows recommends at least 180 seconds. The Python version uses ZenRows' SDK, which returns non-2xx responses instead of raising. UpRock gets one `POST /v1/crawl` request with your key in an `Authorization: Bearer` header; the MCP and CLI tabs do the same through UpRock's MCP server (TypeScript) or CLI (Python).

### TypeScript (HTTP)

```ts
// Node 18+   env: ZENROWS_API_KEY, UPROCK_API_KEY
// Calls ZenRows' HTTP API directly so the request can time out: the Node SDK has no timeout.

// ZenRows error codes (docs.zenrows.com/api-error-codes, checked 21 Oct 2026) worth sending
// to a second network: needs a pricier tier (REQS002), couldn't get the content (RESP001),
// challenge not solved (RESP006), concurrency or rate limit (AUTH006, AUTH008), out of
// credits (AUTH004), at capacity (RESP009), timeout (CTX0002), internal errors, and a
// missing or bad key (AUTH001 to AUTH003), so your agent keeps working while you fix it.
const TRY_UPROCK = new Set([
  "REQS002",
  "RESP001",
  "RESP006",
  "AUTH004",
  "AUTH006",
  "AUTH008",
  "RESP009",
  "CTX0002",
  "ERR0000",
  "ERR0001",
  "AUTH001",
  "AUTH002",
  "AUTH003",
]);
// Everything else is an answer: REQS001 (a forbidden domain), RESP002 (the page is gone),
// RESP007 (the domain doesn't resolve), RESP008 (a broken certificate), bad parameters.

async function viaUpRock(url: string, country: string) {
  // One call: UpRock loads the page on a real device in that country, tries a fresh device
  // every 7 s (up to 3 tries) and returns markdown. Failed calls aren't billed.
  const res = await fetch("https://edge.uprock.com/v1/crawl", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.UPROCK_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url, country }),
    signal: AbortSignal.timeout(90_000),
  });
  const page = await res.json();
  if (!res.ok) throw new Error(`UpRock ${res.status} ${page.code}: ${page.error}`);
  return page.markdown ?? "";
}

async function viaZenRows(url: string, country: string) {
  const params = new URLSearchParams({
    apikey: process.env.ZENROWS_API_KEY ?? "",
    url,
    js_render: "true",
    premium_proxy: "true", // a country requires premium proxies: 25 credits with js_render
    proxy_country: country.toLowerCase(),
    response_type: "markdown",
  });
  // ZenRows asks for a client timeout of 180 s or more.
  const res = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (res.ok) return { markdown: await res.text() };
  const problem = await res.json().catch(() => ({})); // RFC 7807: { code, title, detail, status }
  return {
    code: problem.code ?? `HTTP_${res.status}`,
    detail: problem.detail ?? problem.title ?? "",
  };
}

export async function read(url: string, country = "US") {
  try {
    const result = await viaZenRows(url, country);
    if (result.markdown !== undefined)
      return { via: "zenrows", markdown: result.markdown };
    if (!TRY_UPROCK.has(result.code))
      throw new Error(`ZenRows ${result.code}: ${result.detail}`);
    console.warn(`ZenRows ${result.code}; trying UpRock`);
  } catch (err) {
    if ((err as Error).message.startsWith("ZenRows ")) throw err;
    console.warn(
      `ZenRows unreachable or timed out (${(err as Error).message}); trying UpRock`,
    );
  }
  return { via: "uprock", markdown: await viaUpRock(url, country) };
}

// node read.ts https://developer.mozilla.org/en-US/docs/Web/HTTP DE
const [url = "https://developer.mozilla.org/en-US/docs/Web/HTTP", country = "US"] =
  process.argv.slice(2);
const page = await read(url, country);
console.log(page.via, page.markdown.slice(0, 200));
```

### Python (HTTP)

```python
# pip install zenrows requests   env: ZENROWS_API_KEY, UPROCK_API_KEY
import os
import sys

import requests
from zenrows import ZenRowsClient

zenrows = ZenRowsClient(os.environ.get("ZENROWS_API_KEY", ""), retries=0)

# ZenRows error codes (docs.zenrows.com/api-error-codes, checked 21 Oct 2026) worth sending
# to a second network: needs a pricier tier (REQS002), couldn't get the content (RESP001),
# challenge not solved (RESP006), concurrency or rate limit (AUTH006, AUTH008), out of
# credits (AUTH004), at capacity (RESP009), timeout (CTX0002), internal errors, and a
# missing or bad key (AUTH001 to AUTH003), so your agent keeps working while you fix it.
TRY_UPROCK = {
    "REQS002",
    "RESP001",
    "RESP006",
    "AUTH004",
    "AUTH006",
    "AUTH008",
    "RESP009",
    "CTX0002",
    "ERR0000",
    "ERR0001",
    "AUTH001",
    "AUTH002",
    "AUTH003",
}
# Everything else is an answer: REQS001 (a forbidden domain), RESP002 (the page is gone),
# RESP007 (the domain doesn't resolve), RESP008 (a broken certificate), bad parameters.


def via_uprock(url: str, country: str) -> str:
    # One call: UpRock loads the page on a real device in that country, tries a fresh device
    # every 7 s (up to 3 tries) and returns markdown. Failed calls aren't billed.
    res = requests.post(
        "https://edge.uprock.com/v1/crawl",
        headers={"Authorization": f"Bearer {os.environ['UPROCK_API_KEY']}"},
        json={"url": url, "country": country},
        timeout=90,
    )
    page = res.json()
    if not res.ok:
        raise RuntimeError(f"UpRock {res.status_code} {page.get('code')}: {page.get('error')}")
    return page.get("markdown", "")


def read(url: str, country: str = "US") -> dict:
    params = {
        "js_render": "true",
        "premium_proxy": "true",  # a country requires premium proxies: 25 credits with js_render
        "proxy_country": country.lower(),
        "response_type": "markdown",
    }
    try:
        res = zenrows.fetch(
            url, params=params, timeout=180
        )  # ZenRows asks for 180 s or more
    except Exception as err:  # the SDK raises only on network errors
        print(f"ZenRows unreachable ({err}); trying UpRock", file=sys.stderr)
    else:
        if res.ok:
            return {"via": "zenrows", "markdown": res.text}
        try:
            problem = res.json()  # RFC 7807: {code, title, detail, status}
        except ValueError:
            problem = {}
        code = problem.get("code") or f"HTTP_{res.status_code}"
        if code not in TRY_UPROCK:
            raise RuntimeError(
                f"ZenRows {code}: {problem.get('detail') or problem.get('title', '')}"
            )
        print(f"ZenRows {code}; trying UpRock", file=sys.stderr)
    return {"via": "uprock", "markdown": via_uprock(url, country)}


if __name__ == "__main__":
    # python read.py https://developer.mozilla.org/en-US/docs/Web/HTTP DE
    url = sys.argv[1] if len(sys.argv) > 1 else "https://developer.mozilla.org/en-US/docs/Web/HTTP"
    country = sys.argv[2] if len(sys.argv) > 2 else "US"
    page = read(url, country)
    print(page["via"], page["markdown"][:200])
```

### TypeScript (MCP)

```ts
// npm i @modelcontextprotocol/sdk   (Node 18+)   env: ZENROWS_API_KEY, UPROCK_API_KEY
// Calls ZenRows' HTTP API directly so the request can time out: the Node SDK has no timeout.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const uprock = new Client({ name: "zenrows-fallback", version: "1.0.0" });
await uprock.connect(
  new StreamableHTTPClientTransport(
    new URL("https://mcp.uprock.ai/mcp"),
    { requestInit: { headers: { Authorization: `Bearer ${process.env.UPROCK_API_KEY}` } } },
  ),
);

// ZenRows error codes (docs.zenrows.com/api-error-codes, checked 21 Oct 2026) worth sending
// to a second network: needs a pricier tier (REQS002), couldn't get the content (RESP001),
// challenge not solved (RESP006), concurrency or rate limit (AUTH006, AUTH008), out of
// credits (AUTH004), at capacity (RESP009), timeout (CTX0002), internal errors, and a
// missing or bad key (AUTH001 to AUTH003), so your agent keeps working while you fix it.
const TRY_UPROCK = new Set([
  "REQS002",
  "RESP001",
  "RESP006",
  "AUTH004",
  "AUTH006",
  "AUTH008",
  "RESP009",
  "CTX0002",
  "ERR0000",
  "ERR0001",
  "AUTH001",
  "AUTH002",
  "AUTH003",
]);
// Everything else is an answer: REQS001 (a forbidden domain), RESP002 (the page is gone),
// RESP007 (the domain doesn't resolve), RESP008 (a broken certificate), bad parameters.

const textOf = (content: unknown) =>
  (content as { type: string; text?: string }[]).find((c) => c.type === "text")?.text ??
  "";

async function viaUpRock(url: string, country: string) {
  const res = await uprock.callTool(
    { name: "crawl_fetch", arguments: { url, country } },
    undefined,
    { timeout: 300_000 },
  );
  if (res.isError) throw new Error(`UpRock: ${textOf(res.content)}`);
  const uri = JSON.parse(textOf(res.content)).content?.markdown?.resource;
  if (!uri) throw new Error(`UpRock: no markdown for ${url}`);
  return textOf(
    (await uprock.callTool({ name: "resource_fetch", arguments: { uri } })).content,
  );
}

async function viaZenRows(url: string, country: string) {
  const params = new URLSearchParams({
    apikey: process.env.ZENROWS_API_KEY ?? "",
    url,
    js_render: "true",
    premium_proxy: "true", // a country requires premium proxies: 25 credits with js_render
    proxy_country: country.toLowerCase(),
    response_type: "markdown",
  });
  // ZenRows asks for a client timeout of 180 s or more.
  const res = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (res.ok) return { markdown: await res.text() };
  const problem = await res.json().catch(() => ({})); // RFC 7807: { code, title, detail, status }
  return {
    code: problem.code ?? `HTTP_${res.status}`,
    detail: problem.detail ?? problem.title ?? "",
  };
}

export async function read(url: string, country = "US") {
  try {
    const result = await viaZenRows(url, country);
    if (result.markdown !== undefined)
      return { via: "zenrows", markdown: result.markdown };
    if (!TRY_UPROCK.has(result.code))
      throw new Error(`ZenRows ${result.code}: ${result.detail}`);
    console.warn(`ZenRows ${result.code}; trying UpRock`);
  } catch (err) {
    if ((err as Error).message.startsWith("ZenRows ")) throw err;
    console.warn(
      `ZenRows unreachable or timed out (${(err as Error).message}); trying UpRock`,
    );
  }
  return { via: "uprock", markdown: await viaUpRock(url, country) };
}

// node read.ts https://developer.mozilla.org/en-US/docs/Web/HTTP DE
const [url = "https://developer.mozilla.org/en-US/docs/Web/HTTP", country = "US"] =
  process.argv.slice(2);
try {
  const page = await read(url, country);
  console.log(page.via, page.markdown.slice(0, 200));
} finally {
  await uprock.close();
}
```

### Python (CLI)

```python
# pip install zenrows; npm i -g uprock   env: ZENROWS_API_KEY, UPROCK_API_KEY
import json
import os
import subprocess
import sys

from zenrows import ZenRowsClient

zenrows = ZenRowsClient(os.environ.get("ZENROWS_API_KEY", ""), retries=0)

# ZenRows error codes (docs.zenrows.com/api-error-codes, checked 21 Oct 2026) worth sending
# to a second network: needs a pricier tier (REQS002), couldn't get the content (RESP001),
# challenge not solved (RESP006), concurrency or rate limit (AUTH006, AUTH008), out of
# credits (AUTH004), at capacity (RESP009), timeout (CTX0002), internal errors, and a
# missing or bad key (AUTH001 to AUTH003), so your agent keeps working while you fix it.
TRY_UPROCK = {
    "REQS002",
    "RESP001",
    "RESP006",
    "AUTH004",
    "AUTH006",
    "AUTH008",
    "RESP009",
    "CTX0002",
    "ERR0000",
    "ERR0001",
    "AUTH001",
    "AUTH002",
    "AUTH003",
}
# Everything else is an answer: REQS001 (a forbidden domain), RESP002 (the page is gone),
# RESP007 (the domain doesn't resolve), RESP008 (a broken certificate), bad parameters.


def via_uprock(url: str, country: str) -> str:
    run = subprocess.run(
        ["uprock", "ai", "crawl", url, "--country", country, "--content"],
        capture_output=True,
        text=True,
        timeout=330,
    )
    if run.returncode != 0:
        raise RuntimeError(f"UpRock: {run.stderr.strip()}")
    return json.loads(run.stdout).get("inlined_markdown", "")


def read(url: str, country: str = "US") -> dict:
    params = {
        "js_render": "true",
        "premium_proxy": "true",  # a country requires premium proxies: 25 credits with js_render
        "proxy_country": country.lower(),
        "response_type": "markdown",
    }
    try:
        res = zenrows.fetch(
            url, params=params, timeout=180
        )  # ZenRows asks for 180 s or more
    except Exception as err:  # the SDK raises only on network errors
        print(f"ZenRows unreachable ({err}); trying UpRock", file=sys.stderr)
    else:
        if res.ok:
            return {"via": "zenrows", "markdown": res.text}
        try:
            problem = res.json()  # RFC 7807: {code, title, detail, status}
        except ValueError:
            problem = {}
        code = problem.get("code") or f"HTTP_{res.status_code}"
        if code not in TRY_UPROCK:
            raise RuntimeError(
                f"ZenRows {code}: {problem.get('detail') or problem.get('title', '')}"
            )
        print(f"ZenRows {code}; trying UpRock", file=sys.stderr)
    return {"via": "uprock", "markdown": via_uprock(url, country)}


if __name__ == "__main__":
    # python read.py https://developer.mozilla.org/en-US/docs/Web/HTTP DE
    url = sys.argv[1] if len(sys.argv) > 1 else "https://developer.mozilla.org/en-US/docs/Web/HTTP"
    country = sys.argv[2] if len(sys.argv) > 2 else "US"
    page = read(url, country)
    print(page["via"], page["markdown"][:200])
```

Tested 21 Oct 2026 against the live UpRock API with an invalid ZenRows key: ZenRows answered with `AUTH002`, and both versions fell back to UpRock. Without an account, the routing of the other codes follows ZenRows' published error catalog. On 6 Nov 2026 we ran the HTTP and MCP versions again with an UpRock API key in an `Authorization` header: with an invalid ZenRows key, each fell back to UpRock and got the page.

## FAQ

### Why does a page from one country cost more on ZenRows?

ZenRows' docs say Proxy Country requires Premium Proxy, and you pay the premium price (10 times the standard) whatever the country. With JavaScript rendering on top, a country-specific page costs 25 credits.

### Does ZenRows charge for failed requests?

No. Its pricing docs say you're charged only for successful requests, and failed or retried requests don't use your balance. 404 and 410 count as successful and are billed, and a markdown request is billed on any 200, even if the content isn't what you expected.

### Should I retry a ZenRows request myself first?

For some codes. ZenRows' own guidance is to retry 422, 429, 500, 502, 503 and 504 and network errors, and not to retry 400, 401, 402, 403 and 404. It also warns that it may temporarily ban a key that sends too many failing requests, which is another reason to send a stubborn page elsewhere rather than keep retrying.

### What happened to the Universal Scraper API?

ZenRows renamed it Fetch, and the Scraping Browser became Browser Sessions. Its site-specific Scraper APIs are deprecated in favor of Fetch, and residential proxies are no longer sold on their own.

### Does ZenRows have an MCP server?

Yes, an official open-source one, hosted at mcp.zenrows.com/mcp or run locally with npx.

## Other use cases to explore

- [Cap what a single page can cost](https://uprock.ai/compare/scrapingbee#what-a-page-costs-in-credits): another credit table, from 1 to 75 credits a page, and how to stop before the expensive tiers.
- [Tell a retryable failure from a refusal](https://uprock.ai/compare/bright-data#reading-a-web-unlocker-failure): which errors to retry, which to send elsewhere, and which to respect, including robots.txt.
- [Fetch from countries your crawler doesn't cover](https://uprock.ai/compare/firecrawl#when-a-second-crawler-helps): such as South Korea and Indonesia, sent straight to a device in that country.
- [Search public video by moment](https://uprock.ai/video-semantic-search): find the second something is shown or said, across platforms.

## Related articles

- [How do I add a backup crawler to Firecrawl?](https://uprock.ai/compare/firecrawl.md): When to send a page from Firecrawl to UpRock, published prices side by side, and a tested fallback in TypeScript and Python.
- [How do I add a backup for Tavily Extract?](https://uprock.ai/compare/tavily.md): Retry the URLs in Tavily Extract's failed results on a real device, and read pages as they appear in a chosen country. Prices side by side and tested code.
- [How do I add a fallback for Exa contents errors?](https://uprock.ai/compare/exa.md): Read the per-URL statuses in Exa's /contents response and retry the right ones live on a real device. Error tags explained, prices side by side, tested code.
- [How do I add a backup to Jina Reader, and when is it cheaper?](https://uprock.ai/compare/jina-reader.md): Spot a failed read in Reader's JSON (it still answers 200), retry it on a real device, and see what six real pages cost in Reader tokens next to a flat per-page price.
- [How do I add a hosted fallback to Crawl4AI?](https://uprock.ai/compare/crawl4ai.md): Plug UpRock into Crawl4AI's fallback_fetch_function so blocked pages are fetched on a real device in the country you choose, then run through Crawl4AI's own markdown pipeline.
- [How do I add a backup to Bright Data's Web Unlocker?](https://uprock.ai/compare/bright-data.md): Which Web Unlocker errors to retry, which to send to a second network and which to respect, with Bright Data's published prices next to ours and tested fallback code.
- [How do I add a fallback to an Apify or Crawlee crawler?](https://uprock.ai/compare/apify.md): Hand the requests Crawlee gives up on to a second network with failedRequestHandler, see what a fetched page costs on Apify's own Actors, and run the tested code.
- [When should a Browserbase agent read a page without opening a browser?](https://uprock.ai/compare/browserbase.md): What 1,000 page reads cost on Browserbase three ways, when to send a read to UpRock instead (including when every browser is busy), and tested fallback code.
- [How do I cap ScrapingBee's credit cost and send the rest to a backup?](https://uprock.ai/compare/scrapingbee.md): ScrapingBee charges 1 to 75 credits a page depending on the options. Cap it with Auto-Mode and max_cost, watch the country fallback, and send the rest to UpRock with tested code.
- [Spider costs less per page than UpRock. When is a backup still worth it?](https://uprock.ai/compare/spider.md): Spider is cheaper per page, so keep it as your primary. How Spider reports a page it couldn't get (from our own tests), and tested code that sends only those pages to UpRock.
- [How do I add a fallback to Parallel's Extract API?](https://uprock.ai/compare/parallel.md): Make Parallel Extract report a failed live fetch instead of quietly serving an old cached copy, then retry those URLs on UpRock. Prices side by side and tested code.
- [How do I retry a ScraperAPI batch's failed jobs on another network?](https://uprock.ai/compare/scraperapi.md): Send the jobs ScraperAPI's async batch gives up on to UpRock, see which countries each ScraperAPI plan can target, and compare credit costs. Tested code for both.
- [How do I add a backup to Oxylabs' new Web API?](https://uprock.ai/compare/oxylabs.md): Which Oxylabs Web API failures to send to a second network and which to respect, what a generic page really costs after its October 2026 pricing change, and tested fallback code.
- [How do I add a backup to Context.dev's scrape API?](https://uprock.ai/compare/context-dev.md): Context.dev reports a failed scrape inside an HTTP 200. Which of its error codes to retry on a second network, what a page costs by plan, and tested fallback code.
- [How do I add a backup to Scrapfly instead of paying for residential?](https://uprock.ai/compare/scrapfly.md): A Scrapfly page costs 1 to 30 credits depending on proxy pool and browser. What that is per 1,000 pages on each plan, which errors to send on, and tested code.
- [How do I keep Crawlbase's site tiers from surprising my bill?](https://uprock.ai/compare/crawlbase.md): Crawlbase bills each site at 1 to 20 times its base rate. What that does to the cost per 1,000 pages, and tested code that moves pricey domains to UpRock.
- [Which unlocker API fits: Bright Data, Oxylabs, Zyte or UpRock?](https://uprock.ai/compare/web-unlocker-apis.md): Bright Data's Web Unlocker, Oxylabs' Web API and Zyte API priced per 1,000 hard pages, with what each bills and when a second network helps.
- [Compare: all articles](https://uprock.ai/compare.md)

## References

1. [ZenRows docs, Pricing (credit weights, plans, charging)](https://docs.zenrows.com/first-steps/pricing), checked 21 Oct 2026
2. [ZenRows, Pricing](https://www.zenrows.com/pricing), checked 21 Oct 2026
3. [ZenRows docs, API error codes](https://docs.zenrows.com/api-error-codes), checked 21 Oct 2026
4. [ZenRows docs, Proxy country (requires Premium Proxy)](https://docs.zenrows.com/fetch/features/proxy-country), checked 21 Oct 2026
5. [ZenRows docs, Fetch API reference](https://docs.zenrows.com/fetch/api-reference), checked 21 Oct 2026
6. [ZenRows docs, Retrying failed requests](https://docs.zenrows.com/academy/retry-failed-requests), checked 21 Oct 2026
7. [ZenRows docs, Forbidden sites](https://docs.zenrows.com/forbidden-sites), checked 21 Oct 2026
8. [ZenRows docs, MCP overview](https://docs.zenrows.com/mcp/overview), checked 21 Oct 2026
9. [UpRock, everything in one file (API, CLI, MCP, pricing)](https://uprock.ai/llms-full.txt), checked 21 Oct 2026

## Get started

Get an API key: https://uprock.ai/app/api-keys

Paste this into your agent:

```text
Set up UpRock for me: read https://uprock.ai/agents.md, connect the UpRock MCP server, and run one test crawl. Tell me what it costs before you spend anything.
```

Setup for every client (MCP, skill, CLI): https://uprock.ai/agents.md
