Edge settings control how the edge serves the proxied names of a zone: WAF mode and level, origin and origin TLS, HSTS, cache, access gate, AI agents, RUM and more. Settings apply to every proxied name of the zone, and each name can override them under hosts.
| Operation | Endpoint |
|---|---|
| Get edge settings | GET /v1/orgs/{org}/zones/{zone}/edge |
| Update edge settings | PATCH /v1/orgs/{org}/zones/{zone}/edge |
| Get effective configuration of a name | GET /v1/orgs/{org}/zones/{zone}/hosts/{host}/effective |
Get edge settings
GET /v1/orgs/{org}/zones/{zone}/edgeReturns the edge settings of the zone (the defaults if they were never saved). The ETag header carries the current configuration version.
Required role: readonly or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
Response
200: Returns ZoneEdgeSettings.
| Field | Type | Description |
|---|---|---|
zone_id | string (uuid) | Zone id. |
mode | string | One of active, log, off. Default active. |
waf_level | string | One of off, low, medium, high. Default medium. |
rate_limit_rpm | integer | Basic limit per IP and minute; 0 = off. Default 0. Minimum 0. |
timeout_ms | integer | Default 0. Minimum 0. |
enable_feed | boolean | Default true. |
stack | string | One of generic, wordpress, nextjs, static. Default generic. |
block_page_mode | string | One of nginx, branded, apache, generic, custom. Default branded. |
fake_server_header | string | Default "". |
block_page_html | string | At most 262144 characters. |
access_gate | object | |
access_gate.enabled | boolean | Default false. |
access_gate.path | string | Default /. |
access_gate.ttl_hours | integer | Default 0. Minimum 0. |
access_gate.emails | array of string | Email addresses allowed through the access gate. |
max_body_mb | integer | Default 0. Minimum 0. |
redirect_allow_hosts | array of string | |
cors_allow_origins | array of string | |
hsts | object | |
hsts.max_age | integer | Default 0. From 0 to 63072000. |
hsts.include_subdomains | boolean | |
hsts.preload | boolean | |
compression | boolean | Default false. |
origin | object | The site's upstream URL = scheme://<record content>[:port] (IPv6 in brackets). |
origin.scheme | string | One of http, https. Default https. |
origin.port | integer | 0 = the scheme's default. Default 0. From 0 to 65535. |
origin.tls_verify | string | Verification of an https origin's certificate: full encrypts without verifying; full_strict verifies the chain (public roots + OpenWAF Origin CA), dates, name and revocation (failure = 526). New zones: full_strict; zones created before the feature: full. Only affects https origins. One of full, full_strict. |
origin.server_name | string | SNI and verification name; empty = the origin host (or the site's domain if the origin is an IP). At most 253 characters. |
origin.client_cert | boolean | The edge presents its client certificate to the origin (mTLS). Default false. |
hosts | HostSettingsMap | Own settings of some names of the zone: each name's site uses the zone's settings merged with its own field by field. {} when there are none (always present in answers). In a PATCH it is a JSON merge patch (RFC 7396) per name. |
redirects | array of object | Redirect rules: the edge answers status with Location = target (+ the rest of the path after path_prefix if preserve_path, + the query string if preserve_query) without reaching the origin. The longest prefix of the name wins. [] when there are none (always present in answers). In a PATCH the list is replaced. The name needs a proxied record. At most 50 items. |
redirects[].host | string | Full name in the zone (the zone or below it, no wildcard); lowercased. |
redirects[].path_prefix | string | Starts with /; no ?, #, spaces or control characters. Stored in canonical form (non-ASCII and reserved characters as uppercase %XX: /año → /a%C3%B1o). /blog matches /blog and /blog/x, not /blogging. Default /. At most 1024 characters. |
redirects[].target | string | Absolute http(s) URL without credentials, fragment or spaces; may have a query string. Stored with a lowercase scheme and non-ASCII path and query characters as %XX; the host in ASCII (punycode). It cannot point to a path that the rule itself captures. At most 2048 characters. |
redirects[].preserve_path | boolean | Default true. |
redirects[].preserve_query | boolean | Default true. |
redirects[].status | integer | One of 301, 302, 307, 308. Default 301. |
cache | EdgeCache | Response cache at the edge, off by default. In a PATCH it is a JSON merge patch: objects are merged, lists (rules, query.params) are replaced, "dev_mode_until": null turns development mode off and "cache": null restores the defaults. |
cache.enabled | boolean | Without rules only static extensions are eligible (never HTML or JSON); the origin's Cache-Control is respected. The WAF always runs first. Default false. |
cache.dev_mode_until | string (date-time) or null | Development mode: the cache is not used until then (nothing is deleted); null = off. |
cache.query | CacheQuery | |
cache.browser_ttl | integer | Minimum browser TTL in seconds (a longer max-age from the origin wins); 0 = respect the origin. Default 0. From 0 to 31536000. |
cache.deception_armor | boolean | Do not store responses whose Content-Type does not match the extension. Default true. |
cache.serve_stale | CacheServeStale | |
cache.max_object_mb | integer | Default 100. From 1 to 512. |
cache.rules | array of EdgeCacheRule | In order; if several match, the last one wins for each setting. At most 25 items. |
early_hints | EdgeEarlyHints | 103 Early Hints, off by default. In a PATCH it is a JSON merge patch: fields are merged, extra_origins is replaced and "early_hints": null restores the defaults. |
early_hints.enabled | boolean | Answer 103 to HTML navigations that go to the origin. Default false. |
early_hints.mode | string | Where links are learned from: the origin's Link headers, the first 32 KiB of the HTML, or both. One of origin_links, html_head, both. Default both. |
early_hints.extra_origins | array of string | Origins besides the site's own whose resources can be announced: https://host[:port], without a path. They are normalized (spaces, lowercase, a trailing / and :443 removed); duplicates or non-https values answer 422. At most 10 items. |
ai_agents | ZoneEdgeAIAgents | |
rum | ZoneEdgeRUM | Real user measurement of Core Web Vitals, off by default. In a PATCH it is a JSON merge patch: the given fields replace the current ones (exclude_paths as a whole), a field set to null returns to its default and "rum": null restores the default object. It is validated even when off. |
rum.enabled | boolean | The edge injects /__waf/rum.js in the site's HTML pages and receives its beacons at /__waf/rum. Default false. |
rum.sample_rate | number | Fraction of page views that get the script, (0, 1]. Default 1. Maximum 1. |
rum.exclude_paths | array of string | Path prefixes that never get the script (spaces are trimmed; empty entries are dropped). Default []. At most 50 items. Items match ^/[^?#]*$. |
rate_rules | array of EdgeRateRule | Per-path rate limits, in evaluation order; none by default. In a PATCH the whole list is replaced ([] removes them all). Each name can have its own list in hosts.<name>.rate_rules. At most 25 items. |
updated_at | string (date-time) | Last change. |
Response headers: ETag.
Errors
| Status | Description |
|---|---|
404 | Not found, or not visible to the token (not_found). |
Example
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/edge" \
-H "Authorization: Bearer $OPENWAF_TOKEN"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getZoneEdge({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.get_zone_edge(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"){
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"mode": "active",
"waf_level": "medium",
"rate_limit_rpm": 0,
"timeout_ms": 0,
"enable_feed": true,
"stack": "generic",
"block_page_mode": "branded",
"fake_server_header": "",
"block_page_html": "",
"access_gate": {
"enabled": false,
"path": "/checkout",
"ttl_hours": 0,
"emails": [
"[email protected]"
]
},
"max_body_mb": 0,
"redirect_allow_hosts": [
"www.example.com"
],
"cors_allow_origins": [
"https://app.example.com"
],
"hsts": {
"max_age": 0,
"include_subdomains": false,
"preload": false
},
"compression": false,
"origin": {
"scheme": "https",
"port": 0,
"tls_verify": "full",
"server_name": "",
"client_cert": false
},
"hosts": {},
"redirects": [
{
"host": "www.example.com",
"path_prefix": "/api/",
"target": "https://www.example.com/new",
"preserve_path": true,
"preserve_query": true,
"status": 301
}
],
"cache": {
"enabled": false,
"dev_mode_until": "2026-10-27T09:30:00Z",
"query": {
"mode": "all",
"sort": false,
"params": [
"example"
]
},
"browser_ttl": 0,
"deception_armor": true,
"serve_stale": {
"while_revalidate": true,
"on_error_max_s": 86400
},
"max_object_mb": 100,
"rules": [
{
"id": "example",
"hosts": [],
"match": {},
"action": "block",
"edge_ttl": {},
"browser_ttl": {},
"query": {},
"serve_stale": {},
"vary_bypass_if_present": []
}
]
},
"early_hints": {
"enabled": false,
"mode": "both",
"extra_origins": [
"https://cdn.example.com"
]
},
"ai_agents": {
"enabled": false,
"mode": "log",
"spoofed_challenge": "log",
"template": "search_ok_block_training",
"policy": {
"ai_training": "allow",
"ai_search": "allow",
"ai_user_agent": "allow",
"search_engine": "allow",
"unverified_automation": "allow",
"spoofed": "allow",
"unverified_bot": "allow",
"trap_hit": "allow",
"secret_probe": "allow",
"robots_violation": "allow",
"agent_burst": "allow"
},
"bot_overrides": [
{
"bot_id": "948186e3-4508-48d6-a001-c683a3ec9626",
"action": "block"
}
],
"trap": {
"enabled": false
},
"tarpit": {
"enabled": false
},
"robots": {
"enabled": false,
"content_signals": {
"search": "yes",
"ai_input": "yes",
"ai_train": "yes"
}
},
"markdown": {
"enabled": false
},
"block_response": {
"status": 403,
"message": ""
},
"endpoints": [
{
"path_prefix": "/api/",
"methods": [
"POST"
],
"max_body_bytes": 0,
"rpm_per_ip": 0,
"rpm_per_session": 0,
"session_header": "X-Session-Id",
"session_cookie": "session",
"injection_action": "log",
"limit_action": "log",
"kind": "mcp",
"mcp": {}
}
]
},
"rum": {
"enabled": false,
"sample_rate": 1,
"exclude_paths": []
},
"rate_rules": [
{
"id": "example",
"name": "example.com",
"enabled": true,
"hosts": [
"www.example.com"
],
"path_prefix": "/auth/login",
"path_exact": "/login",
"methods": [
"POST"
],
"header_present": "X-Api-Key",
"query_present": "example",
"key": "/pricing",
"key_name": "X-Api-Key",
"requests": 128,
"period": 1,
"algorithm": "sliding_window",
"action": "block",
"mitigation_timeout": 0,
"per_node_divisor": true
}
],
"updated_at": "2026-10-27T09:30:00Z"
}Update edge settings
PATCH /v1/orgs/{org}/zones/{zone}/edgeChanges any subset of the edge settings. Nested objects (access_gate, hsts, origin…) are merged field by field and lists are replaced. The settings apply to every proxied name of the zone, except what a name overrides in hosts.
hosts is a JSON merge patch (RFC 7396) per name: {"hosts": {"shop.example.com": {"stack": "nextjs"}}} is merged with that name's own settings; null for a name removes its overrides, null for a key returns it to the zone's value (also nested, e.g. "access_gate": {"enabled": null}), and "hosts": null removes them all. Names are lowercased without a trailing dot, must be the zone or below it, and up to 100 fit. A name without a proxied record is accepted with a warning (the edge does not serve it).
Required role: tech or higher.
Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
X-Change-Reason | header | string | Yes | Reason of the change: 1-500 UTF-8 characters after trimming spaces, no control characters. If it is missing or invalid: 422 reason_required. 1 to 500 characters. |
If-Match | header | string | No | Expected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict. |
dry_run | query | string | No | 1/true simulates the change: answers 200 with the plan (Applied) and saves nothing. 0/false accepted; any other value: 400. One of 1, true, 0, false. |
Request body
Content type application/json. Schema: ZoneEdgeSettings.
| Field | Type | Required | Description |
|---|---|---|---|
zone_id | string (uuid) | No | Zone id. |
mode | string | No | One of active, log, off. Default active. |
waf_level | string | No | One of off, low, medium, high. Default medium. |
rate_limit_rpm | integer | No | Basic limit per IP and minute; 0 = off. Default 0. Minimum 0. |
timeout_ms | integer | No | Default 0. Minimum 0. |
enable_feed | boolean | No | Default true. |
stack | string | No | One of generic, wordpress, nextjs, static. Default generic. |
block_page_mode | string | No | One of nginx, branded, apache, generic, custom. Default branded. |
fake_server_header | string | No | Default "". |
block_page_html | string | No | At most 262144 characters. |
access_gate | object | No | |
access_gate.enabled | boolean | No | Default false. |
access_gate.path | string | No | Default /. |
access_gate.ttl_hours | integer | No | Default 0. Minimum 0. |
access_gate.emails | array of string | No | Email addresses allowed through the access gate. |
max_body_mb | integer | No | Default 0. Minimum 0. |
redirect_allow_hosts | array of string | No | |
cors_allow_origins | array of string | No | |
hsts | object | No | |
hsts.max_age | integer | No | Default 0. From 0 to 63072000. |
hsts.include_subdomains | boolean | No | |
hsts.preload | boolean | No | |
compression | boolean | No | Default false. |
origin | object | No | The site's upstream URL = scheme://<record content>[:port] (IPv6 in brackets). |
origin.scheme | string | No | One of http, https. Default https. |
origin.port | integer | No | 0 = the scheme's default. Default 0. From 0 to 65535. |
origin.tls_verify | string | No | Verification of an https origin's certificate: full encrypts without verifying; full_strict verifies the chain (public roots + OpenWAF Origin CA), dates, name and revocation (failure = 526). New zones: full_strict; zones created before the feature: full. Only affects https origins. One of full, full_strict. |
origin.server_name | string | No | SNI and verification name; empty = the origin host (or the site's domain if the origin is an IP). At most 253 characters. |
origin.client_cert | boolean | No | The edge presents its client certificate to the origin (mTLS). Default false. |
hosts | HostSettingsMap | No | Own settings of some names of the zone: each name's site uses the zone's settings merged with its own field by field. {} when there are none (always present in answers). In a PATCH it is a JSON merge patch (RFC 7396) per name. |
redirects | array of object | No | Redirect rules: the edge answers status with Location = target (+ the rest of the path after path_prefix if preserve_path, + the query string if preserve_query) without reaching the origin. The longest prefix of the name wins. [] when there are none (always present in answers). In a PATCH the list is replaced. The name needs a proxied record. At most 50 items. |
redirects[].host | string | Yes | Full name in the zone (the zone or below it, no wildcard); lowercased. |
redirects[].path_prefix | string | No | Starts with /; no ?, #, spaces or control characters. Stored in canonical form (non-ASCII and reserved characters as uppercase %XX: /año → /a%C3%B1o). /blog matches /blog and /blog/x, not /blogging. Default /. At most 1024 characters. |
redirects[].target | string | Yes | Absolute http(s) URL without credentials, fragment or spaces; may have a query string. Stored with a lowercase scheme and non-ASCII path and query characters as %XX; the host in ASCII (punycode). It cannot point to a path that the rule itself captures. At most 2048 characters. |
redirects[].preserve_path | boolean | No | Default true. |
redirects[].preserve_query | boolean | No | Default true. |
redirects[].status | integer | No | One of 301, 302, 307, 308. Default 301. |
cache | EdgeCache | No | Response cache at the edge, off by default. In a PATCH it is a JSON merge patch: objects are merged, lists (rules, query.params) are replaced, "dev_mode_until": null turns development mode off and "cache": null restores the defaults. |
cache.enabled | boolean | No | Without rules only static extensions are eligible (never HTML or JSON); the origin's Cache-Control is respected. The WAF always runs first. Default false. |
cache.dev_mode_until | string (date-time) or null | No | Development mode: the cache is not used until then (nothing is deleted); null = off. |
cache.query | CacheQuery | No | |
cache.query.mode | string | Yes | all: the whole query string (Standard); ignore: no query string in the cache key (static extensions only); none: only requests without a query string use the cache; include/exclude: only, or all but, the params. One of all, ignore, none, include, exclude. |
cache.query.sort | boolean | No | Default false. |
cache.query.params | array of string | No | Parameter names (include/exclude); a trailing * matches a prefix (utm_*). At most 32 items. |
cache.browser_ttl | integer | No | Minimum browser TTL in seconds (a longer max-age from the origin wins); 0 = respect the origin. Default 0. From 0 to 31536000. |
cache.deception_armor | boolean | No | Do not store responses whose Content-Type does not match the extension. Default true. |
cache.serve_stale | CacheServeStale | No | |
cache.serve_stale.while_revalidate | boolean | No | Serve stale (UPDATING) while revalidating in the background. Default true. |
cache.serve_stale.on_error_max_s | integer | No | Seconds after expiry during which a stale response is served if the origin fails (0 = never). Default 86400. From 0 to 2592000. |
cache.max_object_mb | integer | No | Default 100. From 1 to 512. |
cache.rules | array of EdgeCacheRule | No | In order; if several match, the last one wins for each setting. At most 25 items. |
cache.rules[].id | string | No | Unique; r1, r2… by default. Pattern ^[A-Za-z0-9_-]{1,64}$. |
cache.rules[].hosts | array of string | No | Names of the zone it applies to (empty = all; not allowed in a name's own list). |
cache.rules[].match | object | No | Every list present must match, and within a list any value; without lists it matches everything (GET and HEAD only). |
cache.rules[].match.path_prefix | array of string | No | The path starts with the value (canonical form). At most 32 items. |
cache.rules[].match.path_exact | array of string | No | At most 32 items. |
cache.rules[].match.extensions | array of string | No | Without the dot, lowercase; "" = no extension. At most 32 items. |
cache.rules[].match.cookie_present | array of string | No | Cookie name; a trailing * matches a prefix. At most 32 items. |
cache.rules[].match.query_present | array of string | No | The query string has a parameter with that name (decoded); a trailing * matches a prefix. At most 32 items. |
cache.rules[].action | string | Yes | bypass: never cache (no other parameters); cache: eligible even if the extension is not static. One of bypass, cache. |
cache.rules[].edge_ttl | object | No | |
cache.rules[].edge_ttl.mode | string | No | override ignores the origin's Cache-Control, removes Set-Cookie and turns off the cache deception armor. One of respect_origin, bypass_by_default, override. |
cache.rules[].edge_ttl.ttl_s | integer | No | Only with override (1..31536000). From 0 to 31536000. |
cache.rules[].edge_ttl.status | array of object | No | At most 10 items. |
cache.rules[].edge_ttl.status[].from | integer | No | From 100 to 599. |
cache.rules[].edge_ttl.status[].to | integer | No | From 100 to 599. |
cache.rules[].edge_ttl.status[].ttl_s | integer | No | -1 = do not store; 0 = store and always revalidate. From -1 to 31536000. |
cache.rules[].browser_ttl | object | No | |
cache.rules[].browser_ttl.mode | string | No | One of respect_origin, override. |
cache.rules[].browser_ttl.ttl_s | integer | No | From 0 to 31536000. |
cache.rules[].query | CacheQuery | No | |
cache.rules[].query.mode | string | Yes | all: the whole query string (Standard); ignore: no query string in the cache key (static extensions only); none: only requests without a query string use the cache; include/exclude: only, or all but, the params. One of all, ignore, none, include, exclude. |
cache.rules[].query.sort | boolean | No | Default false. |
cache.rules[].query.params | array of string | No | Parameter names (include/exclude); a trailing * matches a prefix (utm_*). At most 32 items. |
cache.rules[].serve_stale | CacheServeStale | No | |
cache.rules[].serve_stale.while_revalidate | boolean | No | Serve stale (UPDATING) while revalidating in the background. Default true. |
cache.rules[].serve_stale.on_error_max_s | integer | No | Seconds after expiry during which a stale response is served if the origin fails (0 = never). Default 86400. From 0 to 2592000. |
cache.rules[].vary_bypass_if_present | array of string | No | Only with cache. Request headers (stored lowercase; not accept-encoding, accept or origin): a request carrying any of them is neither looked up nor stored (DYNAMIC); the origin's Vary may list them without causing BYPASS. Changing the list moves the name's cache to new keys. At most 32 items. |
early_hints | EdgeEarlyHints | No | 103 Early Hints, off by default. In a PATCH it is a JSON merge patch: fields are merged, extra_origins is replaced and "early_hints": null restores the defaults. |
early_hints.enabled | boolean | No | Answer 103 to HTML navigations that go to the origin. Default false. |
early_hints.mode | string | No | Where links are learned from: the origin's Link headers, the first 32 KiB of the HTML, or both. One of origin_links, html_head, both. Default both. |
early_hints.extra_origins | array of string | No | Origins besides the site's own whose resources can be announced: https://host[:port], without a path. They are normalized (spaces, lowercase, a trailing / and :443 removed); duplicates or non-https values answer 422. At most 10 items. |
ai_agents | ZoneEdgeAIAgents | No | |
ai_agents.enabled | boolean | No | Default false. |
ai_agents.mode | string | No | log: only counts what it would do (enforced=false). One of log, enforce. Default log. |
ai_agents.spoofed_challenge | string | No | enforce: only verified crawlers skip the proof-of-work challenge. One of log, enforce. Default log. |
ai_agents.template | string | No | One of search_ok_block_training, verified_only, block_unverified_automation. |
ai_agents.policy | object | No | |
ai_agents.policy.ai_training | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.ai_search | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.ai_user_agent | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.search_engine | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.unverified_automation | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.spoofed | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.unverified_bot | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.trap_hit | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.secret_probe | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.robots_violation | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.policy.agent_burst | string | No | One of allow, log, challenge, block, tarpit. |
ai_agents.bot_overrides | array of object | No | At most 100 items. |
ai_agents.bot_overrides[].bot_id | string | Yes | |
ai_agents.bot_overrides[].action | string | Yes | One of allow, log, challenge, block, tarpit. |
ai_agents.trap | object | No | Trap link; the zone's HMAC key is generated when published and never leaves the API. |
ai_agents.trap.enabled | boolean | No | |
ai_agents.tarpit | object | No | |
ai_agents.tarpit.enabled | boolean | No | |
ai_agents.robots | object | No | |
ai_agents.robots.enabled | boolean | No | |
ai_agents.robots.content_signals | object | No | |
ai_agents.robots.content_signals.search | string | No | One of yes, no, "". |
ai_agents.robots.content_signals.ai_input | string | No | One of yes, no, "". |
ai_agents.robots.content_signals.ai_train | string | No | One of yes, no, "". |
ai_agents.markdown | object | No | |
ai_agents.markdown.enabled | boolean | No | |
ai_agents.block_response | object | No | |
ai_agents.block_response.status | integer | No | One of 401, 402, 403, 404, 410, 429, 451, 503. Default 403. |
ai_agents.block_response.message | string | No | At most 1024 characters. |
ai_agents.endpoints | array of object | No | At most 20 items. |
ai_agents.endpoints[].path_prefix | string | No | At most 256 characters. Pattern ^/. |
ai_agents.endpoints[].methods | array of string | No | POST, PUT, PATCH, GET; with kind mcp, POST, GET and DELETE (with POST; empty = the three). |
ai_agents.endpoints[].max_body_bytes | integer | No | 1-1048576; 0 with kind mcp. From 0 to 1048576. |
ai_agents.endpoints[].rpm_per_ip | integer | No | From 0 to 100000. |
ai_agents.endpoints[].rpm_per_session | integer | No | From 0 to 100000. |
ai_agents.endpoints[].session_header | string | No | |
ai_agents.endpoints[].session_cookie | string | No | |
ai_agents.endpoints[].injection_action | string | No | Empty with kind mcp. One of log, block, "". |
ai_agents.endpoints[].limit_action | string | No | Empty with kind mcp. One of log, block, "". |
ai_agents.endpoints[].kind | string | No | Absent = AI endpoint; mcp = MCP server protected by MCP Shield (the AI endpoint fields at 0 or empty). One of mcp. |
ai_agents.endpoints[].mcp | AiMcpPolicy | No | |
ai_agents.endpoints[].mcp.methods_allowed | array of string | No | Exact JSON-RPC methods or x/* prefixes; by default initialize, ping, notifications/*, tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, completion/complete, logging/setLevel. 1 to 64 items. Items match ^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*(/\*)?$. |
ai_agents.endpoints[].mcp.tools_allowed | array of string | No | [] = any tool that is not denied. At most 200 items. Items match ^[A-Za-z0-9_.:/-]{1,128}$. |
ai_agents.endpoints[].mcp.tools_denied | array of string | No | At most 200 items. Items match ^[A-Za-z0-9_.:/-]{1,128}$. |
ai_agents.endpoints[].mcp.max_request_kb | integer | No | Default 256. From 1 to 1024. |
ai_agents.endpoints[].mcp.inspect_requests | string | No | One of off, log, block. Default log. |
ai_agents.endpoints[].mcp.inspect_responses | string | No | One of off, log, block. Default log. |
ai_agents.endpoints[].mcp.rate_per_token | integer | No | Requests per minute per token (Authorization); 0 = no limit. Default 0. From 0 to 100000. |
rum | ZoneEdgeRUM | No | Real user measurement of Core Web Vitals, off by default. In a PATCH it is a JSON merge patch: the given fields replace the current ones (exclude_paths as a whole), a field set to null returns to its default and "rum": null restores the default object. It is validated even when off. |
rum.enabled | boolean | No | The edge injects /__waf/rum.js in the site's HTML pages and receives its beacons at /__waf/rum. Default false. |
rum.sample_rate | number | No | Fraction of page views that get the script, (0, 1]. Default 1. Maximum 1. |
rum.exclude_paths | array of string | No | Path prefixes that never get the script (spaces are trimmed; empty entries are dropped). Default []. At most 50 items. Items match ^/[^?#]*$. |
rate_rules | array of EdgeRateRule | No | Per-path rate limits, in evaluation order; none by default. In a PATCH the whole list is replaced ([] removes them all). Each name can have its own list in hosts.<name>.rate_rules. At most 25 items. |
rate_rules[].id | string | No | Unique in the list; empty = derived from the name (login, login-2…). Events carry it as rule_name rate_rule:<id>. Pattern ^[a-z0-9][a-z0-9-]{0,39}$. |
rate_rules[].name | string | No | Name. At most 100 characters. |
rate_rules[].enabled | boolean | No | Default true. |
rate_rules[].hosts | array of string | No | Names of the zone it applies to; [] = all (always [] in a name's own list). |
rate_rules[].path_prefix | string | No | Paths starting with it at a segment boundary; / = all. Stored lowercase, without a trailing /. At most 512 characters. |
rate_rules[].path_exact | string | No | Only this path (a trailing / does not count). Stored lowercase. At most 512 characters. |
rate_rules[].methods | array of string | No | Methods; [] = any. Default []. At most 16 items. |
rate_rules[].header_present | string | No | Only if the request has this header. At most 64 characters. |
rate_rules[].query_present | string | No | Only if the request has this query parameter. At most 64 characters. |
rate_rules[].key | string | No | What is counted: IP (IPv6 per /64), header, cookie or JSON body field (counted per IP when the value is missing). One of ip, header, cookie, body_field. Default ip. |
rate_rules[].key_name | string | No | Header, cookie or path of the JSON field (a.b, up to 4 levels); empty with ip. At most 64 characters. |
rate_rules[].requests | integer | Yes | Requests. From 1 to 1000000. |
rate_rules[].period | integer | Yes | Seconds. From 1 to 86400. |
rate_rules[].algorithm | string | No | One of sliding_window, gcra. Default sliding_window. |
rate_rules[].action | string | No | One of block, challenge, log. Default block. |
rate_rules[].mitigation_timeout | integer | No | Seconds the action keeps applying to the key after it exceeds the limit; 0 = only while it exceeds it. Default 0. From 0 to 86400. |
rate_rules[].per_node_divisor | boolean | No | Divide requests among the edge entry points that serve the site, so the limit applies to the total. Default true. |
updated_at | string (date-time) | No | Last change. |
Response
200: Settings saved (with X-Config-Version and ETag) or, withdry_run, the plan (Applied). Returns ZoneEdgeSettings or Applied.
| Field | Type | Description |
|---|---|---|
zone_id | string (uuid) | Zone id. |
mode | string | One of active, log, off. Default active. |
waf_level | string | One of off, low, medium, high. Default medium. |
rate_limit_rpm | integer | Basic limit per IP and minute; 0 = off. Default 0. Minimum 0. |
timeout_ms | integer | Default 0. Minimum 0. |
enable_feed | boolean | Default true. |
stack | string | One of generic, wordpress, nextjs, static. Default generic. |
block_page_mode | string | One of nginx, branded, apache, generic, custom. Default branded. |
fake_server_header | string | Default "". |
block_page_html | string | At most 262144 characters. |
access_gate | object | |
access_gate.enabled | boolean | Default false. |
access_gate.path | string | Default /. |
access_gate.ttl_hours | integer | Default 0. Minimum 0. |
access_gate.emails | array of string | Email addresses allowed through the access gate. |
max_body_mb | integer | Default 0. Minimum 0. |
redirect_allow_hosts | array of string | |
cors_allow_origins | array of string | |
hsts | object | |
hsts.max_age | integer | Default 0. From 0 to 63072000. |
hsts.include_subdomains | boolean | |
hsts.preload | boolean | |
compression | boolean | Default false. |
origin | object | The site's upstream URL = scheme://<record content>[:port] (IPv6 in brackets). |
origin.scheme | string | One of http, https. Default https. |
origin.port | integer | 0 = the scheme's default. Default 0. From 0 to 65535. |
origin.tls_verify | string | Verification of an https origin's certificate: full encrypts without verifying; full_strict verifies the chain (public roots + OpenWAF Origin CA), dates, name and revocation (failure = 526). New zones: full_strict; zones created before the feature: full. Only affects https origins. One of full, full_strict. |
origin.server_name | string | SNI and verification name; empty = the origin host (or the site's domain if the origin is an IP). At most 253 characters. |
origin.client_cert | boolean | The edge presents its client certificate to the origin (mTLS). Default false. |
hosts | HostSettingsMap | Own settings of some names of the zone: each name's site uses the zone's settings merged with its own field by field. {} when there are none (always present in answers). In a PATCH it is a JSON merge patch (RFC 7396) per name. |
redirects | array of object | Redirect rules: the edge answers status with Location = target (+ the rest of the path after path_prefix if preserve_path, + the query string if preserve_query) without reaching the origin. The longest prefix of the name wins. [] when there are none (always present in answers). In a PATCH the list is replaced. The name needs a proxied record. At most 50 items. |
redirects[].host | string | Full name in the zone (the zone or below it, no wildcard); lowercased. |
redirects[].path_prefix | string | Starts with /; no ?, #, spaces or control characters. Stored in canonical form (non-ASCII and reserved characters as uppercase %XX: /año → /a%C3%B1o). /blog matches /blog and /blog/x, not /blogging. Default /. At most 1024 characters. |
redirects[].target | string | Absolute http(s) URL without credentials, fragment or spaces; may have a query string. Stored with a lowercase scheme and non-ASCII path and query characters as %XX; the host in ASCII (punycode). It cannot point to a path that the rule itself captures. At most 2048 characters. |
redirects[].preserve_path | boolean | Default true. |
redirects[].preserve_query | boolean | Default true. |
redirects[].status | integer | One of 301, 302, 307, 308. Default 301. |
cache | EdgeCache | Response cache at the edge, off by default. In a PATCH it is a JSON merge patch: objects are merged, lists (rules, query.params) are replaced, "dev_mode_until": null turns development mode off and "cache": null restores the defaults. |
cache.enabled | boolean | Without rules only static extensions are eligible (never HTML or JSON); the origin's Cache-Control is respected. The WAF always runs first. Default false. |
cache.dev_mode_until | string (date-time) or null | Development mode: the cache is not used until then (nothing is deleted); null = off. |
cache.query | CacheQuery | |
cache.browser_ttl | integer | Minimum browser TTL in seconds (a longer max-age from the origin wins); 0 = respect the origin. Default 0. From 0 to 31536000. |
cache.deception_armor | boolean | Do not store responses whose Content-Type does not match the extension. Default true. |
cache.serve_stale | CacheServeStale | |
cache.max_object_mb | integer | Default 100. From 1 to 512. |
cache.rules | array of EdgeCacheRule | In order; if several match, the last one wins for each setting. At most 25 items. |
early_hints | EdgeEarlyHints | 103 Early Hints, off by default. In a PATCH it is a JSON merge patch: fields are merged, extra_origins is replaced and "early_hints": null restores the defaults. |
early_hints.enabled | boolean | Answer 103 to HTML navigations that go to the origin. Default false. |
early_hints.mode | string | Where links are learned from: the origin's Link headers, the first 32 KiB of the HTML, or both. One of origin_links, html_head, both. Default both. |
early_hints.extra_origins | array of string | Origins besides the site's own whose resources can be announced: https://host[:port], without a path. They are normalized (spaces, lowercase, a trailing / and :443 removed); duplicates or non-https values answer 422. At most 10 items. |
ai_agents | ZoneEdgeAIAgents | |
rum | ZoneEdgeRUM | Real user measurement of Core Web Vitals, off by default. In a PATCH it is a JSON merge patch: the given fields replace the current ones (exclude_paths as a whole), a field set to null returns to its default and "rum": null restores the default object. It is validated even when off. |
rum.enabled | boolean | The edge injects /__waf/rum.js in the site's HTML pages and receives its beacons at /__waf/rum. Default false. |
rum.sample_rate | number | Fraction of page views that get the script, (0, 1]. Default 1. Maximum 1. |
rum.exclude_paths | array of string | Path prefixes that never get the script (spaces are trimmed; empty entries are dropped). Default []. At most 50 items. Items match ^/[^?#]*$. |
rate_rules | array of EdgeRateRule | Per-path rate limits, in evaluation order; none by default. In a PATCH the whole list is replaced ([] removes them all). Each name can have its own list in hosts.<name>.rate_rules. At most 25 items. |
updated_at | string (date-time) | Last change. |
Response headers: ETag, X-Config-Version.
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
409 | zone_merged: the zone is merged into another one. |
412 | version_conflict: If-Match does not match the current version. |
422 | Validation error (invalid) or missing or invalid reason (reason_required). |
Example
curl -X PATCH "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/edge" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Enable the WAF in block mode" \
-H 'If-Match: "12"' \
-H "Content-Type: application/json" \
--data '{
"mode": "active",
"waf_level": "high",
"hsts": {
"max_age": 31536000,
"include_subdomains": true
}
}'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.updateZoneEdge({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body: {
mode: "active",
waf_level: "high",
hsts: {
max_age: 31536000,
include_subdomains: true
}
}
}, {
reason: "Enable the WAF in block mode",
ifMatch: 12
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.update_zone_edge(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body={
"mode": "active",
"waf_level": "high",
"hsts": {
"max_age": 31536000,
"include_subdomains": True
}
},
reason="Enable the WAF in block mode",
if_match=12,
){
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"mode": "active",
"waf_level": "medium",
"rate_limit_rpm": 0,
"timeout_ms": 0,
"enable_feed": true,
"stack": "generic",
"block_page_mode": "branded",
"fake_server_header": "",
"block_page_html": "",
"access_gate": {
"enabled": false,
"path": "/checkout",
"ttl_hours": 0,
"emails": [
"[email protected]"
]
},
"max_body_mb": 0,
"redirect_allow_hosts": [
"www.example.com"
],
"cors_allow_origins": [
"https://app.example.com"
],
"hsts": {
"max_age": 0,
"include_subdomains": false,
"preload": false
},
"compression": false,
"origin": {
"scheme": "https",
"port": 0,
"tls_verify": "full",
"server_name": "",
"client_cert": false
},
"hosts": {},
"redirects": [
{
"host": "www.example.com",
"path_prefix": "/api/",
"target": "https://www.example.com/new",
"preserve_path": true,
"preserve_query": true,
"status": 301
}
],
"cache": {
"enabled": false,
"dev_mode_until": "2026-10-27T09:30:00Z",
"query": {
"mode": "all",
"sort": false,
"params": [
"example"
]
},
"browser_ttl": 0,
"deception_armor": true,
"serve_stale": {
"while_revalidate": true,
"on_error_max_s": 86400
},
"max_object_mb": 100,
"rules": [
{
"id": "example",
"hosts": [],
"match": {},
"action": "block",
"edge_ttl": {},
"browser_ttl": {},
"query": {},
"serve_stale": {},
"vary_bypass_if_present": []
}
]
},
"early_hints": {
"enabled": false,
"mode": "both",
"extra_origins": [
"https://cdn.example.com"
]
},
"ai_agents": {
"enabled": false,
"mode": "log",
"spoofed_challenge": "log",
"template": "search_ok_block_training",
"policy": {
"ai_training": "allow",
"ai_search": "allow",
"ai_user_agent": "allow",
"search_engine": "allow",
"unverified_automation": "allow",
"spoofed": "allow",
"unverified_bot": "allow",
"trap_hit": "allow",
"secret_probe": "allow",
"robots_violation": "allow",
"agent_burst": "allow"
},
"bot_overrides": [
{
"bot_id": "948186e3-4508-48d6-a001-c683a3ec9626",
"action": "block"
}
],
"trap": {
"enabled": false
},
"tarpit": {
"enabled": false
},
"robots": {
"enabled": false,
"content_signals": {
"search": "yes",
"ai_input": "yes",
"ai_train": "yes"
}
},
"markdown": {
"enabled": false
},
"block_response": {
"status": 403,
"message": ""
},
"endpoints": [
{
"path_prefix": "/api/",
"methods": [
"POST"
],
"max_body_bytes": 0,
"rpm_per_ip": 0,
"rpm_per_session": 0,
"session_header": "X-Session-Id",
"session_cookie": "session",
"injection_action": "log",
"limit_action": "log",
"kind": "mcp",
"mcp": {}
}
]
},
"rum": {
"enabled": false,
"sample_rate": 1,
"exclude_paths": []
},
"rate_rules": [
{
"id": "example",
"name": "example.com",
"enabled": true,
"hosts": [
"www.example.com"
],
"path_prefix": "/auth/login",
"path_exact": "/login",
"methods": [
"POST"
],
"header_present": "X-Api-Key",
"query_present": "example",
"key": "/pricing",
"key_name": "X-Api-Key",
"requests": 128,
"period": 1,
"algorithm": "sliding_window",
"action": "block",
"mitigation_timeout": 0,
"per_node_divisor": true
}
],
"updated_at": "2026-10-27T09:30:00Z"
}Get effective configuration of a name
GET /v1/orgs/{org}/zones/{zone}/hosts/{host}/effectiveShows the configuration that one name of the zone gets at the edge, exactly as the edge nodes receive it, the zone's settings merged with the name's own, the rules that apply to it and its redirects. The ETag header carries the zone's version.
Required role: readonly or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
host | path | string | Yes | Full name (the zone or below it). |
Response
200: Returns EffectiveConfig.
| Field | Type | Description |
|---|---|---|
zone_id | string (uuid) | Zone id. |
host | string | |
proxied | boolean | false: the name has no proxied record and site is a preview without upstream_url. |
items | array of object | |
items[].site | EdgeSite or any or null |
Response headers: ETag.
Errors
| Status | Description |
|---|---|
404 | Unknown zone, or host_not_in_zone: the name is neither the zone nor below it. |
422 | Validation error (invalid). |
Example
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/hosts/www.example.com/effective" \
-H "Authorization: Bearer $OPENWAF_TOKEN"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getHostEffective({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
host: "www.example.com"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.get_host_effective(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d", host="www.example.com"){
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"host": "www.example.com",
"proxied": false,
"items": [
{
"site": {
"id": 0,
"hostname": "www.example.com",
"upstream_url": "https://203.0.113.10",
"rules": [],
"redirects": [],
"cache": {},
"early_hints": {},
"rum": {},
"rate_rules": []
}
}
]
}