ScreenshotNeo

BlogHow-to

How to Call a Screenshot API from Python

Send an authenticated screenshot request from Python, handle image bytes or JSON responses, and troubleshoot provider-specific options and errors.

By the ScreenshotNeo team4 October 202611 min read

To call a screenshot API from Python, make an authenticated HTTP request with the page URL and capture options supported by your provider, check the HTTP status, and handle the response in the format that endpoint documents. Some APIs return image bytes directly; others return JSON containing a screenshot URL. Those response formats, authentication schemes, parameter names, and HTTP methods are provider-specific.

1. Choose the provider contract

Before writing code, check the provider’s current endpoint documentation for five details:

  1. The endpoint URL and whether it expects GET query parameters or a POST body.
  2. How it authenticates requests, such as a bearer token or an API-key header.
  3. Which capture options it accepts and their exact parameter names.
  4. Whether success returns image bytes, JSON metadata, or a URL to an image.
  5. How the provider reports validation errors, quota limits, rate limits, and rendering timeouts.

Do not assume that an option or response format from one API works with another. Keep your key outside source control, usually in an environment variable, and use HTTPS.

2. Install the Python HTTP client and set your key

python -m pip install requests

Set the environment variable in your shell. Replace the example value with a key issued by your chosen provider:

export SCREENSHOT_API_KEY="your_api_key"

On Windows PowerShell, use $env:SCREENSHOT_API_KEY="your_api_key" for the current session. Avoid putting real keys in scripts, notebooks shared with others, logs, or committed configuration files. For deployed applications, use the secret-management mechanism provided by your hosting environment.

3. Send a request and save a binary image response

Use this pattern when the endpoint returns the image itself as the HTTP response body. The URL, authentication header, and query parameters below are placeholders: replace them with the exact values from your provider’s documentation.

import os
from pathlib import Path

import requests

api_key = os.environ["SCREENSHOT_API_KEY"]
endpoint = "https://provider.example/api/screenshot"
params = {
    "url": "https://example.com",
    # Add only options this provider documents, for example:
    # "format": "png",
    # "fullPage": "true",
}

try:
    response = requests.get(
        endpoint,
        params=params,
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=(10, 90),
    )
    response.raise_for_status()
except requests.exceptions.Timeout as exc:
    raise SystemExit(f"Screenshot request timed out: {exc}")
except requests.exceptions.HTTPError as exc:
    detail = exc.response.text[:1000] if exc.response is not None else str(exc)
    raise SystemExit(f"Screenshot API returned an HTTP error: {detail}")
except requests.exceptions.RequestException as exc:
    raise SystemExit(f"Could not reach screenshot API: {exc}")

content_type = response.headers.get("Content-Type", "").lower()
if not content_type.startswith("image/"):
    raise SystemExit(
        f"Expected an image response, got {content_type or 'unknown content type'}: "
        f"{response.text[:500]}"
    )

output = Path("screenshot.png")
output.write_bytes(response.content)
print(f"Saved {output} ({len(response.content)} bytes)")

The two-part timeout is a connect timeout and a read timeout in seconds. Adjust them to suit the provider’s documented rendering behavior and your application’s deadline. Writing with write_bytes (or opening a file with "wb") preserves binary image data; text mode can corrupt it.

4. Handle a JSON response containing a screenshot URL

Some APIs return metadata or a hosted screenshot URL rather than image bytes. Parse JSON and use the documented field. This example follows the Screenshot API documentation’s provider-specific pattern: POST to its screenshot endpoint, send a bearer token and JSON body, then read screenshotUrl. Confirm the current parameter names and response schema in its REST API reference.

import os
from pathlib import Path
from urllib.parse import urlparse

import requests

api_key = os.environ["SCREENSHOT_API_KEY"]
endpoint = "https://api.screenshot-api.org/api/v1/screenshot"
payload = {
    "url": "https://example.com",
    "viewport": {"width": 1440, "height": 900},
    "format": "png",
    "fullPage": True,
}

try:
    response = requests.post(
        endpoint,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        json=payload,
        timeout=(10, 90),
    )
    response.raise_for_status()
    data = response.json()
except requests.exceptions.Timeout as exc:
    raise SystemExit(f"Screenshot request timed out: {exc}")
except requests.exceptions.HTTPError as exc:
    detail = exc.response.text[:1000] if exc.response is not None else str(exc)
    raise SystemExit(f"Screenshot API returned an HTTP error: {detail}")
except requests.exceptions.RequestException as exc:
    raise SystemExit(f"Could not reach screenshot API: {exc}")
