How to Compare Screenshot API Latency from Mumbai and Singapore
Measure screenshot API latency from Mumbai and Singapore with matched workloads, verified execution regions, and repeatable results that include failures and cost.
To compare screenshot API latency from Mumbai and Singapore, run the same screenshot workload from verified execution points in both cities, measure the time until each usable image is received, and report repeated results as distributions alongside success rate and cost. Keep client-observed end-to-end latency separate from any provider-reported render duration. A nearby API endpoint alone does not prove that the browser worker rendered the page in that region.
No published benchmark directly comparing screenshot API latency from Mumbai and Singapore was identified for this guide. There is no supported basis for naming either city or a provider as universally faster. A useful benchmark must state what was measured, where the capture ran, which pages and settings were used, and how failures were counted.
1. Define what “from Mumbai” and “from Singapore” mean
A screenshot request can cross several regions. Record each separately:
- Benchmark client: where your script or measurement runner executes.
- API ingress: the endpoint or region that receives the request.
- Browser worker: where the browser opens and renders the target page.
- Target-site path: how the target site is reached, including any proxy or routing configuration.
These locations may differ. If a provider cannot document or confirm its browser-worker location, call the result a Mumbai-client or Singapore-client comparison. Do not describe it as Mumbai or Singapore rendering. For context, ScreenshotMAX documents Asia-Pacific API infrastructure in Taiwan, while Shotbot documents Singapore among its screenshot-worker egress regions; those details do not establish Mumbai worker availability or prove a like-for-like rendering location. Check current provider documentation and ask providers about worker geography before benchmarking.
2. Fix a representative workload
Use the same target URLs in both locations and for every provider. Select pages that match your actual use, including static content and JavaScript-rendered or delayed content when relevant. For each URL, keep these settings the same wherever the APIs allow:
- Viewport width and height, device scale, and mobile or desktop mode.
- Full-page versus viewport capture and output format.
- Navigation wait condition, post-load delay, and request timeout.
- Cache policy, authentication, cookies, and request headers.
- Retry policy and any proxy or regional routing settings.
Screenshot APIs expose controls such as viewport, wait condition, delay, cache, and timeout; those choices change both the work performed and when a response is ready. Use a common-settings run first. If you also want to compare each service using its own best settings, label that as a separate optimized-settings run. Record every option that cannot be matched.
3. Measure both end-to-end time and render time
Measure the client-visible time from request start until the complete result is usable: for example, the image bytes have arrived and passed basic validation, or an asynchronous job’s final result has been retrieved and validated. This can include network transport, API processing, rendering, and result delivery. If the provider returns a render-time value, store it as a separate provider-reported metric. Its timing boundaries may differ between providers, so do not assume the values are directly comparable unless the definition is documented.
For example, some screenshot API documentation describes X-Render-Time-Ms and X-Page-Status response headers. Treat those as provider-reported values and page-status evidence, respectively; retain the client measurement as well.
A minimal Python measurement harness
This runnable example measures client-observed duration for a synchronous endpoint that returns an image. It records HTTP failures and invalid or empty image responses instead of silently dropping them. Replace the endpoint, authentication, and capture parameters with the provider’s documented interface. Run the same script and workload from separately verified Mumbai and Singapore runners.
import csv
import hashlib
import io
import os
import time
from datetime import datetime, timezone
import requests
from PIL import Image
API_URL = os.environ["SCREENSHOT_API_URL"]
API_KEY = os.environ["SCREENSHOT_API_KEY"]
LOCATION = os.environ["TEST_LOCATION"] # e.g. mumbai or singapore
TARGETS = [
"https://example.com/",
"https://example.org/",
]
fields = [
"timestamp_utc", "location", "target_url", "elapsed_ms", "http_status",
"page_status", "render_time_ms", "result", "image_bytes", "image_sha256",
"error",
]
with open("results.csv", "w", newline="", encoding="utf-8") as output:
writer = csv.DictWriter(output, fieldnames=fields)
writer.writeheader()
for url in TARGETS:
started_utc = datetime.now(timezone.utc).isoformat()
started = time.perf_counter()
row = {
"timestamp_utc": started_utc,
"location": LOCATION,
"target_url": url,
"elapsed_ms": "",
"http_status": "",
"page_status": "",
"render_time_ms": "",
"result": "error",
"image_bytes": "",
"image_sha256": "",
"error": "",
}
try:
response = requests.get(
API_URL,
params={
"access_key": API_KEY,
"url": url,
"width": 1365,
"height": 768,
"format": "png",
"cache": "false",
"timeout": 60,
},
timeout=90,
)
row["http_status"] = response.status_code
row["page_status"] = response.headers.get("X-Page-Status", "")
row["render_time_ms"] = response.headers.get("X-Render-Time-Ms", "")
response.raise_for_status()
content = response.content
if not content:
raise ValueError("empty response body")
with Image.open(io.BytesIO(content)) as image:
image.verify()
row["image_bytes"] = len(content)
row["image_sha256"] = hashlib.sha256(content).hexdigest()
row["result"] = "success"
except Exception as exc:
row["error"] = f"{type(exc).__name__}: {exc}"
finally:
row["elapsed_ms"] = round((time.perf_counter() - started) * 1000, 2)
writer.writerow(row)
output.flush()
Install the dependencies with python -m pip install requests pillow. The example uses common parameter names to illustrate the method; providers differ, so confirm the actual names and semantics in their API documentation. If the API returns a job ID rather than an image, include polling and result download in end-to-end time, and log each stage separately if useful.
4. Run paired, repeated measurements
- Deploy one measurement client in Mumbai and one in Singapore, and record how their locations were verified.
- Send the same request set, with the same settings and retry rules, from both locations.
- Repeat across multiple time windows. Alternate or randomize provider order to reduce ordering bias.
- Preserve every request outcome, including timeouts, HTTP errors, invalid images, incomplete captures, retries, and cache hits.
- Keep raw per-request results and publish the sample counts and collection dates with any summary.
There is no universal minimum sample count that makes a benchmark conclusive. Use enough paired observations to show meaningful variation for your workload, and be explicit when the sample is small. Do not imply statistical certainty from a handful of requests.
5. Summarize results without hiding failures
Report results separately by client location, workload, provider, and cache state. At minimum, include:
- Observation count and measurement dates or time windows.
- Median and a tail measure such as p95 client-observed completion time.
- Successful-capture rate, with the definition of a valid capture.
- Timeout, error, and incomplete-capture counts.
- Provider-reported render duration where available, with the provider’s definition.
- Worker-region evidence and whether the run was a client-location or render-location comparison.
- Effective cost for expected usage, accounting for retries, failed jobs, cached results, and the required output quality.
A quick HTTP response does not necessarily mean a successful screenshot. Validate the intended page status and the output itself. A technically valid image can still be incomplete, such as a page captured before important content appeared; define correctness against the target page and use consistent checks.
6. Compare more than speed
| Dimension | What to compare | Why it matters |
|---|---|---|
| Client completion latency | Median and p95, by location and page type | Reflects the wait your application or user observes. |
| Provider render duration | Reported value and documented timing definition | Can help distinguish server-side rendering work from network and delivery time. |
| Capture correctness | Valid intended page, incomplete image, error, or timeout | A fast failure is not a useful successful capture. |
| Repeatability | Variation across runs and time windows | Shows whether a typical result is dependable. |
| Regional evidence | Client, API ingress, browser worker, and target route | Prevents an endpoint location from being mistaken for render location. |
| Effective cost | Expected volume, billing of failures and retries, cache treatment | Latency alone does not determine operating cost. |
| Feature fit | Required viewport, format, wait controls, cache, and authentication | A service that cannot produce the required capture is not comparable. |
Do not compress these measures into one universal “fastest” ranking. A result applies to the workload, settings, locations, providers, and time period that were actually measured. Recheck current provider terms and prices directly; this research did not establish current prices.
7. cURL and Node.js request-timing examples
These examples show how to time a completed response from a runner in each city. They use ScreenshotNeo’s documented one-call endpoint and save the returned bytes. For a provider comparison, substitute each provider’s endpoint and documented parameters, while keeping the workload matched.
cURL
curl -sS -G "https://api.screenshotneo.com/v1/shot" \
-d access_key="$SCREENSHOTNEO_API_KEY" \
--data-urlencode url="https://example.com/" \
-d width=1365 -d height=768 -d format=png -d cache=false \
-o shot.png \
-w 'http_code=%{http_code} total_seconds=%{time_total}\n'
time_total is the client’s total transfer duration reported by curl; it does not, on its own, prove which region performed the browser render. Keep the HTTP result and validate the saved file.
Node.js
import { writeFile } from 'node:fs/promises';
const apiKey = process.env.SCREENSHOTNEO_API_KEY;
if (!apiKey) throw new Error('Set SCREENSHOTNEO_API_KEY');
const params = new URLSearchParams({
access_key: apiKey,
url: 'https://example.com/',
width: '1365',
height: '768',
format: 'png',
cache: 'false',
});
const start = performance.now();
const response = await fetch(`https://api.screenshotneo.com/v1/shot?${params}`, {
signal: AbortSignal.timeout(90000),
});
const bytes = Buffer.from(await response.arrayBuffer());
const elapsedMs = performance.now() - start;
if (!response.ok) {
throw new Error(`HTTP ${response.status}; body bytes=${bytes.length}; elapsed=${elapsedMs.toFixed(1)}ms`);
}
if (bytes.length === 0) throw new Error('Empty screenshot response');
await writeFile('shot.png', bytes);
console.log({ elapsedMs: Number(elapsedMs.toFixed(1)), bytes: bytes.length,
pageStatus: response.headers.get('x-page-status'),
billed: response.headers.get('x-billed'),
renderTimeMs: response.headers.get('x-render-time-ms') });
For repeatable runs, wrap the request in a loop, append a row per attempt to a durable CSV or database, and keep failures as records. Avoid printing API keys or including them in published logs.
Or skip the browser setup
ScreenshotNeo offers a one-call screenshot API and an MCP server for AI agents. Cookie banners, newsletter popups, and chat widgets are removed before the shot, and each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/ \
-o shot.webp
For the full parameter list and response details, see the ScreenshotNeo API documentation. Learn more at ScreenshotNeo. Sign up for 1,000 free screenshots a month with no card.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Results are unexpectedly fast | A cache hit, an early error response, or a measurement that stops at headers | Record cache state, validate the body and page status, and measure until the full image or final job result is usable. |
| “Mumbai” and “Singapore” results look nearly identical | The API may route both clients to the same ingress or browser-worker region | Verify client placement, ingress, and worker location independently. Label unverified runs by client location only. |
| One region has many more timeouts | Network path, regional routing, target-site variability, or a timeout that is too short | Keep the timeout consistent for a common-settings run, retain timeout rows, inspect routes and provider logs, and run a separately labelled timeout-sensitivity test if needed. |
| Images arrive but are blank or incomplete | The page failed, content was delayed, or the capture wait condition was insufficient | Check page-status evidence and the image; align wait conditions and delays, then define correctness checks for the content you need. |
| Latency changes greatly from run to run | Dynamic pages, changing network or provider load, different cache states, or tests sent in a fixed order | Repeat across time windows, alternate provider order, separate cached and fresh runs, and report distribution and sample count. |
| Provider render metrics disagree with client timing | The metrics cover different intervals | Read the provider’s definition. Report each metric under its own name; do not infer that the difference is network time unless measurement boundaries support that conclusion. |
| Images differ between providers | Unmatched viewport, device scale, locale, authentication, headers, or page timing | Compare settings and request context, document controls that cannot be matched, and visually or programmatically validate expected page content. |
| CSV has missing or malformed rows | Process termination, concurrent writes, or unescaped fields | Flush after each record, use a CSV library, write separate files per runner, then merge by run ID and timestamp. |
Performance, reliability, and cost notes
- Measure the actual capture workflow. Browser navigation and rendering add work beyond a basic HTTP ping. Include polling or result download for asynchronous APIs if the application waits for those steps.
- Separate cache behavior. A cached result and a fresh render answer different latency questions. Test cache disabled for fresh-render comparisons, then measure cache-enabled behavior separately if production uses it.
- Keep retries visible. Report initial-attempt success and eventual success after retries separately, plus the added time and cost. Apply the same retry policy across services.
- Protect benchmark validity. Do not overload a provider or target site. Keep concurrency consistent and within provider limits, and identify test traffic where required.
- Compare effective cost at expected volume. Include failures, retries, cache treatment, quality requirements, and current billing terms. Pricing and failure billing can change, so check provider sources at the time of the benchmark.
- Avoid false precision. Client clock synchronization matters when comparing timestamps across machines, but elapsed duration should use a monotonic clock on each runner. Store UTC timestamps for ordering and audit context.
FAQ
Can I use an ordinary API latency checker?
It can help characterize the API network path, but it does not measure a completed browser screenshot. For this comparison, time the capture request through usable output and validate the result.
Should I rank providers by the lowest median?
No. Include tail latency, successful-capture rate, regional execution evidence, workload fit, and effective cost. A median alone can hide slow or failed captures.
Does a provider’s Singapore API domain prove its browser renders in Singapore?
No. API ingress and browser-worker geography are separate. Use provider documentation or written confirmation for worker location, and state uncertainty when it cannot be verified.
Can Mumbai and Singapore results be compared if workers run elsewhere?
Yes, as a comparison of client locations for the configured service. Label it accurately and avoid claiming the render occurred in either city.


