cURL Screenshot API — Capture Websites from the Command Line
Every developer machine already has cURL, which makes it the fastest way to test a screenshot API without installing anything. No package manager, no SDK, no runtime. When you need a website screenshot from a server, a CI pipeline, or a Docker container running nothing but Alpine Linux, cURL is the tool that's always there. One GET request to ScreenshotRun, and the response comes back as a raw image file: PNG, JPEG, WebP, AVIF, or PDF. No polling, no async callbacks, no JSON parsing.
The cURL screenshot API approach works everywhere a shell prompt exists. Bash scripts, Makefiles, GitHub Actions, cron jobs, even a quick test from the terminal while SSHed into a remote box. If your workflow already lives in the command line, there's no reason to leave it.
Your first cURL screenshot in 30 seconds
Create an account at the dashboard to grab an API key (free plan, 200 captures per month, no credit card). Export it as an environment variable so every command picks it up automatically:
export SCREENSHOTRUN_KEY="YOUR_API_KEY"
Now capture a webpage and save the result to a file:
curl -s -o github.png \
-H "Authorization: Bearer $SCREENSHOTRUN_KEY" \
"https://api.screenshotrun.com/v1/screenshots/capture?url=https://github.com&response_type=image"
That's it. The response_type=image parameter tells the API to return the screenshot as raw bytes, so cURL's -o flag saves it straight to disk. No JSON to parse, no second request to download. Open github.png and you'll see the rendered page.
Production-ready script with error handling
A one-liner works for testing. In production, you want HTTP status checks, proper quoting, and meaningful exit codes so your automation doesn't silently swallow failures:
#!/bin/bash
set -euo pipefail
API_KEY="${SCREENSHOTRUN_KEY:?Missing SCREENSHOTRUN_KEY}"
TARGET_URL="$1"
OUTPUT="${2:-screenshot.png}"
HTTP_CODE=$(curl -s -o "$OUTPUT" -w "%{http_code}" \
-H "Authorization: Bearer $API_KEY" \
--max-time 60 \
"https://api.screenshotrun.com/v1/screenshots/capture?url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$TARGET_URL', safe=''))")&response_type=image&format=png&width=1280&height=720")
if [ "$HTTP_CODE" -ne 200 ]; then
echo "Error: API returned HTTP $HTTP_CODE" >&2
cat "$OUTPUT" >&2
rm -f "$OUTPUT"
exit 1
fi
echo "Saved: $OUTPUT ($(wc -c < "$OUTPUT") bytes)"
Save it as screenshot.sh, run chmod +x screenshot.sh, and call it:
./screenshot.sh https://github.com github.png
The script URL-encodes the target, checks the HTTP response code, and cleans up on failure. If something goes wrong, the error body prints to stderr instead of leaving a corrupt file on disk.
Batch captures with a bash loop
Grabbing screenshots of fifty URLs is where cURL pulls its weight. I needed this on a monitoring project last year and was surprised how little code it took. Just a file with one URL per line and a four-line loop:
#!/bin/bash
API_KEY="${SCREENSHOTRUN_KEY:?Missing SCREENSHOTRUN_KEY}"
BASE="https://api.screenshotrun.com/v1/screenshots/capture"
while IFS= read -r url; do
FILENAME=$(echo "$url" | sed 's|https\?://||;s|/|_|g;s|[^a-zA-Z0-9_.-]||g').png
curl -s -o "$FILENAME" \
-H "Authorization: Bearer $API_KEY" \
--max-time 60 \
"${BASE}?url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$url', safe=''))")&response_type=image&format=webp&width=1280"
echo "Captured: $url -> $FILENAME"
done < urls.txt
Create urls.txt with one URL per line, then run the script. Each URL gets a sanitized filename derived from the domain and path. For parallel captures, pipe through xargs -P 4 to run four requests at once.
Available parameters
All parameters go into the query string on the GET request. The table below covers what you'll reach for most often from the command line:
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | — | The webpage to capture |
format | string | png | Output: png, jpeg, webp, avif, pdf |
width | integer | 1280 | Viewport width in pixels |
height | integer | 720 | Viewport height in pixels |
full_page | boolean | false | Capture the entire scrollable page |
device | string | desktop | Device type: desktop, mobile, tablet |
retina | boolean | false | Render at 2x pixel density |
dark_mode | boolean | false | Force dark color scheme |
selector | string | — | CSS selector to capture a specific element |
delay | integer | 0 | Wait N milliseconds before capturing |
block_cookies | boolean | false | Remove cookie consent banners |
block_ads | boolean | false | Remove advertisements |
block_chats | boolean | false | Remove live chat widgets |
css | string | — | Inject custom CSS before capture |
js | string | — | Execute JavaScript before capture |
response_type | string | json | Set to image for direct binary response |
Check the API documentation for the complete parameter list, including PDF margins, geolocation, stealth mode, and webhook callbacks.
Dark mode, element targeting, CSS injection
These three show up in almost every scripting job I've done with the API. All query parameters on the same GET request, so the cURL syntax stays identical.
Dark mode triggers prefers-color-scheme: dark on the target page. Any site that respects that media query will render its dark theme:
curl -s -o github-dark.png \
-H "Authorization: Bearer $SCREENSHOTRUN_KEY" \
"https://api.screenshotrun.com/v1/screenshots/capture?url=https://github.com&dark_mode=true&response_type=image"
Element targeting crops the output to a specific CSS selector's bounding box instead of capturing the full viewport:
curl -s -o repo-list.png \
-H "Authorization: Bearer $SCREENSHOTRUN_KEY" \
"https://api.screenshotrun.com/v1/screenshots/capture?url=https://github.com/anthropics&selector=.js-repo-list&response_type=image"
CSS injection lets you hide elements, change fonts, or override styles before the capture fires. URL-encode the CSS value when passing it in the query string:
curl -s -o clean.png \
-H "Authorization: Bearer $SCREENSHOTRUN_KEY" \
"https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com&css=header%7Bdisplay%3Anone%21important%7D%20.sidebar%7Bdisplay%3Anone%21important%7D&response_type=image"
All three combine in a single request. Dark mode, cropped to one element, with custom styles applied. One cURL call.
Retry logic with cURL's built-in flags
cURL has retry support baked in, which honestly saved me from writing a lot of ugly bash wrappers. No need for a loop with sleep timers:
curl -s -o screenshot.png \
-H "Authorization: Bearer $SCREENSHOTRUN_KEY" \
--retry 3 \
--retry-delay 5 \
--retry-max-time 120 \
--max-time 60 \
-w "HTTP %{http_code} | %{size_download} bytes | %{time_total}s\n" \
"https://api.screenshotrun.com/v1/screenshots/capture?url=https://github.com&response_type=image&format=webp"
--retry 3 tries up to three more times on transient failures (timeouts, 5xx errors). --retry-delay 5 waits five seconds between attempts. --retry-max-time 120 puts a ceiling on total retry duration so the command doesn't hang forever. The -w flag prints the HTTP status code, download size, and total time after completion, which is useful for logging in automated scripts.
Automate with cron or GitHub Actions
Periodic screenshots are a common use case for monitoring, archiving, or change detection. With cURL, the setup is straightforward.
A cron job that captures a page every hour and saves timestamped files:
# crontab -e
0 * * * * curl -s -o "/var/screenshots/site-$(date +\%Y\%m\%d-\%H\%M).png" \
-H "Authorization: Bearer sk_live_your_key" \
"https://api.screenshotrun.com/v1/screenshots/capture?url=https://yoursite.com&response_type=image&format=png"
For CI/CD pipelines, the same cURL command drops into a GitHub Actions step. Here's a workflow that captures a screenshot after each deployment and uploads it as an artifact:
- name: Post-deploy screenshot
run: |
curl -s -o deploy-screenshot.png \
-H "Authorization: Bearer ${{ secrets.SCREENSHOTRUN_KEY }}" \
--retry 3 --retry-delay 5 --max-time 60 \
"https://api.screenshotrun.com/v1/screenshots/capture?url=${{ env.DEPLOY_URL }}&response_type=image&full_page=true"
- name: Upload screenshot
uses: actions/upload-artifact@v4
with:
name: deploy-screenshot
path: deploy-screenshot.png
No Node.js action to install, no Docker image to pull. Runs natively on every GitHub Actions runner. Cleanest CI screenshot setup I've found so far.
cURL vs. language SDKs
| cURL | Node.js / Python / PHP SDK | |
|---|---|---|
| Dependencies | None (pre-installed on Linux, macOS, Windows 10+) | Runtime + package manager + HTTP library |
| Setup time | Export one environment variable | Install runtime, create project, install packages |
| Best for | Scripts, cron jobs, CI/CD, quick tests, server automation | Application code, web apps, complex integrations |
| Error handling | HTTP status codes + --retry flags | Try/catch, custom error classes, logging frameworks |
| Batch processing | xargs -P for parallelism, bash loops | Promise.all, asyncio, threading |
| JSON parsing | Pipe to jq (separate install) | Built into the language |
| IDE support | Shell scripts only | Full autocomplete, type checking, debugging |
| When to switch | When logic gets beyond a single request-response cycle | When you need screenshots inside application code |
A cURL screenshot API setup handles the cases where writing actual code feels like overkill. Monitoring a page every hour, grabbing a post-deploy screenshot in CI, testing API parameters from the terminal. The moment you need conditional logic, database storage, or user-facing features, switch to Node.js, Python, or PHP. For more terminal examples including PDF, thumbnails, and HTML rendering, see our cURL screenshot tutorial.
Start capturing screenshots from the command line
Copy any example above, paste your API key, and run it. 200 free captures every month, no credit card required.
Get Your Free API Key