## Value-added protection schemes (`protect-schemes`)

One scheme each for replay, data leak and web tamper protection (one per tenant; admins edit the platform scheme); saving also updates sites that follow the scheme, sites with their own settings are left alone; part of WAF Enhanced.

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

### GET `/api/v2/protect-schemes/leak`

Get the data leak protection scheme · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/protect-schemes/leak" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/protect-schemes/leak`

Save the data leak protection scheme · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `custom` | SiteLeakGuardCustom[] | Yes | Custom regular expressions, at most 16. |
| `detect` | SiteLeakGuardDetect | Yes | Action per built-in pattern; omit a pattern to not check it. |
| `exclude` | string[] | Yes | Path prefixes that are not checked (a trailing * is optional), at most 32. |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (when null, i.e. never saved, 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. |
| `observe_days` | integer | Yes | Days a site observes when turned on by the scheme (record only, no action); 0 = act right away. |

```sh
curl -X PUT "https://panel.example.com/api/v2/protect-schemes/leak" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### GET `/api/v2/protect-schemes/replay`

Get the replay protection scheme · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/protect-schemes/replay" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/protect-schemes/replay`

Save the replay protection scheme · 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). |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (when null, i.e. never saved, 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/protect-schemes/replay" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### GET `/api/v2/protect-schemes/tamper`

Get the web tamper protection scheme · Authentication: User (some parameters admin only)

```sh
curl "https://panel.example.com/api/v2/protect-schemes/tamper" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### PUT `/api/v2/protect-schemes/tamper`

Save the web tamper protection scheme · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | ReplayGuardAction | Yes | Without a token or on replay: block = 403; challenge = JS challenge. Omitted = block. Values: ["block","challenge"] |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (when null, i.e. never saved, 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. |
| `mode` | string | Yes | What counts as tampering: structure = structural changes only; full = any change to the page. Values: ["structure","full"] |
| `observe_days` | integer | Yes | Days a site observes when turned on by the scheme (record only, no action); 0 = act right away. |
| `scopes` | SiteTamperScope[] | Yes | Protected scopes, at most 50. |

```sh
curl -X PUT "https://panel.example.com/api/v2/protect-schemes/tamper" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/protect-schemes/{kind}/apply`

Apply the scheme to sites in bulk · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `auto_join` | object |  | The rule that sites newly entering the range join the scheme automatically; omitted when off. |
| `dry_run` | boolean |  | When true, only return the counts of what would happen; nothing is written. Default: false |
| `override_custom` | boolean |  | When true, sites with their own settings are switched to the scheme too; by default they are left alone and counted in skipped_custom. Default: false |
| `selection` | ProtectSchemeSelection | Yes | The range of sites to apply to. |

```sh
curl -X POST "https://panel.example.com/api/v2/protect-schemes/{kind}/apply" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### GET `/api/v2/protect-schemes/{kind}/sites`

List the sites that have this protection set up · Authentication: User (some parameters admin only)

Query parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `page` | integer |  | Page number, starting from 1 Default: 1 |
| `page_size` | integer |  | Items per page Default: 20 |
| `tenant_id` | integer |  | Admin only: filter by tenant. Ignored for tenant credentials, which only see their own tenant's data |
| `q` | string |  | Fuzzy search by domain or site name (case-insensitive). |
| `status` | ProtectSchemeSiteStatus |  | Filter by status. Values: ["enabled","disabled","pending","capture_failed"] |
| `source` | ProtectSiteSource |  | Filter by source: scheme = follows the scheme; custom = set for the site alone (including turned off). Values: ["scheme","custom"] (read-only) |

```sh
curl "https://panel.example.com/api/v2/protect-schemes/{kind}/sites" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

### POST `/api/v2/protect-schemes/{kind}/sites/bulk`

Reset or turn off this protection for many sites · Authentication: User (some parameters admin only)

Request body:

| Field | Type | Required | Description |
|---|---|---|---|
| `action` | ReplayGuardAction | Yes | Without a token or on replay: block = 403; challenge = JS challenge. Omitted = block. Values: ["block","challenge"] |
| `filter` | ProtectSchemeSiteFilter |  | The same filters as the list; expanded to a set of sites by the server. |
| `ids` | integer[] |  | Site ids. |

```sh
curl -X POST "https://panel.example.com/api/v2/protect-schemes/{kind}/sites/bulk" \
  -H "X-API-Key: $GOCDN_API_KEY"
```

Value-added protection schemes 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. |
| `applied` | integer |  | Sites written (to be written on dry_run). |
| `auto_join` | object |  | The rule that sites newly entering the range join the scheme automatically; omitted when off. (read-only) |
| `bind_ip` | boolean |  | Also bind the token to the client IP (a new IP invalidates it). |
| `counts` | ProtectSchemeCounts |  | Site counts among the sites visible to the caller: total, turned on, following the scheme, and set alone. (read-only) |
| `custom` | SiteLeakGuardCustom[] |  | Custom regular expressions, at most 16. |
| `demoted_sites` | integer |  | Sites switched to their own settings with values kept because their owner lacks WAF Enhanced (only in the save response). (read-only) |
| `detect` | SiteLeakGuardDetect |  | Action per built-in pattern; omit a pattern to not check it. |
| `domain` | string |  | Primary domain (the site name when there is none). |
| `dry_run` | boolean |  | When true, only return the counts of what would happen; nothing is written. Default: false |
| `enabled` | boolean |  | Whether it is on. |
| `exclude` | string[] |  | Path prefixes that are not checked (a trailing * is optional), at most 32. |
| `expected_updated_at` | string |  | Optimistic lock - the updated_at as the caller read it (when null, i.e. never saved, 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. |
| `failed` | ProtectSchemeFailure[] |  | Sites that could not be handled, with the reason; at most 100. |
| `failed_total` | integer |  | Total sites that could not be handled (larger than failed when truncated). |
| `filter` | ProtectSchemeSiteFilter |  | The same filters as the list; expanded to a set of sites by the server. |
| `gate` | ProtectSchemeGate |  | WAF Enhanced status of the scheme owner (the tenant or the platform): full = editable; readonly and basic = may only be reduced. Values: ["full","readonly","basic"] (read-only) |
| `ids` | integer[] |  | Site ids. |
| `matched` | integer |  | Sites in the range the caller can see. |
| `methods` | ReplayGuardMethod[] |  | Protected methods; omitted = POST/PUT/PATCH/DELETE. |
| `mode` | string |  | What counts as tampering: structure = structural changes only; full = any change to the page. Values: ["structure","full"] |
| `observe_days` | integer |  | Days a site observes when turned on by the scheme (record only, no action); 0 = act right away. |
| `observe_until` | integer |  | End of the observation period (Unix milliseconds); set only for leak protection that is on and still observing, null when expired, never observing, off, or for other protections. |
| `override_custom` | boolean |  | When true, sites with their own settings are switched to the scheme too; by default they are left alone and counted in skipped_custom. Default: false |
| `owner` | string |  | Owner username. |
| `paths` | ReplayGuardPath[] |  | Protected API paths, at most 64; empty = nothing protected. |
| `scopes` | SiteTamperScope[] |  | Protected scopes, at most 50. |
| `selection` | ProtectSchemeSelection | Yes | The range of sites to apply to. |
| `site_id` | integer |  | Site id. |
| `skipped_custom` | integer |  | Sites left alone because they have their own settings (override_custom is false). |
| `skipped_gate` | integer |  | Sites not written because their owner's WAF Enhanced is not full. |
| `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) |
| `status` | ProtectSchemeSiteStatus |  | Status of the site's setting: enabled; disabled; pending = on but the tamper baseline is still queued; capture_failed = on but baseline capture failed repeatedly and stopped retrying (the last two exist for web tamper protection only). Values: ["enabled","disabled","pending","capture_failed"] |
| `tenant_id` | integer |  | Owner user id. |
| `today_blocked` | integer |  | Block and error-page count today (UTC), excluding hits during the observation period (leak protection only, same basis as counts.today_blocked in the scheme response); null for other protections, or when ClickHouse is unavailable or the query fails. |
| `today_hits` | integer |  | Hits today (data leak protection = masked plus blocked outside the observation period, i.e. today_masked + today_blocked; web tamper protection = pages found tampered today); null for replay protection. |
| `today_masked` | integer |  | Masking count today (UTC), excluding hits during the observation period (leak protection only, same basis as counts.today_masked in the scheme response); null for other protections, or when ClickHouse is unavailable or the query fails. |
| `ttl_s` | integer |  | Token lifetime in seconds, also used for signed-header timestamps; omitted = 120. |
| `updated_at` | string |  | Version (null if never saved); use expected_updated_at on update. (read-only) |
| `updated_sites` | integer |  | Sites following the scheme that this save updated (only in the save response). (read-only) |
