Screenshotlayer API Example in Node.js with Axios
Call Screenshotlayer from Node.js with Axios, save its image response safely, and handle common errors and capture options.
Short answer: Make a GET request to Screenshotlayer’s capture endpoint with your access key and target URL, then treat the response as image bytes. With Axios, set responseType: 'arraybuffer', check the response content type, and write the bytes to a file. Keep the access key in an environment variable.
Screenshotlayer is a hosted website screenshot REST API. Its FAQ documents PNG as the default output, with JPEG and GIF also available. The service’s current docs and your plan determine which endpoint scheme and options you can use; the examples below use the HTTPS endpoint and assume HTTPS is enabled for your account. Check the Screenshotlayer site, official FAQ, and API documentation before shipping. The FAQ currently shows HTTP in its example URL, while the product describes HTTPS availability for paid plans.
1. Set up a Node.js project
Use Node.js 18 or newer for the examples. Axios works with supported Node versions; its exact response and error behavior depends on the Axios version in your project. This example uses the documented Axios configuration responseType: 'arraybuffer' and Node’s Buffer to handle binary data.
mkdir screenshotlayer-axios
cd screenshotlayer-axios
npm init -y
npm install axios
Set your API key in the process environment. Do not hard-code it in a source file or commit a .env file containing the real key.
export SCREENSHOTLAYER_ACCESS_KEY="YOUR_ACCESS_KEY"
2. Make a screenshot request with Axios
Save this as capture.mjs. The target URL is encoded by Axios’s params option, so query characters in the target do not corrupt the API request. The script writes the returned bytes to shot.png only when the response is an image; if the server returns an API error body instead, it prints that body as text.
import axios from 'axios';
import { writeFile } from 'node:fs/promises';
const accessKey = process.env.SCREENSHOTLAYER_ACCESS_KEY;
if (!accessKey) {
throw new Error('Set SCREENSHOTLAYER_ACCESS_KEY before running this script.');
}
const endpoint = 'https://api.screenshotlayer.com/api/capture';
const targetUrl = 'https://stripe.com';
try {
const response = await axios.get(endpoint, {
params: {
access_key: accessKey,
url: targetUrl,
// Optional documented examples include viewport, fullpage, and width.
// Add only parameters supported by your current API plan/docs.
},
responseType: 'arraybuffer',
timeout: 90000,
// Keep Axios's normal non-2xx rejection so API errors enter catch.
});
const contentType = String(response.headers['content-type'] ?? '').toLowerCase();
if (!contentType.startsWith('image/')) {
const body = Buffer.from(response.data).toString('utf8');
throw new Error(`Expected image response, received ${contentType || 'unknown content type'}: ${body}`);
}
const extension = contentType.includes('jpeg') ? 'jpg'
: contentType.includes('gif') ? 'gif'
: contentType.includes('png') ? 'png'
: 'img';
const outputPath = `shot.${extension}`;
await writeFile(outputPath, Buffer.from(response.data));
console.log(`Saved ${outputPath} (${response.data.byteLength} bytes)`);
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
const contentType = String(error.response.headers['content-type'] ?? '');
const detail = Buffer.from(error.response.data ?? []).toString('utf8');
console.error(`Screenshotlayer returned HTTP ${error.response.status} (${contentType}): ${detail}`);
} else if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
console.error('Request timed out. Increase the timeout or retry with backoff.');
} else {
console.error(`Request failed before an HTTP response: ${error.message}`);
}
} else {
console.error(error instanceof Error ? error.message : error);
}
process.exitCode = 1;
}
Run it with node capture.mjs. The saved extension is selected from the response content type because the FAQ says PNG is the default but the service can also return JPEG or GIF. If your account or endpoint responds differently, follow the current API documentation rather than assuming every response is a JSON object.
Request anatomy
| Part | Purpose |
|---|---|
GET /api/capture |
Screenshotlayer capture endpoint shown in the official examples. |
access_key |
Your personal API credential. |
url |
The page to capture; pass a fully qualified public URL. |
responseType: 'arraybuffer' |
Asks Axios in Node.js to preserve binary response bytes. |
timeout |
Bounds how long this client waits. A timeout does not establish whether the remote capture finished. |
3. Add capture parameters carefully
Screenshotlayer’s homepage examples show options including viewport, fullpage, and width. Its FAQ also documents format, custom User-Agent and Accept-Language headers, delay, and ttl. Consult the live documentation for accepted values, defaults, and plan restrictions before adding parameters.
| Option | Use | Things to check |
|---|---|---|
viewport |
Set the browser viewport dimensions for responsive layouts. | Use the documented parameter syntax; the homepage presents it as a capture option. |
fullpage |
Capture beyond the initial viewport. | Long pages can take longer and produce larger files; confirm current limits. |
width |
Request a thumbnail width, according to the FAQ. | Confirm whether this affects output dimensions or only a thumbnail in your API plan. |
format |
Choose PNG, JPEG, or GIF; PNG is the documented default. | Use a matching output extension and validate the response content type. |
| User-Agent / Accept-Language | Request custom browser identity or language headers. | Use the exact parameter names and encoding from the current docs. |
delay |
Wait before capture so page effects can finish loading. | A larger delay increases response time; it does not guarantee every asynchronous asset is ready. |
ttl |
Set a cache lifetime shorter than the documented default of 2,592,000 seconds (30 days). | Verify accepted bounds and cache semantics in the live docs. |
Pass additional query options alongside access_key and url using Axios’s params object. Avoid manually concatenating query strings. For example, if the current docs confirm these names for your plan:
params: {
access_key: accessKey,
url: targetUrl,
fullpage: '1',
format: 'PNG',
delay: 2
}
The sample option values illustrate placement only; verify whether the API expects booleans, strings, or numeric values before relying on them.
4. cURL equivalent
Use --data-urlencode for the target URL so reserved characters are encoded correctly. This writes the response body to a file; inspect the HTTP status and content type when scripting production jobs.
curl --fail-with-body -G "https://api.screenshotlayer.com/api/capture" \
--data-urlencode "access_key=$SCREENSHOTLAYER_ACCESS_KEY" \
--data-urlencode "url=https://stripe.com" \
-o shot.png
Use the endpoint scheme supported by your plan. The dossier notes that Screenshotlayer advertises HTTPS for paid plans, so confirm access before deploying this HTTPS form.
5. Python equivalent
This version uses Requests and streams the binary response to disk. Install the dependency with python -m pip install requests.
import os
import requests
access_key = os.environ["SCREENSHOTLAYER_ACCESS_KEY"]
response = requests.get(
"https://api.screenshotlayer.com/api/capture",
params={"access_key": access_key, "url": "https://stripe.com"},
timeout=90,
)
content_type = response.headers.get("Content-Type", "").lower()
if not response.ok:
raise RuntimeError(f"Screenshotlayer HTTP {response.status_code}: {response.text}")
if not content_type.startswith("image/"):
raise RuntimeError(f"Expected image, received {content_type}: {response.text}")
extension = "jpg" if "jpeg" in content_type else "gif" if "gif" in content_type else "png"
with open(f"shot.{extension}", "wb") as output:
output.write(response.content)
print(f"Saved shot.{extension} ({len(response.content)} bytes)")
6. Node.js built-in fetch equivalent
Axios is not required for the HTTP request. This Node.js 18+ example uses built-in fetch and arrayBuffer(), while preserving the same binary-response checks.
import { writeFile } from 'node:fs/promises';
const accessKey = process.env.SCREENSHOTLAYER_ACCESS_KEY;
if (!accessKey) throw new Error('Set SCREENSHOTLAYER_ACCESS_KEY.');
const query = new URLSearchParams({
access_key: accessKey,
url: 'https://stripe.com',
});
const response = await fetch(`https://api.screenshotlayer.com/api/capture?${query}`, {
signal: AbortSignal.timeout(90000),
});
const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
const bytes = Buffer.from(await response.arrayBuffer());
if (!response.ok || !contentType.startsWith('image/')) {
throw new Error(`Screenshotlayer HTTP ${response.status} (${contentType}): ${bytes.toString('utf8')}`);
}
const extension = contentType.includes('jpeg') ? 'jpg' : contentType.includes('gif') ? 'gif' : 'png';
await writeFile(`shot.${extension}`, bytes);
console.log(`Saved shot.${extension} (${bytes.length} bytes)`);
7. Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| 401 or an authentication error | Missing, mistyped, expired, or reset access key. | Check the account dashboard and environment variable; rotate exposed keys. Do not print the key in logs. |
| HTTPS request rejected or inaccessible | Your current plan may not include HTTPS access, or the documented endpoint scheme changed. | Check the current plan and official endpoint documentation. Avoid silently downgrading a production credential request to HTTP. |
| Axios gives a strange string or corrupted image | Binary bytes were interpreted as text or JSON. | Set responseType: 'arraybuffer' in Node.js and write a Buffer; do not parse a successful image as JSON. |
| Saved file is actually an error message | The endpoint returned an error payload or non-image body. | Check the HTTP status and Content-Type before saving; log the response body only after excluding credentials. |
| 400 or invalid parameter response | Unknown parameter name, unsupported value, missing target URL, or malformed query encoding. | Use Axios params, confirm exact option names and permitted values in live docs, and test with only required fields first. |
| Timeout | Slow target page, large full-page render, network delay, or a client timeout set too low. | Try a known fast public page, raise the client timeout within your request budget, and retry transient failures with bounded exponential backoff. |
| Screenshot looks incomplete | Lazy-loaded content, delayed scripts, or animations were not ready at capture time. | Use documented delay support where appropriate and compare viewport versus full-page behavior. A delay can increase latency and cannot guarantee a site has finished all work. |
| Unexpected old screenshot | A cached capture may be returned. | Review the documented cache behavior and lower ttl if current documentation permits it. |
| Works locally but not in production | Environment variable missing, egress restriction, TLS configuration, or different plan credentials. | Check deployment secrets, outbound HTTPS access, and account settings without exposing the key. |
8. Reliability, performance, and cost
- Use bounded timeouts. Set a request timeout suitable for your workload. Full-page captures and slow target sites can take longer than a simple page; avoid leaving requests unbounded.
- Retry selectively. Retry network failures and transient server errors with a small attempt limit and exponential backoff plus jitter. Do not repeatedly retry authentication or invalid-parameter errors. A client timeout does not prove that the remote service did not process the request, so avoid blind retries if duplicate calls affect your quota.
- Limit concurrency. Use a queue or semaphore for batch work. Screenshotlayer plan capacity and request allowance are service-side limits; local concurrency cannot increase them.
- Cache at the application layer where appropriate. Reuse a screenshot when the target and relevant options have not changed. The FAQ reports a default cache duration of 30 days and says
ttlcan request a shorter period; confirm current details. - Track bytes and content types. Large image responses consume bandwidth and storage. Choose an output format and dimensions that fit the use case, based on current supported options.
- Budget by the current plan. Screenshotlayer’s official pages advertise a 100-snapshot monthly free tier and paid plans, but quotas, pricing, overages, and features can change. Check the live pricing page before estimating cost. Its FAQ says usage over the plan allowance can incur overage fees; configure usage alerts and limits accordingly.
9. Or skip the browser setup
Screenshotlayer is one hosted API option. ScreenshotNeo is a website screenshot API and MCP server for developers. It accepts one GET request and returns a PNG, JPEG, WebP, or PDF. The API parameters used by other screenshot APIs also work, which can make switching straightforward. 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,
)
r.raise_for_status()
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())));
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
10. FAQ
Does Axios return a JSON object for a Screenshotlayer screenshot?
Do not assume so. The documented successful outputs are image formats. Configure Axios for binary data and handle a non-image response separately.
Can I use Screenshotlayer from a browser frontend?
A personal access key should be treated as a secret. Keep calls that use the key on a server you control rather than shipping it in public client-side code.
Can I request JPEG or GIF instead of PNG?
The official FAQ lists PNG as the default and JPEG and GIF as alternatives through the format parameter. Check the current documentation for exact parameter values.
Does a successful HTTP status guarantee the screenshot is valid?
Check both status and content type. A useful downstream safeguard is to validate the image before storing or publishing it.


