ScreenshotNeo

BlogHow-to

Fix a Blank Screenshot of an Indian Website: Check the URL and Wait Settings

A blank screenshot can come from a bad URL, unfinished JavaScript rendering, access controls, or an API error. Diagnose the response first, then choose a targeted wait.

By the ScreenshotNeo team4 October 202610 min read

If a website screenshot API returns a blank image for an Indian website, check the exact URL and the API response before increasing the wait. Then determine whether the page needs JavaScript rendering, a specific content selector, authentication, or authorized access. There is no documented URL syntax or universal wait duration just for Indian websites; the right fix depends on the site and the screenshot provider.

A useful first distinction: did the API return an actual blank screenshot, or did your code save an error response as an image? Check the HTTP status, content type, redirect destination, and any structured error body. A file ending in .png or .webp does not prove the response contains an image.

1. Inspect the request and response first

Record these details for a failed capture:

  • The exact URL sent, including scheme, hostname, path, and query string.
  • The final URL after redirects, if the provider reports it.
  • The HTTP status and response Content-Type.
  • The provider’s error code and response body, if present.
  • The navigation, selector, and overall request timeout settings.

Use the exact same URL in a normal browser. If that page is blank or denied there too, the screenshot API’s wait setting is unlikely to fix the underlying issue. If it renders in your browser but not in the capture, investigate JavaScript readiness, authentication, bot checks, and differences in region or browser environment.

Error names and response formats are provider-specific. For example, one API may distinguish invalid requests, rate limits, quota exhaustion, failed renders, and selectors that were not found; do not assume another provider uses the same codes. Follow the error contract in the API you actually call.

2. Check and encode the URL correctly

Pass a complete URL with https:// or http://, not just a hostname. Verify spelling, path, query parameters, and redirects. When the URL is a query parameter, let your HTTP client encode it; a URL containing &, ?, or other reserved characters can otherwise be parsed incorrectly.

Check whether the destination redirects to a login page, consent page, regional landing page, or an error route. A redirect can succeed at the HTTP level while leading the browser to a page with no content you expected.

If the API returns a JSON error, log it as JSON rather than treating it as image bytes. The following Python pattern illustrates the checks; adapt the request parameters and error handling to your provider’s documented response contract:

import requests

endpoint = "https://YOUR_SCREENSHOT_PROVIDER_ENDPOINT"
params = {"url": "https://example.in/page"}
r = requests.get(endpoint, params=params, timeout=90, allow_redirects=True)
print("status:", r.status_code)
print("content type:", r.headers.get("Content-Type"))
print("final response URL:", r.url)
r.raise_for_status()
content_type = r.headers.get("Content-Type", "").lower()
if not content_type.startswith("image/"):
    print(r.text[:2000])
    raise RuntimeError("Provider response is not an image; inspect its error body")
with open("shot.png", "wb") as f:
    f.write(r.content)

This is diagnostic scaffolding, not a universal screenshot API request: providers use different endpoints, authentication, and parameter names.

3. Choose a wait condition based on how the page renders

Navigation readiness and application-content readiness are different. A browser may finish loading the initial document before a JavaScript app has fetched data and drawn its main content. Cloudflare documents this behavior for JavaScript-heavy pages and SPAs, and supports networkidle0, networkidle2, and waiting for a selector. See its screenshot guide and timeout reference.

Condition Use it when Watch for
domcontentloaded The initial document is enough or speed matters. App-rendered content may not exist yet.
load You need the browser’s load event, including page resources. It does not guarantee that an app’s later data request has finished.
networkidle2 The page keeps a small number of background connections open. Support and semantics vary by provider.
networkidle0 The page becomes fully idle and its requests settle. Analytics, polling, or long-lived connections can delay or prevent idleness.
Wait for a selector A stable element appears only after the content you need is ready. A misspelled, hidden, or conditional selector can time out.
Fixed delay A known animation or late UI needs a short extra pause. It wastes time on fast pages and can still be too short on slow ones.

Prefer a selector that represents the actual content, such as the results container or page heading, when the API supports it. Use a fixed delay only when you have identified a late-rendering step and the provider supports that option. Avoid continually increasing waits without checking why content is missing.

Wait limits are not universal. Cloudflare’s documentation lists a 60-second maximum navigation timeout and up to 120 seconds for selector and action waits in its API reference. Those limits apply to Cloudflare’s API only. Use your own provider’s current documentation for parameter names, supported conditions, and maximums.

4. A concrete Cloudflare Browser Run example

If you use Cloudflare Browser Run, its screenshot endpoint accepts a URL and options such as gotoOptions.waitUntil and waitForSelector. Replace the account ID, API token, target URL, and selector below. The examples save the response to a temporary file; inspect the status and content type in your application before treating any response as a valid screenshot.

cURL

curl -sS -X POST \
  "https://api.cloudflare.com/client/v4/accounts/YOUR_ACCOUNT_ID/browser-run/screenshot" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.in/page",
    "gotoOptions": {
      "waitUntil": "networkidle2",
      "timeout": 45000
    },
    "waitForSelector": {
      "selector": "main h1",
      "visible": true,
      "timeout": 20000
    },
    "screenshotOptions": {
      "type": "png",
      "fullPage": true
    }
  }' \
  -o response.bin

Cloudflare’s endpoint response behavior and options are documented in its screenshot endpoint guide and API reference. Confirm whether your chosen endpoint returns image bytes or a structured response, and handle it accordingly.

Python

import os
import requests

