How to Schedule Recurring Website Screenshots with ScreenshotMachine
Use ScreenshotMachine’s GET API with a separate scheduler. This guide shows a recurring capture workflow, runnable examples, storage choices, and troubleshooting.
Short answer: ScreenshotMachine’s documented API captures a supplied webpage through an HTTP GET request. Its reviewed API reference does not document recurring scheduling, so run that request repeatedly from a separate scheduler such as cron, a CI scheduler, or a cloud job runner. Save each successful response under a unique name if you need an image history.
This guide uses a small Python script and cron. The same design works with another scheduler: keep the capture configuration and credentials in the job environment, invoke the API at the interval you want, and decide how to retain the resulting files. ScreenshotMachine documents request settings including dimensions, format, cache age, delay, zoom, and a CSS selector to click before capture. ScreenshotMachine’s official site is the starting point for its API information; its documented API endpoint is https://api.screenshotmachine.com/.
1. Decide what each recurring capture should represent
Before scheduling a request, define the comparison you want to make. A recurring capture is useful only if the settings and conditions stay consistent enough for the images to be compared.
- Target: the exact page URL, including relevant query parameters.
- Viewport or full page: select a fixed width and height, or use
fullfor the height when you want the full-length page. - Output: choose a supported format such as JPG, PNG, or GIF.
- Freshness: set the cache age deliberately. The API documentation identifies
cacheLimit=0as requesting a fresh screenshot. - Timing and interaction: use the documented capture delay when the page needs time to render, or the CSS selector click option if a page must be interacted with before capture.
- History: determine where images will be stored, how filenames will be made unique, and when older captures can be removed.
Dimensions are expressed as width by height. Keep the same URL, dimensions, format, cache setting, delay, zoom, and click behavior across runs unless you intentionally want to change the capture.
2. Create a recurring capture script
The following Python 3 script uses only the standard library. It makes one API request, writes the returned image to a timestamped file, and exits with a nonzero status if the request fails. The scheduler, rather than an endless loop inside the script, handles repetition.
#!/usr/bin/env python3
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
API_ENDPOINT = "https://api.screenshotmachine.com/"
TARGET_URL = os.environ.get("TARGET_URL", "https://example.com/")
OUTPUT_DIR = Path(os.environ.get("SCREENSHOT_DIR", "./screenshots"))
# Keep these settings fixed between scheduled runs for comparable images.
PARAMS = {
"key": os.environ["SCREENSHOTMACHINE_KEY"],
"url": TARGET_URL,
"dimension": "1365x900",
"format": "png",
"cacheLimit": "0",
# Optional documented settings can be added here, for example:
# "delay": "1000", # milliseconds; documented range is 0–10000
# "zoom": "100",
# "click": "button.accept",
}
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
destination = OUTPUT_DIR / f"capture-{stamp}.png"
request_url = API_ENDPOINT + "?" + urlencode(PARAMS)
request = Request(request_url, headers={"User-Agent": "recurring-screenshot-job/1.0"})
try:
with urlopen(request, timeout=90) as response:
content_type = response.headers.get("Content-Type", "")
body = response.read()
if not body:
raise RuntimeError("The API returned an empty response body")
# Avoid archiving an error page or API message as if it were an image.
if not content_type.lower().startswith("image/"):
raise RuntimeError(f"Expected an image response; received Content-Type {content_type!r}")
destination.write_bytes(body)
print(destination)
except HTTPError as exc:
print(f"Screenshot API returned HTTP {exc.code}: {exc.reason}", file=sys.stderr)
if exc.code == 429 or exc.code >= 500:
return temporary_failure()
return 1
except (URLError, TimeoutError, RuntimeError, OSError) as exc:
print(f"Capture failed: {exc}", file=sys.stderr)
return temporary_failure()
return 0
def temporary_failure():
# A nonzero exit lets the scheduler or monitoring wrapper detect failure.
return 1
if __name__ == "__main__":
raise SystemExit(main())
Save it as capture.py. Set SCREENSHOTMACHINE_KEY in the job environment; optionally set TARGET_URL and SCREENSHOT_DIR. The script’s default target is a placeholder and should be replaced before running. Store the API key in a secret store or protected environment variable. Do not put it in a public webpage or commit it to source control. The API documentation advises protecting requests made from public webpages with its secret-phrase hash mechanism; consult the official API documentation for the exact signing procedure before using a browser-exposed request.
3. Schedule the script
Linux or macOS with cron
Use an absolute path to Python and the script. For example, to run every day at 09:00 UTC, add a cron entry like this, adjusting paths to your deployment:
CRON_TZ=UTC
0 9 * * * SCREENSHOTMACHINE_KEY=replace_with_secret TARGET_URL=https://example.com/ SCREENSHOT_DIR=/var/lib/site-captures /usr/bin/python3 /opt/site-captures/capture.py >> /var/log/site-captures.log 2>&1
Cron implementations vary in support for CRON_TZ. If yours does not support it, configure the host timezone or express the schedule in its local timezone and document that choice. Prefer a protected environment file or the scheduler’s secret configuration over placing the literal key in a crontab that other users can read.
Other schedulers
A CI scheduled workflow, a cloud scheduler that starts a job, or an existing orchestration system can run the same script. Configure the same essentials in each case:
- Set the schedule and timezone explicitly.
- Provide the API key through the scheduler’s secret mechanism.
- Install or select a Python 3 runtime.
- Run the script from a known working directory, or set an absolute output directory.
- Capture the exit status and send failures to logs or an alerting destination you control.
- Make sure the output directory persists after the job exits. Some CI job filesystems are temporary; upload the image to durable storage if you need a long-term history.
4. Configure capture options consistently
| Setting | Use | Practical note |
|---|---|---|
dimension |
Viewport width and height, or a full-height capture | Use a stable viewport for visual comparisons. The docs describe full as the height value for a full-length page. |
format |
Image encoding | The reviewed docs list JPG, PNG, and GIF. Match the file extension to the selected format. |
cacheLimit |
Maximum age of a cached image that may be returned | Use 0 when requesting a fresh screenshot. Cache behavior matters if each scheduled run must represent the page at that time. |
delay |
Wait before capture | The documented range is 0 through 10000 milliseconds. A fixed delay can help pages that render after initial navigation, but it also adds time to every run. |
zoom |
Capture zoom | Keep it fixed across the series so scale changes do not look like page changes. |
click |
Click a CSS selector before capture | Use only when the page requires the interaction. If the selector changes or disappears, the capture may not reflect the intended state. |
Use only the documented parameter spellings supported by the API reference. ScreenshotMachine’s API accepts the customer key and target URL along with capture options. Refer to its official documentation for the current request details and account-specific requirements.
5. cURL and Node.js request examples
The scheduler does not need to be written in Python. These examples each perform one capture; place the command or program in your scheduler and provide the key as a secret.
cURL
export SCREENSHOTMACHINE_KEY='replace_with_secret'
curl --fail --show-error --silent --get 'https://api.screenshotmachine.com/' \
--data-urlencode "key=$SCREENSHOTMACHINE_KEY" \
--data-urlencode 'url=https://example.com/' \
--data-urlencode 'dimension=1365x900' \
--data-urlencode 'format=png' \
--data-urlencode 'cacheLimit=0' \
--output "capture-$(date -u +%Y%m%dT%H%M%SZ).png"
With --fail, cURL exits unsuccessfully for HTTP error responses. If you use this command in a job, check its exit status and ensure the destination directory exists before the call.
Node.js
import { mkdir, writeFile } from 'node:fs/promises';
const key = process.env.SCREENSHOTMACHINE_KEY;
if (!key) throw new Error('Set SCREENSHOTMACHINE_KEY');
const params = new URLSearchParams({
key,
url: process.env.TARGET_URL ?? 'https://example.com/',
dimension: '1365x900',
format: 'png',
cacheLimit: '0',
});
const response = await fetch(`https://api.screenshotmachine.com/?${params}`, {
signal: AbortSignal.timeout(90_000),
});
if (!response.ok) {
throw new Error(`Screenshot API returned HTTP ${response.status}`);
}
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.toLowerCase().startsWith('image/')) {
throw new Error(`Expected image response; received ${contentType}`);
}
const bytes = Buffer.from(await response.arrayBuffer());
if (bytes.length === 0) throw new Error('Empty screenshot response');
const stamp = new Date().toISOString().replaceAll(':', '').replaceAll('-', '');
await mkdir('./screenshots', { recursive: true });
await writeFile(`./screenshots/capture-${stamp}.png`, bytes);
Run the Node.js example as an ES module in a runtime that supports global fetch and AbortSignal.timeout. Use a compatible timeout approach if your runtime is older. The timestamped filename avoids overwriting prior captures.
6. Storage, retries, and reliability
The screenshot API returns an image for an individual request; the recurring history is your workflow to design. Choose a durable destination if files must survive job cleanup. Use timestamps or another unique run identifier in object names, and define retention based on how long you need the visual record.
- Retries: Retry transient network failures, rate limiting, and server errors with a bounded policy and backoff. Do not retry a permanent authentication or invalid-parameter error indefinitely.
- Duplicates: A retry after a timeout may create another image even if the first request completed. Give each scheduled occurrence a run identifier and decide whether duplicate results are acceptable.
- Timeouts: Set the client timeout to allow for navigation and rendering, then ensure the scheduler’s own job timeout is longer than the request timeout.
- Missed runs: Decide whether a delayed job should capture immediately, skip to the next scheduled time, or catch up. Scheduler defaults differ.
- Validation: Check HTTP status, response content type, and nonzero file size before treating the output as an image. A successful process exit alone should not be the only quality check.
- Credentials: Restrict access to the secret, rotate it if exposed, and avoid printing full request URLs because query strings can contain credentials.
7. Performance and cost considerations
Each scheduled run makes an API request, so the number of requests grows with the number of monitored pages and the schedule frequency. A useful planning estimate is:
monthly captures ≈ pages × captures per day × days in month
For example, one page captured hourly requires about 24 requests per day; multiply that by the number of pages and the days in your billing period to estimate volume. This is arithmetic for planning, not a statement of ScreenshotMachine pricing or quota. The supplied documentation review does not establish current prices, quotas, or account limits, so check the provider’s current account information before choosing a schedule.
Full-page images and higher dimensions can increase response size and storage use. A longer capture delay increases job duration. Avoid overlapping runs for the same page unless that is intentional, and keep retention limits so an archive does not grow without bound. If visual comparisons or alerts are required, add those as separate workflow components; the reviewed API documentation does not establish them as built-in recurring-schedule features.
8. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| HTTP authentication or authorization error | Missing, incorrect, or inaccessible customer key | Confirm the secret is present in the scheduler environment, check for accidental whitespace, and verify the key with the provider. Never paste it into public logs. |
| Invalid request or unexpected response | Incorrect parameter spelling/value or malformed target URL | Compare the request with the current API reference. URL-encode values, especially the target URL, and use the documented option values. |
| Old image appears again | A cached result may be returned | Review cacheLimit; the docs specify cacheLimit=0 for requesting a fresh screenshot. |
| Page is captured before content appears | Client-rendered content or delayed page rendering | Try a suitable fixed delay within the documented 0–10000 ms range. Keep the delay constant across the recurring series. |
| Click-dependent state is missing | The selector is incorrect, unavailable, or changed on the page | Inspect the page selector and confirm the element exists in the relevant state. Use the documented CSS selector click option and avoid depending on unstable selectors. |
| Image file is empty or is not an image | Request failed, or an error response was saved with an image extension | Check HTTP status and response headers before writing the file. Preserve the error status and message in protected logs. |
| Manual run works but scheduled job does not | Different environment, path, timezone, permissions, or missing secret | Use absolute paths, set the scheduler timezone, verify the job user can write to the destination, and inspect the scheduler’s environment and logs. |
| Two captures run at once | A previous slow run overlaps the next scheduled invocation | Reduce frequency, set a job concurrency limit, or use a lock so only one capture for that target runs at a time. |
9. A lower-setup alternative: ScreenshotNeo
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. If you want a single API call without managing browser capture setup, it accepts a URL and returns an image or PDF. You can still schedule the call with cron or your job runner and save each result using the workflow above. See the ScreenshotNeo API documentation for 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
For a recurring workflow, substitute your page URL and use a timestamped output filename for each run. The same endpoint can be called from Python or Node.js:
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 and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are never billed; response headers report the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
FAQ
Does ScreenshotMachine run a schedule for me?
The reviewed API reference documents individual GET capture requests, not a recurring schedule. Use a separate scheduler to repeat the request.
Can I keep a daily or hourly visual history?
Yes. Schedule the request and save each response to a durable destination with a unique name. The archive and retention policy are part of your workflow.
Should every recurring capture bypass the cache?
Use cacheLimit=0 when each run should request a fresh capture. If cached output is acceptable for your use case, choose the documented cache behavior that fits it.
What should I do if the page changes its layout?
Keep the capture viewport stable and review the saved images. If the page’s structure changes, revisit any CSS selector used for a click and update your capture configuration as needed.


