ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team4 October 20269 min read

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 %2F can 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

  1. Start with the documented endpoint and an absolute target URL with http:// or https://.
  2. Keep the target URL as a single raw string in a parameter map under url.
  3. Pass the map to the HTTP client’s query builder, alongside access_key.
  4. Inspect the serialized request if it fails: there should be one outer url parameter 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.
  1. Confirm the endpoint is exactly https://api.screenshotlayer.com/api/capture.
  2. Confirm the target is absolute and includes its scheme.
  3. Use a parameter map or cURL’s --data-urlencode; avoid string concatenation.
  4. Inspect the request after serialization. Avoid logging the access key.
  5. Read the API’s error type and info field, then address the reported category.
  6. 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-Verdict and X-Billed headers.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_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.