ScreenshotNeo

BlogHow-to

Screenshot API returns 403 or 429 errors: troubleshooting guide

A 403 or 429 can mean different things across screenshot APIs. Learn how to inspect the response, identify the cause, and retry safely.

By the ScreenshotNeo team4 October 20268 min read

A screenshot API’s 403 or 429 does not have one universal meaning. First determine whether the status came from the API endpoint or the page being captured. Then inspect the response body and headers, compare them with that provider’s current documentation, and check account usage before retrying.

A 429 Too Many Requests may indicate a temporary rate window or an exhausted monthly quota. A 403 Forbidden may indicate credentials, permissions, an account or trial state, provider-specific quota handling, or a target page that rejected the render. The machine-readable error code and provider documentation are more useful than the status number alone.

1. Confirm which layer returned the error

There are two separate outcomes to distinguish:

  • API request failed: the screenshot service rejected the request and returned an HTTP error.
  • Capture succeeded, target page failed: the service returned an image of a login, access-denied, or error page. Some providers expose the target document’s final status in a separate header or response field.

Check the endpoint’s HTTP status and Content-Type. Do not assume a file saved as .png is an image: failed requests may return JSON or HTML. If the provider documents a target-page status field, inspect it separately. Screenshot API, for example, documents X-Page-Status; a target-page 401 or 403 can mean the screenshot is a login or error page, even when the API request itself succeeded. See its documentation.

2. Preserve the complete response safely

For diagnosis, record the request method and endpoint, HTTP status, response body, response headers, provider error code and message, request ID if supplied, timestamp with timezone, and the relevant usage or quota state. Redact API keys, bearer tokens, cookies, passwords, and other secrets before sharing logs or opening a support ticket.

Header names and units differ by provider. Examples in official documentation include Retry-After, x-ratelimit-remaining, and x-ratelimit-reset. Screenshot API documents its own X-RateLimit-* and X-Quota-* headers. Follow the provider’s current documentation rather than assuming another service’s conventions.

3. Diagnose a 429 response

A 429 means the request was refused or limited according to that API’s policy. It does not by itself say whether waiting briefly will solve the problem.

Evidence Likely cause Next action
Error body says rate limit, throttled, or too many requests; a documented remaining counter is zero Per-window limit or burst throttling Stop requests for the stated interval, reduce concurrency, and honor Retry-After if supplied.
Error body says quota exceeded; dashboard or usage endpoint shows the allowance is depleted Monthly or account quota Pause until reset or follow the provider’s documented account or billing steps. Retrying does not replenish quota.
Body provides a provider-specific code or details that do not match either case Another provider-defined limit or request condition Use that code and the current provider docs to choose the fix.

Provider mappings vary even among screenshot APIs. Screenshot API’s retrieved reference listed rate_limited and quota_exceeded as distinct 429 codes. ScreenshotEngine documents both temporary limits and monthly allowance exhaustion as possible 429 causes. These are examples of those providers’ documented behavior, not universal rules; check the current docs and dashboard for the service you use.

4. Diagnose a 403 response

Do not assume every 403 means a bad API key. Check the provider’s error code and message, then verify the likely causes:

  1. Credentials: confirm that the key is present, current, and associated with the account or project you intended to use. Check whether it was accidentally truncated, revoked, or sent in the wrong parameter or header.
  2. Permissions and endpoint: confirm the key and subscription are allowed to use this endpoint and requested feature. Check account, project, billing, and spending status in the provider’s dashboard.
  3. Provider-specific quota or trial state: some providers use 403 for conditions another provider reports differently. ScreenshotAPI.net’s retrieved error table, for example, maps screenshots_limit_reached and trial_expired to 403. Verify the current table and actual error code before changing plans.
  4. Target website blocked the render: inspect the target-page status header or field if the service provides one. The API may have returned an image successfully, but that image may show a login or access-denied page. Use only authorized access methods; increasing the wait time will not necessarily resolve a site’s access control.

Rate limits can also use 403 in some APIs. GitHub’s general REST guidance says a primary rate-limit breach can return either 403 or 429 and advises clients to inspect rate-limit headers. That illustrates why status codes must be interpreted in the context of the specific provider’s response.

5. Retry only transient failures

Retry a 429 only when the response and provider guidance suggest temporary throttling. Honor Retry-After when present. If the provider documents a reset time or remaining counter, use those values according to its documented units. Reduce request bursts and concurrent captures so the same limit is not immediately hit again.

