Caching

RenderScreenshot keeps every render on a CDN and can hand an earlier render back instead of making a new one. Used well, caching makes repeat requests instant and free. This page explains the cache key, the TTL limits, the X-Cache-URL link and purging, in the order a request meets them. It is part of the API documentation.

Step by step: Reuse screenshots for 0 credits walks through this task with screenshots of every step.

How a request moves through the cache

GET or POST /v1/screenshot
        │
        ▼
 cache.available = true? ──── no ─────────────────┐
        │ yes                                      │
        ▼                                          │
 cache.refresh = true? ────── yes ────────────────┤
        │ no                                       │
        ▼                                          ▼
 same workspace + same cache key,          render in the browser
 rendered within cache.ttl?                (1 credit)
   │ yes                 │ no ────────────────────▶│
   ▼                                               ▼
 return that render                         store a new object on the CDN
 0 credits, X-Cache-Hit: true               X-Cache-Hit: false
   │                                               │
   └────────────────────┬──────────────────────────┘
                        ▼
      response with X-Cache-URL, X-Cache-Key, X-Cache-Expires
                        │
                        ▼
      DELETE /v1/cache/:key or POST /v1/cache/purge removes CDN objects

Two things follow from this flow. Reuse happens only when you ask for it with cache.available. And every fresh render, including a forced refresh, creates a new CDN object with its own URL.

The cache key

The cache key is a SHA-256 hash of the rendering options in your request, after the preset and device defaults are filled in. The cache and storage groups are left out. Lookups are scoped to the API key's workspace, so another customer's render of the same page is never returned to you. Each workspace has its own cache: the same request in two workspaces of one account is rendered and charged twice.

What that means in practice:

  • Any change to a rendering option gives a new key: another width, format, wait condition, blocked resource or target URL, even a different query string on the target.
  • The preset name is part of the key. preset: "og_card" and an explicit 1200×630 PNG request do not share an entry.
  • cache.ttl is not part of the key. You can change the TTL without losing earlier renders.

TTL

Setting Value
Default 86400 seconds (24 hours)
Minimum 3600 seconds (1 hour)
Maximum 2592000 seconds (30 days)

Set it with cache.ttl in POST or cache_ttl in GET. The TTL does two jobs. It sets X-Cache-Expires, the time until which the CDN copy is kept. And for reuse, it is the maximum age of an earlier render that this request will accept. The TTL in the current request decides, not the TTL of the earlier one.

A value outside the range fails before any render, with HTTP 400:

{
  "error": {
    "type": "validation_error",
    "code": "invalid_cache_ttl",
    "message": "Cache TTL must be between 3600 (1 hour) and 2592000 (30 days) seconds.",
    "param": "cache.ttl",
    "retryable": false,
    "docs": "https://renderscreenshot.com/docs/errors/error-codes"
  },
  "request_id": "e8b14f27-6c3a-4d59-9a02-71f5c8d3b6e4"
}

The usual cause is a TTL in minutes or milliseconds. Convert it to seconds.

Reuse a render for 0 credits

Send cache.available: true (GET: cache_available=true). If the same workspace rendered the same cache key within the TTL, you get that render back: X-Credits-Used: 0 and X-Cache-Hit: true on an image response, or cache.hit: true and usage.credits: 0 in JSON. Otherwise the API renders as normal and uses one credit. Your balance must be at least one credit for the request to start, because a miss needs it.

// Reuse an identical render from the last 7 days instead of paying for a new one.
async function cachedScreenshot(url) {
  const res = await fetch('https://api.renderscreenshot.com/v1/screenshot', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.RS_API_KEY}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      url,
      preset: 'og_card',
      cache: { available: true, ttl: 604800 }, // 7 days
    }),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);

  // body.cache.hit is true when an earlier render was reused (0 credits).
  return { cdnUrl: body.image.url, reused: body.cache.hit, credits: body.usage.credits };
}

console.log(await cachedScreenshot('https://example.com/pricing'));
console.log(await cachedScreenshot('https://example.com/pricing'));

Tested on 2026-10-15 with Node 22.18 against a local stub that returns the API's JSON response shape. The first call reported reused: false, credits: 1, the second reused: true, credits: 0, with the same CDN URL.

To skip the lookup and force a new render, add cache.refresh: true (GET: cache_refresh=true). The new render is what later cache.available requests reuse.

Every successful image response carries a public CDN link to the stored file:

X-Cache-URL: https://cdn.renderscreenshot.com/screenshots/a/3f9c2b7e1d0a4c8b6e5f/g/9b1e4c2a7d3f0e8c5a6b1d2e3f4a5b6c.png

It needs no key or signature, so it is safe in public HTML. It is the cheapest way to show a screenshot: render once on your server, then serve the link. Keep these points in mind:

  • The link is kept until X-Cache-Expires. Do not rely on it after that.
  • Each fresh render has a new link. If a page must show a stable image URL, store the link from the first response, or request with cache.available so repeats return the same render.
  • For a link longer than 30 days, copy the file into your own bucket.

In JSON responses the same link is image.url and cache.url.

Cache headers

Header Meaning
X-Cache-Hit true when an earlier render was reused, false for a fresh render
X-Cache-Key The storage key of this render. Use it with the cache endpoints.
X-Cache-URL The public CDN link
X-Cache-Expires When the CDN copy expires (render time plus TTL)
X-Credits-Used 0 for a reused render, 1 for a fresh one

The response headers page lists the rest.

Purge cached screenshots

Delete a stored render before its TTL ends, for example after the page it shows has changed. The cache management endpoints take the key from X-Cache-Key exactly as it is (slashes and extension included, raw or URL-encoded), or without the screenshots/ prefix:

Request Removes
DELETE /v1/cache/:key One render
POST /v1/cache/purge with "keys": [...] A list of renders
POST /v1/cache/purge with "url": "https://example.com/blog/*" Your renders of matching target URLs. * matches any characters.
POST /v1/cache/purge with "before": "2026-10-01T00:00:00Z" Your renders made before that time (ISO 8601)
curl -X POST https://api.renderscreenshot.com/v1/cache/purge \
  -H "Authorization: Bearer $RS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/blog/*"}'

The response reports how many objects were removed, for example {"purged": 12, "url": "https://example.com/blog/*"}. A malformed before returns invalid_date. After a delete or purge, cache.available no longer reuses those renders: the next identical request renders fresh and uses one credit. For purge-on-deploy patterns, see the cache invalidation guide.

See also

Last verified 2026-10-15 against the API's cache lookup and purge code.

Related in this topic

Was this page helpful?