Replace the attack protection settings
PUT /v1/attack-protection · permission attack-protection:write
Replaces the whole attack protection object (application/json). Sections and fields you leave out return to their defaults. If-Match is required: missing gives 428, a stale ETag gives 412. Writing the same object again writes nothing and keeps the ETag. To change a few fields, use PATCH.
Request
| Field | Type | Required | Description |
|---|---|---|---|
If-Match |
header | yes | The ETag of the object as last read. Missing: 428; stale: 412. |
| body | object | yes | The configured object, at most 32 KiB. Each section is optional; each field inside a section is optional. |
| Field | Type | Range or values | Default | Floor |
|---|---|---|---|---|
brute_force.mode |
string | log, enforce |
enforce |
|
brute_force.max_failures |
integer | 5-100 | 20 | at most 100 |
brute_force.window_minutes |
integer | 5-60 | 15 | |
brute_force.lock_minutes |
integer | 5-60 | 15 | at least 5 |
brute_force.notify_user |
boolean | true |
||
suspicious_ip_throttling.mode |
string | log, enforce |
enforce |
|
suspicious_ip_throttling.login_failures_per_ip_per_day |
integer | 10-10000 | 100 | |
suspicious_ip_throttling.signups_per_ip_per_hour |
integer | 1-20 | 20 | at most 20 |
suspicious_ip_throttling.allowlist |
string[] | up to 100 CIDR prefixes; IPv4 no wider than /8, IPv6 no wider than /32 | [] |
|
credential_stuffing.mode |
string | log, enforce |
enforce |
|
credential_stuffing.attack_mode_auto |
boolean | true |
||
credential_stuffing.attack_mode_duration_minutes |
integer | 15-240 | 30 | |
bot_protection.mode |
string | off, risk_based, always |
risk_based |
|
bot_protection.provider |
string | altcha |
altcha |
|
breached_password.on_signup |
string | block |
block |
block |
breached_password.on_password_change |
string | block |
block |
block |
breached_password.on_login |
string | require_reset, notify |
require_reset |
|
breached_password.notify_user |
boolean | true |
||
signup_protection.block_disposable_email |
boolean | true |
||
signup_protection.block_email_subaddress |
boolean | false |
||
signup_protection.max_signups_per_device_30d |
integer | 1-50 | 3 | |
sms_protection.allowed_countries |
string[] | up to 250 ISO 3166-1 alpha-2 codes | [] (plan default) |
|
sms_protection.daily_cap |
integer | null | 1-1000000; null is the plan value |
null |
|
sms_protection.conversion_breaker |
boolean | true |
true |
|
risk_policy.mode |
string | log, enforce (enforce needs the Startup plan or higher) |
log |
|
risk_policy.challenge_threshold |
integer | 3-8 | 4 | |
risk_policy.block_threshold |
integer | null | 6-12, greater than challenge_threshold; null is off |
null |
|
risk_policy.notify_user_on_new_device |
boolean | true |
A value outside the security floor is refused with 422 below_security_floor even though it is inside the field's general range: the floor protects every environment and cannot be lowered.
Behavior
- The change applies to new sign-ins immediately: the cached settings are invalidated in the same transaction.
- The audit record carries the changed field names and the previous and new objects.
Response
200 OK with the object, as in Get the attack protection settings, and the new ETag.
Errors
| HTTP | code | When |
|---|---|---|
| 400 | unknown_field |
A section or field is not part of the object. errors[] lists the pointers. |
| 400 | malformed_request |
The body is not valid JSON, or the If-Match header is malformed. |
| 401 | unauthenticated |
The credential is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The credential lacks attack-protection:write. |
| 403 | plan_feature_unavailable |
risk_policy.mode is enforce and the plan does not include it; the body carries feature and required_plan. |
| 412 | precondition_failed |
The object changed since you read it. Read it again and retry. |
| 413 | payload_too_large |
The object is larger than 32 KiB. |
| 422 | validation_failed |
A value has the wrong type or is out of range, or block_threshold is not greater than challenge_threshold. errors[] lists the pointers. |
| 422 | below_security_floor |
A value is below the security floor. Each errors[] entry names the field and its floor in detail. |
| 422 | allowlist_range_too_broad |
An allowlist prefix is wider than /8 (IPv4) or /32 (IPv6). |
| 428 | precondition_required |
If-Match is missing. |
| 429 | rate_limited |
The rate limit was exceeded; retry after the Retry-After period. |
| 503 | service_unavailable |
A dependency is temporarily unavailable; retry after the Retry-After period. |
When several kinds of error are present, only the most severe kind is returned, in this order: unknown_field, validation_failed, below_security_floor, allowlist_range_too_broad, plan_feature_unavailable. The written values are never echoed back.
Example
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"}}'