# How do I see a website as people in another country see it?

Load the page from a device in that country and compare it with your own view. Many sites pick the currency, price, cookie banner and sometimes the language from the visitor's IP address and browser rather than the URL, so the link alone won't show you. When we loaded one Steam store page from five countries with UpRock on 25 Oct 2026, it came back with five currencies, two languages and a cookie banner in one country only.

Updated 25 Oct 2026. Web page: https://uprock.ai/use-cases/page-from-another-country

## On this page

- [Why one URL shows different pages](#why-one-url-shows-different-pages)
- [One store page from five countries](#one-store-page-from-five-countries)
- [Getting a fair comparison](#getting-a-fair-comparison)
- [Compare a URL from several countries](#compare-a-url-from-several-countries)
- [FAQ](#faq)
- [Other use cases to explore](#explore)
- [Related articles](#related)
- [References](#references)
- [Get started](#get-started)

## Why one URL shows different pages

A website can't see where you are, but it can make a good guess, and most commercial sites do. Three inputs decide most of what changes:

| What the site reads | What it usually changes | Can a URL set it? |
|---|---|---|
| The IP address, looked up in a geolocation database | Currency, prices and tax, what's on offer, legal notices such as cookie consent banners, sometimes a redirect to a country site | Rarely |
| The browser's language (the `Accept-Language` header) | The page language | Sometimes, on sites with language paths |
| Cookies and account settings | Whatever the visitor chose before: a region, a currency, a language | Not for a new visitor |

So one link can show a different price, in a different currency and language, with or without a cookie banner, to each visitor. An agent that fetches every page from one data center sees one version of the web, and it's often not the one your users see. Putting `/de/` in the URL only helps on sites that use country paths. The store page below has one URL for every country.

## One store page from five countries

On 25 Oct 2026 we loaded the Steam store page for Portal 2 (`store.steampowered.com/app/620/Portal_2/`), a game sold worldwide since 2011, from five countries with UpRock's `crawl_fetch`. Same URL each time, no cookies, no login, each fetch on a device in the country we named. (On the [Tavily page](https://uprock.ai/compare/tavily#a-page-as-it-looks-in-one-country) we only checked where a fetch came from, with an IP lookup page; this is what that location changes.)

| Country | Price shown for Portal 2 | Page language | Cookie banner | Redirect |
|---|---|---|---|---|
| United States | $9.99 | English | No | None |
| Germany | 9,69€ | English | Yes, a cookie preferences popup | None |
| Japan | ¥ 1,400 | Japanese (title "Steam：Portal 2") | No | None |
| Brazil | R$ 33,99 | English | No | None |
| India | ₹ 509 | English | No | None |

Five currencies and five prices came from one URL with no redirect, so nothing in the link tells you which one you'll get. The cookie popup appeared in Germany, the one EU country in the set (in an earlier run, a device in the UK got it too). The language followed the device more than the country: Japan got Japanese, the others English, and in that earlier run the UK device got the page in Brazilian Portuguese (`lang="pt-br"`), most likely because that's the language its browser asks for.

Prices aren't the only thing that moves. The Netflix Help Center's [Plans and Pricing](https://help.netflix.com/en/node/24926) article also has a single URL and shows the plans for the visitor's location. Fetched from Germany the same day, it listed "Pricing (Euro)", Standard at 15.99€ a month and extra members at 4.99€ or 5.99€. From Japan it listed "Pricing (Japanese Yen)", Standard at 1,590 JPY, no extra-member option at all, and a different note about the old Basic plan. Same article, a different offer.

## Getting a fair comparison

- **Fetch all countries in the same run.** Stores change prices for sales and promotions, so a German price from Monday and a Brazilian one from Friday don't compare. The code below fetches every country at once.
- **Parse the HTML for prices.** The markdown UpRock makes is meant for reading, and it can leave parts out: on the Steam page from Japan, the markdown dropped the buy box with the price, while the HTML had it. For the Netflix article from Japan, there was no markdown at all, only HTML. The code reads the HTML for that reason.
- **Look past `meta.url` for redirects.** The crawl result's `meta.url` is the URL you asked for, not where a redirect ended. If a site sends visitors to a country path, you'll see it in the page's links or its canonical tag.
- **Check what the price includes.** Prices in the EU include VAT, and US prices are usually shown before sales tax, so part of a gap is tax.
- **Treat one device as one visitor.** Language, and sometimes content, follows that person's settings. Fetch twice before you act on one odd result.
- **Expect the logged-out view.** UpRock fetches public pages and never logs in, so member prices and saved regions don't show.

## Compare a URL from several countries

Every version fetch one URL from every country you list at the same time, read the stored HTML, and print the page title, the `lang` attribute and the first three prices on the page, marking which of them differ. 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). Pass two-letter country codes (63 are supported) or a region such as `EU`.

### TypeScript (HTTP)

```ts
// Node 18+   env: UPROCK_API_KEY
// node compare.ts <url> US DE JP BR IN

// Prices such as 9,69€, ¥ 1,400, R$ 32,99, ₹ 465, $9.99 or 890 JPY.
const PRICE =
  /(?:R\$|US\$|CA\$|A\$|[$€£¥₹₩])\s?\d[\d.,]*|\d[\d.,]*\s?(?:[€£¥₹]|(?:zł|kr|JPY|EUR|USD|GBP|INR|BRL|CAD)\b)/g;

type View = { country: string; title: string; lang: string; prices: string };

async function viewFrom(url: string, country: string): Promise<View> {
  // 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 the rendered HTML next to the markdown.
  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, format: "html" }),
    signal: AbortSignal.timeout(90_000),
  });
  const page = await res.json();
  if (!res.ok) throw new Error(`UpRock ${res.status} ${page.code}: ${page.error}`);
  // The HTML keeps <html lang>, which the markdown drops.
  const html: string = page.html ?? "";
  const text = html
    .replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ")
    .replace(/<[^>]+>/g, " ")
    .replace(/&nbsp;/g, " ");
  return {
    country,
    title: page.title ?? "",
    lang: /<html[^>]*\slang="([^"]*)"/i.exec(html)?.[1] ?? "",
    prices: [...new Set(text.match(PRICE))].slice(0, 3).join(", "),
  };
}

// Fetch the URL from every country at once; each call waits for a device there.
const [url, ...countries] = process.argv.slice(2);
const results = await Promise.allSettled(countries.map((c) => viewFrom(url, c)));

const views: View[] = [];
results.forEach((r, i) =>
  r.status === "fulfilled"
    ? views.push(r.value)
    : console.log(`${countries[i]}: failed (${r.reason})`),
);
for (const field of ["title", "lang", "prices"] as const) {
  const differs = new Set(views.map((v) => v[field])).size > 1;
  console.log(`\n${field}: ${differs ? "differs" : "same everywhere"}`);
  for (const v of views) console.log(`  ${v.country}  ${v[field]}`);
}
```

### Python (HTTP)

```python
# pip install requests   env: UPROCK_API_KEY
# python compare.py <url> US DE JP BR IN
import os
import re
import sys
from concurrent.futures import ThreadPoolExecutor

import requests

# Prices such as 9,69€, ¥ 1,400, R$ 32,99, ₹ 465, $9.99 or 890 JPY.
PRICE = re.compile(
    r"(?:R\$|US\$|CA\$|A\$|[$€£¥₹₩])\s?\d[\d.,]*"
    r"|\d[\d.,]*\s?(?:[€£¥₹]|(?:zł|kr|JPY|EUR|USD|GBP|INR|BRL|CAD)\b)"
)



def view_from(url: str, country: str) -> dict:
    # 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 the rendered HTML next to the markdown.
    res = requests.post(
        "https://edge.uprock.com/v1/crawl",
        headers={"Authorization": f"Bearer {os.environ['UPROCK_API_KEY']}"},
        json={"url": url, "country": country, "format": "html"},
        timeout=90,
    )
    page = res.json()
    if not res.ok:
        raise RuntimeError(f"UpRock {res.status_code} {page.get('code')}: {page.get('error')}")
    # The HTML keeps <html lang>, which the markdown drops.
    html = page.get("html", "")
    text = re.sub(r"<(script|style)[\s\S]*?</\1>", " ", html, flags=re.IGNORECASE)
    text = re.sub(r"<[^>]+>", " ", text).replace("&nbsp;", " ")
    lang = re.search(r'<html[^>]*\slang="([^"]*)"', html, re.IGNORECASE)
    return {
        "country": country,
        "title": page.get("title", ""),
        "lang": lang.group(1) if lang else "",
        "prices": ", ".join(list(dict.fromkeys(PRICE.findall(text)))[:3]),
    }


if __name__ == "__main__":
    # Fetch the URL from every country at once; each call waits for a device there.
    url, *countries = sys.argv[1:]
    with ThreadPoolExecutor(max_workers=len(countries)) as pool:
        futures = {c: pool.submit(view_from, url, c) for c in countries}
    views = []
    for country, future in futures.items():
        try:
            views.append(future.result())
        except Exception as err:
            print(f"{country}: failed ({err})")
    for field in ("title", "lang", "prices"):
        differs = len({v[field] for v in views}) > 1
        print(f"\n{field}: {'differs' if differs else 'same everywhere'}")
        for v in views:
            print(f"  {v['country']}  {v[field]}")
```

### TypeScript (MCP)

```ts
// npm i @modelcontextprotocol/sdk   (Node 18+)   env: UPROCK_API_KEY
// node compare.ts <url> US DE JP BR IN
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

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

// Prices such as 9,69€, ¥ 1,400, R$ 32,99, ₹ 465, $9.99 or 890 JPY.
const PRICE =
  /(?:R\$|US\$|CA\$|A\$|[$€£¥₹₩])\s?\d[\d.,]*|\d[\d.,]*\s?(?:[€£¥₹]|(?:zł|kr|JPY|EUR|USD|GBP|INR|BRL|CAD)\b)/g;

type View = { country: string; title: string; lang: string; prices: string };

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

async function viewFrom(url: string, country: string): Promise<View> {
  const res = await uprock.callTool(
    { name: "crawl_fetch", arguments: { url, country } },
    undefined,
    { timeout: 300_000 },
  );
  if (res.isError) throw new Error(textOf(res.content));
  const page = JSON.parse(textOf(res.content));
  // The HTML keeps <html lang> and is stored even when markdown extraction fails.
  const uri = page.content.html.resource;
  const html = textOf(
    (await uprock.callTool({ name: "resource_fetch", arguments: { uri } })).content,
  );
  const text = html
    .replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ")
    .replace(/<[^>]+>/g, " ")
    .replace(/&nbsp;/g, " ");
  return {
    country,
    title: page.meta?.title ?? "",
    lang: /<html[^>]*\slang="([^"]*)"/i.exec(html)?.[1] ?? "",
    prices: [...new Set(text.match(PRICE))].slice(0, 3).join(", "),
  };
}

// Fetch the URL from every country at once; each call waits for a device there.
const [url, ...countries] = process.argv.slice(2);
const results = await Promise.allSettled(countries.map((c) => viewFrom(url, c)));
await uprock.close();

const views: View[] = [];
results.forEach((r, i) =>
  r.status === "fulfilled"
    ? views.push(r.value)
    : console.log(`${countries[i]}: failed (${r.reason})`),
);
for (const field of ["title", "lang", "prices"] as const) {
  const differs = new Set(views.map((v) => v[field])).size > 1;
  console.log(`\n${field}: ${differs ? "differs" : "same everywhere"}`);
  for (const v of views) console.log(`  ${v.country}  ${v[field]}`);
}
```

### Python (CLI)

```python
# npm i -g uprock   env: UPROCK_API_KEY
# python compare.py <url> US DE JP BR IN
import json
import re
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor

# Prices such as 9,69€, ¥ 1,400, R$ 32,99, ₹ 465, $9.99 or 890 JPY.
PRICE = re.compile(
    r"(?:R\$|US\$|CA\$|A\$|[$€£¥₹₩])\s?\d[\d.,]*"
    r"|\d[\d.,]*\s?(?:[€£¥₹]|(?:zł|kr|JPY|EUR|USD|GBP|INR|BRL|CAD)\b)"
)


def uprock(*args: str) -> str:
    run = subprocess.run(
        ["uprock", "ai", *args],
        capture_output=True,
        text=True,
        timeout=330,
        check=False,
    )
    if run.returncode != 0:
        raise RuntimeError(run.stderr.strip())
    return run.stdout


def view_from(url: str, country: str) -> dict:
    page = json.loads(uprock("crawl", url, "--country", country))
    # The HTML keeps <html lang> and is stored even when markdown extraction fails.
    html = uprock("fetch", page["content"]["html"]["resource"])
    text = re.sub(r"<(script|style)[\s\S]*?</\1>", " ", html, flags=re.IGNORECASE)
    text = re.sub(r"<[^>]+>", " ", text).replace("&nbsp;", " ")
    lang = re.search(r'<html[^>]*\slang="([^"]*)"', html, re.IGNORECASE)
    return {
        "country": country,
        "title": page["meta"].get("title", ""),
        "lang": lang.group(1) if lang else "",
        "prices": ", ".join(list(dict.fromkeys(PRICE.findall(text)))[:3]),
    }


if __name__ == "__main__":
    # Fetch the URL from every country at once; each call waits for a device there.
    url, *countries = sys.argv[1:]
    with ThreadPoolExecutor(max_workers=len(countries)) as pool:
        futures = {c: pool.submit(view_from, url, c) for c in countries}
    views = []
    for country, future in futures.items():
        try:
            views.append(future.result())
        except Exception as err:
            print(f"{country}: failed ({err})")
    for field in ("title", "lang", "prices"):
        differs = len({v[field] for v in views}) > 1
        print(f"\n{field}: {'differs' if differs else 'same everywhere'}")
        for v in views:
            print(f"  {v['country']}  {v[field]}")
```

Run against the Steam page with `US DE JP BR IN`, the TypeScript version printed:

```text
title: differs
  US  Portal 2 on Steam
  DE  Portal 2 on Steam
  JP  Steam：Portal 2
  BR  Portal 2 on Steam
  IN  Portal 2 on Steam

lang: differs
  US  en
  DE  en
  JP  ja
  BR  en
  IN  en

prices: differs
  US  $9.99, $1.99, $14.98
  DE  9,69€, 1,59€, 14,54€
  JP  ¥ 1,400, ¥ 205, ¥ 2,100
  BR  R$ 33,99, R$ 4,49, R$ 50,98
  IN  ₹ 509, ₹ 85, ₹ 764
```

Tested 25 Oct 2026 against the live UpRock API, running the code blocks on this page: the TypeScript version (type-checked with `tsc --strict`) from the five countries above, and the Python version from the same five plus the UK, which printed the Portuguese title and pound prices. Both runs came back in about a second, because UpRock served copies it had stored from our first fetches of the same page and countries earlier that day; those first fetches took 23 to 47 seconds per country. On 6 Nov 2026 we ran the HTTP and MCP versions again with an UpRock API key in an `Authorization` header, on MDN from the US, Germany and Japan.

## FAQ

### Can't I just change the URL or the language setting?

Only on sites that put the country in the URL. The Steam page we tested has one URL for every country and picks the currency from where the visitor is, and the Netflix help article we tested does the same with its prices. A language setting changes the language, not the price.

### Is a VPN or a cloud server in that country enough?

It gives you an IP address in that country, which is often what decides prices and banners. It doesn't give you a local browser, and some sites treat cloud and VPN addresses differently from home connections. A real device in the country is closer to what a local visitor gets.

### Why didn't the page language always match the country?

Because the language usually follows the browser, not the IP address. On the Steam page, Japan came back in Japanese while the US, Germany, Brazil and India came back in English, and in an earlier run a device in the UK got the page in Brazilian Portuguese. These are real people's computers, so their language settings are part of what you see.

### Does UpRock see prices for logged-in users?

No. UpRock fetches public pages and never logs in, so you see what a logged-out visitor in that country sees. Account prices, coupons and saved regions don't apply.

### What does a comparison cost?

One credit per country per page, so one URL from five countries is five page fetches, at $0.50 per 1,000 pages by card or USDC ($0.40 with UPT). You can pass any of 63 countries, or a region such as EU or APAC when any country in it will do.

## Other use cases to explore

- [Check a site from many regions at once](https://uprock.ai/use-cases/check-a-site-from-many-regions): status and load times for one URL from several regions in a single call.
- [Find out why your agent got a 403](https://uprock.ai/use-cases/agent-gets-403): which refusals a request from the right country can fix, and which ones to respect.
- [Confirm where a fetch came from](https://uprock.ai/compare/tavily#a-page-as-it-looks-in-one-country): an IP lookup page fetched with the country set to Germany, Japan and Brazil reported three different cities.
- [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.

## Related articles

- [Why is my agent getting a 403, and what's the legitimate fix?](https://uprock.ai/use-cases/agent-gets-403.md): A diagnosis table from status code or symptom to likely cause and the legitimate fix, when to stop, and tested code that checks robots.txt and backs off.
- [How do I convert a web page to markdown for an LLM?](https://uprock.ai/use-cases/web-pages-to-markdown.md): Token counts for six public pages as HTML and as markdown, what the conversion kept and dropped, and code that fetches both versions and counts them.
- [How do I check my website's speed from different countries?](https://uprock.ai/use-cases/check-a-site-from-many-regions.md): What a verify run measures, four public sites we loaded from five regions on 25 Oct 2026, what failed, and a tested post-deploy check in TypeScript and Python.
- [How do I keep my AI agent's web costs under control?](https://uprock.ai/use-cases/agent-web-costs.md): A research task priced on five setups, what caching, reading fewer pages and routing save, and a tested wrapper with a cache, page cap and router.
- [Use cases: all articles](https://uprock.ai/use-cases.md)

## References

1. [Steam, Portal 2 store page (the page we fetched)](https://store.steampowered.com/app/620/Portal_2/), checked 25 Oct 2026
2. [Netflix Help Center, Plans and Pricing (prices shown for your location)](https://help.netflix.com/en/node/24926), checked 25 Oct 2026
3. [MDN, Accept-Language header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language), checked 25 Oct 2026
4. [UpRock, everything in one file (API, CLI, MCP, pricing)](https://uprock.ai/llms-full.txt), checked 25 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
