ScreenshotNeo

BlogHow-to

Screenshot API Returns a Blank Page: Causes and Fixes

A blank screenshot can mean an API error, blocked page, unfinished JavaScript, missing selector, or wrong capture area. Diagnose the response and fix the cause.

By the ScreenshotNeo team4 October 20269 min read

A blank screenshot from a website screenshot API can be an actual empty page, an API error saved as an image, a login or bot-challenge page, content captured before JavaScript finishes, a missing selector, or a capture region that excludes the content. Check the HTTP status, Content-Type, and response body first. Then verify credentials and page access, wait for the content to render, and check the selector and capture dimensions.

These checks apply across providers, but parameter names, response formats, and error codes vary. Use the documentation for the API you call; the examples below show how to inspect a response and illustrate provider-specific settings where relevant.

1. Identify what “blank” means

Before changing render settings, classify the result. A white image, a corrupt image file, a login page, and a JSON error are different problems with different fixes.

What you see Likely explanation First check
A very small or invalid image file The client may have saved an error response as an image. HTTP status, content type, and response body.
A valid, uniformly white image The page may be empty, still rendering, or styled with a white background and no visible content. Open the target URL and check page access and render readiness.
A login, denial, or challenge screen The renderer reached a page that requires access or presents a challenge. Authentication and the target site’s access rules.
A page shell with missing content Client-side rendering or data loading may not have finished. Wait for a relevant selector or a supported network-idle condition.
Only part of the expected page The viewport or capture mode may exclude content, or the selected element may be too narrow. Viewport size, full-page setting, and selector.

2. Inspect the HTTP response before saving it as an image

Some screenshot endpoints return image bytes on success but structured JSON on failure. Others return JSON by default and require a redirect option to deliver the image. If a client writes an error body to result.png, the file extension does not turn that body into an image.

Check all three: status code, Content-Type, and body. Do not assume a particular status code or response shape across providers. For example, one API documents 401 for authentication, 429 for rate limits or quota, and 502 for rendering failures; other services may report errors differently. Follow the provider’s response and any Retry-After header.

curl -sS -D response-headers.txt -o response-body.bin \
  "YOUR_SCREENSHOT_ENDPOINT?YOUR_PARAMETERS"

cat response-headers.txt
file response-body.bin

For an API that returns JSON by default, inspect the JSON error fields or use its documented redirect mode when you need image bytes. Do not pass a provider’s parameter name to another provider unless its documentation supports it.

3. Confirm the URL, credentials, and page access

  1. Check that the target is a complete, valid URL, including its scheme such as https://.
  2. Verify the API key is present in the documented header or parameter and that the account has available quota.
  3. Open the target independently and determine whether it requires a login, session, private network route, or other access.
  4. Check whether the renderer receives a login page, access-denied response, or bot challenge instead of the intended page.

Use supported authentication controls when a page requires them. Some screenshot APIs document cookies or basic authentication; availability and syntax are provider-specific. Avoid exposing API keys in source control, public URLs, logs, or screenshots. Prefer the provider’s recommended credential mechanism.

Increasing the wait time does not grant access to a private page or bypass a login or bot challenge. A changed user agent is not a guaranteed way around bot protection. Resolve access through an authorized method or the target site’s configuration.

4. Wait for JavaScript content to render

Single-page applications and JavaScript-heavy sites can show an initial document before the visible content appears. A screenshot taken at the initial load event can therefore contain a blank shell or incomplete page.

Use a readiness signal supported by your API. A network-idle condition can help when the page becomes quiet after loading. A selector for the specific content you need is usually a more targeted signal and may avoid waiting for unrelated requests. Some APIs document modes such as load, domcontentloaded, networkidle0, or networkidle2, and options such as waitForSelector or a delay. These are not universal parameter names.

# Provider-specific illustration only. Confirm parameter names in your API docs.
curl -G "YOUR_SCREENSHOT_ENDPOINT" \
  --data-urlencode "url=https://example.com/app" \
  --data-urlencode "waitUntil=networkidle2" \
  --data-urlencode "waitForSelector=main [data-ready='true']"

Prefer a selector that indicates the needed content is ready, rather than a generic element that appears before data is loaded. A fixed delay can help with late UI or animations, but it can be too short on a slow response and waste time on a fast one. Network activity that never settles can also make network-idle waiting time out; use a selector or another documented strategy when appropriate.

5. Verify selectors, viewport, and capture mode

Selector capture and selector waits

If the request captures an element or waits for a selector, confirm that the selector matches the rendered page. Check spelling, capitalization, escaping, and whether the element appears only after a different interaction or data request. A selector that is absent can produce a provider error such as selector_not_found, a timeout, or a capture with no desired element, depending on the service.

Viewport and full-page capture

A normal viewport capture contains only the visible browser area. Content below the fold may be missing even when the page rendered correctly. Increase the viewport or use the provider’s full-page option when the missing content is lower on the page. Full-page capture can also interact with sticky elements and lazy-loaded content, so inspect whether the API scrolls to load images or other deferred content.

