# Caching

> Store renders on a CDN and serve repeat requests for free, with control over lifetime and cache keys.

## Turn it on

With `cache=true`, the result is stored and identical requests get the stored file instead of a new render:

```bash
curl -X POST "https://shotkit.net/api/take" \
  -H "X-Access-Key: YOUR_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","cache":true,"cache_ttl":86400}' \
  --fail-with-body -o shot.jpg
```

- **Cache hits are free.** They don't count toward your monthly screenshots (they do count toward the [per-minute rate limit](https://shotkit.net/docs/limits.md)).
- `GET` hits are redirected (`302`) to the file on the CDN, so `<img src>` tags load straight from it. `POST` hits return the file.
- `cache_ttl` sets the lifetime, from 4 hours (`14400`, the default) to 30 days (`2592000`).
- Caches are per workspace; other customers never get your renders.
- [Async](https://shotkit.net/docs/async-and-webhooks.md) requests always render and don't use the cache.

This makes [signed URLs](https://shotkit.net/docs/authentication.md#signed-urls) cheap to embed: the first view renders, and every later view of the same URL is a free redirect.

## What counts as identical

The cache key is your workspace plus every option except these, which don't change the render: `cache`, `cache_ttl`, `response_type`, `async`, `webhook_url`, `webhook_sign`, `webhook_errors` and `external_identifier`. Option order doesn't matter.

The site's content isn't part of the key: if the page changes, you keep getting the cached version until it expires.

### Force a fresh render

Send a `Cache-Control: no-cache` request header. The page is rendered again and the cache entry is replaced:

```bash
curl "https://shotkit.net/api/take?url=https://example.com&cache=true" \
  -H "X-Access-Key: YOUR_ACCESS_KEY" -H "Cache-Control: no-cache" -o shot.jpg
```

### Separate entries with `cache_key`

`cache_key` is any string that becomes part of the key. Use it to roll a new version on your schedule, for example one render per day:

```bash
curl -X POST "https://shotkit.net/api/take" \
  -H "X-Access-Key: YOUR_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","cache":true,"cache_key":"2026-10-09"}' \
  --fail-with-body -o shot.jpg
```

## Response headers

| Header | Value |
| --- | --- |
| `X-Cache` | `MISS` when rendered now, `HIT` when served from the cache. |
| `X-Cache-Url` | The file's CDN URL, valid until the entry expires. |
| `Cache-Control` | `private, max-age=<seconds left>`, so browsers keep their copy too. |

With `response_type=json`, the body also has `cache_url`.

## Warm the cache

Render ahead of time without downloading the file by combining `cache=true` with `response_type=empty`:

```bash
curl -X POST "https://shotkit.net/api/take" \
  -H "X-Access-Key: YOUR_ACCESS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","cache":true,"response_type":"empty"}' \
  --fail-with-body -o shot.jpg
```