account_id = os.environ["CLOUDFLARE_ACCOUNT_ID"]
token = os.environ["CLOUDFLARE_API_TOKEN"]
endpoint = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/browser-run/screenshot"
payload = {
    "url": "https://example.in/page",
    "gotoOptions": {"waitUntil": "networkidle2", "timeout": 45000},
    "waitForSelector": {
        "selector": "main h1",
        "visible": True,
        "timeout": 20000,
    },
    "screenshotOptions": {"type": "png", "fullPage": True},
}
r = requests.post(
    endpoint,
    headers={"Authorization": f"Bearer {token}"},
    json=payload,
    timeout=90,
)
print("status:", r.status_code, "content type:", r.headers.get("Content-Type"))
r.raise_for_status()
with open("response.bin", "wb") as f:
    f.write(r.content)

Node.js

const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
const token = process.env.CLOUDFLARE_API_TOKEN;
const endpoint = `https://api.cloudflare.com/client/v4/accounts/${accountId}/browser-run/screenshot`;
const payload = {
  url: 'https://example.in/page',
  gotoOptions: { waitUntil: 'networkidle2', timeout: 45000 },
  waitForSelector: { selector: 'main h1', visible: true, timeout: 20000 },
  screenshotOptions: { type: 'png', fullPage: true }
};
const res = await fetch(endpoint, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(payload)
});
console.log('status:', res.status, 'content type:', res.headers.get('content-type'));
const bytes = new Uint8Array(await res.arrayBuffer());
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('response.bin', bytes));

Do not put API tokens in source control or log output. Treat timeouts and waits as separate controls: navigation can complete while a later selector wait is still pending, and the total request also needs enough time for the screenshot action and network overhead.

5. Check authentication, access, and region-specific content

If the page requires a login, use the screenshot provider’s documented way to supply session cookies, HTTP credentials, or authorized headers. Confirm that the capture is permitted and that the credentials are valid for the final redirected URL. Never paste session secrets into shared logs or public examples.

A server-side denial, CAPTCHA, or bot challenge is an access issue, not a slow-rendering issue. Increasing the wait does not grant access. Cloudflare explicitly says its configurable User-Agent does not bypass bot protection; see its guide. Use only access methods allowed by the destination site and your provider.

Some APIs expose locale, timezone, or geolocation controls. Consider them only if the page intentionally varies by region, language, or local settings. Their availability does not establish that Indian websites generally need those settings to render. There is no evidence here for a single India-specific root cause, render location, or wait duration.

6. Troubleshooting by symptom

Symptom Likely cause Next check or fix
Saved file is not a readable image An error response or JSON body was saved with an image extension. Check status and Content-Type; parse the provider’s error contract before writing image bytes.
Browser works; capture is blank JavaScript content is rendered after the selected navigation event. Use a supported network-idle condition or wait for a stable content selector.
Selector wait times out Selector is wrong, content is conditional, or the page was denied before rendering. Inspect the page and final URL; verify the selector exists and is visible in the rendered state.
Long wait still returns a challenge or error page Bot protection, access restrictions, or authentication. Inspect the response and use an authorized documented access method. Do not treat a longer wait or user-agent change as a bypass.
Only some content is missing Lazy loading, scroll-triggered content, blocked requests, or a capture boundary. Check provider support for scrolling/lazy-load handling, request blocking settings, and full-page capture.
Works for one URL, fails after redirect The destination path changes to a login, consent, or region-dependent page. Record the redirect chain and ensure credentials and URL parameters apply to the destination.
401 or 403 Invalid API credentials or denied target access, depending on which server returned the status. Determine whether the response came from the screenshot provider or destination; fix that layer’s authorization.
429 or quota error Rate limit or account quota reached. Follow the provider’s retry guidance and quota details; extra render time will not resolve it.
Timeout or render-failed error Slow navigation, a selector that never appears, heavy page work, or action timeout. Identify which timer expired. Increase only that bounded timer when appropriate, or choose a more reliable readiness signal.
Regional content differs from browser Locale, timezone, geolocation, cookies, or server-side location affects the page. Compare the relevant settings and use supported controls only when the site is designed to vary by them.

7. Make captures faster and more reliable

  • Wait for the thing you need. A specific content selector often avoids waiting on analytics or persistent connections, while a fixed long delay slows every capture.
  • Keep a timeout budget. Allow for navigation, selector readiness, screenshot generation, and the API request itself. Stay within documented provider limits.
  • Change one variable at a time. First correct the URL, then change the wait condition, then test authentication or region settings. Log each result.
  • Use bounded retries. Retry transient provider failures according to its guidance. Do not retry invalid URLs, authorization failures, quota exhaustion, or a selector that cannot exist.
  • Capture the smallest useful output. If you need only one component, use an element capture when available rather than a full-page image. This can reduce work and output size, depending on the service.
  • Account for cache behavior. If the provider supports caching, a cached capture may reflect an earlier page state. Check its cache controls before diagnosing a timing change.

Cost and billing rules vary by provider. Check whether failed renders, retries, cache hits, and image formats are billable before running large diagnostic batches. Longer waits can occupy capacity and increase latency even when they do not change the billing unit.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single GET request returns a PNG, JPEG, WebP, or PDF. The examples below use the API’s documented request form; see the ScreenshotNeo API documentation.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.in -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.in"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.in' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

FAQ

Is there a special URL format for Indian websites?

No India-specific format is established here. Use the complete URL accepted by your provider and inspect redirects and response details.

Should I always use networkidle0?

No. Pages with persistent connections may never become fully idle. Try a selector that represents the required content, or use the provider’s other documented readiness condition.

Can a longer wait get past a CAPTCHA?

No. A challenge or access denial needs an authorized access path; waiting longer does not authenticate or authorize the request.

What should I include when asking for help?

Share the provider and endpoint, a redacted request shape, status and content type, sanitized error body, final URL, and wait settings. Remove API keys, cookies, and personal data.