Add full_page=true to a Screenshot API call and you get the whole page in one image, top of the header to bottom of the footer. It's what you want for archiving a page, reviewing a layout, or diffing a release against the last one. It's also where modern pages misbehave the most, so half of this guide is about that.
The call
curl -G "https://apixies.io/api/v1/screenshot" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "url=https://laravel.com" \
-d full_page=true \
-o laravel-full.png
I got a PNG of 1280 x 6282 pixels. width still decides how the page lays itself out. height is ignored: I sent height=300 with the same request and got the same 6282 pixels back.
Tall images get heavy, and this is where JPEG earns its keep:
| Request | Dimensions | Size |
|---|---|---|
full_page=true |
1280 x 6282 | 1.96 MB |
full_page=true&format=jpeg |
1280 x 6282 | 668 KB |
full_page=true&format=jpeg&width=375 |
375 x 9742 | 406 KB |
The last row is the phone-width version. It's narrower and half as long again, because everything that sat side by side on desktop now stacks. That's the one to look at when you're checking a responsive layout.
Each of those took four to five seconds from my machine. GitHub's homepage, 11,198 pixels tall, took about three.
Where it goes wrong
A full-page capture doesn't scroll. The browser loads the page, waits for the network to go quiet, measures the document and photographs all of it in one go. Anything the page only does when a person scrolls never happens.
Things that appear on scroll stay empty. GitHub's homepage is the clearest case I found. The headings and text are all there, all 11,198 pixels of them, but several of the big feature panels are plain black boxes. Whatever fills them waits for the panel to scroll into view, and nothing ever scrolled. Laravel's homepage came out complete, pictures and all.
You can't tell which kind a site is without looking. So look, once, before you build on it.
Infinite feeds stop at the first batch. dev.to's front page came back 5,393 pixels tall and ends, fittingly, with the word "loading..." under the last post. You get the first page of the feed and never the second.
Consent dialogs and bot walls show up here exactly as they do in a normal capture. The main screenshot guide has examples, and the link preview guide has a way to catch the bot walls.
If it's your own site that captures badly, look at how the missing parts get loaded. Laravel's page marks 10 of its 11 images with loading="lazy" and still photographed fine, so the browser's own lazy loading seems to cope. Content that a script adds when it scrolls into view is what goes missing.
Visual checks after a deploy
The classic use is comparing today's pages with yesterday's. Capture a handful of key pages after each deploy, diff them against the stored baseline with something like pixelmatch or ImageMagick's compare, and have a person look when the difference is large.
Two limits shape how you do it.
The target has to be reachable from the internet. localhost, 10.x and the other private ranges are refused with RESTRICTED_TARGET, so this works against a public staging or production URL and not against the CI runner itself.
And the free tier is 75 requests a day. Ten pages at two widths after every deploy is 20 requests, so you've got room for three deploys a day. Pick the pages that matter.
Use PNG for diffing. JPEG artifacts shift a little between runs and show up as noise in the diff.
Python, a batch run over a few pages:
import os
import requests
PAGES = {
"home": "https://laravel.com",
"docs": "https://laravel.com/docs",
}
def capture_full_page(url, file, width=1280):
res = requests.get(
"https://apixies.io/api/v1/screenshot",
params={"url": url, "full_page": "true", "width": width},
headers={"X-API-Key": os.environ["APIXIES_API_KEY"]},
timeout=90,
)
if not res.headers.get("Content-Type", "").startswith("image/"):
body = res.json()
raise RuntimeError(f"{url}: {body['code']}: {body['message']}")
with open(file, "wb") as f:
f.write(res.content)
for name, url in PAGES.items():
for width in (1280, 375):
capture_full_page(url, f"{name}-{width}.png", width)
print(f"saved {name}-{width}.png")
JavaScript
import { writeFile } from "node:fs/promises";
async function captureFullPage(url, file, width = 1280) {
const params = new URLSearchParams({ url, full_page: "true", width: String(width) });
const res = await fetch(`https://apixies.io/api/v1/screenshot?${params}`, {
headers: { "X-API-Key": process.env.APIXIES_API_KEY },
});
if (!res.headers.get("content-type")?.startsWith("image/")) {
const body = await res.json();
throw new Error(`${url}: ${body.code}: ${body.message}`);
}
await writeFile(file, Buffer.from(await res.arrayBuffer()));
}
await captureFullPage("https://laravel.com", "home-1280.png");
await captureFullPage("https://laravel.com", "home-375.png", 375);
PHP
function captureFullPage(string $url, string $file, int $width = 1280): void
{
$query = http_build_query(['url' => $url, 'full_page' => 'true', 'width' => $width]);
$ch = curl_init("https://apixies.io/api/v1/screenshot?$query");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('APIXIES_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
]);
$body = curl_exec($ch);
$type = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
if (! str_starts_with($type, 'image/')) {
$error = json_decode((string) $body, true);
throw new RuntimeException("$url: " . ($error['code'] ?? 'ERROR') . ': ' . ($error['message'] ?? 'no response'));
}
file_put_contents($file, $body);
}
captureFullPage('https://laravel.com', 'home-1280.png');
A cheap sanity check for any of them: read the image height after saving. A page that's suddenly a third as tall as last week has lost something, and you'll know before you open the file.
Keeping a record of a page
The other common use is proof of what a page said on a given day: a price list, terms, a job ad. A full-page capture is good for that because it shows the page the way a visitor saw it. Store the file with the URL and the capture time, and keep the PNG, since it's the one you may need to read closely later.
It's a picture, though, not an archive. Text isn't selectable, links are gone, and anything behind a click or a login isn't in it. If you need the text as well, save the page's HTML next to the image. The HTML to Markdown API turns it into something readable.
Full page or viewport
Use the viewport (the default) for thumbnails, cards and link previews. You want a fixed size, and the top of the page is the recognisable part.
Use full_page when a person or a diff tool is going to inspect the page itself.
Next steps
- Screenshot API reference: parameters and limits
- Website screenshot API: all parameters, error codes, what a 200 doesn't tell you
- Link previews from screenshots: fixed-size captures, stored once
- Screenshot tool: try a full-page capture in the browser
- All guides