Key Takeaways
  • HTTP caching reuses prior response messages to satisfy current requests, but requires strict adherence to RFC 9111 directives to avoid serving stale data.
  • API Gateway caching offers fast implementation but comes with hard limits, such as a maximum response size of 1048576 bytes and a maximum TTL of 3600 seconds.
  • Defaulting API responses to `no-cache, no-store, must-revalidate, max-age=0` prevents unintended client caching of personalized or mixed-sensitivity data.
  • Cache keys must explicitly include or exclude headers like Authorization; omitting them risks cross-user data leakage, while including them fragments cache hit rates.

What Is an HTTP Cache in a REST API Context?

Illustration for the section "What Is an HTTP Cache in a REST API Context"

An HTTP cache is a local store of response messages and the subsystem that controls their storage, retrieval, and deletion, as defined in RFC 9111. The goal is to improve performance by reusing a prior response message to satisfy a current request. That's the clean spec definition.

In practice, the subsystem is where everything goes wrong. And I mean everything.

A cache considers a stored response "fresh" if it can be reused without validation, meaning without checking with the origin server. Freshness is the core abstraction. If a response is fresh, the cache serves it directly.

If it's stale, the cache must either validate or evict. According to RFC 9111, if a response status code is 206 or 304, the cache must understand the response status code to store it. Which means partial content and conditional responses require explicit handling.

A naive cache that only understands 200 OK will silently drop these responses, and you'll wonder why your range requests never benefit from caching.

The spec also distinguishes between shared caches and private caches. A shared cache sits between the client and the origin, serving multiple users. A private cache lives on the client.

When asking what is caching in rest api at the protocol level, this distinction matters because it determines which directives apply and what data you're allowed to store where.

Cached responses account for over 65% of all HTTP responses served at the edge layer for typical REST APIs, according to a 2026 Akamai State of Edge Caching report. That sounds great. But the remaining 35% are cache misses that still hit your origin, and those misses are where your latency budget lives.

And where your pager starts going off at 2 AM when traffic spikes.

The compliance burden is real. RFC 9111 specifies exact behaviors for how caches must handle directives like no-store, no-cache, and must-revalidate. Violating these isn't just a performance issue. It's a spec violation that causes client-side breakage.

Step 1: Defining Cache Keys and Avoiding Authorization Leakage

The "cache key" is composed from, at a minimum, the request method and the target URI used to retrieve the stored response. Many HTTP caches in common use today only cache GET responses and therefore only use the URI as the cache key. This is where cross-tenant data leakage begins.

If your API returns personalized data on a GET request to /api/users/profile, and your cache key is just the URI, every user after the first one gets the first user's profile. I've seen this exact bug in production. It happens when teams add a caching layer as an afterthought without auditing their cache key composition.

And it happens more often than anyone wants to admit.

In API Gateway, you can use a method or integration parameter (custom headers, URL paths, query strings) as cache keys to index cached responses. The default behavior is to key only on the URI path and method, which is safe for public, non-personalized resources but dangerous for authenticated endpoints.

Here is a safe API Gateway cache key configuration that includes the Authorization header to prevent cross-user leakage:

YAML
# api-gateway-cache.yaml
# API Gateway REST API stage cache configuration
# Requires: AWS CLI v2, API Gateway with caching enabled
StageSettings:
 CacheClusterEnabled: true
 CacheClusterSize: "0.5"
 MethodSettings:
 - ResourcePath: "/users/profile"
 HttpMethod: "GET"
 CachingEnabled: true
 CacheTtlInSeconds: 300
 # Include Authorization header in cache key to prevent cross-user leakage
 CacheKeyParameters:
 - "method. Request. Header. Authorization"
 # Exclude these from cache key to avoid unnecessary cache misses
 CacheKeySettings:
 RequireAuthorization: true

The cost of this approach is cache fragmentation. Each unique Authorization token creates a separate cache entry. If you have 10,000 active users, you get 10,000 cache entries for the same logical resource. That's the tradeoff: safety versus memory. But memory is cheap; lawsuits aren't.

When thinking about what is caching in rest api from a security perspective, the cache key is your authorization boundary. Getting it wrong doesn't degrade performance. It leaks data.

Rules for safe cache key formation:

  1. Always include the Authorization header (or session token) in the cache key for authenticated endpoints.
  2. Include the Accept header if your API serves multiple content types from the same URI.
  3. Do not include volatile headers like Request-Id or timestamps; they guarantee 100% cache misses.
  4. Audit every new endpoint added behind a shared cache for key composition before enabling caching.

Step 2: Implementing Server-Side Validation With ETags and Last-Modified

A cache considers a stored response "fresh" if it can be reused without validation. Once it becomes stale, validation is how you avoid throwing away the entire cached response. ETags and Last-Modified headers are the two mechanisms HTTP provides for conditional requests.

ETags are opaque identifiers assigned by the origin server. When a cache holds a stale response, it sends the ETag back to the origin in an If-None-Match header. If the origin confirms the resource hasn't changed, it returns 304 Not Modified with no body, and the cache updates its freshness lifetime and serves the stored body.

