How to Capture a Website Screenshot with a Screenshot API Using a POST Request
Send a POST request to a screenshot API, save the returned image safely, and handle capture options, errors, and production concerns.
To capture a website screenshot with a POST request, send the target URL and capture settings in the JSON body, authenticate using the provider’s documented header, then save the successful response body as an image. The endpoint, field names, defaults, and response format vary by provider. This guide uses ScreenshotEngine’s documented POST contract for the do-it-yourself example; it then shows ScreenshotNeo’s one-call alternative.
1. Understand the POST request contract
A typical screenshot API request has four parts:
- Endpoint: the provider’s POST URL.
- Authentication: a secret API key in the documented header.
- Request body: JSON containing the URL and any supported capture options.
- Response: often image bytes on success and a structured error on failure.
Do not assume these details are interchangeable across APIs. ScreenshotEngine documents POST https://api.screenshotengine.com/v1/screenshot, bearer-token authentication, and a JSON body whose only required field is url. A successful capture returns image bytes directly; errors return JSON. Its docs also distinguish POST JSON names from GET query names, and POST field names are case-sensitive. See the ScreenshotEngine quickstart and API reference for its request contract.
2. Capture a screenshot with cURL
Set the API key in your shell environment, then run this request. It asks for a full-page PNG and writes the response body to screenshot.png.
export SCREENSHOTENGINE_API_KEY='your-secret-api-key'
curl --fail-with-body --request POST 'https://api.screenshotengine.com/v1/screenshot' \
--header "Authorization: Bearer $SCREENSHOTENGINE_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"url": "https://example.com",
"format": "png",
"height": "full"
}' \
--output screenshot.png
--output saves the response body instead of printing binary image data in the terminal. The provider’s example uses --fail-with-body, which requires cURL 7.76 or newer. It makes HTTP failures visible while retaining the response body for diagnosis. An error body may be JSON, so check the request’s status before treating the output file as a valid image.
Keep the key in a server-side secret or environment variable. POST keeps the key out of the URL, but the request is still unsafe if you expose the key in a public repository, browser bundle, logs, or shared terminal history.
3. Make the same request from Python
This example checks the status before writing the response. If the API returns an error, it raises an exception rather than saving JSON or an error page with a PNG extension.
import os
import requests
api_key = os.environ["SCREENSHOTENGINE_API_KEY"]
response = requests.post(
"https://api.screenshotengine.com/v1/screenshot",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"url": "https://example.com",
"format": "png",
"height": "full",
},
timeout=90,
)
response.raise_for_status()
with open("screenshot.png", "wb") as image_file:
image_file.write(response.content)
Install the HTTP client with python -m pip install requests. Use json= so the library serializes a Python dictionary as JSON. The timeout is a client-side ceiling, not a guarantee that a capture will finish in that time; choose it to fit your application’s request budget and the provider’s documented limits.
4. Make the same request from Node.js
With a current Node.js release that provides global fetch, send JSON and write the binary response only after checking the status.
const apiKey = process.env.SCREENSHOTENGINE_API_KEY;
if (!apiKey) throw new Error('Set SCREENSHOTENGINE_API_KEY');
const response = await fetch('https://api.screenshotengine.com/v1/screenshot', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://example.com',
format: 'png',
height: 'full',
}),
signal: AbortSignal.timeout(90_000),
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Screenshot API returned HTTP ${response.status}: ${errorBody}`);
}
const imageBytes = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('screenshot.png', imageBytes)
);
In a web application, call the screenshot API from a trusted server route. Do not put a long-lived API key in frontend JavaScript: visitors can inspect it and reuse it.
5. Choose capture settings and handle the response
For ScreenshotEngine, sending just {"url":"https://example.com"} uses the documented default: a 1280 × 720 viewport JPEG. Full-page capture is opt-in. Its quickstart demonstrates setting height to "full" and format to "png". Other settings must be checked against that provider’s parameter reference; do not copy option names from a different API and expect them to work.
| Concern | What to do |
|---|---|
| JSON types | Use real JSON booleans and numbers where the provider documents them. Do not send "true" as a string when a boolean is required. |
| Field spelling | Use the exact, case-sensitive POST field names from the selected API’s documentation. |
| Image format | Request a documented output format and use a matching file extension and content type expectations. |
| Full page | Enable it explicitly when needed. Long pages can take longer and produce much larger files than a viewport capture. |
| Response parsing | Check HTTP status first. Save bytes only on success; read an error response as text or JSON according to the provider’s contract. |
| Validation | For robust pipelines, check response headers or decode the image before passing it to downstream processing. |
Cookie-banner blocking, dark mode, dimensions, and other render options are provider-specific. The reviewed ScreenshotEngine quickstart describes cookie-banner blocking and dark mode as opt-in. Cloudflare Browser Run documents a separate POST /screenshot endpoint that accepts a URL or supplied HTML; its request and response contract should be taken from its own documentation, not inferred from ScreenshotEngine’s.
6. Production concerns: security, retries, and cost
Protect credentials and control the target URL
- Store the key in a secret manager or server environment variable, and rotate it if it is exposed.
- Never place a secret key in a public URL, browser code, or client-visible logs.
- If your service accepts a URL from an end user, validate it and restrict destinations as appropriate for your application. A screenshot service can fetch remote pages, so unrestricted user input can create abuse and security problems.
- Avoid logging authorization headers or full request objects that contain secrets.
Plan for variable render time
Rendering depends on the target site, its scripts, network conditions, and page length. Set a client timeout that fits your job and request budget. For batches or user-facing workflows, consider a background job and a status path rather than holding an interactive request open. Do not retry every failure blindly: distinguish authentication and invalid-request errors from transient network or service errors.
Retry without multiplying work
Use bounded retries with backoff for failures that are plausibly transient, and respect any provider-supplied retry guidance. A timeout can be ambiguous: the server may have completed the capture even though the client did not receive the response. If the provider supports idempotency keys or job IDs, use them according to its documentation; do not assume a second POST cannot create a second capture.
Estimate operational cost
Estimate usage from the number of captures, the provider’s billing unit, and any plan limits before running recurring jobs. Full-page images can increase transfer, storage, and downstream processing even when billing is per request. The research reviewed here does not establish ScreenshotEngine pricing or limits, so check its current account terms rather than relying on an assumed rate.
7. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or 403 response | Missing, invalid, expired, or incorrectly formatted bearer token. | Confirm the key is set and send Authorization: Bearer … exactly as the provider documents. Do not put api_key in the JSON body for ScreenshotEngine; its authentication docs say that does not authenticate the request. |
| 400 or validation error | Missing URL, unsupported option, wrong field casing, or string used instead of a JSON boolean/number. | Compare the body to the selected provider’s POST reference. Start with only the required url, then add supported options one at a time. |
| Saved file is JSON or cannot be opened | An API error body was written to an image path. | Check the HTTP status before saving and inspect the error response. Write to a temporary file and rename only after validation if the pipeline must not expose partial output. |
| cURL reports an unknown option | The installed cURL is older than 7.76 and does not support --fail-with-body. |
Upgrade cURL or use its available failure/status handling and inspect the response body separately. |
| Timeout | The site is slow, scripts keep loading, the page is large, or the client timeout is too short. | Check the target independently, allow a suitable timeout, and use an asynchronous workflow if supported. Avoid unlimited retries. |
| Screenshot is clipped | The request used viewport capture or the provider’s full-page option was omitted or misnamed. | Enable the documented full-page setting and confirm the field is correct for POST. |
| Screenshot differs from browser | The remote render may see different cookies, geolocation, authentication, timing, or content than your local session. | Check which headers, cookies, user agent, and render settings the chosen provider supports. Do not assume a browser session transfers to an API request. |
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its GET endpoint returns an image or PDF from a URL, so this is a one-request alternative when you do not need to manage a browser capture service yourself. See the ScreenshotNeo API documentation for the request options.
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}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned HTTP ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, with page verdict and billing information in response headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf 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. Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card required.
9. FAQ
Can I use POST so the target URL is not in the query string?
Yes, when the selected provider supports a POST request with the URL in its body. The URL is still present in the request body and may appear in server logs, so handle it according to your privacy and logging requirements.
Does every screenshot API return a PNG file?
No. Providers may return different image formats, redirects, or structured responses. Follow the endpoint’s documented success and error behavior; ScreenshotEngine’s documented success response is image bytes.
Can I send HTML instead of a public website URL?
Some endpoints support it. Cloudflare Browser Run documents URL or supplied HTML input for its POST screenshot endpoint; check its own documentation for the exact payload.
Should screenshots be generated synchronously?
For a small, short capture, a direct request is simple. For slow pages, bulk work, or workflows that must survive client disconnects, use a provider’s documented asynchronous option or a queue in your application.
Sources
- ScreenshotEngine API quickstart and parameter reference — endpoint, authentication, request body, defaults, and response behavior.
- Cloudflare Browser Run screenshot documentation — separate POST endpoint and URL or HTML input.
- ScreenshotNeo API documentation — ScreenshotNeo request configuration.


