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 · Read as markdown
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 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 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.urlfor redirects. The crawl result'smeta.urlis 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.
// 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(/ /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]}`);}# pip install requests env: UPROCK_API_KEY# python compare.py <url> US DE JP BR INimport osimport reimport sysfrom concurrent.futures import ThreadPoolExecutorimport 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(" ", " ") 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]}")// npm i @modelcontextprotocol/sdk (Node 18+) env: UPROCK_API_KEY// node compare.ts <url> US DE JP BR INimport { 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(/ /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]}`);}# npm i -g uprock env: UPROCK_API_KEY# python compare.py <url> US DE JP BR INimport jsonimport reimport subprocessimport sysfrom 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.stdoutdef 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(" ", " ") 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:
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 Steamlang: differs US en DE en JP ja BR en IN enprices: 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, ₹ 764Tested 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: status and load times for one URL from several regions in a single call.
- Find out why your agent got a 403: which refusals a request from the right country can fix, and which ones to respect.
- Confirm where a fetch came from: 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: such as South Korea and Indonesia, sent straight to a device in that country.
References
- Steam, Portal 2 store page (the page we fetched), checked 25 Oct 2026
- Netflix Help Center, Plans and Pricing (prices shown for your location), checked 25 Oct 2026
- MDN, Accept-Language header, checked 25 Oct 2026
- UpRock, everything in one file (API, CLI, MCP, pricing), checked 25 Oct 2026
Get started
Add UpRock in one paste.
Paste this into Claude Code, Codex, Cursor or any agent with a terminal. It reads our docs, sets up the MCP server or CLI, and tells you the cost before it spends anything.
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.