/v1/screenshot and /v1/extract both take three separate parameters for controlling when a page
is considered "ready." They solve different problems, and mixing them up is the most common reason
a capture comes back either too early (missing content) or slower than it needs to be.
wait_until: a navigation-level signal
wait_until answers "has the page finished navigating," not "has the page finished rendering
everything I care about." Three values:
load(default) — theloadevent has fired: the document and its initial subresources (images referenced directly in HTML, stylesheets, etc.) have loaded. Fastest, and sufficient for most static or server-rendered pages.domcontentloaded— resolves even earlier, once the HTML is parsed, before images and other subresources necessarily finish. Rarely what you want for a visual capture; useful mainly for/v1/extractagainst text-heavy pages where you don't need images loaded at all.networkidle— waits until the page has been quiet on the network for a short window. Closer to "a human would consider this loaded," and usually the right choice for JavaScript-heavy pages that fetch data or lazy-load content after the initialloadevent.
wait_until is a blunt, page-wide signal. It doesn't know anything about your page's specific
content — it's watching network activity, not your DOM.
wait_for: a content-level signal
wait_for takes a CSS selector and blocks the capture until a matching element exists in the DOM.
This is the sharpest tool of the three, because it checks the thing you actually care about instead
of a proxy for it:
&wait_for=.dashboard-chart[data-loaded="true"]
Use it whenever there's a reliable selector for "the thing I'm trying to capture is actually there" — a chart library that adds a class once it's done drawing, a price that replaces a loading skeleton, the last card in a paginated list. It has no fixed timeout cost: if the selector appears in 200ms, the capture proceeds in 200ms.
The tradeoff is that it requires knowing the page's markup, and it breaks (with a
408 selector_timeout) if that markup changes or the selector never appears.
delay: a fixed pause, no signal at all
delay (0–5000ms) adds a flat wait on top of whatever wait_until/wait_for condition already
resolved. It doesn't check anything — it's not "wait until X," it's "then also just wait."
Reach for delay when there's no reliable selector to target and networkidle doesn't fully
cover it — a CSS transition that finishes after the network has already gone quiet, or a
canvas/WebGL render that doesn't touch the DOM in a way wait_for can see.
Combining them
The three stack, and most non-trivial pages need at least two:
curl "https://snapurl.app/v1/screenshot?url=https://example.com&wait_until=networkidle&wait_for=.hero-loaded&delay=300" \
-H "Authorization: Bearer $SNAPURL_KEY" \
-o shot.png
Order of preference, from most to least precise: a wait_for selector, then wait_until=networkidle,
then delay as the fallback for whatever the first two can't see. All three are documented in full
in the API reference, including how they interact with
full-page captures on lazy-loaded pages.