Cache and response headers
The edge cache answers GET and HEAD requests at the edge: a hit never reaches the WAF
or your origin, and only responses that passed the WAF are stored. Response headers and
CORS are set at the edge as well. Its place in the request path:
pipeline. Requests need API access.
Endpoints
| Method |
Path |
Key |
Purpose |
GET |
/services/{fqdn}/cache/purge |
read |
Recent purges |
POST |
/services/{fqdn}/cache/purge |
write |
Purge the cache |
GET |
/services/{fqdn}/cache/stats |
read |
Cache hit ratio and volume |
GET /services/{fqdn}/cache/purge
Returns: 200, Purges
Answer 200
{
"generation": 1,
"all": [
{
"generation": 1,
"at": "2026-10-23T10:13:25.538807+00:00",
"by": "API key ci"
}
],
"urls": [
{
"id": "58dc70c951364ae1b5577905bb6980af",
"host": "a.example.com",
"url": "/img/logo.png?v=2",
"at": "2026-10-23T10:13:25.418554+00:00",
"by": "API key ci"
}
],
"prefixes": []
}
POST /services/{fqdn}/cache/purge
Body: CachePurge
Returns: 202, PurgeResult
Request: url
{
"url": "/img/logo.png?v=2"
}
Request: prefix
Request: all
Answer 202: url
{
"type": "url",
"id": "58dc70c951364ae1b5577905bb6980af",
"host": "a.example.com",
"url": "/img/logo.png?v=2",
"at": "2026-10-23T10:13:25.418554+00:00",
"by": "key:TGloIsNcCavQ"
}
Answer 202: prefix
{
"type": "prefix",
"id": "9e37874cfe68",
"prefix": "/img/",
"at": "2026-10-23T10:13:25.468399+00:00",
"by": "key:TGloIsNcCavQ"
}
Answer 202: all
{
"type": "all",
"generation": 1,
"at": "2026-10-23T10:13:25.538807+00:00",
"by": "key:TGloIsNcCavQ"
}
GET /services/{fqdn}/cache/stats
| Parameter |
In |
Meaning |
range |
query |
Time window; one of 1h, 6h, 24h; default "24h" |
Returns: 200, CacheStats
Answer 200
{
"range": "24h",
"requests": {
"HIT": 7
},
"bytes": {
"HIT": 7
},
"hit_ratio": 1.0,
"byte_share": 1.0,
"token_denied": null
}
Fields
Part of the service: send them with PATCH /services/{fqdn}.
| Field |
Type |
Notes |
cache |
CachePatch |
may be null; Edge cache |
response_headers |
ResponseHeadersPatch |
may be null; Response headers set at the edge |
content_negotiation |
ContentNegotiationPatch |
may be null; Pre-compressed static files |
Cache
| Field |
Type |
Notes |
enabled |
boolean |
default true; Edge cache for GET and HEAD requests. Off: every request goes to your origin |
engine |
string |
one of haproxy, ats; read-only; Cache engine at the edge. Set by support on request; ignored when you send it |
stale_if_error |
integer |
0–86400; Seconds an expired object may still be served while your origin fails, 0 to 86400. Default 86400; 0: never |
stale_while_revalidate |
integer |
0–300; Seconds an expired object is still served while the edge fetches a fresh copy, 0 to 300. Default 30; 0: off |
min_hits |
integer |
2–10; may be null; Store an object only from its Nth request on, 2 to 10, to keep one-off requests out. Null: from the first request |
purge_all_interval |
integer |
60–86400; may be null; read-only; Set by support on request; ignored when you send it |
negative_ttl |
integer |
1–3600; may be null; Seconds a 404 or 410 from your origin is served from the cache, 1 to 3600. Null: never |
large_files |
boolean |
may be null; Cache video, audio, archives, installers, disk images and PDFs (by file extension) in 4 MB blocks; your origin must answer Range requests |
key |
CacheKey |
What the cache key holds besides hostname, path and scheme |
rules |
list of CacheRule |
at most 25; Up to 25 rules on the request path, the first match wins; a path no rule matches follows your origin's headers |
token_gate |
CacheTokenGate |
read-only; Set by support on request; ignored when you send it |
CacheKey
| Field |
Type |
Notes |
params |
string |
one of all, include, none; all (the default): every query parameter except ignore_params. include: only include_params. none: no parameter, so ?x=1 is no cache miss |
ignore_params |
list of string |
at most 20; Query parameters left out of the key, such as per-viewer tokens; a name ending in * matches a prefix (utm_*). Up to 20 |
include_params |
list of string |
at most 20; With params include: the only query parameters in the key; a name ending in * matches a prefix. Up to 20 |
sort_params |
boolean |
Sort the query parameters first, so ?a=1&b=2 and ?b=2&a=1 are one object |
CacheRule
| Field |
Type |
Notes |
path (required) |
string |
Glob on the request path, starting with / or *; * matches any characters, / included |
action (required) |
string |
one of cache, bypass; cache: cache matching responses, for ttl when set. bypass: matching requests go to your origin without the cache |
ttl |
integer |
1–31536000; may be null; Seconds the edge keeps a matching response, 1 to 31536000, whatever max-age or Expires your origin sends, also when it sends none. Null: your origin decides. cache rules only |
cookie |
string |
may be null; bypass rules only: apply only when the request carries this cookie; a name ending in * matches a prefix (wordpress_logged_in_*) |
header |
string |
may be null; bypass rules only: apply only when the request carries this header |
browser_ttl |
integer |
0–31536000; may be null; Cache-Control max-age in seconds sent to browsers on matching 200 responses, 0 to 31536000; never on private, no-store or Set-Cookie responses. cache rules only |
CacheTokenGate
| Field |
Type |
Notes |
enabled |
boolean |
default false; Only requests with a token your origin confirmed get cached objects; all others go to your origin uncached |
params |
list of string |
at most 5; Query parameters carrying the viewer token, up to 5; their URL-decoded values together are the token |
ttl |
integer |
10–86400; default 60; Seconds a token stays confirmed after your origin last answered it with 2xx, 10 to 86400; a 401 or 403 from your origin ends it at once |
unknown_per_10s |
integer |
1–100000; default 500; GET and HEAD requests per 10 s a source may send without a confirmed token; above it the edge answers 429 |
outage_grace |
integer |
0–86400; default 1800; Seconds a confirmation outlives ttl while every origin fails its health check, 0 to 86400. Default 1800 |
CachePurge
| Field |
Type |
Notes |
url |
string |
Path and query string of one object, starting with / |
host |
string |
Alias the URL belongs to; default the vService |
prefix |
string |
Purge every object whose path starts with this, any query string, on the vService and its aliases. Starts with /, not / alone |
all |
boolean |
true: purge every cached object of the vService and its aliases |
| Field |
Type |
Notes |
hsts |
Hsts |
Strict-Transport-Security |
cors |
Cors |
CORS |
x_frame_options |
string |
one of DENY, SAMEORIGIN; may be null; X-Frame-Options on the responses the edge passes on, replacing your origin's; null: the edge sets none |
x_content_type_options |
boolean |
default false; Set X-Content-Type-Options: nosniff on the responses the edge passes on |
referrer_policy |
string |
one of no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url; may be null; Referrer-Policy on the responses the edge passes on, replacing your origin's; null: the edge sets none |
Cors
| Field |
Type |
Notes |
enabled |
boolean |
default false; Answer CORS at the edge with the rules |
rules |
list of CorsRule |
at most 10; Up to 10 rules; the first whose origin and method match applies. The edge answers its preflight with 204; a request no rule matches passes to your origin unchanged, and CORS headers your origin sets win |
CorsRule
| Field |
Type |
Notes |
allowed_origins (required) |
list of string |
at most 20; Origins the rule covers, case-insensitive: * for any, or http(s)://host[:port], where * in the host stands for any letters, digits, dots and dashes. The edge echoes the request's Origin, or answers * when the list holds * |
allowed_methods (required) |
list of string |
at most 5; Methods the rule covers, also its Access-Control-Allow-Methods |
allowed_headers |
list of string |
at most 20; Access-Control-Allow-Headers of the preflight answer; * repeats the headers the browser asked for |
expose_headers |
list of string |
at most 20; Access-Control-Expose-Headers on responses |
max_age_seconds |
integer |
0–86400; default 3600; Access-Control-Max-Age of the preflight answer in seconds, 0 to 86400; 0 leaves it out |
ContentNegotiation
| Field |
Type |
Notes |
enabled |
boolean |
default false; For the listed file types, request the compressed copy the client accepts (app.css.zst, app.css.gz, app.css.br) and set Content-Encoding. Your origin must hold every copy: the edge does not fall back to the original |
encodings |
list of string |
at most 3; Encodings in order of preference: zstd (.zst), gzip (.gz), br (.br); the first the client's Accept-Encoding names wins |
file_types |
list of string |
at most 8; File extensions served pre-compressed |
Example
Purge everything under /img/:
curl -s -X POST -H "Authorization: Bearer $ZP_KEY" -H 'Content-Type: application/json' \
$API/services/www.example.com/cache/purge -d '{"prefix": "/img/"}'
Behaviour
- Lifetime follows your origin:
Cache-Control: max-age/s-maxage or Expires; only Last-Modified keeps an object one hour; private or no-store is never cached.
X-ZP-Cache on the response: HIT, EXPIRED (revalidated, or served stale), MISS, BYPASS. Requests a bypass rule matches, and other methods, go to the origin without it.
- Key: host, path and scheme always.
params: all (default) minus ignore_params, include only include_params, or none. A name ending in * matches a prefix (utm_*); sort_params makes ?a=1&b=2 and ?b=2&a=1 one object. Your origin always gets the full query string.
rules match the path as a glob (* includes /), top to bottom, first match wins; no match follows the origin's headers. cache: ttl at the edge, browser_ttl as Cache-Control: max-age on 200 responses that are not private, no-store or setting a cookie. bypass: optionally only with a cookie (name or prefix*, e.g. wordpress_logged_in_*) or header.
- Defaults:
stale_if_error one day, stale_while_revalidate 30 s, negative_ttl (404/410) off, min_hits off (stores on the first request). large_files caches video, audio, archives, installers, disk images and PDFs in 4 MB blocks; your origin must answer Range requests, and X-ZP-Cache shows the first block.
token_gate (streaming with viewer tokens in the URL) and purge_all_interval are set by support. With a token gate, segments go only to viewers whose token your origin accepted recently; too many unconfirmed tokens from one source get 429 (token-unknown in the log).
- Purge one
url (with host for an alias), a prefix or all; every edge applies it within a minute. Per service: URLs 30 per minute, prefixes 50 until the next full purge, everything once every 5 minutes unless support set another interval. GET …/cache/purge lists recent purges and who triggered them.
cache/stats: hit ratio among lookups (hit, revalidated, miss, bypass), share of bytes from the cache and token-gate rejections, for 1h, 6h or 24h.
content_negotiation rewrites app.css to app.css.zst (zstd) or .gz (gzip) and adds Vary: Accept-Encoding; default types css, js, html, json, svg, xml. Your deploy places the variants next to the originals; a missing one returns 404.
- CORS: the first rule whose origin (exact,
https://*.example.com or *) and method match applies; the origin is echoed (with Vary: Origin) or *. Preflights get 204 at the edge, before proof-of-work and the WAF. No match: unchanged to your origin. CORS headers your origin sets win.
In the portal: Caching tab; purge on Advanced, statistics on Metrics.