ScreenshotNeo

BlogHow-to

How to Fix a Screenshot API Response That Is Not a Valid Image

A .png filename does not guarantee image bytes. Check the HTTP status, Content-Type, body, and endpoint contract before saving or decoding a screenshot response.

By the ScreenshotNeo team4 October 20269 min read

If a screenshot API response will not open as an image, first check the HTTP status and Content-Type. A file named screenshot.png may contain JSON describing an error, a redirect response, or another non-image body. Save the response as an image only when the endpoint’s contract, status, and media type say it contains image bytes.

Screenshot APIs do not all return the same thing. Depending on the provider and endpoint mode, success may mean raw image bytes, JSON containing a URL or encoded image, or a redirect. Check the documentation for the exact endpoint and mode you call before choosing how to read its response.

1. Inspect the response before saving it

For one failing request, record the status code, Content-Type, and a short, safe sample of the body. Do not log API keys, authorization headers, cookies, or other secrets.

What you see What it can mean What to do
Failure status and JSON or text The API returned an error body, not an image. Read the provider’s documented error fields and fix the request, credentials, quota, or transient service issue.
Success status and image/png, image/jpeg, or image/webp The endpoint may be returning raw image bytes. Write the body as binary bytes. Do not parse it as JSON.
Success status and JSON media type The endpoint may return a wrapper with a URL, base64 data, or other documented result. Parse the documented schema, then fetch or decode the image as specified.
Redirect status The endpoint may deliver the image or PDF at another URL. Check whether your client follows redirects, then inspect the final status and media type.
Image media type, but the picture looks blank or incomplete The file can be a valid image with a capture or target-page problem. Open it as an image, then investigate navigation, page state, access restrictions, and capture settings separately.
Missing or unexpected media type A proxy, gateway, or endpoint may have returned an unexpected response. Inspect a safe body sample and confirm you called the intended endpoint and response mode.

For example, ScreenshotEngine documents binary image bytes for successful captures and JSON error responses. Its documented image media types include image/jpeg, image/png, and image/webp; the endpoint also supports PDF and video outputs. Screenshot API documents JSON as its default response and a redirect=1 mode that returns a redirect to the image or PDF. These are provider-specific contracts: verify the documentation for the endpoint and API version you use.

2. Use a safe response-handling pattern

The examples below are runnable templates. Set SCREENSHOT_API_URL to your provider’s documented endpoint and SCREENSHOT_API_KEY to a valid key in your environment. Replace the example target URL and parameter names with the ones required by that API. The logic checks the response before writing it and avoids saving error JSON with an image extension.

cURL

curl --silent --show-error --location \
  --get "$SCREENSHOT_API_URL" \
  --data-urlencode "access_key=$SCREENSHOT_API_KEY" \
  --data-urlencode "url=https://example.com" \
  --dump-header response-headers.txt \
  --output response-body.bin \
  --write-out 'HTTP %{http_code}\nContent-Type: %{content_type}\nFinal URL: %{url_effective}\n'

# Inspect response-headers.txt and the reported status and media type.
# If the body is JSON or text, inspect it as an error/result, not as an image.
# Only rename response-body.bin to an image extension after confirming the
# final status, Content-Type, and endpoint contract.

--location follows redirects; the reported status and effective URL help identify the final response. If you need to inspect the redirect itself, omit --location for the first request. Avoid printing the API key as part of a diagnostic command.

Python

import json
import os
import sys

import requests

endpoint = os.environ["SCREENSHOT_API_URL"]
api_key = os.environ["SCREENSHOT_API_KEY"]

response = requests.get(
    endpoint,
    params={"access_key": api_key, "url": "https://example.com"},
    timeout=90,
    allow_redirects=True,
)
content_type = response.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()

print("HTTP status:", response.status_code)
print("Content-Type:", content_type or "(missing)")
print("Final URL:", response.url)

