# How do I add a backup to Jina Reader, and when is it cheaper?

Keep Reader for quick reads and retry the pages it couldn't get on UpRock, from the country you need; since Reader still answers HTTP 200 when a site refused it, the check is the httpStatus and warning fields in its JSON. On price, Reader bills by output tokens, so pages under about 10,000 tokens are cheaper on Reader and longer ones are cheaper at UpRock's flat $0.50 per 1,000 pages.

Updated 24 Oct 2026. Web page: https://uprock.ai/compare/jina-reader

## On this page

- [What Jina Reader does well](#what-jina-reader-does-well)
- [How a failed read looks](#how-a-failed-read-looks)
- [Tokens or pages: what 1,000 reads cost](#tokens-or-pages-what-1000-reads-cost)
- [Jina Reader and UpRock side by side](#jina-reader-and-uprock-side-by-side)
- [The fallback, in Bash, TypeScript and Python](#the-fallback-in-bash-typescript-and-python)
- [FAQ](#faq)
- [Other use cases to explore](#explore)
- [Related articles](#related)
- [References](#references)
- [Get started](#get-started)

## What Jina Reader does well

Reader is the shortest path from a URL to markdown: put `https://r.jina.ai/` in front of any address and you get the page back as LLM-friendly text, with no SDK and, for light use, no key (20 requests a minute). It reads PDFs and Office files, can caption images, can return HTML, plain text or a screenshot instead, and takes headers for CSS selectors, waiting for an element, cookies and cache control. `s.jina.ai` does the same for search results, there's an official MCP server, and the code is open source under Apache-2.0.

For short pages it's also very cheap. Keep it.

## How a failed read looks

Reader answers HTTP 200 even when the target page didn't. Ask for JSON (`Accept: application/json`) and look inside `data`:

```json
{
  "code": 200,
  "data": {
    "url": "https://en.wikipedia.org/wiki/Zzqx_nonexistent_article_9137",
    "httpStatus": 404,
    "warning": "Target URL returned error 404: Not Found",
    "content": "…"
  }
}
```

That's a real response from 19 Oct 2026, trimmed. The same `warning` field carries Reader's other notes, such as a page that may need a CAPTCHA or may not have finished loading. Reader's own errors do use HTTP status codes:

| What you see from Reader | What it means | Retry on UpRock? |
|---|---|---|
| 200 with `data.httpStatus` 401, 403 or 429 | The target site refused Reader's request | Yes |
| 200 with a `warning` about a CAPTCHA or an unfinished load | The content is probably a challenge page or a shell | Yes |
| 403 | Reader is limiting traffic to that site (it does this for heavy anonymous use) | Yes, or use a Reader key |
| 422 | Reader couldn't process the page (a downstream failure) | Yes |
| 429 | You hit Reader's rate limit (20, 500 or 5,000 requests a minute, by key) | Yes, or wait for `retryAfter` |
| 402 or 409 | Your token balance or budget ran out | Yes, or top up |
| 451 | Reader flagged the content as harmful | No |
| 200 with `data.httpStatus` 404 or 410 | The page is gone | No |

## Tokens or pages: what 1,000 reads cost

Reader bills the tokens in its output: $50 buys 1 billion tokens ($0.05 per million) and $500 buys 11 billion ($0.045 per million, with the premium key). So the price of a page depends on how long it is. UpRock charges per page, whatever its length.

We read six public pages through Reader with its default settings on 19 Oct 2026 and noted the token count it reported for each:

| Page | Reader tokens | Reader, per 1,000 such pages ($50 pack) | UpRock, per 1,000 pages |
|---|---|---|---|
| example.com | 341 | $0.02 | $0.50 |
| A Cloudflare blog post | 2,600 | $0.13 | $0.50 |
| RFC 9309 (robots.txt) | 6,637 | $0.33 | $0.50 |
| Python `json` module docs | 9,342 | $0.47 | $0.50 |
| Wikipedia, "Web crawler" | 20,478 | $1.02 | $0.50 |
| Apple MacBook Air product page | 24,134 | $1.21 | $0.50 |

The break-even is about 10,000 tokens a page on the $50 pack, 11,100 on the $500 pack, and 8,000 if you pay UpRock in UPT ($0.40 per 1,000). Pages shorter than that are cheaper on Reader; longer ones, like the Wikipedia article and the product page above, are cheaper on UpRock. Trimming Reader's output with `X-Target-Selector` or `X-Retain-Images: none` moves the line in Reader's favour.

## Jina Reader and UpRock side by side

Reader's prices and rate limits come from jina.ai/reader, checked 24 Oct 2026; the rest from the Reader repository, checked 19 Oct 2026.

| | Jina Reader | UpRock |
|---|---|---|
| Price | [$0.05 per million output tokens](https://jina.ai/reader) ($50 pack), $0.045 on the $500 pack; see the table above | $0.50 per 1,000 pages by card or USDC, $0.40 with UPT |
| Without a key | [20 requests a minute](https://jina.ai/reader) | A few trial crawls a day over HTTP (10 on 3 Nov 2026), then a key is required |
| Rate limits with a key | [500 a minute, 5,000 with a premium key](https://jina.ai/reader) | Not published yet |
| Country | [`X-Proxy: <country>`](https://github.com/jina-ai/reader/blob/main/cookbooks.md), a residential IP, premium key only | Every request: 63 countries, or a region |
| Where pages are fetched from | [Headless Chrome on Google Cloud Run](https://github.com/jina-ai/reader/blob/main/architecture.md) (US and EU regions) | Real desktop and mobile devices whose owners opted in |
| PDFs, image captions, screenshots | Yes | Not documented |
| MCP server | [Official](https://github.com/jina-ai/MCP), remote | Yes, remote (streamable HTTP) |
| Video | [No](https://jina.ai/reader); video embeds become links | Search public video by moment, $1 per 1,000 searches |
| Open source | [Apache-2.0](https://github.com/jina-ai/reader), self-hostable | No. The device runtime (Fuel Engine) is open source |

Where Reader is better: zero setup, a keyless tier, PDFs and images, short pages for fractions of a cent, and self-hosting. Where UpRock fits: the reads that come back with a refusal in `data.httpStatus`, a country on any key, long pages at a flat price, and video.

## The fallback, in Bash, TypeScript and Python

Each version asks Reader for JSON, checks `data.httpStatus` and `data.warning`, and only calls UpRock when the read failed in a way a second try can fix. UpRock gets one `POST /v1/crawl` request with your key in an `Authorization: Bearer` header; the CLI and MCP tabs do the same through the UpRock CLI (`npm i -g uprock`) or its MCP server.

### Bash (HTTP)

```bash
#!/usr/bin/env bash
# read.sh <url> [country]   needs curl and jq
# env: UPROCK_API_KEY; JINA_API_KEY is optional (Reader works without one at 20 requests a minute)
set -o pipefail
url=$1; country=${2:-US}

auth=(); [ -n "$JINA_API_KEY" ] && auth=(-H "Authorization: Bearer $JINA_API_KEY")
resp=$(curl -s -w '\n%{http_code}' -H 'Accept: application/json' "${auth[@]}" "https://r.jina.ai/$url")
code=${resp##*$'\n'}; body=${resp%$'\n'*}
target=$(jq -r '.data.httpStatus // empty' <<<"$body" 2>/dev/null)
warning=$(jq -r '.data.warning // empty' <<<"$body" 2>/dev/null)

case "$code:$target" in
  200:404 | 200:410) echo "The page is gone ($target)" >&2; exit 1 ;;
  200:401 | 200:403 | 200:429 | 200:5??) ;;   # the site refused Reader: try UpRock
  200:*)
    if ! grep -qiE 'captcha|not yet fully loaded' <<<"$warning"; then
      jq -r .data.content <<<"$body"; exit 0
    fi ;;
  400:* | 401:* | 451:*) echo "Reader said $code: $(jq -r '.readableMessage // .message' <<<"$body")" >&2; exit 1 ;;
esac

echo "Reader: HTTP $code, target ${target:-none} ${warning}. Trying UpRock from $country" >&2
curl -s --max-time 90 https://edge.uprock.com/v1/crawl \
  -H "Authorization: Bearer $UPROCK_API_KEY" -H 'Content-Type: application/json' \
  -d "$(jq -n --arg url "$url" --arg country "$country" '{url: $url, country: $country}')" |
  jq -r 'if .markdown then .markdown else "UpRock: \(.code): \(.error)\n" | halt_error end'
```

### TypeScript (HTTP)

```ts
// Node 18+   env: UPROCK_API_KEY, optional JINA_API_KEY

// Statuses from the target site (data.httpStatus) and from Reader itself that a second try can fix.
const RETRY_TARGET = new Set([401, 403, 429, 500, 502, 503, 504]);
const RETRY_READER = new Set([402, 403, 409, 422, 429, 500, 503]);

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 ?? "";
}

export async function read(url: string, country = "US") {
  const key = process.env.JINA_API_KEY;
  const res = await fetch(`https://r.jina.ai/${url}`, {
    headers: {
      Accept: "application/json",
      ...(key ? { Authorization: `Bearer ${key}` } : {}),
    },
  });
  const body = await res.json().catch(() => ({}));
  const target: number | undefined = body.data?.httpStatus;
  const warning = String(body.data?.warning ?? "");

  if (res.ok) {
    if (target === 404 || target === 410)
      throw new Error(`The page is gone: ${warning}`);
    const unfinished = /captcha|not yet fully loaded/i.test(warning);
    if (!RETRY_TARGET.has(target ?? 200) && !unfinished)
      return { via: "jina", content: body.data.content as string };
  } else if (!RETRY_READER.has(res.status)) {
    throw new Error(
      `Reader ${res.status}: ${body.readableMessage ?? body.message ?? ""}`,
    );
  }
  console.warn(
    `Reader: HTTP ${res.status}, target ${target ?? "none"} ${warning}. Trying UpRock from ${country}`,
  );
  return { via: "uprock", content: 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.content.slice(0, 200));
```

### Python (HTTP)

```python
# pip install requests   env: UPROCK_API_KEY, optional JINA_API_KEY
import os
import re
import sys

import requests

# Statuses from the target site (data.httpStatus) and from Reader itself that a second try can fix.
RETRY_TARGET = {401, 403, 429, 500, 502, 503, 504}
RETRY_READER = {402, 403, 409, 422, 429, 500, 503}


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:
    headers = {"Accept": "application/json"}
    if os.environ.get("JINA_API_KEY"):
        headers["Authorization"] = f"Bearer {os.environ['JINA_API_KEY']}"
    res = requests.get(f"https://r.jina.ai/{url}", headers=headers, timeout=120)
    try:
        body = res.json()
    except ValueError:
        body = {}
    data = body.get("data") or {}
    target, warning = data.get("httpStatus"), data.get("warning") or ""

    if res.ok:
        if target in (404, 410):
            raise LookupError(f"The page is gone: {warning}")
        unfinished = re.search(r"captcha|not yet fully loaded", warning, re.I)
        if (target or 200) not in RETRY_TARGET and not unfinished:
            return {"via": "jina", "content": data.get("content", "")}
    elif res.status_code not in RETRY_READER:
        raise RuntimeError(
            f"Reader {res.status_code}: {body.get('readableMessage') or body.get('message')}"
        )

    print(
        f"Reader: HTTP {res.status_code}, target {target} {warning}. Trying UpRock from {country}",
        file=sys.stderr,
    )
    return {"via": "uprock", "content": 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["content"][:200])
```

### Bash (CLI)

```bash
#!/usr/bin/env bash
# read.sh <url> [country]   needs curl, jq and the UpRock CLI (npm i -g uprock)
# env: UPROCK_API_KEY; JINA_API_KEY is optional (Reader works without one at 20 requests a minute)
set -o pipefail
url=$1; country=${2:-US}

auth=(); [ -n "$JINA_API_KEY" ] && auth=(-H "Authorization: Bearer $JINA_API_KEY")
resp=$(curl -s -w '\n%{http_code}' -H 'Accept: application/json' "${auth[@]}" "https://r.jina.ai/$url")
code=${resp##*$'\n'}; body=${resp%$'\n'*}
target=$(jq -r '.data.httpStatus // empty' <<<"$body" 2>/dev/null)
warning=$(jq -r '.data.warning // empty' <<<"$body" 2>/dev/null)

case "$code:$target" in
  200:404 | 200:410) echo "The page is gone ($target)" >&2; exit 1 ;;
  200:401 | 200:403 | 200:429 | 200:5??) ;;   # the site refused Reader: try UpRock
  200:*)
    if ! grep -qiE 'captcha|not yet fully loaded' <<<"$warning"; then
      jq -r .data.content <<<"$body"; exit 0
    fi ;;
  400:* | 401:* | 451:*) echo "Reader said $code: $(jq -r '.readableMessage // .message' <<<"$body")" >&2; exit 1 ;;
esac

echo "Reader: HTTP $code, target ${target:-none} ${warning}. Trying UpRock from $country" >&2
uprock ai crawl "$url" --country "$country" --content | jq -r .inlined_markdown
```

### TypeScript (MCP)

```ts
// npm i @modelcontextprotocol/sdk   (Node 18+)   env: UPROCK_API_KEY, optional JINA_API_KEY
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const uprock = new Client({ name: "jina-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}` } } },
  ),
);

// Statuses from the target site (data.httpStatus) and from Reader itself that a second try can fix.
const RETRY_TARGET = new Set([401, 403, 429, 500, 502, 503, 504]);
const RETRY_READER = new Set([402, 403, 409, 422, 429, 500, 503]);

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,
  );
}

export async function read(url: string, country = "US") {
  const key = process.env.JINA_API_KEY;
  const res = await fetch(`https://r.jina.ai/${url}`, {
    headers: {
      Accept: "application/json",
      ...(key ? { Authorization: `Bearer ${key}` } : {}),
    },
  });
  const body = await res.json().catch(() => ({}));
  const target: number | undefined = body.data?.httpStatus;
  const warning = String(body.data?.warning ?? "");

  if (res.ok) {
    if (target === 404 || target === 410)
      throw new Error(`The page is gone: ${warning}`);
    const unfinished = /captcha|not yet fully loaded/i.test(warning);
    if (!RETRY_TARGET.has(target ?? 200) && !unfinished)
      return { via: "jina", content: body.data.content as string };
  } else if (!RETRY_READER.has(res.status)) {
    throw new Error(
      `Reader ${res.status}: ${body.readableMessage ?? body.message ?? ""}`,
    );
  }
  console.warn(
    `Reader: HTTP ${res.status}, target ${target ?? "none"} ${warning}. Trying UpRock from ${country}`,
  );
  return { via: "uprock", content: 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.content.slice(0, 200));
} finally {
  await uprock.close();
}
```

### Python (CLI)

```python
# pip install requests; npm i -g uprock   env: UPROCK_API_KEY, optional JINA_API_KEY
import json
import os
import re
import subprocess
import sys

import requests

# Statuses from the target site (data.httpStatus) and from Reader itself that a second try can fix.
RETRY_TARGET = {401, 403, 429, 500, 502, 503, 504}
RETRY_READER = {402, 403, 409, 422, 429, 500, 503}


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:
    headers = {"Accept": "application/json"}
    if os.environ.get("JINA_API_KEY"):
        headers["Authorization"] = f"Bearer {os.environ['JINA_API_KEY']}"
    res = requests.get(f"https://r.jina.ai/{url}", headers=headers, timeout=120)
    try:
        body = res.json()
    except ValueError:
        body = {}
    data = body.get("data") or {}
    target, warning = data.get("httpStatus"), data.get("warning") or ""

    if res.ok:
        if target in (404, 410):
            raise LookupError(f"The page is gone: {warning}")
        unfinished = re.search(r"captcha|not yet fully loaded", warning, re.I)
        if (target or 200) not in RETRY_TARGET and not unfinished:
            return {"via": "jina", "content": data.get("content", "")}
    elif res.status_code not in RETRY_READER:
        raise RuntimeError(
            f"Reader {res.status_code}: {body.get('readableMessage') or body.get('message')}"
        )

    print(
        f"Reader: HTTP {res.status_code}, target {target} {warning}. Trying UpRock from {country}",
        file=sys.stderr,
    )
    return {"via": "uprock", "content": 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["content"][:200])
```

Tested 19 Oct 2026 on Reader's keyless tier with the live UpRock API: example.com stayed on Reader, a test URL that always answers 403 went to UpRock, and a missing Wikipedia page stopped as gone. The Bash version runs on the bash 3.2 that ships with macOS. On 6 Nov 2026 we ran the HTTP and MCP versions again with an UpRock API key in an `Authorization` header: the 403 test URL went from Reader to UpRock.

## FAQ

### Does Jina Reader return an error when the target page fails?

Not as an HTTP error. When we asked Reader for a Wikipedia page that doesn't exist, the response was HTTP 200 with data.httpStatus set to 404 and data.warning saying "Target URL returned error 404". Check those two fields, not just the response code.

### How many Reader tokens does a web page use?

It depends on the page. With Reader's default settings we measured 341 tokens for example.com, 2,600 for a blog post, 9,342 for a Python docs page and 24,134 for a product page (19 Oct 2026). Selectors such as X-Target-Selector, or dropping images, cut that down.

### Can Jina Reader fetch from a specific country?

Yes. The X-Proxy header takes a country code and, per Reader's cookbook, exits through a residential IP in that country. It needs a premium key, which comes with the $500 token pack. UpRock takes a country on every request.

### Is there an UpRock URL prefix like r.jina.ai?

Not today. UpRock takes one `POST /v1/crawl` request per page, which the fallback on this page leads with, or runs through its MCP server or CLI. Without a key you get a few trial crawls a day over HTTP (10 on 3 Nov 2026); after that it needs an API key.

### When should I not bother with a fallback?

When the page is gone (404 or 410) or behind a login. Reader and UpRock both fetch public pages only, so a second try won't help there.

## Other use cases to explore

- [See a page as it looks in another country](https://uprock.ai/use-cases/page-from-another-country): one store page from five countries, with five prices, two languages and a cookie banner in one country only.
- [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.
- [Add a fallback to a crawler you host](https://uprock.ai/compare/crawl4ai#plug-uprock-into-fallback_fetch_function): send only the pages your server can't load to a device in the country you choose.
- [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 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 add a backup to ZenRows?](https://uprock.ai/compare/zenrows.md): Which ZenRows error codes to send to a second network and which to respect, what a page from one country costs once premium proxies are required, and tested fallback 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. [Jina AI, Reader (pricing, rate limits, headers)](https://jina.ai/reader), checked 24 Oct 2026
2. [jina-ai/reader on GitHub (README, Apache-2.0)](https://github.com/jina-ai/reader), checked 19 Oct 2026
3. [jina-ai/reader cookbook (country proxy needs a premium key)](https://github.com/jina-ai/reader/blob/main/cookbooks.md), checked 19 Oct 2026
4. [jina-ai/reader architecture (headless Chrome on Google Cloud Run)](https://github.com/jina-ai/reader/blob/main/architecture.md), checked 19 Oct 2026
5. [Reader API spec (error codes)](https://r.jina.ai/openapi.json), checked 19 Oct 2026
6. [Jina AI MCP server](https://github.com/jina-ai/MCP), checked 19 Oct 2026
7. [UpRock, everything in one file (API, CLI, MCP, pricing)](https://uprock.ai/llms-full.txt), checked 24 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
