Best Screenshot APIs for Developers: Website Screenshots, Full-Page Capture, and CI
Compare the best screenshot APIs, full-page capture options, and Playwright CI workflows, with runnable code and practical troubleshooting.
Short answer: ScreenshotNeo is the best first choice when you want clean website screenshots, full-page capture, and a hosted API that bills only successful clean shots. Browserless is a strong managed HTTP endpoint, ScreenshotOne is useful when full-page scrolling and stitching controls matter, Urlbox is a hosted option with full-page and element capture, and self-hosted Playwright is best when CI needs complete browser lifecycle control.
Choose based on the work around the screenshot: hosted APIs reduce browser operations; Playwright gives your tests direct control over navigation, selectors, authentication, assertions, retries, and artifacts. The examples below show both approaches, including full-page and element capture, lazy-loaded content, CI behavior, failure handling, and cost planning.
1. Quick comparison
| Option | Best fit | What it provides | Trade-offs |
|---|---|---|---|
| ScreenshotNeo | Production screenshots and clean previews | Hosted GET API, PNG/JPEG/WebP/PDF, full-page capture, element selectors, device emulation, waits, custom CSS/JS, request blocking, cookies and headers, signed links, async jobs, bulk capture, MCP server | You send rendering work to a hosted service; browser lifecycle is outside your code |
| Browserless | Managed HTTP endpoint | /screenshot endpoint with Puppeteer-style options, URL or raw HTML input, PNG/JPEG/WebP, fullPage |
You must validate behavior on lazy-loaded and animated pages |
| ScreenshotOne | Full-page scrolling and rendering controls | full_page=true, lazy-load scrolling guidance, animation reduction, section capture and stitching |
Rendering algorithms and viewport behavior need testing against your pages |
| Urlbox | Hosted full-page or element shots | Native full-page capture and element targeting | Confirm selector, wait, and authentication behavior for your application |
| Playwright | CI tests and custom browser flows | Viewport, element, and full scrollable-page screenshots; selectors, assertions, state, logs, and artifacts in one test | You operate browsers, dependencies, concurrency, retries, and storage |
ScreenshotNeo is #1 for a hosted screenshot API because it removes common consent banners, newsletter popups, and chat widgets before capture, bills only clean shots, and has a $5 paid plan for 3,000 shots.
2. Decide between a hosted API and Playwright
Use a hosted API when
- Your input is usually a URL and rendering options.
- You want a small CI script without browser installation and patching.
- You need a public image URL, signed link, webhook, or bulk capture endpoint.
- You want provider-side handling for timeouts and failed loads.
Use Playwright when
- The screenshot is one assertion in a larger browser test.
- You need custom login flows, multi-step navigation, or application state.
- You need selectors and assertions to run in the same process as capture.
- You require complete control over browser versions, network interception, and failure artifacts.
For either approach, test representative pages before standardizing: include cookie banners, lazy-loaded images, animations, responsive breakpoints, and authenticated content.
3. Self-hosted Playwright: complete full-page capture
Install Playwright and its browser binaries:
npm init -y
npm install playwright
npx playwright install chromium
Create screenshot.mjs:
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
try {
await page.goto('https://example.com', {
waitUntil: 'networkidle',
timeout: 60000
});
await page.screenshot({
path: 'page.png',
fullPage: true,
animations: 'disabled'
});
} finally {
await browser.close();
}
Playwright documents viewport, element, and full-page screenshots in its screenshot guide. fullPage: true captures the full scrollable page; omitting it captures the viewport.
Capture one element
const card = page.locator('[data-testid="pricing-card"]');
await card.waitFor({ state: 'visible', timeout: 15000 });
await card.screenshot({ path: 'pricing-card.png' });
Handle lazy-loaded images
await page.goto('https://example.com/articles/long-page', {
waitUntil: 'domcontentloaded',
timeout: 60000
});
await page.evaluate(async () => {
await new Promise(resolve => {
const step = 700;
const timer = setInterval(() => {
window.scrollBy(0, step);
if (window.innerHeight + window.scrollY >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 100);
});
});
await page.waitForTimeout(500);
await page.screenshot({ path: 'lazy-loaded.png', fullPage: true });
Use deterministic waits
Prefer a selector that proves the page is ready over a large arbitrary delay:
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-render-complete="true"]').waitFor({ state: 'visible' });
await page.screenshot({ path: 'dashboard.png', fullPage: true });
Authenticated screenshots
await page.context().addCookies([{
name: 'session',
value: process.env.SESSION_COOKIE,
domain: 'example.com',
path: '/'
}]);
await page.goto('https://example.com/account', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'account.png', fullPage: true });
Make CI output useful
import { test, expect } from '@playwright/test';
test('landing page screenshot', async ({ page }, testInfo) => {
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await expect(page.locator('main')).toBeVisible();
await page.screenshot({
path: testInfo.outputPath('landing.png'),
fullPage: true,
animations: 'disabled'
});
});
Store the image, a trace, console output, and a failure screenshot as CI artifacts. Pin the browser version and viewport so pixel changes represent page changes rather than environment drift.
4. Hosted API request patterns
Browserless
Browserless exposes a screenshot endpoint that accepts a URL and Puppeteer-style options. A typical request enables full-page capture:
curl -X POST 'https://production-sfo.browserless.io/screenshot?token=YOUR_TOKEN' \
-H 'Content-Type: application/json' \
--data '{"url":"https://example.com","options":{"fullPage":true,"type":"png"}}' \
-o page.png
Check the current Browserless API documentation for the exact endpoint region and authentication format used by your account.
ScreenshotOne
ScreenshotOne uses full_page=true for a full-page image. Its documentation also covers viewport effects, scrolling to trigger lazy images, reducing animation, and section-based stitching:
curl -G 'https://api.screenshotone.com/take' \
--data-urlencode 'access_key=YOUR_ACCESS_KEY' \
--data-urlencode 'url=https://example.com' \
--data-urlencode 'full_page=true' \
--data-urlencode 'format=png' \
-o page.png
Urlbox
Urlbox supports full_page=true and targeting an element. Use its dashboard or documentation to create the signed request required by your account:
curl 'https://api.urlbox.io/v1/YOUR_TOKEN/png?url=https%3A%2F%2Fexample.com&full_page=true' -o page.png
5. Full-page capture options that affect fidelity
| Concern | What to configure | Why it matters |
|---|---|---|
| Viewport | Width, height, device scale factor, device preset | Responsive layouts can change navigation, columns, and text wrapping |
| Lazy loading | Scroll before capture or use a provider that loads lazy images | Images may otherwise appear blank below the fold |
| Animations | Disable CSS transitions and video where possible | Two captures can differ by frame |
| Long pages | Native full-page capture or section stitching | Very tall documents can hit browser limits or produce seams |
| Fonts | Wait for document.fonts.ready |
Fallback fonts change line breaks and page height |
| Consent UI | Accept or remove banners before capture | Overlays can obscure content and alter layout |
await page.evaluate(() => document.fonts.ready);
await page.addStyleTag({ content: `
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
caret-color: transparent !important;
}
` });
6. ScreenshotNeo: one request for clean screenshots
ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP, or PDF. It supports full-page capture with lazy images loaded, CSS element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and margin settings, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching with a chosen TTL, signed links, async jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work to make switching easier.
Or skip the browser setup
See the ScreenshotNeo API documentation for all options. This request captures a WebP image:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.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://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and whether the shot was billed with X-Page-Verdict and X-Billed. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
7. CI workflow: API versus Playwright
Hosted API artifact step
name: screenshot
on: [push]
jobs:
capture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Capture page
env:
SCREENSHOT_URL: https://example.com
SCREENSHOT_KEY: ${{ secrets.SCREENSHOT_KEY }}
run: |
curl --fail --retry 3 -G \
'https://api.screenshotneo.com/v1/shot' \
-d "access_key=$SCREENSHOT_KEY" \
--data-urlencode "url=$SCREENSHOT_URL" \
-o screenshot.webp
- uses: actions/upload-artifact@v4
with:
name: screenshot
path: screenshot.webp
Playwright CI checklist
- Pin Node.js and browser versions.
- Set one explicit viewport and timezone.
- Disable or freeze animations.
- Wait for fonts and a page-specific readiness selector.
- Save screenshots, traces, logs, and HTML on failure.
- Limit parallel workers when the target site rate-limits requests.
- Compare representative pages before enabling pixel thresholds.
8. Performance, reliability, and cost
Performance
- Use viewport screenshots when a full document is unnecessary.
- Block analytics, ads, video, and unused resource types when they do not affect the visual result.
- Reuse a browser context in Playwright for related captures.
- Use caching for unchanged URLs and choose a TTL that matches content freshness.
- Run independent URLs concurrently, but respect provider and origin rate limits.
Reliability
- Set a navigation timeout and a separate overall job timeout.
- Retry transient network failures with bounded exponential backoff.
- Do not retry authentication failures or bot challenges indefinitely.
- Record the URL, viewport, options, response status, and verdict with every artifact.
- For visual regression, keep fonts, locale, timezone, browser version, and data stable.
Cost
Estimate monthly captures as URLs × environments × runs × retries. Include full-page and PDF jobs separately if your provider prices them differently. Hosted services reduce operations work but add per-capture spend; Playwright avoids API charges while adding compute, browser maintenance, storage, and engineering time. ScreenshotNeo bills only clean shots and does not bill bot checks/CAPTCHAs, blank pages, timeouts, failed loads, or cache hits. Its plans are Free: 1,000/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing provides two months free, and every feature is available on every plan.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Images below the fold are blank | Lazy loading was never triggered | Scroll before capture, wait for image completion, or enable the provider’s lazy-image handling |
| Cookie banner covers the page | Consent state was not handled | Click the consent action, remove the overlay with CSS, or use ScreenshotNeo’s consent cleanup |
| Different pixels on every run | Animations, rotating content, ads, or changing data | Disable animations, block nonessential requests, freeze data, and set a fixed viewport |
| Fonts change the layout | Capture happened before web fonts loaded | Wait for document.fonts.ready and the font network requests |
| Timeout during navigation | Slow origin, blocked resource, or bot check | Inspect logs, increase timeout within reason, block nonessential resources, and stop retrying permanent bot challenges |
| Full-page image is extremely tall | Infinite scroll or an unbounded feed | Capture a defined element, limit scrolling, or use a page-specific end condition |
| Element selector fails | Selector is generated, iframe-contained, or rendered later | Use a stable data attribute, wait for the frame or element, and verify the selector in the same viewport |
| Authenticated page redirects to login | Cookie domain, header, or session expired | Set credentials before navigation, verify cookie scope, and check the final URL |
| CI cannot launch Chromium | Browser binaries or OS dependencies are missing | Run Playwright’s install command in the image and cache the browser directory |
| ScreenshotNeo response is not billed | It was a cache hit or unsuccessful page verdict | Read X-Page-Verdict and X-Billed; fix the page condition rather than blindly retrying |
10. Selection checklist
- Pick ScreenshotNeo for clean production images, PDF output, API or MCP access, and predictable billing for successful clean shots.
- Pick Browserless when a managed Puppeteer-style HTTP endpoint is the main requirement.
- Pick ScreenshotOne when full-page scrolling, animation controls, and section stitching are central.
- Pick Urlbox when you need a hosted API with full-page and element targeting.
- Pick Playwright when screenshots belong inside browser tests with custom state and assertions.
11. FAQ
Is a full-page screenshot the same everywhere?
No. Viewport width, lazy loading, animation timing, browser limits, and section-stitching behavior affect the result. Validate on your own long pages.
Should screenshots run in the same CI job as end-to-end tests?
Use the same Playwright job when the image is an assertion or depends on test state. Use a hosted API job when the screenshot is an independent artifact.
How should I handle a page protected by a bot check?
Do not build an endless retry loop. Record the verdict, investigate whether the page permits automated access, and use an authenticated or approved test environment.
Can I capture only a component?
Yes. Playwright can screenshot a locator, and hosted services such as ScreenshotNeo and Urlbox support CSS-targeted element capture.
What is the simplest way to start?
For a one-off or scheduled image, call a hosted API. For a screenshot coupled to selectors and assertions, start with Playwright.