if not response.ok:
    print("Request failed; response body follows:", file=sys.stderr)
    try:
        print(json.dumps(response.json(), indent=2), file=sys.stderr)
    except ValueError:
        print(response.text[:2000], file=sys.stderr)
    response.raise_for_status()

if content_type in {"image/png", "image/jpeg", "image/webp"}:
    with open("screenshot.bin", "wb") as output:
        output.write(response.content)
    print("Saved image bytes to screenshot.bin")
elif content_type == "application/json" or content_type.endswith("+json"):
    print("Successful JSON response; follow the provider's documented schema:")
    print(json.dumps(response.json(), indent=2))
else:
    sample = response.content[:500].decode("utf-8", errors="replace")
    raise RuntimeError(
        f"Unexpected response type {content_type!r}; body starts with: {sample!r}"
    )

This example accepts the three image media types documented by ScreenshotEngine. Adjust the allowlist to match your endpoint, including PDF or video types if those are the requested output. For JSON success responses, implement the provider’s documented result schema instead of treating the wrapper as an image.

Node.js

const endpoint = process.env.SCREENSHOT_API_URL;
const apiKey = process.env.SCREENSHOT_API_KEY;

if (!endpoint || !apiKey) {
  throw new Error("Set SCREENSHOT_API_URL and SCREENSHOT_API_KEY");
}

const requestUrl = new URL(endpoint);
requestUrl.searchParams.set("access_key", apiKey);
requestUrl.searchParams.set("url", "https://example.com");

const response = await fetch(requestUrl, { signal: AbortSignal.timeout(90_000) });
const contentType = (response.headers.get("content-type") || "")
  .split(";", 1)[0]
  .trim()
  .toLowerCase();

console.log("HTTP status:", response.status);
console.log("Content-Type:", contentType || "(missing)");
console.log("Final URL:", response.url);

const body = Buffer.from(await response.arrayBuffer());

if (!response.ok) {
  const sample = body.toString("utf8", 0, Math.min(body.length, 2000));
  throw new Error(`Screenshot request failed (${response.status}): ${sample}`);
}

if (["image/png", "image/jpeg", "image/webp"].includes(contentType)) {
  const { writeFile } = await import("node:fs/promises");
  await writeFile("screenshot.bin", body);
  console.log("Saved image bytes to screenshot.bin");
} else if (contentType === "application/json" || contentType.endsWith("+json")) {
  console.log("Successful JSON response; follow the provider's documented schema:");
  console.log(JSON.stringify(JSON.parse(body.toString("utf8")), null, 2));
} else {
  throw new Error(`Unexpected Content-Type: ${contentType || "(missing)"}`);
}

Modern Node.js provides fetch and AbortSignal.timeout. If your runtime does not, use its HTTP client’s timeout and redirect options, and still validate the final status and media type.

3. Diagnose the common failure cases

The image viewer says the file is corrupt

Likely cause: The body is an error message or JSON wrapper saved under an image filename, or the response was truncated.

Fix: Check the status and media type first. Inspect a safe body sample. For a raw-image endpoint, write the response bytes in binary mode. For a JSON endpoint, parse its documented schema and retrieve or decode the image from the documented field.

The response is JSON even though the request succeeded

Likely cause: The API returns a JSON result by default, or the request selects a response mode different from the one your code expects.

Fix: Confirm whether success is raw bytes, JSON, or a redirect for this exact endpoint and mode. Screenshot API, for example, documents JSON by default and a redirect=1 option for redirect delivery. Do not assume another provider has the same behavior.

The request returns an HTML page

Likely cause: A proxy, gateway, authentication layer, or upstream service returned an HTML error page. A redirect may also have led to a page instead of the expected file.

Fix: Inspect the final status, final URL, and a short body sample. Confirm that the request reaches the API endpoint and that the client’s redirect behavior matches the endpoint’s documented contract.

The image opens but is blank or incomplete

