ScreenshotNeo

BlogHow-to

How to Access a Screenshot API from an Unsupported Programming Language

Use raw HTTP to call any screenshot API from an unsupported language. Learn authentication, JSON, binary responses, options, errors, and reusable wrapper patterns.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: you do not need an official SDK. A screenshot API is an HTTP service, so any language that can make an HTTP request, set headers, encode JSON, inspect status codes, and write bytes to a file can use it. Build a small wrapper around the provider’s REST endpoint and expose only the options your application needs.

The portable sequence is:

  1. Store the API key in an environment variable or secret manager.
  2. Create a GET request for simple query parameters or a POST request for advanced options.
  3. Authenticate with the documented header.
  4. Send the target page in the url field.
  5. Check the HTTP status before treating the body as an image.
  6. Save successful binary bytes, or parse JSON when the provider returns a job or error.

1. The provider-neutral HTTP contract

Screenshot API documents a REST API that works with any programming language. Its endpoints include GET /api/v1/screenshot for query parameters, POST /api/v1/screenshot for a JSON request, and POST /api/v1/screenshot/batch for multiple URLs.

A minimal POST request looks like this:

POST https://api.screenshot-api.org/api/v1/screenshot
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{
  "url": "https://example.com",
  "format": "png",
  "fullPage": true,
  "viewport": { "width": 1280, "height": 720 }
}

The response may be image bytes, JSON describing a result, or a JSON error. Do not decide which one it is from the HTTP method alone. Check the status and, when available, the Content-Type header.

2. Generic pseudocode for an unsupported language

apiKey = environment.get("SCREENSHOT_API_KEY")

request = HTTP.POST("https://api.screenshot-api.org/api/v1/screenshot")
request.setHeader("Authorization", "Bearer " + apiKey)
request.setHeader("Content-Type", "application/json")
request.body = JSON.encode({
  "url": "https://example.com",
  "format": "png",
  "fullPage": true,
  "viewport": { "width": 1280, "height": 720 }
})

response = request.send()

if response.status >= 200 and response.status < 300:
    contentType = response.header("Content-Type")
    if contentType starts with "application/json":
        result = JSON.decode(response.body)
        handleResult(result)
    else:
        FILE.writeBytes("example.png", response.body)
else:
    error = tryParseJson(response.body)
    raise ScreenshotError(response.status, error or response.body)

Map those calls to your language's standard HTTP and JSON libraries. The SDK-shaped wrapper you build should keep authentication, serialization, response handling, and retries in one place.

3. cURL: establish a known-good request

Use cURL before debugging your application. It separates API credentials and request construction from language-specific code.

curl -X POST "https://api.screenshot-api.org/api/v1/screenshot" \
  -H "Authorization: Bearer $SCREENSHOT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "url": "https://example.com",
    "format": "png",
    "fullPage": true,
    "viewport": {"width": 1280, "height": 720}
  }' \
  --output example.png \
  --fail-with-body

For a simple GET request, put parameters in the query string and URL-encode the target URL:

curl -G "https://api.screenshot-api.org/api/v1/screenshot" \
  -H "Authorization: Bearer $SCREENSHOT_API_KEY" \
  --data-urlencode "url=https://example.com" \
  --data-urlencode "format=png" \
  --output example.png \
  --fail-with-body

4. Python example

import os
import requests

api_key = os.environ["SCREENSHOT_API_KEY"]
payload = {
    "url": "https://example.com",
    "format": "png",
    "fullPage": True,
    "viewport": {"width": 1280, "height": 720},
}

response = requests.post(
    "https://api.screenshot-api.org/api/v1/screenshot",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    json=payload,
    timeout=90,
)

if not response.ok:
    raise RuntimeError(f"Screenshot failed ({response.status_code}): {response.text}")

if response.headers.get("content-type", "").startswith("application/json"):
    print(response.json())
else:
    with open("example.png", "wb") as output:
        output.write(response.content)

5. Node.js example

const apiKey = process.env.SCREENSHOT_API_KEY;

const response = await fetch('https://api.screenshot-api.org/api/v1/screenshot', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://example.com',
    format: 'png',
    fullPage: true,
    viewport: { width: 1280, height: 720 }
  })
});

if (!response.ok) {
  throw new Error(`Screenshot failed (${response.status}): ${await response.text()}`);
}

const contentType = response.headers.get('content-type') || '';
if (contentType.startsWith('application/json')) {
  console.log(await response.json());
} else {
  const bytes = Buffer.from(await response.arrayBuffer());
  require('fs').writeFileSync('example.png', bytes);
}

6. Authentication and request construction

Prefer headers for API keys

Use Authorization: Bearer YOUR_API_KEY when the provider supports it. Screenshot API also documents X-API-Key and query-string authentication. Query parameters are convenient for quick experiments, but keys can appear in shell history, proxy logs, browser history, and copied URLs.

GET or POST?

Use Best choice Reason
One URL and a few basic parameters GET Easy to inspect and cache
Viewport objects, CSS, JavaScript, waits, PDF settings, or other nested data POST JSON represents structured options without complicated URL encoding
Many URLs Batch POST One request can represent a batch workflow

7. Options worth exposing in your wrapper

Start with a small typed configuration and pass through only values your application needs. Screenshot API documents these controls:

