Caching
How published reads stay in the CDN cache, what a publish purges, and what your own site may still hold.
Every published read goes through Capa's CDN at https://cdn.capacms.com. The CDN keeps the response, and Capa purges it when the content behind it changes.
You do not configure this. You only choose which key you read with.
Which reads are cached
The key's environment decides. A production key reads published content, so its responses can be shared. A development key reads drafts, which have no business in a shared cache.
| Response | Production key | Development key |
|---|---|---|
GET /api/entries/..., 200 | Cached | Never cached |
GET /api/graphql, no errors | Cached | Never cached |
POST /api/graphql | Never cached | Never cached |
GET /v2/api/... and /v3/api/..., 200 | Cached | Never cached |
GET /v2/seo/..., 200 | Cached | Never cached |
GET /files/..., which takes no key | Cached for a year | Cached for a year |
| Any error | Not cached, with one exception below | Not cached |
The exception: a production key's 404 entry_not_found for a well-formed id is cached like a 200. When an entry is taken down, the edge serves the 404 in its place, and the publish that brings it back purges the 404.
A GraphQL read that selects me, __schema or __type is never cached.
The headers
A production key's 200 on /api/ carries:
| Header | Value |
|---|---|
Cache-Control | public, max-age=60 |
Surrogate-Control | the edge lifetime, with stale windows |
Surrogate-Key | the keys a purge targets (below) |
ETag | a strong tag over the body |
A development key's response carries Cache-Control: no-store, no-cache and no ETag.
Cache-Control: max-age=60 is for your side: a browser or your own server may reuse the response for 60 seconds. The edge keeps it longer and relies on purges instead.
What a publish purges
Each cached response is tagged with surrogate keys. A change purges the keys it touches, so only the affected responses are dropped.
| Key | One per |
|---|---|
t:<projectId> | response |
c:<projectId>:<contract> | response |
k:<keyId> | response |
m:<modelId> | the root model, every expanded relation's model, and every model a filter or sort reaches |
e:<entryId> | entry in the response, and every expanded entry |
f:<fileId> | file a media value renders |
| Change | What it purges |
|---|---|
| Publish an entry | its e: key and its model's m: key, so every response showing it and every list of its model |
| Move an entry to another folder | the same keys |
| Unpublish or delete an entry | the same keys, hard |
| Delete a model | its m: key, hard |
| Delete a project | its t: key, hard |
| Edit a file's alt text | its f: key |
| Delete a file | its f: key, hard |
| Deactivate, rotate, narrow or expire a key | its k: key |
A publish marks the old copies stale. The edge can serve the stale copy once more while it fetches the new one, so a read in the same moment as the publish may still see the old body. A hard purge drops the old copy at once: unpublish and delete use it so taken-down content never lingers.
A response with too many entries to list keeps its t:, c:, k:, m: and f: keys and drops the e: keys. It then carries Capa-Cache-Scope: model. A purge is broader than it needed to be, never narrower.
Revalidate with an ETag
Send the ETag back as If-None-Match. An unchanged response is a 304 with an empty body.
curl -sD - -o /dev/null 'https://cdn.capacms.com/api/entries/articles?limit=2' \
-H "x-api-key: $CAPA_KEY" -H 'If-None-Match: "3f9c1a…"'The tag covers data and page, never meta, because meta.requestId changes on every request.
Rate limits
Capa does not limit how many requests a key makes per minute. It limits how many reads run at once, so a build that sends many reads in parallel can get 429 rate_limit_exceeded with a Retry-After in seconds. Send fewer at a time, and retry after that many seconds.
The legacy API
/v2/api and /v3/api cache the same way for a pk_ key: public, max-age=60 for your side, a longer edge lifetime, and a purge of every cached copy of the model when one of its entries is published. An sk_ key is never cached.
After a publish, allow for the 60 seconds your side may still hold.
Files and images
A file under /files/ is cached for a year, at the edge and in the browser. Every resized or converted variant carries the file's own key, so deleting the file purges every variant at once. Replacing a file does not purge the edge, so the old copy can stay for up to a year. Upload a new file when a change must show. See Images.
Your own caches
The CDN is Capa's half. Anything your site keeps on top is yours to expire:
- Your server or framework. Next.js, for one, can keep fetch results in its data cache. See Next.js for tags and webhook revalidation.
- A CDN of your own. The SDK gives you the response's surrogate keys as
cacheTags, so you can purge by the same keys. - The browser.
max-age=60lets it reuse a response for a minute.
Related
- Drafts and publishing for what each key sees.
- Entries reference: caching for every header.
- Webhooks to hear about a publish the moment it happens.