Screenshotlayer URL Encoding: Fix Errors with Query Strings and Special Characters
Pass a target URL with query strings and special characters to Screenshotlayer safely. Learn how to serialize requests and diagnose encoding errors.
Short answer: pass the complete target address, including https://, as one value for Screenshotlayer’s outer url query parameter. Use your HTTP client’s query-parameter serializer; do not concatenate the target URL into the request by hand or encode it twice. Characters such as &, #, +, %, and = can have structural meaning, so their correct representation depends on which URL component they belong to.
Screenshotlayer documents the capture endpoint as https://api.screenshotlayer.com/api/capture and requires access_key and url. The target URL must include its HTTP or HTTPS scheme. Its specification does not show a nested-query example or explicitly describe how the service decodes one, so the examples below show standard query construction, not a provider-specific parsing guarantee. Screenshotlayer API specification.
Why nested URLs break
A request to Screenshotlayer has an outer query string. The target site can have its own path, query string, and fragment. If you append the target URL raw to the outer request, characters in the target may instead be interpreted as part of Screenshotlayer’s request.
&separates query parameters. An unescaped ampersand in a nested value can look like the start of another outer parameter.#starts a fragment in a URL. In a browser address, it can cause the remainder of a manually assembled request to be treated as a fragment rather than sent to the endpoint.+may represent a space in form-style query serialization, while a literal plus in a value needs context-appropriate encoding.%introduces a percent-encoded byte. An existing escape such as%2Fcan be changed accidentally by encoding the whole value again.=separates a query parameter name and value; inside a nested query it belongs to the target value.
Percent encoding is context-sensitive: the same character may be treated differently depending on whether it appears in a path, query value, or fragment. MDN explains percent encoding and the use of encodeURIComponent. Let a query builder serialize the outer parameter value instead of applying character substitutions yourself.
Build the request safely
- Start with the documented endpoint and an absolute target URL with
http://orhttps://. - Keep the target URL as a single raw string in a parameter map under
url. - Pass the map to the HTTP client’s query builder, alongside
access_key. - Inspect the serialized request if it fails: there should be one outer
urlparameter whose value represents the complete target address.
Use a placeholder for your key and keep it out of public source code. Replace the sample target with the URL you need to capture.
cURL
curl -G 'https://api.screenshotlayer.com/api/capture' \
--data-urlencode 'access_key=YOUR_ACCESS_KEY' \
--data-urlencode 'url=https://example.com/search?q=red&sort=recent#results' \
-o screenshot.png
--data-urlencode lets cURL construct query parameters from the supplied name and value. The shell quotes preserve characters such as & and # as part of the argument. The target’s fragment is part of the URL string you pass; fragments ordinarily are not sent to the destination server in an HTTP request. If you expect the renderer to navigate to a particular in-page fragment, verify Screenshotlayer’s behavior rather than assuming it.
Python
import requests
endpoint = "https://api.screenshotlayer.com/api/capture"
params = {
"access_key": "YOUR_ACCESS_KEY",
"url": "https://example.com/search?q=red&sort=recent#results",
}
response = requests.get(endpoint, params=params, timeout=90)
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
if not content_type.startswith("image/"):
raise RuntimeError(f"Expected an image, got {content_type}: {response.text}")
with open("screenshot.png", "wb") as image_file:
image_file.write(response.content)
requests serializes the parameter map into the request query. Checking the content type helps avoid saving a textual API error response with a .png filename. Choose a timeout appropriate to your application.
Node.js
const endpoint = new URL('https://api.screenshotlayer.com/api/capture');
endpoint.search = new URLSearchParams({
access_key: 'YOUR_ACCESS_KEY',
url: 'https://example.com/search?q=red&sort=recent#results',
}).toString();
const response = await fetch(endpoint);
const contentType = response.headers.get('content-type') ?? '';
if (!response.ok) {
throw new Error(`Screenshotlayer HTTP error ${response.status}: ${await response.text()}`);
}
if (!contentType.startsWith('image/')) {
throw new Error(`Expected an image, got ${contentType}: ${await response.text()}`);
}
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('screenshot.png', image));
URLSearchParams serializes the outer parameters. In JavaScript string literals, & is just an ampersand; HTML escaping is only needed when writing HTML, not in source code. For production code, import writeFile at the top of the module if preferred.
Special characters and encoding edge cases
| Target content | What to do | Why |
|---|---|---|
Inner query, e.g. ?q=red&sort=recent |
Pass the entire target string as the url value. |
The outer serializer encodes the value in its query context. |
| Space | Let the serializer choose the representation. | Spaces may be represented as %20 or as + in form-style query serialization. |
| Literal plus sign | Keep it in the target string and let the library serialize it. | In some query formats, an unescaped plus is read as a space. |
Existing escape such as %2F |
Do not pre-encode an already complete target URL a second time. | Double encoding can turn % into %25, changing the target value. |
Fragment such as #results |
Pass it as part of the target value; verify provider behavior if fragment navigation matters. | A fragment has URL semantics and normally is not included in the destination HTTP request. |
| Unicode or non-ASCII path/query text | Use a URL parser/builder for the target and a query serializer for the outer request. | Encoding requirements depend on the target URL component. |
There are two different operations people often call “URL encoding.” First, construct a valid target URL, encoding characters appropriate to its path or query component. Second, serialize that whole target as the value of Screenshotlayer’s outer url parameter. Do not confuse these steps. If your input is already a valid URL, pass that string to the outer query builder rather than encoding it wholesale yourself.
Screenshotlayer parameters relevant to this issue
The official specification lists these optional parameters in addition to required access_key and url. They do not replace correct serialization of the nested target URL.
| Parameter | Use |
|---|---|
fullpage |
Request a full-page capture. |
width, viewport |
Set capture dimensions or viewport behavior as documented by the API. |
format |
Choose an output format. |
secret_key |
Optional secret-key parameter described by the API. |
css_url |
Provide a CSS URL. |
delay |
Set a capture delay. |
ttl, force |
Control cache lifetime or force behavior as documented. |
placeholder |
Configure placeholder behavior. |
user_agent, accept_lang |
Set request/browser metadata. |
export |
Use the documented export option. |
Check the official parameter documentation for accepted values and account-specific availability. Keep outer parameters distinct from parameters inside the target URL. For example, the target’s format=compact is part of the target address; Screenshotlayer’s own format controls the screenshot output.
Troubleshoot the actual failure
Screenshotlayer documents errors with a code, type, and plain-text info field. Read that response before changing encoding: a key or account limit error is not repaired by percent-encoding the target differently.
| Symptom/error | Likely cause | Fix |
|---|---|---|
invalid_url (210) |
The target is malformed, lacks a scheme, or was split by manual query concatenation. | Use an absolute URL with http:// or https://; pass it as one parameter value through a query builder. |
missing_access_key (101) |
The required key was omitted or not serialized under the expected name. | Check the outgoing parameters for access_key. |
invalid_access_key (101) |
The key is incorrect or not accepted for the account. | Verify the key in the Screenshotlayer account and ensure the application is using the intended environment’s key. |
usage_limit_reached (104) |
The plan’s usage allowance has been reached. | Check account usage and subscription limits; changing URL encoding will not resolve it. |
| Request succeeds but shows the wrong destination | The nested query may have been split, the target may be malformed, or the intended fragment behavior may differ. | Log the final serialized outer URL with credentials redacted; decode only the outer url value for inspection and compare the complete target string. |
| Response saved as an image but is unreadable | The response may be an API error body rather than image bytes. | Check HTTP status, content type, and error body before writing it as an image. |
- Confirm the endpoint is exactly
https://api.screenshotlayer.com/api/capture. - Confirm the target is absolute and includes its scheme.
- Use a parameter map or cURL’s
--data-urlencode; avoid string concatenation. - Inspect the request after serialization. Avoid logging the access key.
- Read the API’s error type and
infofield, then address the reported category. - If the image is valid but stale, check caching as described below.
Caching, performance, reliability, and cost
Screenshotlayer’s FAQ states a default screenshot cache duration of 2,592,000 seconds (30 days) and says ttl can set a lower custom duration. If the response looks like an older capture, check the cache settings and the documented ttl/force behavior. See the Screenshotlayer FAQ for the stated default. Do not mistake a cached image for an encoding failure.
Building query parameters locally adds little work compared with waiting for a remote screenshot render, but no performance benchmark is established here. Use a finite client timeout, handle non-success responses, and avoid retrying malformed URLs or credential errors. Retry transient network failures only with a bounded policy appropriate to your application. A request that reaches a usage limit needs account action, not retries.
Pricing and plan limits can change, and the reviewed specification does not provide a current price schedule. Check your account’s current plan and usage before estimating capture costs. The available sources do not establish uptime or reliability figures, so none are claimed here.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with the target URL; its parameter names used by other screenshot APIs also work, which can make switching easier. See the ScreenshotNeo API documentation.
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}`);
- Cookie banners are accepted like a visitor and removed before the shot; newsletter popups and chat widgets are also removed. Each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. The response includes
X-Page-VerdictandX-Billedheaders. - An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
FAQ
Should I call encodeURIComponent on the whole target URL?
Usually, pass the target string to the outer query builder instead. If you are constructing or repairing a target URL itself, encode components according to their URL context; do not apply a second whole-value encoding to an already serialized value.
Does the target URL need a scheme?
Yes. Screenshotlayer documents that the target URL must include its HTTP protocol, such as https://.
Will a fragment like #results be captured?
The fragment can be preserved as part of the target URL value, but fragments ordinarily are not sent in the destination’s HTTP request. The reviewed Screenshotlayer documentation does not specify fragment navigation behavior, so verify it for your use case.
Which image formats does Screenshotlayer document?
The FAQ says PNG is the default and also names JPEG and GIF. Use the current API documentation for format parameters and accepted values.
Can I tell an encoding error from an account problem?
Inspect the returned error type and info. The specification lists URL, missing-key, invalid-key, and usage-limit errors separately.


