# How do I check my website's speed from different countries?

Run verify. UpRock's verify tool loads your URL on devices in North America, Europe and Asia-Pacific at the same time and returns, for each check, the country it ran in, TTFB, FCP, LCP, CLS, load time, transfer size and protocol, plus a report link you can share. Run it after each deploy and flag the regions over web.dev's thresholds, such as 0.8 s for TTFB and 2.5 s for LCP.

Updated 29 Oct 2026. Web page: https://uprock.ai/use-cases/check-a-site-from-many-regions

## On this page

- [What a verify run measures](#what-a-verify-run-measures)
- [What each number means](#what-each-number-means)
- [Four sites from five regions](#four-sites-from-five-regions)
- [What we learned running it](#what-we-learned-running-it)
- [Check every region after a deploy](#check-every-region-after-a-deploy)
- [FAQ](#faq)
- [Other use cases to explore](#explore)
- [Related articles](#related)
- [References](#references)
- [Get started](#get-started)

## What a verify run measures

A verify run loads one URL from several regions at once. Each check is a separate page load on a device in one of the regions you ask for, and all checks run at the same time, so a verify run never takes much longer than its timeout. Each completed check returns:

- the country it ran in,
- TTFB, FCP, LCP and CLS,
- the DOMContentLoaded and load times,
- the transfer size and HTTP protocol,
- a screenshot, as a `sweep://` resource you can read with `resource_fetch`.

The run as a whole returns a `report_url`, a page on uprock.ai you can send to someone who doesn't use the API.

The same tool is available over MCP (`verify`, formerly `sweep`) and in the CLI (`uprock ai verify`; the old name `uprock ai sweep` still works). The parameters, from the MCP server's own schema and `uprock ai sweep --help` on 25 Oct 2026:

| MCP argument | CLI flag | What it does | Default |
|---|---|---|---|
| `url` | first argument | The page to load | required |
| `regions` | `-r NA,EU` | Any of NA, EU, APAC, LATAM, MEA | NA, EU, APAC over MCP |
| `tries` | `--tries` | Checks per region | 5 |
| `device` | `-d` | `mobile` or `desktop` | mobile |
| `timeout` | `-t` | Seconds for the whole run | 60 |

The tool's own description says NA, EU and APAC "have the fastest and most reliable device pools", which matched what we saw. Verify takes regions, not countries, and the country changes from one verify to the next: our APAC checks came from Malaysia, Vietnam and Japan, and our EU checks from six countries. If you need one particular country, load the page with `crawl_fetch` and a country code instead.

## What each number means

A verify run reports lab numbers from one page load, and web.dev's thresholds are written for the 75th percentile of real users' loads. They're still the right yardsticks for a smoke test (web.dev, checked 25 Oct 2026):

| Metric | What it measures | Good | Poor |
|---|---|---|---|
| TTFB | From the start of navigation to the first byte of the response, including redirects, DNS, connection and TLS. [Not a Core Web Vital](https://web.dev/articles/ttfb), but it shows server and network delay | [0.8 s or less](https://web.dev/articles/ttfb) | over 1.8 s |
| FCP | When the first text or image appears | [1.8 s or less](https://web.dev/articles/fcp) | over 3.0 s |
| LCP | When the largest image, text block or video in the viewport renders. A Core Web Vital | [2.5 s or less](https://web.dev/articles/lcp) | over 4.0 s |
| CLS | The largest burst of unexpected layout shifts. A Core Web Vital | [0.1 or less](https://web.dev/articles/cls) | over 0.25 |
| Load time | Until the page's load event fires | no threshold | |
| Transfer size | Bytes of the HTML document over the wire, compressed, not the whole page | no threshold | |
| Protocol | HTTP version the page was served over, such as `h2` | | |

Transfer size is easy to misread. For the MDN page below, the run reported 21,172 bytes; the same HTML is 210,078 bytes uncompressed. INP, the third Core Web Vital, needs a real click or tap, so a verify run can't measure it.

## Four sites from five regions

We swept four public pages of very different weight on 25 Oct 2026, between 15:19 and 15:31 UTC, one check per region, on the default mobile device. Times are in milliseconds; bold means worse than web.dev's "good" threshold.

| Page | Region (country) | TTFB | FCP | LCP | Load | HTML transfer |
|---|---|---|---|---|---|---|
| [example.com](https://example.com) | NA (US) | 587 | 876 | not measured | 524 | 634 B |
| | EU (IT) | 203 | 1,492 | 1,492 | 335 | 634 B |
| | APAC (MY) | 506 | 796 | not measured | 784 | 634 B |
| [MDN, 403 Forbidden](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/403) | NA (US) | 316 | 1,296 | 1,296 | 1,053 | 21 KB |
| | EU (GB) | 644 | **1,932** | 1,932 | 1,883 | 21 KB |
| | APAC (MY) | **817** | **2,000** | 2,000 | 1,870 | 21 KB |
| [Wikipedia, Web performance](https://en.wikipedia.org/wiki/Web_performance) | NA | no result | | | | |
| | EU (HU) | **1,164** | **3,060** | **3,292** | 5,388 | 27 KB |
| | APAC (MY) | 739 | 1,084 | 1,084 | 1,288 | 27 KB |
| [The Verge, home page](https://www.theverge.com) | NA (CA) | 532 | 1,752 | **8,232** | 23,594 | 926 KB |
| | EU (DE) | 627 | 1,656 | **2,952** | 9,082 | 64 KB |
| | APAC (MY) | **938** | **1,808** | **3,196** | 8,460 | 64 KB |

LATAM and MEA returned no result for any of the four pages. CLS was 0 in every check, and every protocol was `h2` except The Verge from Canada, which reported none.

What the table shows:

- **The light pages stayed close to "good" everywhere they ran.** example.com's TTFB stayed between 203 and 587 ms. MDN's LCP was under 2.5 s in every region, though its FCP was just over 1.8 s from the UK and Malaysia, and its TTFB 17 ms over 0.8 s from Malaysia.
- **The heavy page missed LCP in every region that answered.** The Verge's largest element took 2.9 to 8.2 seconds, and its load event came 8 to 24 seconds in.
- **One check is one device.** Wikipedia was fast from Malaysia and slow from Hungary. With one check per region you can't tell a slow region from a slow phone, which is why the code below flags a region only when every check there is slow, and why `tries: 2` is worth it for a release check.
- **The Canada check is an outlier.** It reported no protocol and a transfer size equal to the uncompressed HTML, as if the page came without compression. Treat a single odd check as a reason to re-run, not as a finding.

## What we learned running it

**In the CLI, one silent region still loses the whole run.** Asked for all five regions, the tool answered "sweep deadline exceeded" with no results, even though the EU and APAC checks finished in seconds when we ran them alone. MCP was fixed on 30 Oct: it now returns the regions that finished, with a `no_device` or `incomplete` reason for the rest. The CLI hasn't changed yet, so the code still checks each region in a separate call, in parallel, and keeps whatever comes back.

**Keep the timeout at 60.** The run's deadline was about 30 seconds past its `timeout`: 90 seconds for 60, 120 for 90. With a timeout of 120, the CLI's connection was reset after 125 seconds instead.

**A zero means "not measured", not "fast".** example.com from Italy once came back with a TTFB of 369 ms and every other metric at 0, and two of its three checks in the table had an FCP but an LCP of 0. A URL that always answers 500 also completed, with only a TTFB. The code skips a threshold when its metric is 0.

**Compare a metric across regions, not metrics within a check.** The timings come from different timers: example.com from the US reported a TTFB larger than its load time.

## Check every region after a deploy

Both versions check each region in parallel with one check, print each region's country, TTFB and LCP, flag the ones over 0.8 s TTFB or 2.5 s LCP, and exit with code 1 if any region is over, so CI can stop a rollout. A region with no result is printed but doesn't fail the run. Pass the regions as the second argument; the default is the three the tool calls most reliable.

### TypeScript (MCP)

```ts
// npm i @modelcontextprotocol/sdk   (Node 18+)   env: UPROCK_API_KEY
// After a deploy: node check-regions.ts https://your-site.example NA,EU,APAC
// Exits 1 if any region is over the thresholds, so CI can stop the rollout.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const MAX_TTFB_MS = 800; // web.dev: a good TTFB is 0.8 s or less
const MAX_LCP_MS = 2500; // web.dev: a good LCP is 2.5 s or less
const TRIES = 1; // checks per region; 2 smooths out one slow device

type Check = {
  status: string;
  country?: string;
  error_message?: string;
  metrics?: { ttfb: number; fcp: number; lcp: number; total_load_time: number };
};
type RegionResult = {
  region: string;
  checks: Check[];
  report?: string;
  error?: string;
};

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

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

// One run per region, run in parallel, so each region reports on its own and a slow
// region can't hold up the rest.
async function sweepRegion(url: string, region: string): Promise<RegionResult> {
  try {
    const res = await uprock.callTool(
      {
        name: "verify",
        arguments: {
          url,
          regions: [region],
          tries: TRIES,
          device: "mobile",
          timeout: 60, // the run gives up about 30 s after this
        },
      },
      undefined,
      { timeout: 120_000 },
    );
    if (res.isError) return { region, checks: [], error: textOf(res.content) };
    const result = JSON.parse(textOf(res.content));
    return {
      region,
      checks: result.results?.[region]?.jobs ?? [],
      report: result.report_url,
    };
  } catch (err) {
    return { region, checks: [], error: (err as Error).message };
  }
}

// What's wrong with one completed check, if anything. A check can complete with only a
// TTFB (every other metric 0), and LCP is sometimes 0 when FCP isn't: treat 0 as "not
// measured", never as fast.
function problems({ metrics: m }: Check): string[] {
  const out: string[] = [];
  if (m && m.ttfb > MAX_TTFB_MS) out.push(`TTFB ${m.ttfb} ms > ${MAX_TTFB_MS}`);
  if (m && m.lcp > MAX_LCP_MS) out.push(`LCP ${m.lcp} ms > ${MAX_LCP_MS}`);
  return out;
}

const [url = "https://example.com", regions = "NA,EU,APAC"] = process.argv.slice(2);
let slow = 0;
try {
  const results = await Promise.all(
    regions.split(",").map((region) => sweepRegion(url, region.trim())),
  );
  for (const { region, checks, report, error } of results) {
    const done = checks.filter((c) => c.status === "completed");
    if (!done.length) {
      const why = error ?? checks.map((c) => c.error_message).join("; ");
      console.log(`${region.padEnd(5)} no result: ${why}`);
      continue;
    }
    // Flag a region only when every completed check there is over a threshold.
    const flagged = done.every((c) => problems(c).length > 0);
    if (flagged) slow++;
    for (const c of done) {
      const { ttfb = 0, lcp = 0 } = c.metrics ?? {};
      const issues = problems(c);
      console.log(
        `${region.padEnd(5)} ${c.country ?? "--"}  TTFB ${ttfb} ms  ` +
          `LCP ${lcp ? `${lcp} ms` : "not measured"}  ` +
          (issues.length ? `SLOW: ${issues.join(", ")}` : "ok"),
      );
    }
    console.log(`      report: ${report}`);
  }
} finally {
  await uprock.close();
}
process.exit(slow ? 1 : 0);
```

### Python (CLI)

```python
# npm i -g uprock   env: UPROCK_API_KEY
# After a deploy: python check_regions.py https://your-site.example NA,EU,APAC
# Exits 1 if any region is over the thresholds, so CI can stop the rollout.
import json
import os
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor

MAX_TTFB_MS = 800  # web.dev: a good TTFB is 0.8 s or less
MAX_LCP_MS = 2500  # web.dev: a good LCP is 2.5 s or less
TRIES = 1  # checks per region; 2 smooths out one slow device


def verify_region(url: str, region: str) -> dict:
    """One run per region: in the CLI, a run where one region gets no device fails as a whole."""
    flags = ["-r", region, "--tries", str(TRIES), "-t", "60"]
    try:
        run = subprocess.run(
            ["uprock", "ai", "verify", url, *flags],
            check=False,
            capture_output=True,
            text=True,
            timeout=150,
        )
    except subprocess.TimeoutExpired:
        return {"region": region, "checks": [], "error": "timed out"}
    if run.returncode != 0:
        # Keep the key out of logs: some CLI errors include the MCP URL, which holds it.
        error = run.stderr.strip().replace(os.environ["UPROCK_API_KEY"], "***")
        return {"region": region, "checks": [], "error": error}
    result = json.loads(run.stdout)
    jobs = result.get("results", {}).get(region, {}).get("jobs", [])
    return {"region": region, "checks": jobs, "report": result.get("report_url")}


def problems(check: dict) -> list[str]:
    """A check can complete with only a TTFB (every other metric 0), and LCP is
    sometimes 0 when FCP isn't: treat 0 as "not measured", never as fast."""
    m = check.get("metrics") or {}
    out = []
    if m.get("ttfb", 0) > MAX_TTFB_MS:
        out.append(f"TTFB {m['ttfb']} ms > {MAX_TTFB_MS}")
    if m.get("lcp", 0) > MAX_LCP_MS:
        out.append(f"LCP {m['lcp']} ms > {MAX_LCP_MS}")
    return out


def main(url: str, regions: list[str]) -> int:
    with ThreadPoolExecutor(len(regions)) as pool:
        results = list(pool.map(lambda r: verify_region(url, r), regions))
    slow = 0
    for res in results:
        region = res["region"]
        done = [c for c in res["checks"] if c.get("status") == "completed"]
        if not done:
            why = res.get("error") or "; ".join(
                c.get("error_message", "") for c in res["checks"]
            )
            print(f"{region:<5} no result: {why}")
            continue
        # Flag a region only when every completed check there is over a threshold.
        if all(problems(c) for c in done):
            slow += 1
        for c in done:
            m = c.get("metrics") or {}
            ttfb, country = m.get("ttfb", 0), c.get("country", "--")
            lcp = f"{m['lcp']} ms" if m.get("lcp") else "not measured"
            issues = problems(c)
            verdict = f"SLOW: {', '.join(issues)}" if issues else "ok"
            print(f"{region:<5} {country}  TTFB {ttfb} ms  LCP {lcp}  {verdict}")
        print(f"      report: {res['report']}")
    return 1 if slow else 0


if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
    regions = (sys.argv[2] if len(sys.argv) > 2 else "NA,EU,APAC").split(",")
    sys.exit(main(target, [r.strip() for r in regions]))
```

A run against the MDN page from all five regions printed this (report links shortened). LCP wasn't measured in two of the checks, so only TTFB was compared there, and the run took 90 seconds because LATAM and MEA waited for the deadline:

```text
NA    US  TTFB 473 ms  LCP not measured  ok
      report: https://uprock.ai/verify/report/5zj31doX…
EU    NL  TTFB 716 ms  LCP not measured  ok
      report: https://uprock.ai/verify/report/dYftiv7X…
APAC  VN  TTFB 484 ms  LCP 1796 ms  ok
      report: https://uprock.ai/verify/report/iuiwAxKn…
LATAM no result: sweep deadline exceeded
MEA   no result: sweep deadline exceeded
```

Tested 25 Oct 2026 against the live UpRock API (MCP SDK 1.32.2, uprock CLI 0.0.37). The TypeScript version ran on the MDN page from all five regions (output above, exit code 0) and on The Verge from APAC (LCP 12,164 ms from Japan, exit code 1). The Python version ran on The Verge with the default regions: EU (Norway) and APAC (Malaysia) were flagged for TTFB and LCP, NA returned no result, and it exited with code 1. We didn't run the code with `tries` above 1 or with the desktop device. On 6 Nov 2026 we ran the TypeScript version again with an UpRock API key in an `Authorization` header, from NA and EU.

## FAQ

### Can I choose the exact country a check runs in?

No. Verify takes regions, and each check runs in whichever country's device picks it up. In our runs on 25 Oct 2026, APAC checks came from Malaysia, Vietnam and Japan, and EU checks from Italy, the Netherlands, the UK, Hungary, Germany and Norway. To load a page in one specific country, use crawl_fetch with a country code instead.

### Why did a region come back with no result?

No device took the check before the deadline. The tool returns "sweep deadline exceeded" and, in the CLI, a run with several regions returns no results at all. Over MCP, since 30 Oct 2026, the regions that finished come back, and each of the others says why it has no result. On 25 Oct 2026 this happened to every LATAM and MEA sweep we ran and to some NA sweeps. We tried again on 29 Oct at 04:20 UTC, after a fix on UpRock's side: LATAM, MEA and NA (night there) got no device in any of five tries each, on mobile or desktop, while EU and APAC answered every time within 10 seconds. It says nothing about your site, so don't fail a deploy on it.

### Does a verify run report the HTTP status code?

No. A check against a URL that always answers 500 came back as completed, with a TTFB and every other metric at 0. Check the status from your CI with a plain request, and use the run for timings.

### Are these my Core Web Vitals?

Not quite. web.dev assesses Core Web Vitals at the 75th percentile of real users' page loads, and INP needs a real interaction. A verify run is one page load per check on one device, so use it as a smoke test right after a deploy, not as a replacement for field data.

### What does a verify run cost?

A verify run does one check per region per try, so the code on this page keeps tries at 1. Each check loads the page on a real device and returns a screenshot and Core Web Vitals, at $1.50 per 1,000 checks by card or USDC ($1.20 with UPT). A verify run of 3 regions with 5 tries each is 15 checks. For comparison, Checkly lists browser checks at $5 per 1,000 runs.

## Other use cases to explore

- [See a page as it looks in one country](https://uprock.ai/use-cases/page-from-another-country#one-store-page-from-five-countries): load a URL on a device in the exact country you pick, when a region isn't specific enough.
- [Handle a page that blocks your agent](https://uprock.ai/use-cases/agent-gets-403): what to retry, what to send to a second network, and what to leave alone.
- [Run verify from Claude Code](https://uprock.ai/integrations/claude-code): add UpRock's MCP server once and ask for a region check after you deploy.
- [Keep your agent's web costs down](https://uprock.ai/use-cases/agent-web-costs): a worked cost-per-task example, and a cache, page cap and router you can paste.

## 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 see a website as people in another country see it?](https://uprock.ai/use-cases/page-from-another-country.md): We loaded one store page on real devices in five countries and compared price, currency, language and cookie banner. Code to compare any URL yourself.
- [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 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. [UpRock MCP server, sweep tool description and input schema (listTools, read 25 Oct 2026)](https://uprock.ai/llms-full.txt), checked 25 Oct 2026
2. [web.dev, Web Vitals (Core Web Vitals and the 75th percentile)](https://web.dev/articles/vitals), checked 25 Oct 2026
3. [web.dev, Time to First Byte (TTFB)](https://web.dev/articles/ttfb), checked 25 Oct 2026
4. [web.dev, First Contentful Paint (FCP)](https://web.dev/articles/fcp), checked 25 Oct 2026
5. [web.dev, Largest Contentful Paint (LCP)](https://web.dev/articles/lcp), checked 25 Oct 2026
6. [web.dev, Cumulative Layout Shift (CLS)](https://web.dev/articles/cls), 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
