## Network-wide URL protection settings (`url-guard-settings`)

Platform-wide default switch and thresholds for network-wide URL protection; admin only. Per-site switch and thresholds live in the site security settings.

Authentication: Admin only · Docs: https://www-demo.kuxitong.com/en/docs/api/url-guard-settings

### GET `/api/v2/url-guard-settings`

Get network-wide URL protection settings · Authentication: Admin only

```sh
curl "https://panel.example.com/api/v2/url-guard-settings" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/url-guard-settings`

Update network-wide URL protection settings · Authentication: Admin only

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | string | Yes | Action on the URL once triggered - challenge, block or per-IP rate limit. Values: ["challenge","block","limit"] |
| `baseline_ratio` | integer |  | Triggering also requires this many times the usual level (usual = the URL's average QPS over the past hour, excluding the last minute). The panel asks for at least 1, default 5; 0 = ignore the multiple (legacy configs lack the field and keep the old rule). Applies to new triggers only - renewing an active rule looks at QPS alone. Omit on update to keep the stored value. |
| `enabled` | boolean | Yes | Global default switch, used by sites that do not set their own. |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (use 1970-01-01T00:00:00Z when the read value is null). If it differs from the stored one, 409 stale_version is returned and nothing is saved; omit to skip the check. |
| `gate` | string |  | WAF enhanced (waf_enhanced) gate - full (active), readonly (expired but within the read-only buffer, License.readonly_days, 7 days by default; still effective, settings read-only), basic (not purchased or fallen back, nothing is pushed); ignored on update. Values: ["full","readonly","basic"] |
| `gate_expired_at` | string |  | Administrator responses with gate_reason=expired only - when the platform WAF Enhanced license expired; not present in tenant responses. |
| `gate_owner` | WafGateOwner |  | The owner of the site, in administrator responses only; tenant responses carry no one else's information. |
| `gate_packages` | string[] |  | Administrator responses only - names of the owner's unexpired packages (at most 20); not present in tenant responses. |
| `gate_reason` | WafGateReason |  | Why WAF Enhanced (waf_enhanced) is not in effect; absent when gate is full or readonly. package = the platform is licensed but the owner has no unexpired package that includes WAF Enhanced; license = the platform has not enabled it (never licensed, revoked by the provider, or the license was disabled); expired = the platform license has expired and its read-only buffer period (License.readonly_days, default 7 days) has passed, so it fell back to basic. Values: ["package","license","expired"] |
| `hold_s` | integer | Yes | Seconds the rule is held; it is renewed while still over the threshold and lapses once traffic drops. |
| `kind` | string |  | Challenge type for action=challenge (default js); must be empty for other actions. Values: ["js","redirect","delay_jump","cookie","captcha","slide","click","rotate"] |
| `max_per_site` | integer | Yes | Max rules in effect at the same time per site. |
| `qps` | integer | Yes | Trigger only when the network-wide total QPS of a URL is at least this (and at least baseline_ratio times its usual level). |
| `rate` | integer |  | Max requests per IP per 10 seconds for action=limit; must be 0 for other actions. |
| `updated_at` | string |  | Version in the response (null if never changed); ignored on update - use expected_updated_at for the optimistic lock. |

```sh
curl -X PUT "https://panel.example.com/api/v2/url-guard-settings" \
  -H "X-API-Key: $GOCDN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"challenge","baseline_ratio":5,"enabled":true,"hold_s":600,"kind":"js","max_per_site":20,"qps":300,"rate":20}'
```

Network-wide URL protection settings object:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | string |  | Action on the URL once triggered - challenge, block or per-IP rate limit. Values: ["challenge","block","limit"] |
| `baseline_ratio` | integer |  | Triggering also requires this many times the usual level (usual = the URL's average QPS over the past hour, excluding the last minute). The panel asks for at least 1, default 5; 0 = ignore the multiple (legacy configs lack the field and keep the old rule). Applies to new triggers only - renewing an active rule looks at QPS alone. Omit on update to keep the stored value. |
| `enabled` | boolean |  | Global default switch, used by sites that do not set their own. |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (use 1970-01-01T00:00:00Z when the read value is null). If it differs from the stored one, 409 stale_version is returned and nothing is saved; omit to skip the check. |
| `gate` | string |  | WAF enhanced (waf_enhanced) gate - full (active), readonly (expired but within the read-only buffer, License.readonly_days, 7 days by default; still effective, settings read-only), basic (not purchased or fallen back, nothing is pushed); ignored on update. Values: ["full","readonly","basic"] (read-only) |
| `gate_expired_at` | string |  | Administrator responses with gate_reason=expired only - when the platform WAF Enhanced license expired; not present in tenant responses. (read-only) |
| `gate_owner` | WafGateOwner |  | The owner of the site, in administrator responses only; tenant responses carry no one else's information. (read-only) |
| `gate_packages` | string[] |  | Administrator responses only - names of the owner's unexpired packages (at most 20); not present in tenant responses. (read-only) |
| `gate_reason` | WafGateReason |  | Why WAF Enhanced (waf_enhanced) is not in effect; absent when gate is full or readonly. package = the platform is licensed but the owner has no unexpired package that includes WAF Enhanced; license = the platform has not enabled it (never licensed, revoked by the provider, or the license was disabled); expired = the platform license has expired and its read-only buffer period (License.readonly_days, default 7 days) has passed, so it fell back to basic. Values: ["package","license","expired"] (read-only) |
| `hold_s` | integer |  | Seconds the rule is held; it is renewed while still over the threshold and lapses once traffic drops. |
| `kind` | string |  | Challenge type for action=challenge (default js); must be empty for other actions. Values: ["js","redirect","delay_jump","cookie","captcha","slide","click","rotate"] |
| `max_per_site` | integer |  | Max rules in effect at the same time per site. |
| `qps` | integer |  | Trigger only when the network-wide total QPS of a URL is at least this (and at least baseline_ratio times its usual level). |
| `rate` | integer |  | Max requests per IP per 10 seconds for action=limit; must be 0 for other actions. |
| `updated_at` | string |  | Version in the response (null if never changed); ignored on update - use expected_updated_at for the optimistic lock. (read-only) |