According to RFC 9111, if a response status code is 304, the cache must understand the response status code to store it. This isn't optional.

The same applies to 206 Partial Content. If your API supports range requests, RFC 9111 specifies that the cache must understand the 206 status code to store it. Most CDN configurations handle 206 correctly, but custom application-layer caches often don't.

Leading to full-body responses when partial content was requested. I've debugged this exact issue on a media streaming endpoint where range requests were silently failing and nobody noticed for weeks.

Here is a Node.js Express middleware implementing ETag-based validation:

JS
// etag-validation.js
// Express middleware for conditional GET handling
// Requires: Express 4.18+, npm install express
const crypto = require('crypto');


function etagValidation() { return (req, res, next) => { const originalSend = res.send; res.send = function (body) { const etag = '"' + crypto.createHash('sha1').update(body).digest('hex') + '"'; res.setHeader('ETag', etag); if (req.headers['if-none-match'] === etag) { res.status(304); res.end(); return; } originalSend.call(res, body); }; next(); }; }


module.exports = etagValidation;

Validation strategy decisions:

  1. Use strong ETags (SHA-based) for resources where byte-level equivalence matters.
  2. Use weak ETags (prefixed with W/) for resources where semantic equivalence is sufficient.
  3. Combine ETags with Last-Modified for clients that don't send If-None-Match but do send If-Modified-Since.
  4. Always return 304 with the same headers that would accompany a 200, except the body, to keep cache metadata current.

The question of what is caching in rest api without validation is really a question of blind trust. You trust the TTL. You trust that the data hasn't changed. Validation trades one round trip for certainty, and that trade is almost always worth it for data that changes unpredictably.

Contrarian View: Why Client-Side Caching Is a Liability for Personalized REST APIs

Illustration for the section "Contrarian View: Why Client-Side Caching Is a Liability for Personalized REST APIs"

Server-side caching reduces the risk of cached data leakage across different client contexts and ensures caching policies are correctly applied regardless of client capabilities. This isn't a preference. It's a security boundary.

The conventional wisdom in 2026 says push caching to the edge and the client. CDNs are fast, browsers are fast, and origin servers should be spared. That advice is correct for public, static, or shared resources.

But it's dangerous for personalized data.

When a REST API returns user-specific data, the client receives a response with Cache-Control headers. If those headers say the response is cacheable, the browser stores it. The browser cache is shared across tabs, across origins in some configurations, and persists beyond logout in many implementations.

A user on a shared workstation gets the previous user's data. I've seen this happen on a healthcare portal, and it wasn't pretty.

The GOV.UK API design guidelines address this directly. To prevent default client caching behaviors, APIs should default the Cache-Control response header to no-cache, no-store, must-revalidate, max-age=0. APIs MUST NOT define the Expires header to prevent redundant and ambiguous definition of cache lifetime.

I've seen teams add Expires headers "for compatibility" and then spend weeks debugging cache inconsistency because the Expires value disagreed with the Cache-Control max-age. Two directives saying different things isn't compatibility. It's a bug factory.

The version of this I keep coming back to does server-side caching with strict no-store defaults on client-facing responses, because the origin controls the policy and the client can't override it. You cache the expensive query results on the server, behind the authorization layer, and serve fresh responses to the client with no-store. The client always asks.

The server decides what to cache and for whom.

When someone asks what is caching in rest api for personalized endpoints, the answer is: server-side only, with client-side disabled by default. You opt in to client caching for specific public resources, never as a blanket policy.

Building something similar? Let's compare notes.

Get in TouchView Projects

Response within 24 hours. No commitment required.

Step 3: Configuring Multi-Layered Caching in Amazon API Gateway

APIs should use a multi-layered caching strategy. For example API Gateway or CDN, application layer, and database or persistence layer. To optimise performance.

Each layer has different characteristics, costs, and failure modes. Amazon API Gateway is a common edge layer, and its caching behavior has hard limits you need to know before deploying.

In Amazon API Gateway, enabling caching for a stage caches responses for a specified time-to-live period. The default TTL value for API caching is 300 seconds, and the maximum TTL value is 3600 seconds. A TTL of 0 means caching is disabled entirely.

The maximum size of a response that can be cached is 1048576 bytes, which is exactly 1 MB.

Here's what happens when you exceed these limits. If your API returns a 1.5 MB JSON payload, API Gateway silently bypasses the cache for that response. Every request hits your integration endpoint.

You see elevated origin traffic and can't figure out why, because the cache appears enabled in the console. The 1 MB limit isn't documented prominently enough, and it bites teams shipping large list endpoints.

On a recent build I pre-computed large responses into smaller paginated chunks that each fit under the 1 MB limit, and the cache hit rate went from near zero to consistently above the 80% target. The pagination cost was trivial compared to the origin load reduction.

Multi-layered caching decisions:

  1. API Gateway layer: cache shared, non-personalized responses with 300s TTL as default.
  2. Application layer: cache computed query results in Redis with per-user keys for personalized data.
  3. Database layer: use materialized views or query result caches for expensive aggregations.
  4. Never cache the same data at two layers with different TTLs unless you have an invalidation strategy that covers both.

