
# Reemplazar la configuración de protección frente a ataques

`PUT /v1/attack-protection` · permiso `attack-protection:write`

Reemplaza el objeto completo de protección frente a ataques (`application/json`). Las secciones y los campos que omita vuelven a sus valores predeterminados. `If-Match` es obligatorio: si falta, se devuelve `428`; si el `ETag` está desactualizado, `412`. Volver a escribir el mismo objeto no escribe nada y conserva el `ETag`. Para cambiar unos pocos campos, use `PATCH`.

## Solicitud

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `If-Match` | encabezado | sí | El `ETag` del objeto tal como se leyó por última vez. Si falta: `428`; si está desactualizado: `412`. |
| cuerpo | objeto | sí | El objeto configurado, como máximo 32 KiB. Cada sección es opcional; cada campo dentro de una sección también lo es. |

| Campo | Tipo | Rango o valores | Predeterminado | Mínimo |
|---|---|---|---|---|
| `brute_force.mode` | texto | `log`, `enforce` | `enforce` | |
| `brute_force.max_failures` | entero | 5-100 | 20 | como máximo 100 |
| `brute_force.window_minutes` | entero | 5-60 | 15 | |
| `brute_force.lock_minutes` | entero | 5-60 | 15 | al menos 5 |
| `brute_force.notify_user` | booleano | | `true` | |
| `suspicious_ip_throttling.mode` | texto | `log`, `enforce` | `enforce` | |
| `suspicious_ip_throttling.login_failures_per_ip_per_day` | entero | 10-10000 | 100 | |
| `suspicious_ip_throttling.signups_per_ip_per_hour` | entero | 1-20 | 20 | como máximo 20 |
| `suspicious_ip_throttling.allowlist` | array de texto | hasta 100 prefijos CIDR; IPv4 no más amplio que /8, IPv6 no más amplio que /32 | `[]` | |
| `credential_stuffing.mode` | texto | `log`, `enforce` | `enforce` | |
| `credential_stuffing.attack_mode_auto` | booleano | | `true` | |
| `credential_stuffing.attack_mode_duration_minutes` | entero | 15-240 | 30 | |
| `bot_protection.mode` | texto | `off`, `risk_based`, `always` | `risk_based` | |
| `bot_protection.provider` | texto | `altcha` | `altcha` | |
| `breached_password.on_signup` | texto | `block` | `block` | `block` |
| `breached_password.on_password_change` | texto | `block` | `block` | `block` |
| `breached_password.on_login` | texto | `require_reset`, `notify` | `require_reset` | |
| `breached_password.notify_user` | booleano | | `true` | |
| `signup_protection.block_disposable_email` | booleano | | `true` | |
| `signup_protection.block_email_subaddress` | booleano | | `false` | |
| `signup_protection.max_signups_per_device_30d` | entero | 1-50 | 3 | |
| `sms_protection.allowed_countries` | array de texto | hasta 250 códigos ISO 3166-1 alpha-2 | `[]` (predeterminado del plan) | |
| `sms_protection.daily_cap` | entero \| null | 1-1000000; `null` es el valor del plan | `null` | |
| `sms_protection.conversion_breaker` | booleano | | `true` | `true` |
| `risk_policy.mode` | texto | `log`, `enforce` (`enforce` requiere el plan Startup o superior) | `log` | |
| `risk_policy.challenge_threshold` | entero | 3-8 | 4 | |
| `risk_policy.block_threshold` | entero \| null | 6-12, mayor que `challenge_threshold`; `null` está desactivado | `null` | |
| `risk_policy.notify_user_on_new_device` | booleano | | `true` | |

Un valor por debajo del mínimo de seguridad se rechaza con `422 below_security_floor`, aunque esté dentro del rango general del campo: el mínimo protege a todos los entornos y no puede rebajarse.

## Comportamiento

- El cambio se aplica de inmediato a los nuevos inicios de sesión: la configuración en caché se invalida en la misma transacción.
- El registro de auditoría contiene los nombres de los campos modificados y los objetos anterior y nuevo.

## Respuesta

`200 OK` con el objeto, como en [Obtener la configuración de protección frente a ataques](get.md), y el nuevo `ETag`.

## Errores

| HTTP | código | Cuándo |
|---|---|---|
| 400 | `unknown_field` | Una sección o un campo no forma parte del objeto. `errors[]` enumera los punteros. |
| 400 | `malformed_request` | El cuerpo no es JSON válido o el encabezado `If-Match` está mal formado. |
| 401 | `unauthenticated` | La credencial falta, no es válida o fue revocada. |
| 403 | `insufficient_permissions` | La credencial no tiene `attack-protection:write`. |
| 403 | `plan_feature_unavailable` | `risk_policy.mode` es `enforce` y el plan no lo incluye; el cuerpo contiene `feature` y `required_plan`. |
| 412 | `precondition_failed` | El objeto cambió desde que lo leyó. Léalo de nuevo y reintente. |
| 413 | `payload_too_large` | El objeto supera 32 KiB. |
| 422 | `validation_failed` | Un valor tiene un tipo incorrecto o está fuera de rango, o `block_threshold` no es mayor que `challenge_threshold`. `errors[]` enumera los punteros. |
| 422 | `below_security_floor` | Un valor está por debajo del mínimo de seguridad. Cada entrada de `errors[]` indica en `detail` el campo y su mínimo. |
| 422 | `allowlist_range_too_broad` | Un prefijo de la lista de permitidos es más amplio que /8 (IPv4) o /32 (IPv6). |
| 428 | `precondition_required` | Falta `If-Match`. |
| 429 | `rate_limited` | Se superó el límite de frecuencia; reintente después del periodo indicado en `Retry-After`. |
| 503 | `service_unavailable` | Una dependencia no está disponible temporalmente; reintente después del periodo indicado en `Retry-After`. |

Si hay varios tipos de error, solo se devuelve el más grave, en este orden: `unknown_field`, `validation_failed`, `below_security_floor`, `allowlist_range_too_broad`, `plan_feature_unavailable`. Los valores escritos nunca se devuelven en la respuesta.

## Ejemplo

```bash
curl -X PUT https://api.eu.keenauth.com/v1/attack-protection \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "3f1c9a0b5d7e2a4c6b8d0e1f2a3b4c5d"' \
  -d '{"brute_force": {"max_failures": 10, "lock_minutes": 30}, "bot_protection": {"mode": "always"}}'
```