Group Options
Output PNG, JPEG, WebP, or PDF; JPEG/WebP quality
Viewport Width, height, device scale factor, and device presets where supported
Page extent Full-page capture or the visible viewport
Timing Navigation wait strategy, selector wait, extra delay, and timeout
Targeting Capture a CSS selector instead of the complete page
Page cleanup Ad and cookie-banner blocking
Rendering Dark mode, custom CSS, and custom JavaScript
Locale Geolocation, timezone, and locale
Operations Cache controls and batch requests

Advanced controls such as custom CSS, JavaScript, hide selectors, geolocation, timezone, locale, and PDF options are documented as POST-only. Keep your public wrapper stable even if provider field names change: validate input, translate your names to provider names, and return a consistent result object.

8. Binary responses, JSON responses, and redirects

  • Read the body as bytes when the response is an image or PDF. Never decode it as UTF-8 first.
  • Parse JSON only when Content-Type indicates JSON or when the provider documents a JSON result.
  • Follow redirects only when the API documentation says they are part of the contract. Preserve authentication rules when following a redirect to a different host.
  • Write to a temporary file and rename it after a successful complete read so downstream jobs never see a partial image.

9. Edge cases to handle

  • Encoded target URLs: encode the entire URL as one query value for GET. JSON avoids most nested escaping problems.
  • Private pages: use the provider's documented headers or cookies feature; do not put credentials in the target URL.
  • Very tall pages: full-page captures can be large. Set a maximum output size and consider element capture or viewport capture.
  • Lazy content: wait for a selector or network idle before capturing; a fixed delay alone is less predictable.
  • Animations: pause or hide animated elements with custom CSS when visual diffs must be stable.
  • Non-HTML responses: verify that the target is a page the renderer can load. A PDF, redirect loop, or download may not produce the expected screenshot.
  • Rate limits: cap concurrency, respect retry headers, and use exponential backoff with jitter.

10. Troubleshooting

Symptom Likely cause Fix
401 or 403 Missing, malformed, expired, or insufficient API key Check the exact header spelling, environment variable, and account permissions.
400 Invalid JSON, missing URL, unsupported option, or incorrectly encoded query Start with only url, validate JSON locally, then add options one at a time.
200 but an unreadable file JSON was saved as an image, or bytes were decoded as text Inspect Content-Type and write the raw response bytes.
Blank or incomplete page Capture happened before client-side rendering or lazy loading finished Use a selector wait, network-idle strategy, or an explicit delay.
Timeout Slow origin, blocked resource, or overly short client timeout Increase the client timeout, set the provider timeout deliberately, and retry only idempotent requests.
Works in cURL but not in the app Different proxy, TLS settings, headers, URL encoding, or environment variables Log method, host, status, and safe request metadata; compare the serialized request with cURL.
Intermittent 429 Concurrency exceeds the provider limit Use a queue, bounded workers, backoff, and provider retry-after guidance.

11. Reliability, performance, and cost

Reliability

Make screenshot jobs idempotent by deriving an output key from the target URL and options. Record the request ID, status, elapsed time, and response content type. Retry network failures and 5xx responses with exponential backoff; avoid blindly retrying validation errors or authentication failures.

Performance

Reuse HTTP connections, keep concurrency below the provider's documented limits, and avoid full-page capture when a component screenshot is sufficient. Cache results when the page and options have not changed. A selector wait is usually more deterministic than an arbitrary long delay, while network-idle waits can be expensive on pages with analytics or streaming requests.

Cost

Cost depends on the provider's plan, quotas, rendering duration, output type, and batch rules. Verify current pricing and retention directly with each provider. Cache screenshots, deduplicate identical jobs, and set maximum timeouts to prevent accidental runaway work.

12. Provider comparison checklist

Before writing a production adapter, compare:

  • GET, POST, and batch endpoint contracts
  • Header and query authentication
  • Image and PDF formats
  • Viewport, full-page, selector, wait, and rendering controls
  • Synchronous versus asynchronous jobs and webhook behavior
  • Error schema, retry headers, quotas, and concurrency limits
  • Execution regions, retention, privacy, and support terms

Cloudflare Browser Run, for example, documents https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-run/screenshot. It requires a custom API token with Browser Rendering - Edit permission and accepts either a url or html field. Its documented use cases include website previews, dashboards, reports, automated testing, and visual regression. Verify current account limits and pricing before selecting it.

13. Or skip the browser setup

ScreenshotNeo exposes the same language-neutral HTTP pattern and includes an MCP server for Claude, Cursor, and other MCP clients. One GET request returns PNG, JPEG, WebP, or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

See the ScreenshotNeo API documentation for all options. The direct calls are:

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}`);

ScreenshotNeo supports full-page and element captures, device and viewport settings, retina scale, PDF controls, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, geolocation, timezone, transparent backgrounds, resizing, caching, signed links, async webhooks, bulk capture, and a usage API. Every feature is on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with 1,000 screenshots per month at no charge.

14. FAQ

Do I need to write a full SDK?

No. A small function that accepts options, sends HTTP, checks status, and returns bytes or structured JSON is enough. Add an SDK-style layer only when multiple applications need the same behavior.

Should the API key be in the URL?

Use a header whenever possible. Query-string authentication is easier to demonstrate but more likely to leak through logs and copied URLs.

Can an unsupported language call a POST endpoint?

Yes, if its HTTP client can set headers and send a string body. JSON serialization can be implemented with a library or a carefully validated encoder.

How do I know whether the response is an image?

Check the status first, then inspect Content-Type. Save non-JSON responses as bytes and parse JSON responses as objects.

When should I use a batch endpoint?

Use batch capture when the provider documents it and your workload contains independent URLs. Still apply bounded concurrency, per-item error handling, and retry rules.