Short answer: Many websites and apps load data from an API as JSON, and each request costs server time and visitor waiting time. Public responses that are the same for everyone, such as product lists or settings, can be cached with Cache-Control headers, validated with ETag or Last-Modified, and served from browser, CDN or server memory. Never cache responses that depend on a user without clear rules, and test that updates reach people in time.
Why API responses are often not cached
Pages built with a content management system usually pass through a page cache. API calls are different. Frameworks often send responses with no caching instructions, or with headers that say not to cache anything, because the developer wanted to be safe. The result is that the same list of products, the same menu or the same exchange rate is built again for every visitor, perhaps hundreds of times an hour.
That has a cost. The server uses CPU and database time for each request, and the visitor waits for it. For a page that loads its main content by calling an API, a slow response means a slow page, and the problem looks like a front-end issue although the cause is at the back. The article on slow external API calls covers the case where the slow service is someone else’s, and this article is about your own endpoints.
Which responses are safe to cache
- Public and identical for everyone. Product lists, categories, store locations, opening hours, public settings, translations, price lists.
- Slow to build and slow to change. Search filters, tag clouds, statistics that update hourly.
- Reference data. Countries, currencies, form options.
Be careful with these:
- Responses that vary by user. Account details, baskets, orders, messages. Do not store them in shared caches. See Cache-Control for private pages.
- Responses that vary by request details. Language, currency, device or country. They need a proper Vary header or separate addresses.
- Responses where freshness is critical. Stock for the last item, payment status, anything where a minute of delay causes harm.
The headers that do the work
Cache-Control
This header tells caches how long they may reuse a response. A public list that can be a minute old might use Cache-Control: public, max-age=60. A response that must never be shared uses private or no-store. The directive s-maxage sets a lifetime only for shared caches such as a CDN, so that you can keep browsers on a short time and the CDN on a longer one. The guide to browser caching and Cache-Control explains the basics, and the article on stale-while-revalidate shows how to avoid making a visitor wait when the copy expires.
ETag and Last-Modified
These headers let a client ask “has this changed?” instead of downloading the answer again. The server sends an ETag, which is a label for the version of the response. On the next request the client sends it back, and if nothing has changed the server replies with status 304, which has no body and costs almost nothing. This saves bandwidth and time even when the cache lifetime has passed. See ETag and Last-Modified.
Vary
If a response changes depending on a request header, for example Accept-Language or Accept-Encoding, the Vary header tells caches to keep separate copies. Without it, a cache may serve a French response to an English reader. Use Vary carefully, since too many variations reduce the chance of a cache hit. In many cases it is simpler to put the language or currency in the address.
Compression
JSON compresses very well. Make sure that responses are compressed with gzip or Brotli, as covered in gzip and Brotli compression. A cached, compressed response is both fast and small.
Where to cache
- In the browser. The cheapest cache is the visitor’s own. A short max-age avoids repeat requests during a visit.
- At a CDN or reverse proxy. A shared cache protects your server from bursts. Make sure the CDN respects your headers and does not cache responses that set cookies by mistake. See CDN cache hit ratio.
- In the application. An in-memory store such as Redis keeps computed results for reuse across requests, as explained in object caching.
- In the database layer. Query caching and good indexes help when the earlier layers miss. See slow database queries.
Keeping data fresh
- Pick lifetimes that match the data. Currency rates for a minute, a product catalogue for ten minutes, static reference data for a day.
- Purge on change. When an editor updates a product, tell the CDN to remove the old copy. Most services have a purge function or API.
- Use versioned addresses for data that you control. Adding a version or a hash to the path lets you use long lifetimes, because a change produces a new address.
- Warm the cache after a purge. See cache warming.
- Show a timestamp where it matters. If data is cached for ten minutes, a “last updated” label avoids confusion.
Security checks
Caching decisions are also security decisions, so review them with the same care as any change to access rules.
The main risk of caching is showing the wrong data to the wrong person. Check these points before you switch it on:
- No personal data in any response that is public or shared.
- Authenticated endpoints send
privateorno-store. - Requests with an Authorization header or cookies are not cached by shared caches unless you have configured it on purpose.
- Cache keys include everything that changes the answer, such as language and currency.
- Error responses are not cached for long. A cached 500 error can outlast the outage.
The issue of cache keys also affects security, as shown in the article on web cache poisoning.
How to test
- Call the endpoint twice with the browser developer tools open, and read the response headers.
- Look for the age header and any cache status header added by your CDN.
- Change the data, wait for the lifetime or purge, and confirm that the new data appears.
- Test as a logged-in user and as a visitor, to check that data does not leak.
- Measure the response time before and after, and the load on the server.
Common mistakes
- Caching responses that contain a session or a personal token.
- Sending
no-cacheeverywhere out of habit, which wastes the chance to cache. - Forgetting that
no-cachedoes not mean “do not store”, it means “check before reuse”. - Using very long lifetimes with no way to purge.
- Ignoring the cache for error responses.
A worked example: a store locator
A chain of eight shops has a page with a map that loads the list of stores from an endpoint. The list changes perhaps twice a month, but the endpoint runs a database query for every visitor and takes 700 milliseconds. The developer adds three small changes. The endpoint sends Cache-Control: public, max-age=300, s-maxage=3600 and an ETag. The CDN now keeps the response for an hour and browsers for five minutes. When the owner edits a store, the content system sends a purge request for that one address. Requests to the database drop from thousands a day to a few dozen, the map appears almost at once and the owner does not notice any staleness, because purge handles the updates. The same pattern suits menus, product feeds and settings that many pages share.
The reverse example is a basket endpoint. It contains the contents of one person’s basket, so it is private and sent with no-store. Both endpoints return JSON, but their rules are opposite, which is why each endpoint needs its own decision.
Versioned addresses for long lifetimes
For data that you publish in files, such as a JSON file of settings or translations, a different strategy works better than short lifetimes. Put a version number or a content hash in the address, for example /data/settings.3f9a1c.json, and serve it with a lifetime of a year and the immutable directive. When the data changes, the build or the publishing step creates a new address and the page refers to it. Browsers and CDNs never need to ask whether it changed, because the old address will always return the same content. The page that references the file is itself cached for a short time, so updates still reach visitors quickly. This pattern is standard for scripts and style sheets, and it is just as useful for data files that are published, not generated per request.
How Site AI Audit helps
Site AI Audit does not inspect your internal API responses or their headers in detail. It measures what visitors feel on your public pages: server response time, the Google PageSpeed score, Core Web Vitals, compression and page weight. If pages that depend on data calls are slow, a re-check after you add caching shows whether the numbers improved. The free check covers up to 50 pages and needs no sign-up. You can run a free check of your website or see the plans for re-checks and weekly monitoring.
Related reading
- Browser Caching Explained: How to Set Cache-Control Headers
- stale-while-revalidate: Fast Pages While Content Refreshes
- ETag and Last-Modified: How Conditional Requests Save Time
- Object Caching with Redis: When Your Website Needs It
The bottom line
If an answer is the same for everyone and changes slowly, there is no reason to rebuild it for every request. Send clear cache headers, use ETag to make repeat checks cheap, keep private data out of shared caches and plan how updates reach visitors. The result is a faster front end and a quieter server.
FAQ
Can I cache POST requests?
Generally no. Caches are designed for safe, repeatable GET requests. Design endpoints that only read data as GET.
What does a 304 response mean?
The client asked whether its copy was still current, and the server said yes. No data is sent, so it is fast and cheap.
How long should I cache JSON?
As long as the data can safely be old. Start with short times such as a minute, watch the effect and lengthen them when you are sure.
Why does my CDN not cache my API?
Common reasons are cookies in the request, a private or no-store header, a Vary header with many variations, or a rule that bypasses certain paths. Check the CDN logs and headers.
Does caching affect SEO?
Indirectly. Faster pages and servers help visitors and crawling, but make sure that crawlers still see current content, especially if your pages render from API data.



