Skip to main content

HTTP Cache Freshness and Cache-Control Calculator

REAL DATA

Compute RFC 9111 cache behavior from your headers: storability, freshness lifetime, current age, fresh/stale verdict and the stale-while-revalidate window.

Authoritative reference data or standards computation

Base URL
/api/http-caching
Capabilities
2 routes
Last updated
July 29, 2026
Data source

GET /api/http-caching/freshness

Evaluates one stored response against RFC 9111 §3 (storability), §4.2.1–§4.2.3 (freshness lifetime and current age) and RFC 5861 (stale windows). now defaults to the current instant, so a bare query answers “right now”.

Live requestRuns against the public API
No key required
GET/api/http-caching/freshness?cacheControl=public%2C+max-age%3D60%2C+s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z

Request parameters

The response Cache-Control field value. Both token and quoted-string arguments are accepted; unrecognized directives are reported and ignored.

The response Date header as an ISO 8601 instant. When omitted, the reception time is used (RFC 9110 §6.6.1).

The response Expires header as an ISO 8601 instant. Used only when neither s-maxage (shared) nor max-age applies.

The response Age header in seconds; feeds correctedAgeValue in the RFC 9111 §4.2.3 calculation.

The response Last-Modified header as an ISO 8601 instant. Required for heuristic freshness.

When the request that produced this response was sent. Defaults to responseTime (a zero-delay exchange).

When the response was received. Defaults to requestTime, then to the Date header, then to now.

The instant to evaluate at. Defaults to the current time, which is echoed in the response and in meta.params.

Model a shared cache (proxy/CDN) rather than a private browser cache. Controls s-maxage, private and Authorization handling.

Fraction of the Date − Last-Modified interval used for heuristic freshness. RFC 9111 §4.2.2 calls 10% typical but mandates no heuristic.

Whether the request carried an Authorization header field, which restricts shared-cache storage under RFC 9111 §3.5.