except ValueError as exc:
    raise SystemExit(f"API did not return valid JSON: {exc}")

screenshot_url = data.get("screenshotUrl")
if not screenshot_url:
    raise SystemExit(f"Response did not contain screenshotUrl: {data}")

parsed = urlparse(screenshot_url)
if parsed.scheme != "https":
    raise SystemExit("Refusing a screenshot URL that is not HTTPS")

try:
    image_response = requests.get(screenshot_url, timeout=(10, 90))
    image_response.raise_for_status()
except requests.exceptions.RequestException as exc:
    raise SystemExit(f"Could not download screenshot: {exc}")

Path("screenshot.png").write_bytes(image_response.content)
print(f"Saved screenshot.png ({len(image_response.content)} bytes)")

The screenshot URL may be temporary or signed; download it promptly if the provider says it expires. If the provider requires authentication to fetch that URL, follow its download instructions rather than assuming it is public. Validate the downloaded content type too if the endpoint can return non-image files.

5. Use Python’s standard library when you do not need Requests

urllib.request can send JSON POST requests without an extra dependency. This generic example expects the response body to contain image bytes; adapt authentication, endpoint, payload, and response handling to your provider.

import json
import os
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

api_key = os.environ["SCREENSHOT_API_KEY"]
body = json.dumps({"url": "https://example.com"}).encode("utf-8")
request = Request(
    "https://provider.example/api/screenshot",
    data=body,
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    method="POST",
)

try:
    with urlopen(request, timeout=90) as response:
        content_type = response.headers.get("Content-Type", "").lower()
        image_bytes = response.read()
except HTTPError as exc:
    detail = exc.read().decode("utf-8", errors="replace")[:1000]
    raise SystemExit(f"HTTP {exc.code}: {detail}")
except (URLError, TimeoutError) as exc:
    raise SystemExit(f"Request failed: {exc}")

if not content_type.startswith("image/"):
    raise SystemExit(f"Expected image bytes, got {content_type or 'unknown type'}")

with open("screenshot.png", "wb") as output:
    output.write(image_bytes)
print(f"Saved screenshot.png ({len(image_bytes)} bytes)")

For production code, include the provider’s documented error-body parsing, response-size limits where appropriate, and retry policy. A standard-library example shown by ScreenshotEngine likewise uses a JSON POST, bearer authentication from an environment variable, a timeout, and binary file writing; its settings are an example of that provider’s contract, not a universal API guarantee. See its code examples.

6. Configure capture options carefully

Common screenshot controls include output format, viewport dimensions, full-page capture, CSS changes, element selection, and waiting for a selector or delayed content. The names and supported combinations vary by provider. Add only documented options to the request.

Need What to check in the provider docs Practical consideration
Viewport screenshot Width and height fields, units, and limits A viewport capture shows the visible browser area; it does not necessarily include content below the fold.
Full-page screenshot Full-page option name and page-height limits Very long pages can take longer and produce larger files. Lazy-loaded content may need scrolling or a provider-specific wait option.
Image format Supported formats and quality controls PNG is lossless; JPEG and WebP may reduce file size, depending on provider support and settings.
Element capture Selector syntax and behavior when no element matches Use a stable selector and wait for it to exist when the page renders asynchronously.
CSS or selector changes Whether custom CSS, hiding selectors, or selector capture is supported These settings may require POST or another advanced route. Never assume GET supports every option.
Wait behavior Selector wait, delay, or network-idle support and limits Network idle can be delayed indefinitely by analytics or streaming requests; a specific selector or bounded delay can be more predictable.

For example, Screenshot API documents GET and POST routes, with advanced settings such as CSS and selectors restricted to POST. ScreenshotAPI.to’s direct HTTP example uses GET, an x-api-key header, raise_for_status(), and writes response bytes. These illustrate different contracts, not interchangeable parameter or authentication conventions. See the ScreenshotAPI.to Python documentation.

7. Add safe retries and operational handling

Do not blindly retry every failed screenshot request. A retry can repeat work, and providers may bill successful captures even if your client did not receive the response. First check whether the provider supports idempotency keys, job IDs, or a status endpoint. Follow its retry guidance for rate limits and transient server errors.

  • Set finite connect and read timeouts. Make the total request budget compatible with your job or web request deadline.
  • On a rate-limit response, honor the provider’s retry guidance and any Retry-After header.
  • Retry only eligible transient failures, with a small attempt limit and exponential backoff plus jitter.
  • Do not retry validation, authentication, or plan errors until the request or account issue is corrected.
  • Log a request or job identifier and status code where available, but redact API keys, authorization headers, and sensitive page URLs.
  • For many URLs or slow captures, use provider-supported asynchronous jobs or batch endpoints rather than holding a user-facing request open.