If no retry interval is supplied and the error appears transient, use bounded exponential backoff with jitter. For example, for a small number of attempts, choose a delay that grows after each failure and add a random offset; cap both the delay and total elapsed retry time. Stop when the cap is reached. Do not retry invalid keys, permission errors, depleted monthly quotas, billing or spending limits, or malformed requests as if they were temporary.

GitHub recommends respecting retry and reset headers, and using increasing waits when a secondary limit continues. OpenAI’s API guidance similarly distinguishes temporary rate limits from exhausted credit or usage limits. Repeatedly retrying an account or quota error will not restore access.

6. Make a safe diagnostic request

Reproduce the issue once with the smallest valid request and inspect the response before saving it as an image. This Python example prints status, content type, selected diagnostic headers, and a short response body. It deliberately does not print the API key. Adapt the endpoint, parameter names, and authentication method to the provider’s documentation.

import os
import requests

endpoint = "https://api.example.com/screenshot"
api_key = os.environ["SCREENSHOT_API_KEY"]

response = requests.get(
    endpoint,
    params={"url": "https://example.org"},
    headers={"Authorization": f"Bearer {api_key}"},
    timeout=90,
)

print("status:", response.status_code)
print("content-type:", response.headers.get("Content-Type"))
for name in ("Retry-After", "X-Request-Id", "X-Page-Status"):
    if name in response.headers:
        print(f"{name}:", response.headers[name])

if not response.ok:
    print("error body:", response.text[:4000])
else:
    content_type = response.headers.get("Content-Type", "")
    if "image/" not in content_type:
        print("Response succeeded, but content is not an image:", response.text[:1000])
    else:
        with open("capture", "wb") as output:
            output.write(response.content)
        print("saved capture")

Set the key in the environment before running, for example export SCREENSHOT_API_KEY='…' in a shell. Replace the sample endpoint and request shape with the provider’s actual API contract. Some providers use query parameters instead of bearer authentication; do not send credentials in a way the provider does not document.

7. Escalate with useful, sanitized details

If the issue continues after the matching fix, contact the screenshot API provider. Include the method and endpoint, sanitized parameters, status and full error code/message, relevant headers, request ID, request time and timezone, account usage state, and the steps already tried. Never include the API key, authorization header, cookies, or passwords.

8. Avoid repeat failures in production

  • Use a concurrency limit and queue bursts so normal traffic stays within the provider’s documented request window.
  • Separate transient rate-window failures from permanent account, permission, and quota failures in application logic.
  • Bound retries by attempt count and elapsed time; add jitter to reduce synchronized retries across workers.
  • Log status, provider error code, request ID, content type, and documented limit headers, while redacting secrets.
  • Monitor monthly usage and reset rules separately from per-minute or per-second throttling.
  • For asynchronous or bulk work, follow the provider’s documented job and batch behavior; do not assume an error can be retried without duplicate work or billing consequences.

Provider limits and plan mappings can change. The cited provider examples were retrieved on 2026-10-03; check the current documentation and dashboard when debugging a live integration. The research does not establish a universal billing rule for failed captures, so verify how your provider handles timed-out or rejected requests.

Or skip the browser setup

If your goal is to get a screenshot without managing a browser, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options and response details.

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,
)
if r.ok:
    open("shot.webp", "wb").write(r.content)
else:
    print(r.status_code, r.headers.get("Content-Type"), r.text)
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) {
  const bytes = new Uint8Array(await res.arrayBuffer());
  await Bun.write('shot.webp', bytes); // In Node.js, write bytes with node:fs/promises.
} else {
  console.error(res.status, res.headers.get('content-type'), await res.text());
}

ScreenshotNeo 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. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing state. Its 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, and every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Does a 429 always mean I should wait and retry?

No. It may be temporary throttling or an exhausted quota. Check the provider’s error body, usage dashboard, and reset information first.

Does a 403 always mean the API key is invalid?

No. It can reflect permissions, account state, provider-specific quota rules, rate limits, or a target page’s access response. The provider’s error code and documented response fields determine which applies.

Why did my image viewer say the screenshot file is corrupt?

The client may have saved a JSON or HTML error response with an image extension. Inspect HTTP status and Content-Type before writing the body as an image.

What should I send support?

Send the sanitized request context, response details, relevant headers, request ID, timestamp, usage state, and what you already tried. Exclude all credentials and cookies.

Sources