Custom Request Headers for URL Screenshots
Pass authorization, cookies, language, and device headers to URL screenshot requests safely, with cURL, Python, Node.js, and troubleshooting.
Short answer: send custom headers as part of the screenshot request before the renderer opens the page. Use headers for bearer tokens, API keys, cookies, Referer, User-Agent, and Accept-Language. Confirm how your provider formats headers, whether they reach redirects and subresources, and whether the final response status is exposed.
This guide covers authenticated pages, existing sessions, localized screenshots, mobile layouts, security controls, provider syntax differences, complete cURL/Python/Node.js examples, and the failure modes that produce a login page or an error image instead of the requested content.
1. What custom request headers do
A screenshot service loads the target URL in a browser-like environment. Custom headers add request metadata to that navigation. The origin can use that metadata to authenticate the request, select a language, choose a device layout, validate a referrer, or restore a session.
| Header | Typical use | Example value |
|---|---|---|
Authorization |
Bearer-token or basic authentication | Bearer eyJ... |
X-API-Key |
Vendor-specific API-key authentication | sk_live_... |
Cookie |
Reuse an authenticated session or consent state | session=abc123; locale=en-GB |
Referer |
Test a flow that checks the referring origin | https://app.example.com/ |
User-Agent |
Request a bot, browser, or device-specific layout | Mozilla/5.0 ... |
Accept-Language |
Request localized content | de-DE,de;q=0.8,en;q=0.5 |
Headers are not the same as JavaScript variables. A header must be attached to the HTTP request made by the renderer. Adding window.headers in page JavaScript does not authenticate the initial navigation.
2. Choose the correct header format
There is no universal wire format. Read the provider’s API documentation before copying an example.
| API style | Request shape | Important detail |
|---|---|---|
| JSON array | header: [{"name":"Authorization","value":"Bearer token"}] |
ScreenshotCenter documents one object per header. |
| Repeated query parameter | header=Authorization: Bearer token |
Screenshot API documents repeatable header parameters and a POST object form. |
| Semicolon-separated string | Authorization: Bearer token; Accept-Language: fr |
Some APIs parse one string into multiple headers. |
| Headers object | {"headers":{"Authorization":"Bearer token"}} |
HTML/CSS to Image accepts a headers parameter and splits entries at the first colon. |
Colons inside a value, such as those in a URL or token, must remain part of the value. Use a structured JSON or POST form when the service supports it; it avoids ambiguity and keeps secrets out of the URL.
3. cURL: send headers to a screenshot endpoint
The following generic pattern uses a repeatable header parameter. Replace the endpoint and parameter names with those documented by your provider.
curl -G 'https://example-screenshot-api.test/capture' \
--data-urlencode 'url=https://app.example.com/account' \
--data-urlencode 'header=Authorization: Bearer YOUR_TOKEN' \
--data-urlencode 'header=Accept-Language: en-US,en;q=0.9' \
--data-urlencode 'header=User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/125 Safari/537.36' \
-o account.png
For a JSON POST API, keep the headers in the request body instead:
curl -X POST 'https://example-screenshot-api.test/capture' \
-H 'Content-Type: application/json' \
--data @request.json \
-o account.png
{
"url": "https://app.example.com/account",
"headers": {
"Authorization": "Bearer YOUR_TOKEN",
"Accept-Language": "en-US,en;q=0.9",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/125 Safari/537.36"
}
}
4. Python: authenticated and localized captures
Use a request library that lets you set a timeout. Keep credentials in environment variables rather than source code or shell history.
import os
import requests
url = "https://app.example.com/account"
headers = {
"Authorization": f"Bearer {os.environ['APP_SCREENSHOT_TOKEN']}",
"Accept-Language": "en-US,en;q=0.9",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/125 Safari/537.36",
}
response = requests.get(
"https://example-screenshot-api.test/capture",
params={"url": url, "header": [f"{name}: {value}" for name, value in headers.items()]},
timeout=90,
)
response.raise_for_status()
with open("account.png", "wb") as image:
image.write(response.content)
If the provider expects a JSON object rather than repeated parameters:
payload = {"url": url, "headers": headers}
response = requests.post(
"https://example-screenshot-api.test/capture",
json=payload,
timeout=90,
)
response.raise_for_status()
open("account.png", "wb").write(response.content)
5. Node.js: send headers without exposing them in a URL
const token = process.env.APP_SCREENSHOT_TOKEN;
const payload = {
url: 'https://app.example.com/account',
headers: {
Authorization: `Bearer ${token}`,
'Accept-Language': 'en-US,en;q=0.9',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/125 Safari/537.36'
}
};
const res = await fetch('https://example-screenshot-api.test/capture', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await require('node:fs').promises.writeFile('account.png', image);
6. Cookies, login pages, and protected content
For a session-based application, send the complete cookie string expected by the origin:
Cookie: session=SESSION_VALUE; csrf=CSRF_VALUE; locale=en-US
Copy only the cookies needed for the target host. A cookie from a different domain or path may be ignored. Session cookies can expire while a capture job is queued, so use a short queue delay and refresh credentials when the application requires it.
Token authentication and browser sessions are different. A bearer token may authenticate the first document request while the application then calls an API that needs a separate cookie or CSRF token. If the resulting image is a login page, inspect the page status and the browser’s network requirements rather than repeatedly changing the screenshot dimensions.
7. Header scope: navigation, redirects, and subresources
Ask whether the service sends your headers only to the initial target request or to every HTTP/HTTPS transaction. Browshot documents propagation to all HTTP/HTTPS transactions, while Screenshot API documents headers sent only to the target host. This distinction affects pages that load protected CSS, images, fonts, or API data from another origin.
- Initial request only: enough when the HTML is self-contained or the page sets a session cookie immediately.
- Target host: safer for secrets and usually sufficient for same-origin assets.
- All transactions: can load protected subresources, but increases the risk of leaking an Authorization header to an unintended host.
Redirects deserve special attention. A redirect from https://app.example.com to an identity provider may drop, transform, or reject credentials. Restrict capture to an approved host when the service supports that control, and never assume a secret is safe across redirects.
8. Verify what was actually captured
A successful HTTP response from the screenshot service does not prove that the requested page rendered. The image may show a 401 page, a 403 page, a consent wall, or an application error.
- Check the provider’s final document status metadata. Screenshot API exposes this as
X-Page-Status. - Inspect whether the response says the page was billed, cached, blocked, or failed when those headers are available.
- Open the image and look for a login form, access-denied message, missing fonts, or an empty application shell.
- Reproduce the same URL and headers with a normal browser or an HTTP client to confirm the credentials themselves work.
9. Security checklist
- Store tokens and cookies in a secret manager or environment variable.
- Prefer short-lived, least-privilege credentials created specifically for capture.
- Use POST or encrypted secret storage instead of putting credentials in query strings.
- Redact authorization values from application logs, tracing, CI output, and error reports.
- Check whether the provider logs request parameters or stores rendered images.
- Restrict headers to the target host when that option exists.
- Do not place passwords, one-time codes, or private customer data in a screenshot that will be shared publicly.
- Confirm that automated capture is allowed and that your account is authorized to access the protected page.
10. Reliability and performance
Headers rarely dominate capture time. Browser startup, DNS, TLS, JavaScript execution, fonts, images, and network-idle waits usually matter more. Keep the request deterministic so retries produce comparable images.
| Concern | Practical approach |
|---|---|
| Timeouts | Set a client timeout longer than the provider’s render window and handle timeout responses explicitly. |
| Expired sessions | Generate or refresh cookies close to capture time; avoid long-lived browser exports. |
| Rate limits | Use bounded concurrency, exponential backoff for transient failures, and an idempotent job key where supported. |
| Dynamic pages | Wait for a selector or network idle when the authenticated content is rendered after navigation. |
| Large assets | Block unnecessary ads, trackers, or resource types if your provider supports it, but do not block required API calls or fonts. |
| Consistency | Pin User-Agent, Accept-Language, timezone, viewport, and cookies for visual comparisons. |
11. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Image shows a 401 page | Missing, expired, or malformed Authorization header | Confirm the exact scheme, token lifetime, and header syntax; inspect final status metadata. |
| Image shows a 403 page | Insufficient permission, IP policy, bot protection, or wrong Referer | Test the same credentials directly, add the required Referer, and verify the renderer’s network is allowed. |
| Header appears ignored | Wrong parameter name or provider expects JSON instead of a string | Use the documented format and verify the outgoing request with a safe, redacted trace. |
| Login works in a browser but not in capture | Authentication depends on multiple cookies, JavaScript, or a CSRF flow | Supply the required cookie set and wait for the post-login selector; a static bearer header may be insufficient. |
| Localized text is wrong | Accept-Language was omitted or the app uses a profile cookie | Send Accept-Language and the locale cookie, then keep timezone and region settings consistent. |
| Mobile layout does not appear | User-Agent alone does not change viewport or device metrics | Configure the provider’s viewport/device option as well as User-Agent when available. |
| Protected images or CSS are missing | Headers reach only the initial navigation | Choose a provider that propagates headers to required subresources, or use a page flow that exchanges the header for a session cookie. |
| Secrets appear in logs | Credentials were placed in a query string or verbose debug output | Move secrets to POST bodies or environment variables and redact request logging. |
| Capture is blank | JavaScript has not rendered, a resource was blocked, or the page timed out | Increase the render wait, wait for a selector, allow required requests, and inspect the final status. |
12. Or skip the browser setup
ScreenshotNeo is a website screenshot API with custom headers, cookies, user-agent, authorization, timezone, and geolocation options. It accepts a URL and returns PNG, JPEG, WebP, or PDF; see the ScreenshotNeo API documentation for the header option names and full configuration.
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}`);
Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers identify the page verdict and whether it was billed. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
13. Cost planning
Header support itself is rarely the main cost driver. Billing depends on the screenshot service’s plan, image or PDF volume, asynchronous jobs, and cache policy. Cache deterministic captures when the page and headers are unchanged, but never cache a response containing private data in a shared location. For batch jobs, record the URL, a redacted header fingerprint, final status, verdict, and billing result so retries do not create unexplained usage.
14. FAQ
Can I send a password in a custom header?
Only if the target application explicitly defines such a header. Prefer the application’s documented token or session mechanism, and never put a raw password in a URL.
Should I use cookies or Authorization?
Use the mechanism the origin expects. Cookies are appropriate for an existing browser session; Authorization is appropriate for token-protected endpoints. Some applications require both.
Why does a screenshot service return an image when authentication failed?
Many services capture whatever document the browser finally rendered, including a 401, 403, or login page. Check final status metadata and inspect the image.
Do custom headers change the screenshot’s visual size?
No. Headers influence the response and application state. Viewport, device preset, scale, and page dimensions control the image geometry.
Can headers authenticate third-party assets?
Only when the provider propagates them to those requests and the target host accepts them. Verify scope before sending credentials broadly.


