Caching

The only capture that costs nothing is the one you do not repeat

A cache hit does not open a browser, so it does not consume a credit. That is a billing rule, not a promotion.

cachecache_ttlcache_key
curl --fail-with-body "https://api.pagecapture.dev/v1/take" \
  -H "X-Access-Key: $PAGECAPTURE_KEY" \
  -H "Content-Type: application/json" \
  --data '{"url":"https://example.com","format":"jpg","cache":true,"cache_ttl":86400}' \
  -o capture.jpg

Verified 2026-08-13 by running the published code against a fixture.

Configure it in the playground
What you are seeing

The same handful of URLs is captured over and over — a preview on every page view, a thumbnail on every list render — and the bill tracks traffic instead of content.

Why the page does that

Without caching, every request is a render. The page did not change; the request did not know that.

What it does

  • Serves a matching previous capture from storage and charges nothing for it.
  • Keys the entry on everything that changes a pixel — viewport, format, dark mode, blockers, scripts — and on nothing that does not.
  • Scopes every entry to your organization. There is no shared cache between customers.

What it does not do

  • Does not cache by URL. Two requests for the same URL with different options are different captures and are stored as such.
  • Does not offer a short TTL. Four hours is the minimum as well as the default; anything shorter would be a cache that mostly misses while still promising freshness.
  • Does not include delivery options in the key. `webhook_url`, `external_identifier`, `attachment_name` and `response_type` do not change a pixel, so they never split an entry.
The contract

Every limit on this page is the one the API enforces

These rows are read from the same parameter table that validates your request. Nothing here is transcribed, so a number cannot drift from the behaviour it describes.

ParameterTypeDefaultRangeEndpointsCache key
cache

Serve and store this capture from the cache. Cache hits are never charged.

booleanfalse/v1/take, /v1/animateOut
cache_ttl

Cache lifetime in seconds. Minimum and default 4 hours, maximum 30 days.

integer1440014,400 – 2,592,000/v1/take, /v1/animateOut
cache_key

Custom cache key, scoped to your organization.

string/v1/take, /v1/animateOut
Group: CacheValues outside a documented range are rejected before a browser opens.

How it behaves next to the rest of the request

cache_ttl

Cache lifetime in seconds. Minimum and default 4 hours, maximum 30 days.

Seconds, from 4 hours to 30 days. Choose it from how often the target changes, not from how often you call — the second number is what the cache is for.

cache_key

Custom cache key, scoped to your organization.

Your own key, scoped to your organization, for when you want the invalidation boundary to be a content version of yours rather than the request shape.

external_identifier

Your own correlation id, echoed in logs, JSON responses and webhooks.

Your correlation id travels with the request and stays out of the cache key on purpose: tagging a capture must never split the cache entry it should have hit.

Errors this option can produce

None of them is billable. No error code in the catalog is.

Questions this raises

How do I know a response was a cache hit?

It is reported on the request record, and it is visible in your usage: a hit consumes no credit. The bytes are identical to the original capture by construction.

How do I force a fresh capture?

Change something that is part of the key, or set a `cache_key` of your own and roll it. There is no purge parameter, because a purge endpoint would be a second, weaker way to say the same thing.

Why is the minimum four hours?

Because a cache with a one-minute TTL is a rendering pipeline with extra steps: it would miss nearly every time while still telling you it was caching.