Every successful screenshot response carries an X-Cache-URL header: a public CDN link to the file that was just rendered. You can put that link in an <img> tag, an email or an og:image tag with no API key and no signature. This guide explains how long the link lives, what the TTL changes, how it compares with signed URLs and your own storage, and a renewal pattern for links that must stay up. It is part of the guides; the rules for the cache itself are in the caching reference.

Step by step: Show link previews in your app and Reuse screenshots for 0 credits walk through this task with screenshots of every step.

Tested on 2026-10-18: the Node.js, Python, Ruby, PHP and Go listings ran against a local stub that returns the API's JSON shape, and the request bodies they sent were checked. The Java listing was not run (no JVM on the test machine). Header formats were checked against the API source. No request went to the live API.

sequenceDiagram
    participant App as Your server
    participant API as RenderScreenshot API
    participant CDN as CDN (stored file)
    participant Viewer as Browser or crawler
    App->>API: GET or POST /v1/screenshot (rs_live_ key)
    API->>CDN: store the render as a new object
    API-->>App: image bytes + X-Cache-URL, X-Cache-Expires<br/>(or JSON: image.url, cache.expires_at)
    App->>Viewer: HTML with the X-Cache-URL link
    Viewer->>CDN: GET the link (no key)
    CDN-->>Viewer: image, until the TTL ends

The API call needs your secret key, so it happens on your server. Only the CDN link reaches the public.

The minimal requests

GET, image response. The link is in the headers:

curl -sS -D headers.txt -o shot.png \
  -H "Authorization: Bearer $RS_API_KEY" \
  "https://api.renderscreenshot.com/v1/screenshot?url=https%3A%2F%2Fexample.com&preset=og_card&cache_ttl=604800"
/usr/bin/grep -i '^x-cache' headers.txt

On an image response, X-Cache-Expires is an HTTP date, such as Sun, 25 Oct 2026 03:00:00 GMT.

POST, JSON response. Send Accept: application/json and read the same link from the body:

curl -sS https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -d '{"url": "https://example.com", "preset": "og_card", "cache": {"ttl": 604800}}'

The link is in image.url and in cache.url. The expiry is cache.expires_at, in ISO 8601. Prefer JSON when you store the link, because you do not download image bytes that you do not need.

Setting Value Effect on the link
cache.ttl / cache_ttl default 86400 s (24 hours) The link is kept for 24 hours
Minimum 3600 s (1 hour) Shorter values fail with invalid_cache_ttl
Maximum 2592000 s (30 days) No link lives longer than 30 days
cache.available / cache_available true An identical render inside the TTL is returned, with its existing link, for 0 credits
cache.refresh / cache_refresh true A new render, a new object and a new link with a full TTL
DELETE /v1/cache/:key, POST /v1/cache/purge The object is removed before its TTL ends

Three rules follow:

  1. The link is not permanent. Treat X-Cache-Expires as its end. Do not rely on it after that time.
  2. Each fresh render gets a new link. Two identical requests without cache.available give two different links. Save the link you published.
  3. A renewal should be a fresh render. When you renew a link that is about to expire, send cache.refresh, so the new object starts a full TTL. A reused render keeps the age of the original.

X-Cache-URL, signed URL or your own storage

Need Use Why
Show an image that you render on your server anyway, for up to 30 days X-Cache-URL No signing, no storage. Loading the link uses no credits.
An og:image that renders on the first crawler fetch, with nothing stored on your side Signed URL The URL itself is the request. Expiry up to 30 days.
A link that must work for months or years Your own bucket, with the storage option or your own upload Only a file you own has no TTL
The image changes often and must always be current A signed URL with cache_ttl set to how stale it may be Each fetch after the TTL renders again
A link you can revoke at once X-Cache-URL plus a purge Purging removes the object; a signed URL works until it expires unless you revoke the whole key pair

A signed URL in a tag costs a credit for each fetch that is not a cache hit. An X-Cache-URL link costs one credit when you render it and nothing when people load it.

Each listing forces a fresh render with a 30-day TTL and prints the link and its expiry. Store both next to the record that owns the image. The method names are on the SDK pages.

Node.js

import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);
const res = await client.takeJson(
  TakeOptions.url('https://example.com').preset('og_card').cacheTtl(2592000).cacheRefresh(),
);
console.log(res.image.url, res.cache.expires_at);

Python

import os
from renderscreenshot import Client, TakeOptions

client = Client(os.environ['RS_API_KEY'])
res = client.take_json(
    TakeOptions.url('https://example.com').preset('og_card').cache_ttl(2592000).cache_refresh()
)
print(res['image']['url'], res['cache']['expires_at'])

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'))
res = client.take_json(
  RenderScreenshot::TakeOptions.url('https://example.com').preset('og_card').cache_ttl(2_592_000).cache_refresh
)
puts res['image']['url'], res['cache']['expires_at']

PHP

<?php
use RenderScreenshot\Client;
use RenderScreenshot\TakeOptions;

$client = new Client(getenv('RS_API_KEY'));
$res = $client->takeJson(
    TakeOptions::url('https://example.com')->preset('og_card')->cacheTtl(2592000)->cacheRefresh()
);
echo $res['image']['url'], ' ', $res['cache']['expires_at'], PHP_EOL;

Go

const ttl = 2592000
requested := time.Now()
res, err := client.TakeJSON(ctx,
    rs.URL("https://example.com").Preset("og_card").CacheTTL(ttl).CacheRefresh(),
)
if err != nil {
    log.Fatal(err)
}
// rs-go's CacheInfo has no expiry field; a forced refresh expires TTL seconds after the request.
expires := requested.Add(ttl * time.Second)
fmt.Println(res.Image.URL, expires.UTC().Format(time.RFC3339))

Java

ScreenshotResponse res = client.takeJson(
    TakeOptions.url("https://example.com").preset("og_card").cacheTtl(2592000).cacheRefresh()
);
System.out.println(res.getImage().getUrl());

A renewal pattern

For links that must stay up longer than one TTL, renew them before they expire:

  1. Store url and expires_at for each image.
  2. Run a daily job that selects images whose expires_at is less than 7 days away.
  3. Render each one again with cache.refresh and a 30-day TTL, then save the new link and expiry.
  4. Publish the new link. Pages that are still cached with the old link keep working until the old expiry.

This costs one credit per image per renewal, about once a month at the maximum TTL. If that is more than a stored file would cost you, copy each render into your own bucket instead; custom storage needs a paid plan, and its path template must contain {hash} and {ext}.

Troubleshooting

Symptom Cause Fix
The image link stopped working The TTL ended, or the entry was purged Render again with cache.refresh; store the new link
Two links for the same page Two fresh renders Send cache.available, or keep the first link
invalid_cache_ttl TTL in minutes or milliseconds, or outside 3600 to 2592000 Send seconds inside the range
Expiry parse error in your code Header and JSON use different formats The header is an HTTP date; cache.expires_at is ISO 8601
The old image still shows after a purge A browser, platform or your own CDN kept a copy Publish a new link instead of reusing the old one
No X-Cache-URL header The request failed, or it was async with a webhook Check the status and the error code; webhook payloads carry the URL instead

Create a free account to see the headers on your own renders.

Related in this topic

Was this page helpful?