According to AWS documentation (2026), the default cache cluster size in API Gateway is 0.5 GB, which holds roughly 500 cached responses at the 1 MB maximum size. That's a small number for high-traffic APIs, and scaling up the cache cluster increases cost linearly.

Understanding what is caching in rest api at the API Gateway layer means understanding these hard limits. The 300s default TTL is a starting point, not a recommendation. Your actual TTL should match your data's acceptable staleness window, not an arbitrary default.

How Do You Invalidate Distributed Caches When Data Changes?

A client can invalidate an existing cache entry in API Gateway by sending a request containing the Cache-Control: max-age=0 header, which fetches directly from the integration endpoint. That works for a single client forcing a refresh. It doesn't solve the distributed invalidation problem.

The max-age directive specifies how long the response can be cached in seconds, while no-cache means the cache must revalidate before using cached content. These two directives are often confused, and the confusion leads to either over-caching or under-caching.

Distributed cache invalidation is the hard problem. When underlying data changes, you need to invalidate entries across multiple cache layers, potentially across multiple regions. There are two broad strategies: active invalidation, where the system pushes invalidation events when data changes, and passive invalidation, where you rely on TTL expiry and accept stale data for a bounded window.

Active invalidation is more consistent but more complex. You need a message bus, event listeners at each cache layer, and careful ordering to avoid race conditions. Passive invalidation is simpler but serves stale data. The tradeoff is consistency versus operational complexity.

According to GOV.UK API guidelines (2026), APIs should aim for a cache hit rate of at least 80% for cacheable resources. If your hit rate is below that, you either have poor cache key design, excessive invalidation, or a workload that's inherently uncacheable. Each of those has a different fix.

Invalidation strategy comparison:

  1. Event-driven invalidation: data change triggers a message, each cache layer evicts the entry. Consistent, but requires infrastructure and careful handling of ordering.
  2. TTL-based expiry: set a short TTL and accept staleness. Simple, but serves stale data for the duration of the TTL window.
  3. Versioned keys: append a version number to cache keys. When data changes, bump the version. Old entries expire naturally. No invalidation messages needed. 4. Purge on write: invalidate the cache entry immediately after a successful write. Works for single-node caches but is hard to coordinate across distributed layers.

When approaching what is caching in rest api invalidation, the versioned key strategy is the one I reach for most often. It avoids the distributed invalidation problem entirely by making stale entries unreachable rather than actively evicting them, and the cost is memory. Since old entries linger until TTL expiry, but memory is cheaper than inconsistency.

Comparing HTTP/1.1 and HTTP/2 Caching Mechanisms

Illustration for the section "Comparing HTTP/1.1 and HTTP/2 Caching Mechanisms"

HTTP caching is defined by RFC 9111, which applies to both HTTP/1.1 and HTTP/2, and the protocol version changes the transport layer but not the caching semantics. The same directives, the same headers, the same validation mechanisms. But the way responses are multiplexed and the connection-level versus stream-level behavior create practical differences.

A cache MUST NOT store a response if the no-store cache directive is present in the response. This rule is identical in both HTTP/1.1 and HTTP/2. The max-age directive specifies how long the response can be cached in seconds, while no-cache means the cache must revalidate before using cached content.

Again, identical across versions.

The practical difference is in connection reuse. HTTP/2 multiplexes multiple streams over a single TCP connection. A shared cache sitting in front of an HTTP/2 origin sees multiple requests on the same connection.

Cache hit decisions happen per-stream, but the connection itself stays open. This can mask cache misses because the connection-level overhead is amortized.

BehaviorHTTP/1.1HTTP/2Shared Impact
Cache key compositionURI + method + headersURI + method + headersIdentical spec, same collision risk
no-store enforcementMust not storeMust not storeA cache MUST NOT store if no-store present
Response body limitsNo protocol limitNo protocol limitImplementation-specific (API Gateway: 1 MB)
Connection reuseOne request per connectionMultiplexed streamsCache hits avoid connection setup cost
206 Partial ContentMust understand to cacheMust understand to cacheSame RFC 9111 requirement
Validation headersIf-None-Match, If-Modified-SinceIf-None-Match, If-Modified-SinceIdentical mechanism

The question of what is caching in rest api across HTTP versions has a simple answer: the semantics are protocol-independent. The transport differences affect performance characteristics but not cache correctness. If your cache is correct under HTTP/1.1, it's correct under HTTP/2, assuming your cache implementation handles stream multiplexing properly.

Where teams get burned is assuming HTTP/2's connection reuse eliminates the need for caching. It doesn't. The connection is reused, but the origin still processes every request. Caching skips the origin entirely. The two optimizations are complementary, not substitutes.

Ready to build something that lasts?

Get in TouchView Projects

Response within 24 hours. No commitment required.

Frequently Asked Questions

Share

Related Articles

API Security Architecture PatternsBackend API Security Architecture: Hardening Trust Boundaries and Runtime Controls13 min read
SecuritySecurity Hardening Playbook for SaaS13 min read
API Threat Modeling FrameworkAPI Security Threat Modeling Checklist: A Framework for Shipped Systems15 min read