Cloudflare also documents a screenshot operation in its Browser Rendering API and a Python SDK response model. That documentation describes its own API and SDK; it does not by itself establish feature or pricing parity with dedicated screenshot services. See the Cloudflare Browser Rendering screenshot API.

8. Troubleshoot common failures

Symptom Likely cause What to do
401 Unauthorized Missing, expired, malformed, or incorrectly placed key Check the provider’s required header or query authentication scheme and confirm the key is available in the process environment. Never copy a bearer-token example into an API that requires a different header.
400 or 422 validation error Malformed target URL, unsupported format, invalid option, or wrong data type Read the provider’s error body, verify the exact parameter names and types, and test with the smallest documented request.
402 or 403 plan/credit error Insufficient credits, plan restriction, or account permission Check the account quota and plan, then confirm the requested option is available to that account. Status mappings vary by provider.
429 Too Many Requests Rate or concurrency limit reached Reduce parallel calls, queue work, and follow Retry-After or the provider’s documented backoff policy.
504 or client timeout The page is slow, the capture is expensive, or the timeout is too short Check whether the provider reports a rendering timeout, simplify the capture, use a specific wait condition, or increase the read timeout within your overall deadline.
JSON parsing error The endpoint returned an image, HTML error page, or other non-JSON content Inspect status and Content-Type before parsing. Follow the documented response format and avoid printing sensitive response data into shared logs.
Image file is corrupt or unreadable Text-mode writing, an error body saved as an image, or a failed image download Write bytes in binary mode, check status and content type, and verify the provider returned a complete image response.
Screenshot is blank or content is missing Page scripts have not finished, a selector is wrong, lazy content has not loaded, or the target blocks automated browsing Use supported wait controls, verify the selector in a normal browser, and check the provider’s page result or error details if available.
Works locally but not in deployment Missing environment variable, outbound network restriction, DNS/TLS issue, or different runtime timeout Check deployment secrets and outbound access; report a redacted request ID and exception category rather than exposing credentials.

For one documented error mapping, HTML to Image lists 400/422 validation errors, 401 authentication, 402/403 credits or plan errors, 429 rate limiting, and 504 rendering timeout. Treat those mappings as specific to that service; use the error contract for the API you call. See its Python integration documentation.

9. Performance, reliability, and cost

  • Keep image transfer proportional to your need. Choose a suitable viewport and output format. Full-page captures and high-resolution output can increase render time, response size, and storage or bandwidth use.
  • Wait for the right condition. A fixed delay can waste time or still be too short. A provider-supported selector wait can target the content that matters; set a limit so a missing selector does not consume the entire job budget.
  • Bound concurrency. Excess parallel requests can hit provider limits and overload your own workers. Use a queue and provider-documented concurrency limits.
  • Separate render and download failures. With a URL-based response, the capture may succeed while the subsequent download fails. Record each step separately and check URL expiry and download authentication.
  • Understand billing and retries. Check how the provider counts captures, retries, failed renders, caches, and asynchronous jobs. The sources cited here do not establish comparable prices or reliability across providers.
  • Protect captured data. A screenshot can expose information visible to the browser. Avoid capturing private pages unless the provider’s security and data handling meet your requirements, and keep signed URLs and screenshots access-controlled.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request can return a PNG, JPEG, WebP, or PDF. Its Python example below saves the response bytes directly; check the ScreenshotNeo API documentation for request options and response details.

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)

Cookie and consent banners are accepted like a visitor and removed before capture, along with 60+ known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

cURL:

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

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}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned HTTP ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

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

FAQ

Do I need a browser automation library in Python?

No. A hosted screenshot API renders the page remotely; your Python code makes an HTTP request. Use browser automation directly when you need local browser control or interactions that the chosen API does not support.

Why does one API example parse JSON while another writes response content?

They document different response contracts. Parse JSON when the endpoint returns structured metadata or an image URL; write bytes when the endpoint returns the image body.

Can I pass a website URL containing query parameters?

Yes, encode it through the HTTP client’s parameter handling rather than assembling the request URL by hand. Confirm that the provider accepts the target URL and any redirects it follows.

Can Python take screenshots of pages behind a login?

Only if the provider supports the required authentication or browser state and your use complies with its terms. Check how credentials, cookies, and captured content are handled before sending private pages.

Is there a universal parameter for full-page capture?

No. Even when multiple providers support full-page capture, the parameter name, accepted values, limits, and behavior are defined by each provider.