## Replay protection (`site-replay-guards`)

Replay protection of one site: protected APIs need a one-time token or the app signature headers, otherwise the request is blocked or challenged; can be set for the site alone or restored to the scheme; part of WAF Enhanced.

Authentication: User (some parameters admin only) · Docs: https://www-demo.kuxitong.com/en/docs/api/site-replay-guards

### GET `/api/v2/site-replay-guards/{site_id}`

Get site replay protection settings · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/site-replay-guards/{site_id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/site-replay-guards/{site_id}`

Set site replay protection · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | ReplayGuardAction |  | Without a token or on replay: block = 403; challenge = JS challenge. Omitted = block. Values: ["block","challenge"] |
| `app_salt` | string |  | Signing salt (app key, 32-byte hex); with app_sign on but no salt, signed headers are never accepted. |
| `app_sign` | boolean |  | App signed-header mode - requests carrying the signature headers are checked by signature, no token needed. |
| `bind_ip` | boolean |  | Also bind the token to the client IP (a new IP invalidates it). |
| `enabled` | boolean | Yes | Whether enabled. |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (when null, i.e. never set, use 1970-01-01T00:00:00Z). If it differs from the stored one, 409 stale_version is returned and nothing is saved; omit to skip the check. |
| `methods` | ReplayGuardMethod[] |  | Protected methods; omitted = POST/PUT/PATCH/DELETE. |
| `paths` | ReplayGuardPath[] |  | Protected API paths, at most 64; empty = nothing protected. |
| `ttl_s` | integer |  | Token lifetime in seconds, also used for signed-header timestamps; omitted = 120. |

```sh
curl -X PUT "https://panel.example.com/api/v2/site-replay-guards/{site_id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### DELETE `/api/v2/site-replay-guards/{site_id}`

Restore site replay protection to the scheme · Authentication: User (some parameters admin only)

```sh
curl -X DELETE "https://panel.example.com/api/v2/site-replay-guards/{site_id}" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

Replay protection object:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | ReplayGuardAction |  | Without a token or on replay: block = 403; challenge = JS challenge. Omitted = block. Values: ["block","challenge"] |
| `app_salt` | string |  | Signing salt (app key, 32-byte hex); with app_sign on but no salt, signed headers are never accepted. |
| `app_sign` | boolean |  | App signed-header mode - requests carrying the signature headers are checked by signature, no token needed. |
| `bind_ip` | boolean |  | Also bind the token to the client IP (a new IP invalidates it). |
| `enabled` | boolean |  | Whether enabled. |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (when null, i.e. never set, use 1970-01-01T00:00:00Z). 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 status of the site's tenant: full = editable; readonly = expired but within the read-only buffer (License.readonly_days, 7 days by default), still in effect but read-only; basic = not subscribed or past the buffer, not in effect and settings kept. 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) |
| `methods` | ReplayGuardMethod[] |  | Protected methods; omitted = POST/PUT/PATCH/DELETE. |
| `paths` | ReplayGuardPath[] |  | Protected API paths, at most 64; empty = nothing protected. |
| `site_id` | integer |  | Site ID (read-only) |
| `source` | ProtectSiteSource |  | Where the site's setting comes from: scheme = follows the scheme (changes when the scheme is saved); custom = set for this site alone (including turned off and rows migrated from before). Values: ["scheme","custom"] (read-only) |
| `ttl_s` | integer |  | Token lifetime in seconds, also used for signed-header timestamps; omitted = 120. |
| `updated_at` | string |  | Version (null if never set); use expected_updated_at on update. (read-only) |
