# Ajustes de borde

> Consulta y cambia los ajustes de borde de una zona (WAF, origen, TLS, caché, agentes IA…) y la configuración efectiva de un nombre.

Los ajustes de borde controlan cómo sirve el borde los nombres con proxy de una zona: modo y nivel del WAF, origen y TLS hacia el origen, HSTS, caché, puerta de acceso, agentes IA, RUM y más. Se aplican a todos los nombres con proxy de la zona, y cada nombre puede redefinirlos en `hosts`.

| Operación | Ruta |
|---|---|
| [Ver los ajustes de borde](https://developers.openwaf.dev/es/api/edge-settings/#get-zone-edge) | `GET /v1/orgs/{org}/zones/{zone}/edge` |
| [Cambiar los ajustes de borde](https://developers.openwaf.dev/es/api/edge-settings/#update-zone-edge) | `PATCH /v1/orgs/{org}/zones/{zone}/edge` |
| [Ver la configuración efectiva de un nombre](https://developers.openwaf.dev/es/api/edge-settings/#get-host-effective) | `GET /v1/orgs/{org}/zones/{zone}/hosts/{host}/effective` |

## Ver los ajustes de borde

```http
GET /v1/orgs/{org}/zones/{zone}/edge
```

Devuelve los ajustes de borde de la zona (los valores por defecto si nunca se guardaron). La cabecera `ETag` lleva la versión de configuración actual.

**Rol necesario:** `readonly` o superior.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |

#### Respuesta

- `200`: Devuelve [ZoneEdgeSettings](https://developers.openwaf.dev/es/api/schemas/#zone-edge-settings).

| Campo | Tipo | Descripción |
|---|---|---|
| `zone_id` | string (uuid) | Id de la zona. |
| `mode` | string | Uno de `active`, `log`, `off`. Por defecto `active`. |
| `waf_level` | string | Uno de `off`, `low`, `medium`, `high`. Por defecto `medium`. |
| `rate_limit_rpm` | integer | Límite básico por IP y minuto; 0 = desactivado. Por defecto `0`. Mínimo 0. |
| `timeout_ms` | integer | Por defecto `0`. Mínimo 0. |
| `enable_feed` | boolean | Por defecto `true`. |
| `stack` | string | Uno de `generic`, `wordpress`, `nextjs`, `static`. Por defecto `generic`. |
| `block_page_mode` | string | Uno de `nginx`, `branded`, `apache`, `generic`, `custom`. Por defecto `branded`. |
| `fake_server_header` | string | Por defecto `""`. |
| `block_page_html` | string | Como mucho 262144 caracteres. |
| `access_gate` | object |  |
| `access_gate.enabled` | boolean | Por defecto `false`. |
| `access_gate.path` | string | Por defecto `/`. |
| `access_gate.ttl_hours` | integer | Por defecto `0`. Mínimo 0. |
| `access_gate.emails` | array de string | Correos que pueden pasar la puerta de acceso. |
| `max_body_mb` | integer | Por defecto `0`. Mínimo 0. |
| `redirect_allow_hosts` | array de string |  |
| `cors_allow_origins` | array de string |  |
| `hsts` | object |  |
| `hsts.max_age` | integer | Por defecto `0`. De 0 a 63072000. |
| `hsts.include_subdomains` | boolean |  |
| `hsts.preload` | boolean |  |
| `compression` | boolean | Por defecto `false`. |
| `origin` | object | upstream_url del sitio = scheme://<contenido del registro>[:port] (IPv6 entre corchetes). |
| `origin.scheme` | string | Uno de `http`, `https`. Por defecto `https`. |
| `origin.port` | integer | 0 = el del esquema. Por defecto `0`. De 0 a 65535. |
| `origin.tls_verify` | string | Verificación del certificado de un origen https: full cifra sin verificar; full_strict verifica la cadena (raíces públicas + OpenWAF Origin CA), las fechas, el nombre y la revocación (fallo = 526). Zonas nuevas: full_strict; las anteriores a la función: full. Solo afecta a orígenes https. Uno de `full`, `full_strict`. |
| `origin.server_name` | string | Nombre de SNI y de la verificación; vacío = el host del origen (o el dominio del sitio si el origen es una IP). Como mucho 253 caracteres. |
| `origin.client_cert` | boolean | El borde presenta su certificado de cliente al origen (mTLS). Por defecto `false`. |
| `hosts` | [HostSettingsMap](https://developers.openwaf.dev/es/api/schemas/#host-settings-map) | Ajustes propios de algunos nombres de la zona: el sitio de cada nombre usa los de la zona mezclados con los suyos campo a campo. `{}` sin ninguno (siempre presente en las respuestas). En un PATCH es un JSON merge patch (RFC 7396) por nombre. |
| `redirects` | array de object | Reglas de redirección: el borde responde `status` con `Location` = `target` (+ el resto de la ruta tras `path_prefix` si `preserve_path`, + la query si `preserve_query`) sin llegar al origen. Gana el prefijo más largo del nombre. `[]` sin ninguna (siempre presente en las respuestas). En un PATCH se sustituye entera. El nombre necesita un registro con proxy. Como mucho 50 elementos. |
| `redirects[].host` | string | Nombre completo de la zona (la zona o por debajo, sin comodín); se pasa a minúsculas. |
| `redirects[].path_prefix` | string | Empieza por /; sin ?, #, espacios ni caracteres de control. Se guarda en forma canónica (lo no ASCII y los reservados con %XX en mayúsculas: /año → /a%C3%B1o). /blog casa con /blog y /blog/x, no con /blogging. Por defecto `/`. Como mucho 1024 caracteres. |
| `redirects[].target` | string | URL http(s) absoluta sin credenciales, fragmento ni espacios; puede llevar query. Se guarda con el esquema en minúsculas y lo no ASCII de ruta y query con %XX; el host en ASCII (punycode). No puede apuntar a una ruta que la propia regla captura. Como mucho 2048 caracteres. |
| `redirects[].preserve_path` | boolean | Por defecto `true`. |
| `redirects[].preserve_query` | boolean | Por defecto `true`. |
| `redirects[].status` | integer | Uno de `301`, `302`, `307`, `308`. Por defecto `301`. |
| `cache` | [EdgeCache](https://developers.openwaf.dev/es/api/schemas/#edge-cache) | Caché de respuestas en el borde, desactivada por defecto. En un PATCH es un JSON merge patch: los objetos se mezclan, las listas (`rules`, `query.params`) se sustituyen, `"dev_mode_until": null` apaga el modo desarrollo y `"cache": null` vuelve a los valores por defecto. |
| `cache.enabled` | boolean | Sin reglas solo son elegibles las extensiones estáticas (nunca HTML ni JSON); se respeta el Cache-Control del origen. El WAF va siempre antes. Por defecto `false`. |
| `cache.dev_mode_until` | string (date-time) o null | Modo desarrollo: hasta entonces no se usa la caché (no se borra nada); null = apagado. |
| `cache.query` | [CacheQuery](https://developers.openwaf.dev/es/api/schemas/#cache-query) |  |
| `cache.browser_ttl` | integer | TTL mínimo hacia el navegador en segundos (un max-age mayor del origen gana); 0 = respetar el origen. Por defecto `0`. De 0 a 31536000. |
| `cache.deception_armor` | boolean | No guardar respuestas cuyo Content-Type no encaja con la extensión. Por defecto `true`. |
| `cache.serve_stale` | [CacheServeStale](https://developers.openwaf.dev/es/api/schemas/#cache-serve-stale) |  |
| `cache.max_object_mb` | integer | Por defecto `100`. De 1 a 512. |
| `cache.rules` | array de [EdgeCacheRule](https://developers.openwaf.dev/es/api/schemas/#edge-cache-rule) | En orden; si varias coinciden, la última gana en cada ajuste. Como mucho 25 elementos. |
| `early_hints` | [EdgeEarlyHints](https://developers.openwaf.dev/es/api/schemas/#edge-early-hints) | 103 Early Hints, desactivadas por defecto. En un PATCH es un JSON merge patch: los campos se mezclan, `extra_origins` se sustituye y `"early_hints": null` vuelve a los valores por defecto. |
| `early_hints.enabled` | boolean | Responder con 103 a las navegaciones HTML que van al origen. Por defecto `false`. |
| `early_hints.mode` | string | De dónde se aprenden los enlaces: cabeceras Link del origen, los primeros 32 KiB del HTML o ambos. Uno de `origin_links`, `html_head`, `both`. Por defecto `both`. |
| `early_hints.extra_origins` | array de string | Orígenes además del propio del sitio cuyos recursos se pueden anunciar: `https://host[:puerto]`, sin ruta. Se normalizan (espacios, minúsculas, una `/` final y `:443` fuera); repetidos o no https dan 422. Como mucho 10 elementos. |
| `ai_agents` | [ZoneEdgeAIAgents](https://developers.openwaf.dev/es/api/schemas/#zone-edge-ai-agents) |  |
| `rum` | [ZoneEdgeRUM](https://developers.openwaf.dev/es/api/schemas/#zone-edge-rum) | Medición de usuarios reales de las Core Web Vitals, desactivada por defecto. En un PATCH es un JSON merge patch: los campos dados sustituyen a los actuales (`exclude_paths` entera), un campo a null vuelve a su valor por defecto y `"rum": null` al objeto por defecto. Se valida aunque esté desactivada. |
| `rum.enabled` | boolean | El borde inyecta /__waf/rum.js en las páginas HTML del sitio y recibe sus balizas en /__waf/rum. Por defecto `false`. |
| `rum.sample_rate` | number | Fracción de las páginas vistas que llevan el script, (0, 1]. Por defecto `1`. Máximo 1. |
| `rum.exclude_paths` | array de string | Prefijos de ruta que nunca llevan el script (se recortan los espacios; las entradas vacías se descartan). Por defecto `[]`. Como mucho 50 elementos. Elementos con el patrón `^/[^?#]*$`. |
| `rate_rules` | array de [EdgeRateRule](https://developers.openwaf.dev/es/api/schemas/#edge-rate-rule) | Límites de peticiones por ruta, en orden de evaluación; ninguno por defecto. En un PATCH la lista se sustituye entera (`[]` los quita todos). Cada nombre puede tener su propia lista en `hosts.<nombre>.rate_rules`. Como mucho 25 elementos. |
| `updated_at` | string (date-time) | Último cambio. |

Cabeceras de la respuesta: `ETag`.

#### Errores

| Estado | Descripción |
|---|---|
| `404` | No existe o el token no lo ve (`not_found`). |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/edge" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.getZoneEdge({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.get_zone_edge(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "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": [
      "ops@example.com"
    ]
  },
  "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"
}
```

## Cambiar los ajustes de borde

```http
PATCH /v1/orgs/{org}/zones/{zone}/edge
```

Cambia cualquier subconjunto de los ajustes de borde. Los objetos anidados (`access_gate`, `hsts`, `origin`…) se mezclan campo a campo y las listas se sustituyen. Los ajustes se aplican a todos los nombres con proxy de la zona, salvo lo que cada nombre redefina en `hosts`.

`hosts` es un JSON merge patch (RFC 7396) por nombre: `{"hosts": {"tienda.example.com": {"stack": "nextjs"}}}` se mezcla con los ajustes propios de ese nombre; `null` en un nombre quita los suyos, `null` en una clave la devuelve al valor de la zona (también anidada, p. ej. `"access_gate": {"enabled": null}`), y `"hosts": null` los quita todos. Los nombres se pasan a minúsculas sin punto final, deben ser la zona o estar por debajo, y caben 100. Un nombre sin registro con proxy se admite con un aviso (el borde no lo sirve).

**Rol necesario:** `tech` o superior.

**Controles de cambio:** [`dry_run`](https://developers.openwaf.dev/es/api/#configuration-changes) · [`If-Match`](https://developers.openwaf.dev/es/api/#configuration-changes) (opcional) · [`X-Change-Reason`](https://developers.openwaf.dev/es/api/#configuration-changes) (obligatorio)

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |
| `X-Change-Reason` | cabecera | string | Sí | Motivo del cambio: 1-500 caracteres UTF-8 tras recortar espacios, sin caracteres de control. Si falta o no es válido: 422 `reason_required`. De 1 a 500 caracteres. |
| `If-Match` | cabecera | string | No | Versión de configuración esperada, p. ej. `"12"` (se admite `W/`). Si no es la actual: 412 `version_conflict`. |
| `dry_run` | consulta | string | No | `1`/`true` simula el cambio: responde 200 con el plan (`Applied`) y no guarda nada. Admite `0`/`false`; otro valor: 400. Uno de `1`, `true`, `0`, `false`. |

#### Cuerpo de la petición

Tipo de contenido `application/json`. Esquema: [ZoneEdgeSettings](https://developers.openwaf.dev/es/api/schemas/#zone-edge-settings).

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `zone_id` | string (uuid) | No | Id de la zona. |
| `mode` | string | No | Uno de `active`, `log`, `off`. Por defecto `active`. |
| `waf_level` | string | No | Uno de `off`, `low`, `medium`, `high`. Por defecto `medium`. |
| `rate_limit_rpm` | integer | No | Límite básico por IP y minuto; 0 = desactivado. Por defecto `0`. Mínimo 0. |
| `timeout_ms` | integer | No | Por defecto `0`. Mínimo 0. |
| `enable_feed` | boolean | No | Por defecto `true`. |
| `stack` | string | No | Uno de `generic`, `wordpress`, `nextjs`, `static`. Por defecto `generic`. |
| `block_page_mode` | string | No | Uno de `nginx`, `branded`, `apache`, `generic`, `custom`. Por defecto `branded`. |
| `fake_server_header` | string | No | Por defecto `""`. |
| `block_page_html` | string | No | Como mucho 262144 caracteres. |
| `access_gate` | object | No |  |
| `access_gate.enabled` | boolean | No | Por defecto `false`. |
| `access_gate.path` | string | No | Por defecto `/`. |
| `access_gate.ttl_hours` | integer | No | Por defecto `0`. Mínimo 0. |
| `access_gate.emails` | array de string | No | Correos que pueden pasar la puerta de acceso. |
| `max_body_mb` | integer | No | Por defecto `0`. Mínimo 0. |
| `redirect_allow_hosts` | array de string | No |  |
| `cors_allow_origins` | array de string | No |  |
| `hsts` | object | No |  |
| `hsts.max_age` | integer | No | Por defecto `0`. De 0 a 63072000. |
| `hsts.include_subdomains` | boolean | No |  |
| `hsts.preload` | boolean | No |  |
| `compression` | boolean | No | Por defecto `false`. |
| `origin` | object | No | upstream_url del sitio = scheme://<contenido del registro>[:port] (IPv6 entre corchetes). |
| `origin.scheme` | string | No | Uno de `http`, `https`. Por defecto `https`. |
| `origin.port` | integer | No | 0 = el del esquema. Por defecto `0`. De 0 a 65535. |
| `origin.tls_verify` | string | No | Verificación del certificado de un origen https: full cifra sin verificar; full_strict verifica la cadena (raíces públicas + OpenWAF Origin CA), las fechas, el nombre y la revocación (fallo = 526). Zonas nuevas: full_strict; las anteriores a la función: full. Solo afecta a orígenes https. Uno de `full`, `full_strict`. |
| `origin.server_name` | string | No | Nombre de SNI y de la verificación; vacío = el host del origen (o el dominio del sitio si el origen es una IP). Como mucho 253 caracteres. |
| `origin.client_cert` | boolean | No | El borde presenta su certificado de cliente al origen (mTLS). Por defecto `false`. |
| `hosts` | [HostSettingsMap](https://developers.openwaf.dev/es/api/schemas/#host-settings-map) | No | Ajustes propios de algunos nombres de la zona: el sitio de cada nombre usa los de la zona mezclados con los suyos campo a campo. `{}` sin ninguno (siempre presente en las respuestas). En un PATCH es un JSON merge patch (RFC 7396) por nombre. |
| `redirects` | array de object | No | Reglas de redirección: el borde responde `status` con `Location` = `target` (+ el resto de la ruta tras `path_prefix` si `preserve_path`, + la query si `preserve_query`) sin llegar al origen. Gana el prefijo más largo del nombre. `[]` sin ninguna (siempre presente en las respuestas). En un PATCH se sustituye entera. El nombre necesita un registro con proxy. Como mucho 50 elementos. |
| `redirects[].host` | string | Sí | Nombre completo de la zona (la zona o por debajo, sin comodín); se pasa a minúsculas. |
| `redirects[].path_prefix` | string | No | Empieza por /; sin ?, #, espacios ni caracteres de control. Se guarda en forma canónica (lo no ASCII y los reservados con %XX en mayúsculas: /año → /a%C3%B1o). /blog casa con /blog y /blog/x, no con /blogging. Por defecto `/`. Como mucho 1024 caracteres. |
| `redirects[].target` | string | Sí | URL http(s) absoluta sin credenciales, fragmento ni espacios; puede llevar query. Se guarda con el esquema en minúsculas y lo no ASCII de ruta y query con %XX; el host en ASCII (punycode). No puede apuntar a una ruta que la propia regla captura. Como mucho 2048 caracteres. |
| `redirects[].preserve_path` | boolean | No | Por defecto `true`. |
| `redirects[].preserve_query` | boolean | No | Por defecto `true`. |
| `redirects[].status` | integer | No | Uno de `301`, `302`, `307`, `308`. Por defecto `301`. |
| `cache` | [EdgeCache](https://developers.openwaf.dev/es/api/schemas/#edge-cache) | No | Caché de respuestas en el borde, desactivada por defecto. En un PATCH es un JSON merge patch: los objetos se mezclan, las listas (`rules`, `query.params`) se sustituyen, `"dev_mode_until": null` apaga el modo desarrollo y `"cache": null` vuelve a los valores por defecto. |
| `cache.enabled` | boolean | No | Sin reglas solo son elegibles las extensiones estáticas (nunca HTML ni JSON); se respeta el Cache-Control del origen. El WAF va siempre antes. Por defecto `false`. |
| `cache.dev_mode_until` | string (date-time) o null | No | Modo desarrollo: hasta entonces no se usa la caché (no se borra nada); null = apagado. |
| `cache.query` | [CacheQuery](https://developers.openwaf.dev/es/api/schemas/#cache-query) | No |  |
| `cache.query.mode` | string | Sí | all: la query entera (Standard); ignore: sin query en la clave (solo extensiones estáticas); none: solo se usa la caché sin query; include/exclude: solo/todos menos `params`. Uno de `all`, `ignore`, `none`, `include`, `exclude`. |
| `cache.query.sort` | boolean | No | Por defecto `false`. |
| `cache.query.params` | array de string | No | Nombres de parámetro (include/exclude); un * final casa por prefijo (utm_*). Como mucho 32 elementos. |
| `cache.browser_ttl` | integer | No | TTL mínimo hacia el navegador en segundos (un max-age mayor del origen gana); 0 = respetar el origen. Por defecto `0`. De 0 a 31536000. |
| `cache.deception_armor` | boolean | No | No guardar respuestas cuyo Content-Type no encaja con la extensión. Por defecto `true`. |
| `cache.serve_stale` | [CacheServeStale](https://developers.openwaf.dev/es/api/schemas/#cache-serve-stale) | No |  |
| `cache.serve_stale.while_revalidate` | boolean | No | Servir caducado (UPDATING) mientras se revalida en segundo plano. Por defecto `true`. |
| `cache.serve_stale.on_error_max_s` | integer | No | Segundos tras caducar en que se sirve caducado si el origen falla (0 = nunca). Por defecto `86400`. De 0 a 2592000. |
| `cache.max_object_mb` | integer | No | Por defecto `100`. De 1 a 512. |
| `cache.rules` | array de [EdgeCacheRule](https://developers.openwaf.dev/es/api/schemas/#edge-cache-rule) | No | En orden; si varias coinciden, la última gana en cada ajuste. Como mucho 25 elementos. |
| `cache.rules[].id` | string | No | Único; por defecto r1, r2…. Patrón `^[A-Za-z0-9_-]{1,64}$`. |
| `cache.rules[].hosts` | array de string | No | Nombres de la zona a los que se aplica (vacío = todos; no se admite en la lista propia de un nombre). |
| `cache.rules[].match` | object | No | Casan todas las listas presentes y, dentro de cada una, cualquier valor; sin listas casa con todo (solo GET y HEAD). |
| `cache.rules[].match.path_prefix` | array de string | No | La ruta empieza por el valor (forma canónica). Como mucho 32 elementos. |
| `cache.rules[].match.path_exact` | array de string | No | Como mucho 32 elementos. |
| `cache.rules[].match.extensions` | array de string | No | Sin punto, minúsculas; "" = sin extensión. Como mucho 32 elementos. |
| `cache.rules[].match.cookie_present` | array de string | No | Nombre de cookie; un * final casa por prefijo. Como mucho 32 elementos. |
| `cache.rules[].match.query_present` | array de string | No | La query trae un parámetro con ese nombre (decodificado); un * final casa por prefijo. Como mucho 32 elementos. |
| `cache.rules[].action` | string | Sí | bypass: nunca cachear (sin más parámetros); cache: elegible aunque la extensión no sea estática. Uno de `bypass`, `cache`. |
| `cache.rules[].edge_ttl` | object | No |  |
| `cache.rules[].edge_ttl.mode` | string | No | override ignora el CC del origen, quita Set-Cookie y desactiva la armadura anti-engaño. Uno de `respect_origin`, `bypass_by_default`, `override`. |
| `cache.rules[].edge_ttl.ttl_s` | integer | No | Solo con override (1..31536000). De 0 a 31536000. |
| `cache.rules[].edge_ttl.status` | array de object | No | Como mucho 10 elementos. |
| `cache.rules[].edge_ttl.status[].from` | integer | No | De 100 a 599. |
| `cache.rules[].edge_ttl.status[].to` | integer | No | De 100 a 599. |
| `cache.rules[].edge_ttl.status[].ttl_s` | integer | No | -1 = no guardar; 0 = guardar y revalidar siempre. De -1 a 31536000. |
| `cache.rules[].browser_ttl` | object | No |  |
| `cache.rules[].browser_ttl.mode` | string | No | Uno de `respect_origin`, `override`. |
| `cache.rules[].browser_ttl.ttl_s` | integer | No | De 0 a 31536000. |
| `cache.rules[].query` | [CacheQuery](https://developers.openwaf.dev/es/api/schemas/#cache-query) | No |  |
| `cache.rules[].query.mode` | string | Sí | all: la query entera (Standard); ignore: sin query en la clave (solo extensiones estáticas); none: solo se usa la caché sin query; include/exclude: solo/todos menos `params`. Uno de `all`, `ignore`, `none`, `include`, `exclude`. |
| `cache.rules[].query.sort` | boolean | No | Por defecto `false`. |
| `cache.rules[].query.params` | array de string | No | Nombres de parámetro (include/exclude); un * final casa por prefijo (utm_*). Como mucho 32 elementos. |
| `cache.rules[].serve_stale` | [CacheServeStale](https://developers.openwaf.dev/es/api/schemas/#cache-serve-stale) | No |  |
| `cache.rules[].serve_stale.while_revalidate` | boolean | No | Servir caducado (UPDATING) mientras se revalida en segundo plano. Por defecto `true`. |
| `cache.rules[].serve_stale.on_error_max_s` | integer | No | Segundos tras caducar en que se sirve caducado si el origen falla (0 = nunca). Por defecto `86400`. De 0 a 2592000. |
| `cache.rules[].vary_bypass_if_present` | array de string | No | Solo con cache. Cabeceras de petición (se guardan en minúsculas; no accept-encoding, accept ni origin): una petición que trae alguna ni se busca ni se guarda (DYNAMIC); el Vary del origen puede listarlas sin provocar BYPASS. Cambiar la lista vuelve a claves nuevas la caché del nombre. Como mucho 32 elementos. |
| `early_hints` | [EdgeEarlyHints](https://developers.openwaf.dev/es/api/schemas/#edge-early-hints) | No | 103 Early Hints, desactivadas por defecto. En un PATCH es un JSON merge patch: los campos se mezclan, `extra_origins` se sustituye y `"early_hints": null` vuelve a los valores por defecto. |
| `early_hints.enabled` | boolean | No | Responder con 103 a las navegaciones HTML que van al origen. Por defecto `false`. |
| `early_hints.mode` | string | No | De dónde se aprenden los enlaces: cabeceras Link del origen, los primeros 32 KiB del HTML o ambos. Uno de `origin_links`, `html_head`, `both`. Por defecto `both`. |
| `early_hints.extra_origins` | array de string | No | Orígenes además del propio del sitio cuyos recursos se pueden anunciar: `https://host[:puerto]`, sin ruta. Se normalizan (espacios, minúsculas, una `/` final y `:443` fuera); repetidos o no https dan 422. Como mucho 10 elementos. |
| `ai_agents` | [ZoneEdgeAIAgents](https://developers.openwaf.dev/es/api/schemas/#zone-edge-ai-agents) | No |  |
| `ai_agents.enabled` | boolean | No | Por defecto `false`. |
| `ai_agents.mode` | string | No | log: solo cuenta lo que haría (enforced=false). Uno de `log`, `enforce`. Por defecto `log`. |
| `ai_agents.spoofed_challenge` | string | No | enforce: solo los rastreadores verificados se saltan el reto PoW. Uno de `log`, `enforce`. Por defecto `log`. |
| `ai_agents.template` | string | No | Uno de `search_ok_block_training`, `verified_only`, `block_unverified_automation`. |
| `ai_agents.policy` | object | No |  |
| `ai_agents.policy.ai_training` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.ai_search` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.ai_user_agent` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.search_engine` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.unverified_automation` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.spoofed` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.unverified_bot` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.trap_hit` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.secret_probe` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.robots_violation` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.policy.agent_burst` | string | No | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.bot_overrides` | array de object | No | Como mucho 100 elementos. |
| `ai_agents.bot_overrides[].bot_id` | string | Sí |  |
| `ai_agents.bot_overrides[].action` | string | Sí | Uno de `allow`, `log`, `challenge`, `block`, `tarpit`. |
| `ai_agents.trap` | object | No | Enlace trampa; la clave HMAC de la zona se genera al publicar y nunca sale por la 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 | Uno de `yes`, `no`, `""`. |
| `ai_agents.robots.content_signals.ai_input` | string | No | Uno de `yes`, `no`, `""`. |
| `ai_agents.robots.content_signals.ai_train` | string | No | Uno de `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 | Uno de `401`, `402`, `403`, `404`, `410`, `429`, `451`, `503`. Por defecto `403`. |
| `ai_agents.block_response.message` | string | No | Como mucho 1024 caracteres. |
| `ai_agents.endpoints` | array de object | No | Como mucho 20 elementos. |
| `ai_agents.endpoints[].path_prefix` | string | No | Como mucho 256 caracteres. Patrón `^/`. |
| `ai_agents.endpoints[].methods` | array de string | No | POST, PUT, PATCH, GET; con kind mcp, POST, GET y DELETE (con POST; vacío = los tres). |
| `ai_agents.endpoints[].max_body_bytes` | integer | No | 1-1048576; 0 con kind mcp. De 0 a 1048576. |
| `ai_agents.endpoints[].rpm_per_ip` | integer | No | De 0 a 100000. |
| `ai_agents.endpoints[].rpm_per_session` | integer | No | De 0 a 100000. |
| `ai_agents.endpoints[].session_header` | string | No |  |
| `ai_agents.endpoints[].session_cookie` | string | No |  |
| `ai_agents.endpoints[].injection_action` | string | No | Vacío con kind mcp. Uno de `log`, `block`, `""`. |
| `ai_agents.endpoints[].limit_action` | string | No | Vacío con kind mcp. Uno de `log`, `block`, `""`. |
| `ai_agents.endpoints[].kind` | string | No | Ausente = endpoint de IA; mcp = servidor MCP protegido por MCP Shield (los campos de endpoint de IA a 0 o vacíos). Uno de `mcp`. |
| `ai_agents.endpoints[].mcp` | [AiMcpPolicy](https://developers.openwaf.dev/es/api/schemas/#ai-mcp-policy) | No |  |
| `ai_agents.endpoints[].mcp.methods_allowed` | array de string | No | Métodos JSON-RPC exactos o prefijos x/*; por defecto initialize, ping, notifications/*, tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, completion/complete, logging/setLevel. De 1 a 64 elementos. Elementos con el patrón `^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*(/\*)?$`. |
| `ai_agents.endpoints[].mcp.tools_allowed` | array de string | No | [] = cualquier tool no denegada. Como mucho 200 elementos. Elementos con el patrón `^[A-Za-z0-9_.:/-]{1,128}$`. |
| `ai_agents.endpoints[].mcp.tools_denied` | array de string | No | Como mucho 200 elementos. Elementos con el patrón `^[A-Za-z0-9_.:/-]{1,128}$`. |
| `ai_agents.endpoints[].mcp.max_request_kb` | integer | No | Por defecto `256`. De 1 a 1024. |
| `ai_agents.endpoints[].mcp.inspect_requests` | string | No | Uno de `off`, `log`, `block`. Por defecto `log`. |
| `ai_agents.endpoints[].mcp.inspect_responses` | string | No | Uno de `off`, `log`, `block`. Por defecto `log`. |
| `ai_agents.endpoints[].mcp.rate_per_token` | integer | No | Peticiones por minuto por token (Authorization); 0 = sin límite. Por defecto `0`. De 0 a 100000. |
| `rum` | [ZoneEdgeRUM](https://developers.openwaf.dev/es/api/schemas/#zone-edge-rum) | No | Medición de usuarios reales de las Core Web Vitals, desactivada por defecto. En un PATCH es un JSON merge patch: los campos dados sustituyen a los actuales (`exclude_paths` entera), un campo a null vuelve a su valor por defecto y `"rum": null` al objeto por defecto. Se valida aunque esté desactivada. |
| `rum.enabled` | boolean | No | El borde inyecta /__waf/rum.js en las páginas HTML del sitio y recibe sus balizas en /__waf/rum. Por defecto `false`. |
| `rum.sample_rate` | number | No | Fracción de las páginas vistas que llevan el script, (0, 1]. Por defecto `1`. Máximo 1. |
| `rum.exclude_paths` | array de string | No | Prefijos de ruta que nunca llevan el script (se recortan los espacios; las entradas vacías se descartan). Por defecto `[]`. Como mucho 50 elementos. Elementos con el patrón `^/[^?#]*$`. |
| `rate_rules` | array de [EdgeRateRule](https://developers.openwaf.dev/es/api/schemas/#edge-rate-rule) | No | Límites de peticiones por ruta, en orden de evaluación; ninguno por defecto. En un PATCH la lista se sustituye entera (`[]` los quita todos). Cada nombre puede tener su propia lista en `hosts.<nombre>.rate_rules`. Como mucho 25 elementos. |
| `rate_rules[].id` | string | No | Único en la lista; vacío = derivado del nombre (login, login-2…). Los eventos lo llevan como rule_name rate_rule:<id>. Patrón `^[a-z0-9][a-z0-9-]{0,39}$`. |
| `rate_rules[].name` | string | No | Nombre. Como mucho 100 caracteres. |
| `rate_rules[].enabled` | boolean | No | Por defecto `true`. |
| `rate_rules[].hosts` | array de string | No | Nombres de la zona a los que se aplica; [] = todos (siempre [] en la lista de un nombre). |
| `rate_rules[].path_prefix` | string | No | Rutas que empiezan por él en un límite de segmento; / = todas. Se guarda en minúsculas, sin / final. Como mucho 512 caracteres. |
| `rate_rules[].path_exact` | string | No | Solo esta ruta (una / final no cuenta). Se guarda en minúsculas. Como mucho 512 caracteres. |
| `rate_rules[].methods` | array de string | No | Métodos; [] = cualquiera. Por defecto `[]`. Como mucho 16 elementos. |
| `rate_rules[].header_present` | string | No | Solo si la petición lleva esta cabecera. Como mucho 64 caracteres. |
| `rate_rules[].query_present` | string | No | Solo si la petición lleva este parámetro. Como mucho 64 caracteres. |
| `rate_rules[].key` | string | No | Qué se cuenta: IP (IPv6 por /64), cabecera, cookie o campo del cuerpo JSON (sin valor, se cuenta por IP). Uno de `ip`, `header`, `cookie`, `body_field`. Por defecto `ip`. |
| `rate_rules[].key_name` | string | No | Cabecera, cookie o ruta del campo JSON (a.b, hasta 4 niveles); vacío con ip. Como mucho 64 caracteres. |
| `rate_rules[].requests` | integer | Sí | Peticiones. De 1 a 1000000. |
| `rate_rules[].period` | integer | Sí | Segundos. De 1 a 86400. |
| `rate_rules[].algorithm` | string | No | Uno de `sliding_window`, `gcra`. Por defecto `sliding_window`. |
| `rate_rules[].action` | string | No | Uno de `block`, `challenge`, `log`. Por defecto `block`. |
| `rate_rules[].mitigation_timeout` | integer | No | Segundos que la acción sigue aplicándose a la clave tras pasarse; 0 = solo mientras se pasa. Por defecto `0`. De 0 a 86400. |
| `rate_rules[].per_node_divisor` | boolean | No | Dividir requests entre los puntos de entrada del borde que sirven el sitio, para que el límite se aplique al total. Por defecto `true`. |
| `updated_at` | string (date-time) | No | Último cambio. |

#### Respuesta

- `200`: Ajustes guardados (con X-Config-Version y ETag) o, con `dry_run`, el plan (`Applied`). Devuelve [ZoneEdgeSettings](https://developers.openwaf.dev/es/api/schemas/#zone-edge-settings) o [Applied](https://developers.openwaf.dev/es/api/schemas/#applied).

| Campo | Tipo | Descripción |
|---|---|---|
| `zone_id` | string (uuid) | Id de la zona. |
| `mode` | string | Uno de `active`, `log`, `off`. Por defecto `active`. |
| `waf_level` | string | Uno de `off`, `low`, `medium`, `high`. Por defecto `medium`. |
| `rate_limit_rpm` | integer | Límite básico por IP y minuto; 0 = desactivado. Por defecto `0`. Mínimo 0. |
| `timeout_ms` | integer | Por defecto `0`. Mínimo 0. |
| `enable_feed` | boolean | Por defecto `true`. |
| `stack` | string | Uno de `generic`, `wordpress`, `nextjs`, `static`. Por defecto `generic`. |
| `block_page_mode` | string | Uno de `nginx`, `branded`, `apache`, `generic`, `custom`. Por defecto `branded`. |
| `fake_server_header` | string | Por defecto `""`. |
| `block_page_html` | string | Como mucho 262144 caracteres. |
| `access_gate` | object |  |
| `access_gate.enabled` | boolean | Por defecto `false`. |
| `access_gate.path` | string | Por defecto `/`. |
| `access_gate.ttl_hours` | integer | Por defecto `0`. Mínimo 0. |
| `access_gate.emails` | array de string | Correos que pueden pasar la puerta de acceso. |
| `max_body_mb` | integer | Por defecto `0`. Mínimo 0. |
| `redirect_allow_hosts` | array de string |  |
| `cors_allow_origins` | array de string |  |
| `hsts` | object |  |
| `hsts.max_age` | integer | Por defecto `0`. De 0 a 63072000. |
| `hsts.include_subdomains` | boolean |  |
| `hsts.preload` | boolean |  |
| `compression` | boolean | Por defecto `false`. |
| `origin` | object | upstream_url del sitio = scheme://<contenido del registro>[:port] (IPv6 entre corchetes). |
| `origin.scheme` | string | Uno de `http`, `https`. Por defecto `https`. |
| `origin.port` | integer | 0 = el del esquema. Por defecto `0`. De 0 a 65535. |
| `origin.tls_verify` | string | Verificación del certificado de un origen https: full cifra sin verificar; full_strict verifica la cadena (raíces públicas + OpenWAF Origin CA), las fechas, el nombre y la revocación (fallo = 526). Zonas nuevas: full_strict; las anteriores a la función: full. Solo afecta a orígenes https. Uno de `full`, `full_strict`. |
| `origin.server_name` | string | Nombre de SNI y de la verificación; vacío = el host del origen (o el dominio del sitio si el origen es una IP). Como mucho 253 caracteres. |
| `origin.client_cert` | boolean | El borde presenta su certificado de cliente al origen (mTLS). Por defecto `false`. |
| `hosts` | [HostSettingsMap](https://developers.openwaf.dev/es/api/schemas/#host-settings-map) | Ajustes propios de algunos nombres de la zona: el sitio de cada nombre usa los de la zona mezclados con los suyos campo a campo. `{}` sin ninguno (siempre presente en las respuestas). En un PATCH es un JSON merge patch (RFC 7396) por nombre. |
| `redirects` | array de object | Reglas de redirección: el borde responde `status` con `Location` = `target` (+ el resto de la ruta tras `path_prefix` si `preserve_path`, + la query si `preserve_query`) sin llegar al origen. Gana el prefijo más largo del nombre. `[]` sin ninguna (siempre presente en las respuestas). En un PATCH se sustituye entera. El nombre necesita un registro con proxy. Como mucho 50 elementos. |
| `redirects[].host` | string | Nombre completo de la zona (la zona o por debajo, sin comodín); se pasa a minúsculas. |
| `redirects[].path_prefix` | string | Empieza por /; sin ?, #, espacios ni caracteres de control. Se guarda en forma canónica (lo no ASCII y los reservados con %XX en mayúsculas: /año → /a%C3%B1o). /blog casa con /blog y /blog/x, no con /blogging. Por defecto `/`. Como mucho 1024 caracteres. |
| `redirects[].target` | string | URL http(s) absoluta sin credenciales, fragmento ni espacios; puede llevar query. Se guarda con el esquema en minúsculas y lo no ASCII de ruta y query con %XX; el host en ASCII (punycode). No puede apuntar a una ruta que la propia regla captura. Como mucho 2048 caracteres. |
| `redirects[].preserve_path` | boolean | Por defecto `true`. |
| `redirects[].preserve_query` | boolean | Por defecto `true`. |
| `redirects[].status` | integer | Uno de `301`, `302`, `307`, `308`. Por defecto `301`. |
| `cache` | [EdgeCache](https://developers.openwaf.dev/es/api/schemas/#edge-cache) | Caché de respuestas en el borde, desactivada por defecto. En un PATCH es un JSON merge patch: los objetos se mezclan, las listas (`rules`, `query.params`) se sustituyen, `"dev_mode_until": null` apaga el modo desarrollo y `"cache": null` vuelve a los valores por defecto. |
| `cache.enabled` | boolean | Sin reglas solo son elegibles las extensiones estáticas (nunca HTML ni JSON); se respeta el Cache-Control del origen. El WAF va siempre antes. Por defecto `false`. |
| `cache.dev_mode_until` | string (date-time) o null | Modo desarrollo: hasta entonces no se usa la caché (no se borra nada); null = apagado. |
| `cache.query` | [CacheQuery](https://developers.openwaf.dev/es/api/schemas/#cache-query) |  |
| `cache.browser_ttl` | integer | TTL mínimo hacia el navegador en segundos (un max-age mayor del origen gana); 0 = respetar el origen. Por defecto `0`. De 0 a 31536000. |
| `cache.deception_armor` | boolean | No guardar respuestas cuyo Content-Type no encaja con la extensión. Por defecto `true`. |
| `cache.serve_stale` | [CacheServeStale](https://developers.openwaf.dev/es/api/schemas/#cache-serve-stale) |  |
| `cache.max_object_mb` | integer | Por defecto `100`. De 1 a 512. |
| `cache.rules` | array de [EdgeCacheRule](https://developers.openwaf.dev/es/api/schemas/#edge-cache-rule) | En orden; si varias coinciden, la última gana en cada ajuste. Como mucho 25 elementos. |
| `early_hints` | [EdgeEarlyHints](https://developers.openwaf.dev/es/api/schemas/#edge-early-hints) | 103 Early Hints, desactivadas por defecto. En un PATCH es un JSON merge patch: los campos se mezclan, `extra_origins` se sustituye y `"early_hints": null` vuelve a los valores por defecto. |
| `early_hints.enabled` | boolean | Responder con 103 a las navegaciones HTML que van al origen. Por defecto `false`. |
| `early_hints.mode` | string | De dónde se aprenden los enlaces: cabeceras Link del origen, los primeros 32 KiB del HTML o ambos. Uno de `origin_links`, `html_head`, `both`. Por defecto `both`. |
| `early_hints.extra_origins` | array de string | Orígenes además del propio del sitio cuyos recursos se pueden anunciar: `https://host[:puerto]`, sin ruta. Se normalizan (espacios, minúsculas, una `/` final y `:443` fuera); repetidos o no https dan 422. Como mucho 10 elementos. |
| `ai_agents` | [ZoneEdgeAIAgents](https://developers.openwaf.dev/es/api/schemas/#zone-edge-ai-agents) |  |
| `rum` | [ZoneEdgeRUM](https://developers.openwaf.dev/es/api/schemas/#zone-edge-rum) | Medición de usuarios reales de las Core Web Vitals, desactivada por defecto. En un PATCH es un JSON merge patch: los campos dados sustituyen a los actuales (`exclude_paths` entera), un campo a null vuelve a su valor por defecto y `"rum": null` al objeto por defecto. Se valida aunque esté desactivada. |
| `rum.enabled` | boolean | El borde inyecta /__waf/rum.js en las páginas HTML del sitio y recibe sus balizas en /__waf/rum. Por defecto `false`. |
| `rum.sample_rate` | number | Fracción de las páginas vistas que llevan el script, (0, 1]. Por defecto `1`. Máximo 1. |
| `rum.exclude_paths` | array de string | Prefijos de ruta que nunca llevan el script (se recortan los espacios; las entradas vacías se descartan). Por defecto `[]`. Como mucho 50 elementos. Elementos con el patrón `^/[^?#]*$`. |
| `rate_rules` | array de [EdgeRateRule](https://developers.openwaf.dev/es/api/schemas/#edge-rate-rule) | Límites de peticiones por ruta, en orden de evaluación; ninguno por defecto. En un PATCH la lista se sustituye entera (`[]` los quita todos). Cada nombre puede tener su propia lista en `hosts.<nombre>.rate_rules`. Como mucho 25 elementos. |
| `updated_at` | string (date-time) | Último cambio. |

Cabeceras de la respuesta: `ETag`, `X-Config-Version`.

#### Errores

| Estado | Descripción |
|---|---|
| `400` | Petición mal formada (`bad_request`). |
| `409` | `zone_merged`: la zona está fusionada en otra. |
| `412` | `version_conflict`: If-Match no coincide con la versión actual. |
| `422` | Error de validación (`invalid`) o motivo ausente/no válido (`reason_required`). |

#### Ejemplo

**curl**


```sh
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
    }
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de 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
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de 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,
)
```



```json
{
  "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": [
      "ops@example.com"
    ]
  },
  "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"
}
```

## Ver la configuración efectiva de un nombre

```http
GET /v1/orgs/{org}/zones/{zone}/hosts/{host}/effective
```

Muestra la configuración que recibe un nombre de la zona en el borde, tal y como la reciben los nodos, los ajustes de la zona mezclados con los propios del nombre, las reglas que le aplican y sus redirecciones. La cabecera `ETag` lleva la versión de la zona.

**Rol necesario:** `readonly` o superior.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |
| `host` | ruta | string | Sí | Nombre completo (la zona o por debajo). |

#### Respuesta

- `200`: Devuelve [EffectiveConfig](https://developers.openwaf.dev/es/api/schemas/#effective-config).

| Campo | Tipo | Descripción |
|---|---|---|
| `zone_id` | string (uuid) | Id de la zona. |
| `host` | string |  |
| `proxied` | boolean | false: el nombre no tiene registro con proxy y `site` es una vista previa sin upstream_url. |
| `items` | array de object |  |
| `items[].site` | [EdgeSite](https://developers.openwaf.dev/es/api/schemas/#edge-site) o any o null |  |

Cabeceras de la respuesta: `ETag`.

#### Errores

| Estado | Descripción |
|---|---|
| `404` | Zona inexistente o `host_not_in_zone`: el nombre no es la zona ni está por debajo. |
| `422` | Error de validación (`invalid`). |

#### Ejemplo

**curl**


```sh
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"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.getHostEffective({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  host: "www.example.com"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.get_host_effective(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d", host="www.example.com")
```



```json
{
  "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": []
      }
    }
  ]
}
```