Likely cause: The bytes are a valid image, but the target did not render as expected. It may require authentication, remain behind a bot challenge, or not have finished loading when captured.

Fix: Treat capture quality as a separate issue from file validity. Check that the target is accessible to the capture service, wait for the relevant content or selector, and review capture settings. Waiting longer alone does not resolve a login screen or bot challenge.

The response is a redirect or the saved file is empty

Likely cause: The client may not follow redirects, may save the first response rather than the final body, or may have encountered an empty or failed final response.

Fix: Check the status and Location header on the initial response. If the endpoint documents redirects, follow them as appropriate and validate the final status, media type, and body length before saving.

4. Interpret errors by status, using the provider’s contract

Status codes are useful clues, but exact meanings and error fields vary between providers and endpoints. The following mappings are documented by ScreenshotEngine; do not apply them automatically to every screenshot API.

Status Documented check Practical response
400 Malformed or unsupported input; URL security validation may also apply. Check the URL, required parameters, parameter names, and types.
401 Authentication problem. Verify the key, authentication method, and any origin restriction.
429 Rate limit or monthly quota limit. Distinguish temporary throttling from exhausted quota. Honor Retry-After when supplied for temporary limits; correct quota issues instead of retrying immediately.
500 Rendering failure. Check navigation, rendering, and selector settings. Retry only a small number of times if the failure appears transient.
503 Temporary service unavailability. Pause briefly and retry with capped backoff.

When an API returns an error, log the status and provider-documented error fields. Redact credentials and private page data before sharing diagnostic logs.

5. Make the integration reliable and efficient

  • Branch on status before decoding. Failed responses should enter the error path, not an image library.
  • Branch on media type before parsing. Read raw image bytes as bytes; parse JSON only when the response is JSON. Do not infer the format from a filename or URL extension.
  • Validate the final response. When redirects are involved, inspect the final status and media type too.
  • Use timeouts. Screenshot rendering and page loading take time, but a request should not wait forever. Choose a timeout based on the provider’s documented limits and your application’s needs.
  • Retry selectively. Retry only errors that can be transient, such as temporary unavailability or a temporary rate limit. Use a small retry limit, capped exponential backoff, and Retry-After when available. Do not immediately repeat malformed requests, invalid credentials, or exhausted-quota failures.
  • Keep diagnostics bounded. Capture status, media type, request correlation information if provided, response length, and a short redacted body sample. Avoid storing entire error pages or sensitive response bodies by default.
  • Account for response size. Full-page captures and high-resolution output can be large. Stream or write bytes directly when supported by your client if buffering them would strain memory.

These checks reduce wasted decoding and retries. Actual capture latency, output size, provider limits, and billing depend on the target page, output settings, endpoint, and account plan; consult the provider’s current documentation for those specifics.

6. Or skip the browser setup

If you want the screenshot response without maintaining browser capture infrastructure, ScreenshotNeo is a website screenshot API and MCP server. Its documented API returns a screenshot or PDF from one GET request. Check the ScreenshotNeo API documentation for authentication, output options, and the current response contract before integrating it.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners are accepted like a visitor; 60+ known consent platforms, newsletter popups, and chat widgets are removed before capture. Each step can be turned off.
  • Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.

7. FAQ

Can I tell whether a response is an image from its file extension?

No. The extension is just a filename. Check the response status, media type, and endpoint contract.

Should I call response.json() for every screenshot response?

No. Parse JSON only when the endpoint returns JSON. Raw image bytes should be written or streamed as bytes.

Does an HTTP 200 always mean the screenshot is usable?

No. It can still be the wrong response type, or a valid image of an empty page. Check the media type and inspect the image content separately.

Is a blank screenshot the same as an invalid image?

No. An invalid image cannot be decoded as the claimed format. A blank screenshot may decode correctly but show an empty page or challenge.

Should I retry when decoding fails?

First inspect the status, media type, and body. Retry only when the evidence points to a transient failure; fix request, authentication, or quota problems directly.