Response status code. Decides whether the response is final and whether RFC 9110 §15.1 makes it heuristically cacheable.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/http-caching/freshness?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z"
const res = await fetch("https://randomapi.dev/api/http-caching/freshness?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/http-caching/freshness?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/http-caching/freshness?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 12
cacheControl string

The response Cache-Control field value. Both token and quoted-string arguments are accepted; unrecognized directives are reported and ignored.

allowed: ≤ 2048
example: cacheControl=public, max-age=60, s-maxage=600, stale-while-revalidate=30
date date

The response Date header as an ISO 8601 instant. When omitted, the reception time is used (RFC 9110 §6.6.1).

example: date=2026-01-15T10:00:00Z
expires date

The response Expires header as an ISO 8601 instant. Used only when neither s-maxage (shared) nor max-age applies.

example: expires=2026-01-15T10:10:00Z
age int

The response Age header in seconds; feeds correctedAgeValue in the RFC 9111 §4.2.3 calculation.

allowed: 0 – 2147483648
example: age=120
lastModified date

The response Last-Modified header as an ISO 8601 instant. Required for heuristic freshness.

example: lastModified=2026-01-01T10:00:00Z
requestTime date

When the request that produced this response was sent. Defaults to responseTime (a zero-delay exchange).

example: requestTime=2026-01-15T09:59:59Z
responseTime date

When the response was received. Defaults to requestTime, then to the Date header, then to now.

example: responseTime=2026-01-15T10:00:00Z
now date

The instant to evaluate at. Defaults to the current time, which is echoed in the response and in meta.params.

example: now=2026-01-15T10:05:00Z
shared boolean

Model a shared cache (proxy/CDN) rather than a private browser cache. Controls s-maxage, private and Authorization handling.

default: true
example: shared=false
heuristicFactor float

Fraction of the Date − Last-Modified interval used for heuristic freshness. RFC 9111 §4.2.2 calls 10% typical but mandates no heuristic.

default: 0.1
allowed: 0 – 1
example: heuristicFactor=0.25
authorization boolean

Whether the request carried an Authorization header field, which restricts shared-cache storage under RFC 9111 §3.5.

default: false
example: authorization=true
status int

Response status code. Decides whether the response is final and whether RFC 9110 §15.1 makes it heuristically cacheable.

default: 200
allowed: 100 – 599
example: status=404
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=now,requestTime
exclude list

Return all fields except these (comma-separated).

example: exclude=withinStaleIfError
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 23
now string (ISO 8601)

Evaluation instant; the server's current time when the 'now' parameter is omitted.

example: 2026-01-15T10:05:00.000Z

requestTime string (ISO 8601)

Resolved RFC 9111 §4.2.3 request_time — when the request that produced the stored response was sent.

example: 2026-01-15T10:00:00.000Z

responseTime string (ISO 8601)

Resolved RFC 9111 §4.2.3 response_time — when the response was received by the cache.

example: 2026-01-15T10:00:00.000Z

shared boolean

Whether a shared (proxy/CDN) cache was modeled rather than a private browser cache.

example: true

heuristicallyCacheable boolean

Whether RFC 9110 §15.1 defines this status code as heuristically cacheable.

example: true

storable boolean

Whether every RFC 9111 §3 storage condition this calculator models is satisfied.

example: true

storabilityReasons string[]

One sentence per RFC 9111 §3 condition that forbids storing the response; empty when storable.

example: []

freshnessLifetimeSeconds integer nullable

RFC 9111 §4.2.1 freshness_lifetime in seconds; null when no explicit rule matched and no heuristic applies. Capped at 2147483648 whichever rule produced it (§1.2.2), with a warning. Can be negative when Expires precedes Date.

example: 600

freshnessSource string nullable

Which rule produced the lifetime: s-maxage, max-age, expires or heuristic; null when none did.

example: s-maxage

apparentAge integer

max(0, responseTime − Date) in whole seconds (RFC 9111 §4.2.3).

example: 0

responseDelay integer

responseTime − requestTime in whole seconds (RFC 9111 §4.2.3).

example: 0

correctedAgeValue integer

Age header value plus responseDelay (RFC 9111 §4.2.3).

example: 120

correctedInitialAge integer

max(apparentAge, correctedAgeValue) — the conservative form from RFC 9111 §4.2.3.

example: 120

residentTime integer

now − responseTime in whole seconds; negative when the evaluation instant precedes the response.

example: 300

currentAge integer

correctedInitialAge + residentTime — the value a cache would put in the Age header.

example: 420

fresh boolean

RFC 9111 §4.2: freshnessLifetime > currentAge. False whenever no freshness lifetime exists. Computed independently of storable — check that first.

example: true

staleSeconds integer nullable

How far currentAge has passed the freshness lifetime (0 while fresh); null when no lifetime exists.

example: 0

mustRevalidate boolean

Whether RFC 9111 §4.2.4 forbids serving this response once stale — set by an unqualified no-cache, must-revalidate, or proxy-revalidate/s-maxage in a shared cache. Not merely an echo of the must-revalidate directive. A qualified no-cache="field-name" does not set it (§5.2.2.4).

example: true

requiresRevalidation boolean

True when the response cannot be reused from cache without contacting the origin: it is not storable at all, an unqualified no-cache is present, or it is not fresh.

example: false

staleWhileRevalidateWindow integer nullable

The stale-while-revalidate window in seconds (RFC 5861 §3): null when the directive is absent, 0 when it is present with an unusable argument.

example: 30

withinStaleWhileRevalidate boolean

True when the response is stale, staleSeconds is at or inside the window, and no directive forbids stale reuse.

example: false

staleIfErrorWindow integer nullable

The stale-if-error window in seconds (RFC 5861 §4): null when the directive is absent, 0 when it is present with an unusable argument.

example: 1200

withinStaleIfError boolean

True when an origin error right now could still be answered from this stale stored response.

example: false

Documented examples

Build-generated requests and complete responses
6
Is this CDN response still fresh?
GET /api/http-caching/freshness?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z
{
  "data": {
    "now": "2026-01-15T10:05:00.000Z",
    "requestTime": "2026-01-15T10:00:00.000Z",
    "responseTime": "2026-01-15T10:00:00.000Z",
    "shared": true,
    "heuristicallyCacheable": true,
    "storable": true,
    "storabilityReasons": [],
    "freshnessLifetimeSeconds": 600,
    "freshnessSource": "s-maxage",
    "apparentAge": 0,
    "responseDelay": 0,
    "correctedAgeValue": 120,
    "correctedInitialAge": 120,
    "residentTime": 300,
    "currentAge": 420,
    "fresh": true,
    "staleSeconds": 0,
    "mustRevalidate": true,
    "requiresRevalidation": false,
    "staleWhileRevalidateWindow": null,
    "withinStaleWhileRevalidate": false,
    "staleIfErrorWindow": null,
    "withinStaleIfError": false
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/freshness",
    "params": {
      "cacheControl": "public, max-age=60, s-maxage=600",
      "date": "2026-01-15T10:00:00.000Z",
      "age": 120,
      "now": "2026-01-15T10:05:00.000Z",
      "shared": true,
      "heuristicFactor": 0.1,
      "authorization": false,
      "status": 200,
      "requestTime": "2026-01-15T10:00:00.000Z",
      "responseTime": "2026-01-15T10:00:00.000Z"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
The same headers in a browser cache ignore s-maxage
GET /api/http-caching/freshness?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600&date=2026-01-15T10%3A00%3A00Z&age=120&now=2026-01-15T10%3A05%3A00Z&shared=false
{
  "data": {
    "now": "2026-01-15T10:05:00.000Z",
    "requestTime": "2026-01-15T10:00:00.000Z",
    "responseTime": "2026-01-15T10:00:00.000Z",
    "shared": false,
    "heuristicallyCacheable": true,
    "storable": true,
    "storabilityReasons": [],
    "freshnessLifetimeSeconds": 60,
    "freshnessSource": "max-age",
    "apparentAge": 0,
    "responseDelay": 0,
    "correctedAgeValue": 120,
    "correctedInitialAge": 120,
    "residentTime": 300,
    "currentAge": 420,
    "fresh": false,
    "staleSeconds": 360,
    "mustRevalidate": false,
    "requiresRevalidation": true,
    "staleWhileRevalidateWindow": null,
    "withinStaleWhileRevalidate": false,
    "staleIfErrorWindow": null,
    "withinStaleIfError": false
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/freshness",
    "params": {
      "cacheControl": "public, max-age=60, s-maxage=600",
      "date": "2026-01-15T10:00:00.000Z",
      "age": 120,
      "now": "2026-01-15T10:05:00.000Z",
      "shared": false,
      "heuristicFactor": 0.1,
      "authorization": false,
      "status": 200,
      "requestTime": "2026-01-15T10:00:00.000Z",
      "responseTime": "2026-01-15T10:00:00.000Z"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
no-store is never stored, whatever else is set
GET /api/http-caching/freshness?cacheControl=no-store%2C%20max-age%3D3600
{
  "data": {
    "now": "2026-07-29T08:13:22.000Z",
    "requestTime": "2026-07-29T08:13:22.000Z",
    "responseTime": "2026-07-29T08:13:22.000Z",
    "shared": true,
    "heuristicallyCacheable": true,
    "storable": false,
    "storabilityReasons": [
      "The 'no-store' response directive forbids storing any part of this response (RFC 9111 §5.2.2.5)."
    ],
    "freshnessLifetimeSeconds": 3600,
    "freshnessSource": "max-age",
    "apparentAge": 0,
    "responseDelay": 0,
    "correctedAgeValue": 0,
    "correctedInitialAge": 0,
    "residentTime": 0,
    "currentAge": 0,
    "fresh": true,
    "staleSeconds": 0,
    "mustRevalidate": false,
    "requiresRevalidation": true,
    "staleWhileRevalidateWindow": null,
    "withinStaleWhileRevalidate": false,
    "staleIfErrorWindow": null,
    "withinStaleIfError": false
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/freshness",
    "params": {
      "cacheControl": "no-store, max-age=3600",
      "shared": true,
      "heuristicFactor": 0.1,
      "authorization": false,
      "status": 200,
      "now": "2026-07-29T08:13:22.000Z",
      "requestTime": "2026-07-29T08:13:22.000Z",
      "responseTime": "2026-07-29T08:13:22.000Z"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Heuristic freshness from Last-Modified alone
GET /api/http-caching/freshness?date=2026-01-15T10%3A00%3A00Z&lastModified=2026-01-01T10%3A00%3A00Z&now=2026-01-15T10%3A30%3A00Z
{
  "data": {
    "now": "2026-01-15T10:30:00.000Z",
    "requestTime": "2026-01-15T10:00:00.000Z",
    "responseTime": "2026-01-15T10:00:00.000Z",
    "shared": true,
    "heuristicallyCacheable": true,
    "storable": true,
    "storabilityReasons": [],
    "freshnessLifetimeSeconds": 120960,
    "freshnessSource": "heuristic",
    "apparentAge": 0,
    "responseDelay": 0,
    "correctedAgeValue": 0,
    "correctedInitialAge": 0,
    "residentTime": 1800,
    "currentAge": 1800,
    "fresh": true,
    "staleSeconds": 0,
    "mustRevalidate": false,
    "requiresRevalidation": false,
    "staleWhileRevalidateWindow": null,
    "withinStaleWhileRevalidate": false,
    "staleIfErrorWindow": null,
    "withinStaleIfError": false
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/freshness",
    "params": {
      "date": "2026-01-15T10:00:00.000Z",
      "lastModified": "2026-01-01T10:00:00.000Z",
      "now": "2026-01-15T10:30:00.000Z",
      "shared": true,
      "heuristicFactor": 0.1,
      "authorization": false,
      "status": 200,
      "requestTime": "2026-01-15T10:00:00.000Z",
      "responseTime": "2026-01-15T10:00:00.000Z"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
The RFC 5861 stale-if-error worked example
GET /api/http-caching/freshness?cacheControl=max-age%3D600%2C%20stale-if-error%3D1200&date=2026-01-15T10%3A00%3A00Z&age=900&now=2026-01-15T10%3A00%3A00Z
{
  "data": {
    "now": "2026-01-15T10:00:00.000Z",
    "requestTime": "2026-01-15T10:00:00.000Z",
    "responseTime": "2026-01-15T10:00:00.000Z",
    "shared": true,
    "heuristicallyCacheable": true,
    "storable": true,
    "storabilityReasons": [],
    "freshnessLifetimeSeconds": 600,
    "freshnessSource": "max-age",
    "apparentAge": 0,
    "responseDelay": 0,
    "correctedAgeValue": 900,
    "correctedInitialAge": 900,
    "residentTime": 0,
    "currentAge": 900,
    "fresh": false,
    "staleSeconds": 300,
    "mustRevalidate": false,
    "requiresRevalidation": true,
    "staleWhileRevalidateWindow": null,
    "withinStaleWhileRevalidate": false,
    "staleIfErrorWindow": 1200,
    "withinStaleIfError": true
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/freshness",
    "params": {
      "cacheControl": "max-age=600, stale-if-error=1200",
      "date": "2026-01-15T10:00:00.000Z",
      "age": 900,
      "now": "2026-01-15T10:00:00.000Z",
      "shared": true,
      "heuristicFactor": 0.1,
      "authorization": false,
      "status": 200,
      "requestTime": "2026-01-15T10:00:00.000Z",
      "responseTime": "2026-01-15T10:00:00.000Z"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
A qualified no-cache does not force revalidation
GET /api/http-caching/freshness?cacheControl=no-cache%3D%22set-cookie%22%2C%20max-age%3D600&date=2026-01-15T10%3A00%3A00Z&now=2026-01-15T10%3A01%3A00Z
{
  "data": {
    "now": "2026-01-15T10:01:00.000Z",
    "requestTime": "2026-01-15T10:00:00.000Z",
    "responseTime": "2026-01-15T10:00:00.000Z",
    "shared": true,
    "heuristicallyCacheable": true,
    "storable": true,
    "storabilityReasons": [],
    "freshnessLifetimeSeconds": 600,
    "freshnessSource": "max-age",
    "apparentAge": 0,
    "responseDelay": 0,
    "correctedAgeValue": 0,
    "correctedInitialAge": 0,
    "residentTime": 60,
    "currentAge": 60,
    "fresh": true,
    "staleSeconds": 0,
    "mustRevalidate": false,
    "requiresRevalidation": false,
    "staleWhileRevalidateWindow": null,
    "withinStaleWhileRevalidate": false,
    "staleIfErrorWindow": null,
    "withinStaleIfError": false
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/freshness",
    "params": {
      "cacheControl": "no-cache=\"set-cookie\", max-age=600",
      "date": "2026-01-15T10:00:00.000Z",
      "now": "2026-01-15T10:01:00.000Z",
      "shared": true,
      "heuristicFactor": 0.1,
      "authorization": false,
      "status": 200,
      "requestTime": "2026-01-15T10:00:00.000Z",
      "responseTime": "2026-01-15T10:00:00.000Z"
    },
    …
    ]
  }
}

GET /api/http-caching/parse

Splits a Cache-Control field value into its directives, validates each argument against the grammar RFC 9111 defines for it, and reports duplicates, wrong-direction and unrecognized names.

Live requestRuns against the public API
No key required
GET/api/http-caching/parse?cacheControl=public%2C+max-age%3D60%2C+s-maxage%3D600%2C+stale-while-revalidate%3D30

Request parameters

Cache-Control field value with 1–32 directives. Malformed syntax is a 400; unrecognized directive names are reported, not rejected.

Which directive set to validate against: RFC 9111 §5.2.1 request directives or §5.2.2 response directives.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/http-caching/parse?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600%2C%20stale-while-revalidate%3D30"
const res = await fetch("https://randomapi.dev/api/http-caching/parse?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600%2C%20stale-while-revalidate%3D30");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/http-caching/parse?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600%2C%20stale-while-revalidate%3D30").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/http-caching/parse?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600%2C%20stale-while-revalidate%3D30"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 2
cacheControl string required

Cache-Control field value with 1–32 directives. Malformed syntax is a 400; unrecognized directive names are reported, not rejected.

allowed: 1 – 2048
example: cacheControl=public, max-age=60, s-maxage=600, stale-while-revalidate=30
direction enum

Which directive set to validate against: RFC 9111 §5.2.1 request directives or §5.2.2 response directives.

default: response
allowed: request | response
example: direction=request
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=cacheControl,direction
exclude list

Return all fields except these (comma-separated).

example: exclude=canonical
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 9
cacheControl string

The Cache-Control field value exactly as supplied.

example: public, max-age=60

direction string

Which directive set was applied: request or response.

example: response

count integer

Number of cache-directive members parsed, including repeated names.

example: 2

directives object[]

Every member in field order with its lowercased name, unquoted argument, parsed delta-seconds, quoting, duplicate/recognized/applicable flags, argument problem and defining section.

example: [{"name":"public","value":null,"seconds":null,"quoted":false,"duplicate":false,"recognized":true,"applicable":true,"problem":null,"reference":"RFC 9111 Section 5.2.2.9"},{"name":"max-age","value":"60","seconds":60,"quoted":false,"duplicate":false,"recognized":true,"applicable":true,"problem":null,"reference":"RFC 9111 Section 5.2.2.1"}]

duplicates string[]

Directive names that appear more than once; RFC 9111 §4.2.1 uses the first occurrence.

example: []

unrecognized string[]

Directive names not defined by RFC 9111 or RFC 5861, which a cache must ignore (§5.2.3).

example: ["immutable"]

notApplicable string[]

Recognized directives that have no meaning in this direction, such as max-stale in a response.

example: []

invalid string[]

Directives whose argument violates the syntax RFC 9111 or RFC 5861 defines for them.

example: []

canonical string

Re-serialization of the honored directives with lowercased names and repeats, unrecognized and wrong-direction members removed. Always a field value a sender may legally generate: an argument RFC 9111 §5.2 does not define for the directive is dropped, a malformed field-name list falls back to the unqualified form, and a delta-seconds directive with a missing or unusable argument is left out entirely (it stays listed in invalid, and /freshness still counts it as present and worth 0 seconds).

example: public, max-age=60

Documented examples

Build-generated requests and complete responses
3
Typical CDN response directives
GET /api/http-caching/parse?cacheControl=public%2C%20max-age%3D60%2C%20s-maxage%3D600%2C%20stale-while-revalidate%3D30
{
  "data": {
    "cacheControl": "public, max-age=60, s-maxage=600, stale-while-revalidate=30",
    "direction": "response",
    "count": 4,
    "directives": [
      {
        "name": "public",
        "value": null,
        "seconds": null,
        "quoted": false,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.2.9"
      },
      {
        "name": "max-age",
        "value": "60",
        "seconds": 60,
        "quoted": false,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.2.1"
      },
      {
        "name": "s-maxage",
        "value": "600",
        "seconds": 600,
        "quoted": false,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.2.10"
      },
      {
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Quoted arguments and a qualified no-cache
GET /api/http-caching/parse?cacheControl=no-cache%3D%22set-cookie%22%2C%20max-age%3D%22600%22
{
  "data": {
    "cacheControl": "no-cache=\"set-cookie\", max-age=\"600\"",
    "direction": "response",
    "count": 2,
    "directives": [
      {
        "name": "no-cache",
        "value": "set-cookie",
        "seconds": null,
        "quoted": true,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.2.4"
      },
      {
        "name": "max-age",
        "value": "600",
        "seconds": 600,
        "quoted": true,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.2.1"
      }
    ],
    "duplicates": [],
    "unrecognized": [],
    "notApplicable": [],
    "invalid": [],
    "canonical": "no-cache=\"set-cookie\", max-age=600"
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/parse",
    "params": {
      "cacheControl": "no-cache=\"set-cookie\", max-age=\"600\"",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Request directives from a forced reload
GET /api/http-caching/parse?cacheControl=no-cache%2C%20max-stale%3D60&direction=request
{
  "data": {
    "cacheControl": "no-cache, max-stale=60",
    "direction": "request",
    "count": 2,
    "directives": [
      {
        "name": "no-cache",
        "value": null,
        "seconds": null,
        "quoted": false,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.1.4"
      },
      {
        "name": "max-stale",
        "value": "60",
        "seconds": 60,
        "quoted": false,
        "duplicate": false,
        "recognized": true,
        "applicable": true,
        "problem": null,
        "reference": "RFC 9111 Section 5.2.1.2"
      }
    ],
    "duplicates": [],
    "unrecognized": [],
    "notApplicable": [],
    "invalid": [],
    "canonical": "no-cache, max-stale=60"
  },
  "meta": {
    "endpoint": "http-caching",
    "route": "/parse",
    "params": {
      "cacheControl": "no-cache, max-stale=60",
    …
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

About this API

Coverage & behavior

Pure RFC 9111 arithmetic over the caching headers you paste in. /freshness answers the two questions a cache actually asks — may this response be stored at all? and is the stored copy still fresh? — and returns every intermediate value rather than just a verdict.

Storability follows the RFC 9111 §3 conditions in order: a non-final (1xx) status, no-store, an unqualified private in a shared cache, and an Authorization request without must-revalidate/public/s-maxage each disqualify the response — as does having no storage condition at all. Every failure comes back as a sentence in storabilityReasons, so "why was this never cached?" is answered directly.

Qualified and unqualified forms. §5.2.2.4 and §5.2.2.7 give no-cache and private two very different meanings depending on whether they carry a #field-name argument, and this endpoint keeps them apart. Unqualified private blocks shared storage and lets a private cache apply heuristic freshness; qualified private="set-cookie" does neither — it only forbids storing the named fields. Unqualified no-cache sets mustRevalidate and requiresRevalidation; qualified no-cache="set-cookie" sets neither, because §5.2.2.4 says a cache MAY reuse the response with those fields excluded (a warning says so, and notes §5.2.2.4's own remark that most caches do not implement the distinction). An argument that is not a valid field-name list — private="", no-cache="bad field" — is not a qualified form at all, so it is honored as the unqualified one: §4.2.1 says the most restrictive directive wins, and failing open on a privacy directive is the wrong way to fail.

Freshness lifetime uses the §4.2.1 first-match chain — s-maxage (shared caches only) → max-ageExpires minus Date → heuristic — and names the winner in freshnessSource. Heuristic freshness (§4.2.2) applies only when nothing explicit matched, lastModified is supplied, and the status is heuristically cacheable (200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501) or the response is explicitly marked cacheable — by public, or by an unqualified private in a private cache, which is the only form §5.2.2.7 grants that extra permission to. Its value is heuristicFactor × (Date − Last-Modified), rounded down; the 0.1 default is the fraction §4.2.2 calls typical — the RFC permits a heuristic but mandates none, so treat the number as this endpoint's documented convention, not as a standard. A directive that is present but carries an unusable argument (max-age=abc, a bare s-maxage) counts as present and worth 0 seconds, because §4.2.1 encourages treating invalid freshness information as stale rather than ignoring it. Whichever rule wins, the lifetime is capped at 2147483648 seconds with a warning — §1.2.2 requires that value whenever a delta-seconds parse or a subsequent calculation overflows, so a far-future Expires is pinned the same way max-age=99999999999999999999 is.

Age is the §4.2.3 algorithm exposed step by step: apparentAge = max(0, responseTime − Date), responseDelay = responseTime − requestTime, correctedAgeValue = Age + responseDelay, correctedInitialAge = max of the two, residentTime = now − responseTime, currentAge = correctedInitialAge + residentTime. When responseTime is omitted it falls back to requestTime, then to Date, then to now; when Date is omitted the reception time is used, per RFC 9110 §6.6.1.

no-cache is not no-store: no-cache never blocks storage, it forbids reuse without revalidation — so a response carrying it and any one §3 storage condition comes back storable: true with requiresRevalidation: true (on its own, no-cache is not a storage condition, so no-cache on a 302 or a 500 is storable: false), while no-store is storable: false no matter what else is set. requiresRevalidation is also true for anything unstorable — a no-store response is fresh by the §4.2 arithmetic yet can never be served from cache, so the origin is contacted every time. must-revalidate and, for shared caches, proxy-revalidate and s-maxage set mustRevalidate, and RFC 9111 §4.2.4's "MUST NOT generate a stale response" outranks RFC 5861's permission — so withinStaleWhileRevalidate is false when any of them applies. Many CDNs deviate here and serve stale alongside s-maxage; this endpoint models the RFC.

/parse is the syntax half: it splits #cache-directive on unquoted commas, accepts both the token and quoted-string argument forms as §5.2 requires of recipients, reports repeated directives (first wins), directives that belong to the other direction, malformed arguments and names outside RFC 9111 / RFC 5861, and re-serializes what survives. canonical is always a value a sender may legally generate, which is not the same as echoing your input back: §5.2 allows no argument on no-store/public/must-revalidate, so no-store=1 canonicalizes to no-store; a field-name list that is not one collapses to the bare directive; and max-age with a missing or non-numeric argument has no legal argument-less form, so it is dropped from canonical while still being reported in invalid. The verbatim argument is always preserved in directives[].

Scope, stated plainly. This evaluates only the values you pass in — it never fetches your URL and therefore cannot tell you whether your CDN, proxy or browser is actually configured correctly; a fresh verdict is what a conforming cache may do, not what your cache will do. Instants are supplied as ISO 8601 timestamps, not raw IMF-fixdate strings (/api/http-preconditions parses those), which also means the §5.3 rule that an unparsable Expires — famously the value 0 — means "already expired" has to be expressed by passing a past instant. The /freshness verdict does not model Vary, request cache directives such as max-stale and min-fresh (/parse still validates them), whether your cache understands a given method or status code, the must-understand override of no-store, or cache extensions that permit storage. Real caches also deviate from RFC 9111 in places; this endpoint models the RFC and says so.

Use it for

  • Explain why a CDN keeps serving a stale copy of an already-redeployed asset
  • Check that a Cache-Control header really makes a response storable before shipping it
  • Reproduce the RFC 9111 age arithmetic when a response comes back with an unexpected Age header
  • Compare shared-cache and private-browser-cache behavior for one set of response headers

Frequently asked questions

What is the difference between max-age and s-maxage?

s-maxage applies only to shared caches, and when present it overrides both max-age and Expires for them. Set shared=false and the same headers fall through the rest of the chain — max-age, then Expires, then a heuristic; freshnessSource always names the rule that won.

Is no-cache the same as no-store?

No. no-store makes the response unstorable, so /freshness returns storable: false whatever else is set. no-cache never blocks storage — it forbids reuse without revalidation — so a response carrying it plus any one RFC 9111 §3 storage condition (max-age, Expires, public, or a heuristically cacheable status) comes back storable: true with requiresRevalidation: true; no-cache is not itself a storage condition, so no-cache alone on a 302 is still storable: false. Only the unqualified form does this: a qualified no-cache="set-cookie" sets neither flag, because §5.2.2.4 lets a cache reuse the response with those fields excluded.

How is the HTTP Age header calculated?

RFC 9111 §4.2.3: apparentAge = max(0, responseTime − Date), correctedAgeValue = Age + (responseTime − requestTime), correctedInitialAge = max of those two, and currentAge = correctedInitialAge + (now − responseTime). All six intermediates are returned so you can check the arithmetic yourself.

Why is my CDN still serving stale content?

Compare staleSeconds with staleWhileRevalidateWindow: inside that window RFC 5861 lets a cache serve the stale copy while revalidating in the background. Note that RFC 9111 §4.2.4 forbids stale reuse whenever an unqualified no-cache, must-revalidate, proxy-revalidate or s-maxage applies, so withinStaleWhileRevalidate is false then — many CDNs deviate and serve stale anyway.

When does heuristic freshness kick in?

Only when no s-maxage, max-age or Expires matched, lastModified is supplied, and the status is heuristically cacheable (200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501) or the response is explicitly marked cacheable by public — or, for a private cache only, by an unqualified private (§5.2.2.7 grants that to the bare form, not to private="set-cookie"). The lifetime is heuristicFactor (default 0.1) × (Date − Last-Modified); RFC 9111 §4.2.2 permits that convention but mandates no particular heuristic.

Does this check my live site's caching?

No. It computes from the header values you paste and never makes a request, so it cannot tell you whether your CDN or browser is configured correctly — only what a conforming RFC 9111 cache would compute from those headers.

Standards & references