When the output is unexpectedly empty, compare a basic viewport capture with the intended full-page or element capture. Confirm that width and height are sensible and that a selector does not point to a zero-sized or hidden element.

6. Runnable response-checking examples

These examples make a request and avoid treating every response as an image. Replace the endpoint, authentication, and parameters with the specific API’s documented values. The sample endpoint is intentionally a placeholder because providers use different URLs and request formats.

cURL

curl -sS -D headers.txt -o response.bin \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "YOUR_SCREENSHOT_ENDPOINT?url=https%3A%2F%2Fexample.com"

# Inspect status and content type in headers.txt, then inspect response.bin.
cat headers.txt
file response.bin

Python

import requests

response = requests.get(
    "YOUR_SCREENSHOT_ENDPOINT",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={"url": "https://example.com"},
    timeout=90,
)

print("status:", response.status_code)
print("content-type:", response.headers.get("Content-Type"))

if response.ok and response.headers.get("Content-Type", "").startswith("image/"):
    with open("screenshot.png", "wb") as image_file:
        image_file.write(response.content)
else:
    print(response.text[:4000])

Node.js

const target = new URL("YOUR_SCREENSHOT_ENDPOINT");
target.searchParams.set("url", "https://example.com");

const response = await fetch(target, {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
  signal: AbortSignal.timeout(90_000),
});

const contentType = response.headers.get("content-type") ?? "";
console.log("status:", response.status);
console.log("content-type:", contentType);

if (response.ok && contentType.startsWith("image/")) {
  const bytes = new Uint8Array(await response.arrayBuffer());
  await Bun.write("screenshot.png", bytes);
} else {
  console.error((await response.text()).slice(0, 4000));
}

The Node.js example uses Bun’s Bun.write to save bytes. In a Node.js application, write the returned buffer with node:fs/promises instead. If the API returns a redirect or JSON wrapper, handle that according to its documentation.

7. Troubleshooting by symptom

Symptom or error Common cause What to do
401 or an unauthorized error Missing, invalid, expired, or incorrectly placed credentials. Check the documented authentication method and key; keep secrets out of public URLs and logs.
429, rate limited, or quota exceeded Request rate or account quota limit. Follow Retry-After if present; reduce concurrency and check plan usage.
502 or render failed The renderer could not complete the capture; causes can include target reachability or a rendering failure. Check URL accessibility and readiness settings. Retry a potentially transient failure a bounded number of times.
Selector not found The selector is misspelled, absent, or appears later than the wait allows. Inspect the rendered DOM or choose a reliable readiness selector and supported wait.
JSON or text file saved with an image extension An error body or JSON response was handled as image bytes. Check status and content type before writing an image; inspect the body.
White image despite successful HTTP response The page may render blank, the content may not be ready, or a challenge/login page may be shown. Check the actual target response, access requirements, and a content-specific wait condition.
Top section appears but lower content is missing Viewport-only capture or below-fold content has not loaded. Use full-page capture or a taller viewport; account for lazy loading.
Intermittent timeout Slow page, unsettled network requests, or a wait condition that is too strict. Use a targeted selector where possible, choose a documented timeout, and retry only when the failure may be temporary.

8. Performance, reliability, and cost

Every wait has a latency tradeoff. A broad network-idle wait may hold a request open for analytics, polling, or long-lived connections. A short fixed delay is quick but can capture too early. A selector tied to the actual content often provides a clearer completion condition. Use the narrowest reliable wait and avoid asking for full-page output when only one viewport or element is needed.

For reliability, classify failures before retrying. Invalid URLs, bad credentials, missing selectors, and access restrictions need a corrected request or access configuration; repeating the same call adds delay and may consume quota. For transient renderer failures, use bounded retries with backoff and respect rate-limit instructions. Check whether failed attempts are billed under the provider’s terms rather than assuming they are free.

Cost depends on the provider’s billing rules, output type, retries, and any account quotas. Log status, content type, request options, and provider error codes so that a broken capture is distinguishable from a successful but empty page. Do not log credentials.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. Its clean-shot flow accepts consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status.

For the full set of request options and response details, see the ScreenshotNeo API documentation.

cURL

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

Python

import requests

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

Node.js

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

ScreenshotNeo also offers an MCP server with 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 shots. Every feature is on every plan. If you need to diagnose a particular response, the ScreenshotNeo site links to the product and its documentation.

Sign up for 1,000 free screenshots a month, with no card required.

10. FAQ

Can a screenshot API return HTTP success with a blank image?

Yes. A successful HTTP response can contain an image of a page that rendered without the expected content. Check the image and target page, not just the status code.

Should I always use network idle?

No. It is useful when supported and when network activity settles, but polling or long-lived requests can delay it. A selector for the required content may be more precise.

Will increasing the timeout fix a bot challenge?

No. A longer timeout only gives rendering more time; it does not resolve an access restriction or authenticate a session.

Why is the screenshot different from what I see in my browser?

The renderer may have a different session, viewport, timing, or access context. Compare those conditions and use documented authentication and rendering options.

Sources