{
  "openapi": "3.1.0",
  "info": {
    "title": "Nexren Shield Customer REST API",
    "version": "1.0.0",
    "description": "High-performance edge security, DDoS mitigation, Origin Lock, and cache control API for Nexren Shield.\n\n**Idempotency:** POST/PUT/PATCH/DELETE requests may send an `Idempotency-Key` header (1-128 characters of `[A-Za-z0-9_.:-]`). The first response for the same token, method, path and key is stored for 24 hours and replayed on retries with `Idempotent-Replayed: true`. Reusing a key with a different body returns 422; a concurrent duplicate returns 409. 5xx responses are not stored.",
    "contact": {
      "name": "Nexren Shield Security Operations",
      "url": "https://shield.nexren.info",
      "email": "support@nexren.com"
    }
  },
  "servers": [
    {
      "url": "https://shield.nexren.info/api",
      "description": "Nexren Shield Production API Gateway"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyAuth": []
    }
  ],
  "paths": {
    "/v1/whoami": {
      "get": {
        "summary": "Inspect Active API Token & Auth Status",
        "description": "Returns token metadata, granted scopes, client IP, and rate limit status.",
        "tags": [
          "Identity"
        ],
        "responses": {
          "200": {
            "description": "Token and auth details"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/identity": {
      "get": {
        "summary": "Get Authenticated Identity Profile",
        "description": "Returns the authenticated user profile, organization info, and active plan.",
        "tags": [
          "Identity"
        ],
        "responses": {
          "200": {
            "description": "Identity profile details"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/sites": {
      "get": {
        "summary": "List Protected Websites",
        "description": "Returns list of all sites accessible to the authenticated user.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "includePending",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Include sites in pending setup"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by domain keyword"
          }
        ],
        "responses": {
          "200": {
            "description": "List of sites"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden - Insufficient scope (requires sites:read)"
          }
        }
      },
      "post": {
        "summary": "Create / Onboard New Site",
        "description": "Registers a new domain for edge protection. The caller must prove DNS control first: a CNAME pointing to the Shield target AND a TXT record at _nexren-challenge.<domain> carrying the caller's own verification token (both are required). Reserved test TLDs (.test, .invalid, .local) are exempt; platform admins may pass skipDnsCheck. When the domain's www/apex sibling or parent domain is registered under another account, the 201 response carries `relatedSite` (relation + hostname, never the other owner).",
        "tags": [
          "Sites"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain"
                ],
                "properties": {
                  "domain": {
                    "type": "string",
                    "example": "api.example.com"
                  },
                  "originUrl": {
                    "type": "string",
                    "example": "https://198.51.100.25:443"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "Off",
                      "Monitor",
                      "Balanced",
                      "Under Attack"
                    ],
                    "default": "Balanced"
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Site created successfully"
          },
          "400": {
            "description": "Bad request - invalid domain or origin"
          },
          "403": {
            "description": "Forbidden - the hostname or its www/apex sibling belongs to a site you are only a team member of"
          },
          "409": {
            "description": "Conflict - domain already registered"
          },
          "422": {
            "description": "dns_verification_required - CNAME and TXT ownership records not found for the caller's token"
          }
        }
      }
    },
    "/v1/sites/{id}": {
      "get": {
        "summary": "Get Site Configuration & Status",
        "description": "Retrieves complete configuration and health status for a site.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Site configuration"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "patch": {
        "summary": "Update Site Settings",
        "description": "Modifies origin URL, protection mode, or enabled state for a site.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "originUrl": {
                    "type": "string"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "Off",
                      "Monitor",
                      "Balanced",
                      "Under Attack"
                    ]
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Site updated"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "delete": {
        "summary": "Delete Site",
        "description": "Deletes a site and removes all edge configurations.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Site deleted"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/purge-cache": {
      "post": {
        "summary": "Purge Edge Cache",
        "description": "Evicts the site's edge static-asset cache (all entries, or only the given paths with every query/encoding variant; a trailing * is a prefix match). Takes effect on the edge within effectiveWithinMs (about 1 s). entries/bytes are real numbers: the cache contents in the edge's last stats report (at most ~30 s old, timestamp countsAsOf). When they are not known, entries/bytes/purgedSize are null, countsExact is false and countsUnknownReason explains why; a made-up 0 is never returned. A full-site purge happens ONLY for {\"purgeAll\": true} (without paths) or for an empty body / {}. Anything else is either a path purge or a 400 that purges nothing: invalid JSON, a null or non-object body, an unknown field (e.g. \"path\"), paths that is null, not an array, empty or only blank strings, '*' or '/*' as a path (use purgeAll:true), a non-boolean purgeAll (null included), purgeAll:true together with paths, or purgeAll:false without paths.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "description": "Optional. An empty body or {} purges the whole site.",
                "properties": {
                  "purgeAll": {
                    "type": "boolean",
                    "description": "true purges the whole site and must not be combined with paths; false requires paths. No default: omit the field instead of sending null."
                  },
                  "paths": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 100,
                    "description": "URL paths to purge, e.g. /static/app.js or /assets/* (trailing * = prefix match). Not null; '*' and '/*' are rejected (use purgeAll:true)."
                  }
                }
              },
              "examples": {
                "wholeSite": {
                  "summary": "Purge the whole site",
                  "value": {
                    "purgeAll": true
                  }
                },
                "somePaths": {
                  "summary": "Purge specific paths",
                  "value": {
                    "paths": [
                      "/static/app.js",
                      "/assets/*"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Purge scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "purgedAt": {
                      "type": "integer"
                    },
                    "purgedSize": {
                      "type": "string",
                      "nullable": true
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "all",
                        "paths"
                      ]
                    },
                    "paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "entries": {
                      "type": "integer",
                      "nullable": true,
                      "description": "null when unknown"
                    },
                    "bytes": {
                      "type": "integer",
                      "nullable": true,
                      "description": "null when unknown"
                    },
                    "countsExact": {
                      "type": "boolean"
                    },
                    "countsUnknownReason": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "no-edge-report",
                        "report-predates-last-purge",
                        "per-path-counts-not-reported",
                        "report-truncated",
                        null
                      ]
                    },
                    "countsAsOf": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Time (ms) of the report the counts describe"
                    },
                    "cacheEnabled": {
                      "type": "boolean"
                    },
                    "generation": {
                      "type": "integer"
                    },
                    "effectiveWithinMs": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (nothing is purged): malformed JSON, null or non-object body, unknown field, null/non-array/empty paths, '*' or '/*' as a path, non-boolean purgeAll, purgeAll:true with paths, or purgeAll:false without paths"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/members": {
      "get": {
        "summary": "List Site Team",
        "description": "Lists the site owner and the members this site is shared with. Each member carries `permissions` (per-area `view` / `edit` map), `role` (derived label: viewer | editor | manager when the set equals a preset, otherwise custom), username, invitedBy, createdAt and `manageable` (whether you may change/remove them). The response also returns your own `myPermissions`, `grantablePermissions`, the `permissionAreas` catalog and the `presets`. Emails are included only for callers with team:edit. Requires sites:read and the site permission team:view. Every site payload also carries `myRole` (owner | viewer | editor | manager | custom) and `myPermissions`.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Owner, members (with permissions), maxMembers, canManage, grantablePermissions, permissionAreas, presets"
          },
          "403": {
            "description": "Insufficient site permission (team:view)"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "post": {
        "summary": "Add Site Member",
        "description": "Shares the site with another existing Nexren Shield account, identified by its exact username or email, with a chosen permission set. Send `permissions` (per-area map, e.g. {\"analytics\":\"view\",\"security\":\"edit\"} or {\"security\":{\"view\":true,\"edit\":true}}) or a `preset` (viewer | editor | manager; `role` is accepted as an alias). Edit implies view; `edit` with an explicit `view: false`, unknown areas, unsupported levels, unmet prerequisites (visitorIps needs analytics:view, maintenanceHtml needs maintenance:edit) and an empty set are rejected with 400. Requires sites:write and team:edit. You can only grant permissions you hold yourself, and only the site owner can grant team:edit. Errors never reveal whether an account exists. Rate limited.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "identifier"
                ],
                "properties": {
                  "identifier": {
                    "type": "string",
                    "description": "Exact username or email of an existing account"
                  },
                  "permissions": {
                    "type": "object",
                    "description": "Per-area permission map. Areas (modules): analytics (traffic & logs), visitorIps, security (WAF/DDoS/bot, mode, timed mode, WAF sensitivity, traffic profile, verification lifetime), rules (custom rules, IP lists, country/TLS, scoped WAF exceptions), access (Zero Trust), origin, ssl, cache, captcha, maintenance, maintenanceHtml, notifications (channels, owner alerts, all-my-sites scope), analysis, team. Values: none | view | edit, or {view: boolean, edit: boolean}.",
                    "additionalProperties": {
                      "oneOf": [
                        {
                          "type": "string",
                          "enum": [
                            "none",
                            "view",
                            "edit"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "view": {
                              "type": "boolean"
                            },
                            "edit": {
                              "type": "boolean"
                            }
                          }
                        }
                      ]
                    }
                  },
                  "preset": {
                    "type": "string",
                    "enum": [
                      "manager",
                      "editor",
                      "viewer"
                    ],
                    "description": "Used when permissions is omitted: viewer = view only (every module view, visitor IPs masked, no team), editor = edits security/rules/maintenance/notifications/cache/analysis, manager = full admin (every module edit except team management, which only the owner grants)"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "manager",
                      "editor",
                      "viewer"
                    ],
                    "description": "Deprecated alias of preset"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Member added"
          },
          "400": {
            "description": "Invalid permissions (code invalid_permissions)"
          },
          "403": {
            "description": "Insufficient site permission, or granting permissions beyond your own"
          },
          "404": {
            "description": "User not found or cannot be added (generic)"
          },
          "409": {
            "description": "Already a member, or member limit reached (the limit is checked before the account lookup, so it never reveals whether an account exists)"
          },
          "429": {
            "description": "Too many invite attempts"
          }
        }
      }
    },
    "/v1/sites/{id}/members/{userId}": {
      "patch": {
        "summary": "Change Member Permissions",
        "description": "Replaces a member's permission set (`permissions` or `preset`; `role` accepted as an alias of preset). You need team:edit; unless you are the owner, the member must hold no permission you lack and must not have team:edit, the new set must be within your own, and you cannot grant team:edit. Nobody can change their own permissions. Every change re-validates the site's credential bindings: channels whose supplier lost notifications:edit and a Zero Trust 2FA credential whose supplier lost access:edit are detached (`detached`, `warnings`). The response includes the `diff` (added / removed permission tokens), which is also written to the audit log. Requires sites:write.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "permissions": {
                    "type": "object"
                  },
                  "preset": {
                    "type": "string",
                    "enum": [
                      "manager",
                      "editor",
                      "viewer"
                    ]
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "manager",
                      "editor",
                      "viewer"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permissions changed"
          },
          "400": {
            "description": "Invalid permissions"
          },
          "403": {
            "description": "Insufficient site permission"
          },
          "404": {
            "description": "Member not found"
          }
        }
      },
      "delete": {
        "summary": "Remove Member / Leave Site",
        "description": "Removes a member (needs team:edit and, unless you are the owner, the member's permissions must be within yours and exclude team:edit) or, with your own user id or `me`, leaves the site. Channels and credentials the member bound to the site are detached immediately; `review` counts the Zero Trust access rules/tunnels the member added, which stay active on the site. Requires sites:write.",
        "tags": [
          "Sites"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Member removed"
          },
          "403": {
            "description": "Insufficient site permission"
          },
          "404": {
            "description": "Member not found"
          }
        }
      }
    },
    "/v1/sites/{id}/origin": {
      "get": {
        "summary": "Get Origin Configuration",
        "description": "Retrieves origin IP/URL and Origin Lock status.",
        "tags": [
          "Origin & Lock"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Origin details"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "put": {
        "summary": "Update Origin Configuration",
        "description": "Updates origin IP/URL and Origin Lock header / secret.",
        "tags": [
          "Origin & Lock"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "originUrl": {
                    "type": "string"
                  },
                  "originLockEnabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Origin updated"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/origin/lock/toggle": {
      "post": {
        "summary": "Toggle Origin Lock",
        "description": "Toggles cryptographic Origin Lock enforcement on or off for the site.",
        "tags": [
          "Origin & Lock"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Origin lock toggled successfully"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/policies/{id}": {
      "get": {
        "summary": "Get Site Policy",
        "description": "Retrieves protection mode and threat response rules for the site. A timed mode exposes `modeRevertTo` (the mode it will return to) and `modeRevertAt` (epoch ms); both are empty/0 for a permanent mode. Maintenance fields include `maintenanceEta` (UTC epoch ms as a string), `maintenanceContact` and `maintenanceAutoEnd`.",
        "tags": [
          "Policies"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Policy details"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "put": {
        "summary": "Update Site Policy",
        "description": "Modifies protection mode ('Off', 'Monitor', 'Balanced', 'Under Attack') and challenge settings. Send `modeDurationMinutes` with `mode` to switch temporarily: the control plane reverts to the previous mode when the duration ends (checked every 30 s; plan limits apply, e.g. it never reverts into Under Attack on a plan without it). `modeDurationMinutes: 0` (or a mode change without a duration) makes the current mode permanent and cancels a pending revert. Also accepted on PATCH. Also per-site customizations: traffic profile, verification lifetime, WAF sensitivity.",
        "tags": [
          "Policies"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "Off",
                      "Monitor",
                      "Balanced",
                      "Under Attack"
                    ]
                  },
                  "wafEnabled": {
                    "type": "boolean"
                  },
                  "botProtectionEnabled": {
                    "type": "boolean"
                  },
                  "challengeEnabled": {
                    "type": "boolean"
                  },
                  "rateLimitEnabled": {
                    "type": "boolean"
                  },
                  "trafficProfile": {
                    "type": "string",
                    "enum": [
                      "web",
                      "api",
                      "webhook",
                      "realtime"
                    ],
                    "default": "web",
                    "description": "How unverified traffic is handled. 'web' serves the HTML verification page (default). 'api', 'webhook' and 'realtime' never serve HTML challenges: challenge decisions are logged and a strict per-IP rate limit applies instead (api 20, webhook 5, realtime 30 req/s; halved under attack; 429 JSON with Retry-After). WAF blocks, IP blacklist, geo blocks, Zero Trust and DDoS protections still apply. 'webhook' additionally does not treat a missing User-Agent or cookie-less POST as a bot signal."
                  },
                  "profilePaths": {
                    "type": "string",
                    "description": "Newline-separated path patterns the traffic profile applies to (e.g. '/api/*\\n/webhook/*'). Empty = whole site. Other paths keep the 'web' behaviour. Max 100 lines, each starting with '/'."
                  },
                  "challengeTtlMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 43200,
                    "default": 30,
                    "description": "Lifetime of the verification (clearance) cookie in minutes (5 minutes – 30 days). Capped at 15 minutes while Under Attack or automatic mitigation is active. Lowering it invalidates longer cookies immediately."
                  },
                  "modeDurationMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10080,
                    "description": "0 = permanent (default); otherwise 5–10080 minutes (7 days) after which the previous mode is restored automatically. Send together with `mode`."
                  },
                  "maintenanceEta": {
                    "type": "string",
                    "description": "Estimated completion as UTC epoch ms or ISO 8601 with a timezone (e.g. 2026-10-09T18:00:00Z). Empty string clears it."
                  },
                  "maintenanceContact": {
                    "type": "string",
                    "description": "E-mail address or https URL shown on the maintenance page. Empty string clears it."
                  },
                  "maintenanceAutoEnd": {
                    "type": "boolean",
                    "description": "Turn maintenance off automatically when maintenanceEta passes."
                  },
                  "wafSensitivity": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high"
                    ],
                    "default": "medium",
                    "description": "WAF score thresholds: low = block 85 / challenge 55, medium = 70 / 40, high = 55 / 30. Under Attack keeps the block threshold at most 45."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Policy updated (includes modeRevertTo / modeRevertAt)"
          },
          "400": {
            "description": "Invalid field value"
          },
          "403": {
            "description": "Insufficient site role or plan"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "patch": {
        "summary": "Patch Site Policy",
        "description": "Same as PUT: partial update, only the fields present in the body are changed.",
        "tags": [
          "Policies"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "Off",
                      "Monitor",
                      "Balanced",
                      "Under Attack"
                    ]
                  },
                  "wafEnabled": {
                    "type": "boolean"
                  },
                  "botProtectionEnabled": {
                    "type": "boolean"
                  },
                  "challengeEnabled": {
                    "type": "boolean"
                  },
                  "rateLimitEnabled": {
                    "type": "boolean"
                  },
                  "trafficProfile": {
                    "type": "string",
                    "enum": [
                      "web",
                      "api",
                      "webhook",
                      "realtime"
                    ],
                    "default": "web",
                    "description": "How unverified traffic is handled. 'web' serves the HTML verification page (default). 'api', 'webhook' and 'realtime' never serve HTML challenges: challenge decisions are logged and a strict per-IP rate limit applies instead (api 20, webhook 5, realtime 30 req/s; halved under attack; 429 JSON with Retry-After). WAF blocks, IP blacklist, geo blocks, Zero Trust and DDoS protections still apply. 'webhook' additionally does not treat a missing User-Agent or cookie-less POST as a bot signal."
                  },
                  "profilePaths": {
                    "type": "string",
                    "description": "Newline-separated path patterns the traffic profile applies to (e.g. '/api/*\\n/webhook/*'). Empty = whole site. Other paths keep the 'web' behaviour. Max 100 lines, each starting with '/'."
                  },
                  "challengeTtlMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 43200,
                    "default": 30,
                    "description": "Lifetime of the verification (clearance) cookie in minutes (5 minutes – 30 days). Capped at 15 minutes while Under Attack or automatic mitigation is active. Lowering it invalidates longer cookies immediately."
                  },
                  "modeDurationMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10080,
                    "description": "0 = permanent (default); otherwise 5–10080 minutes (7 days) after which the previous mode is restored automatically. Send together with `mode`."
                  },
                  "maintenanceEta": {
                    "type": "string",
                    "description": "Estimated completion as UTC epoch ms or ISO 8601 with a timezone (e.g. 2026-10-09T18:00:00Z). Empty string clears it."
                  },
                  "maintenanceContact": {
                    "type": "string",
                    "description": "E-mail address or https URL shown on the maintenance page. Empty string clears it."
                  },
                  "maintenanceAutoEnd": {
                    "type": "boolean",
                    "description": "Turn maintenance off automatically when maintenanceEta passes."
                  },
                  "wafSensitivity": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high"
                    ],
                    "default": "medium",
                    "description": "WAF score thresholds: low = block 85 / challenge 55, medium = 70 / 40, high = 55 / 30. Under Attack keeps the block threshold at most 45."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Policy updated"
          },
          "400": {
            "description": "Invalid field value"
          },
          "403": {
            "description": "Insufficient site role or plan"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/waf/rules": {
      "get": {
        "summary": "List Site WAF Rules",
        "description": "Returns custom WAF rules defined for the site.",
        "tags": [
          "Policies"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of WAF rules"
          }
        }
      },
      "post": {
        "summary": "Create Site WAF Rule",
        "description": "Creates a new custom WAF rule.",
        "tags": [
          "Policies"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "WAF rule created"
          }
        }
      }
    },
    "/v1/sites/{id}/waf/rules/{ruleId}": {
      "delete": {
        "summary": "Delete Site WAF Rule",
        "description": "Deletes a custom WAF rule.",
        "tags": [
          "Policies"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "WAF rule deleted"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "summary": "Query Security Events",
        "description": "Returns paginated stream of security events, challenge outcomes, and blocked threats.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "name": "siteId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by site ID"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "block",
                "challenge",
                "allow"
              ]
            },
            "description": "Filter by event action"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            },
            "description": "Number of events (max 100)"
          }
        ],
        "responses": {
          "200": {
            "description": "Security events log"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/analytics/summary": {
      "get": {
        "summary": "Get Summary Metrics",
        "description": "Request counts, estimated bandwidth and mitigation statistics. summary.cachedRatio is the measured edge cache hit ratio (hits / (hits + misses) reported by the edge processes since they started), null when there were no cache-eligible requests.",
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1h",
                "24h",
                "7d",
                "30d"
              ],
              "default": "24h"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics summary"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/tokens": {
      "get": {
        "summary": "List API Tokens",
        "description": "Returns all API tokens owned by the authenticated user with masked prefix previews.",
        "tags": [
          "Tokens"
        ],
        "responses": {
          "200": {
            "description": "List of API tokens"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Create API Token",
        "description": "Creates a new scoped API token. Secret key is returned only once in response.",
        "tags": [
          "Tokens"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Terraform Key"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "sites:read",
                      "sites:write",
                      "analytics:read"
                    ]
                  },
                  "expiresInDays": {
                    "type": "integer",
                    "default": 30
                  },
                  "ipRestrictions": {
                    "type": "string",
                    "example": "198.51.100.10"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Token created"
          },
          "400": {
            "description": "Invalid scope or name"
          }
        }
      }
    },
    "/v1/tokens/{id}": {
      "delete": {
        "summary": "Delete / Revoke API Token",
        "description": "Permanently deletes and revokes an API token.",
        "tags": [
          "Tokens"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token revoked"
          },
          "404": {
            "description": "Token not found"
          }
        }
      }
    },
    "/v1/sites/{id}/origin/lock/snippets": {
      "get": {
        "summary": "Get Origin Lock Web Server Snippets",
        "description": "Returns ready-to-paste firewall and web server configuration snippets (Nginx, PHP, Apache, Caddy, Cloudflare, UFW, Iptables) configured with the site's unique cryptographic header and secret.",
        "tags": [
          "Origin & Lock"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Origin Lock code snippets"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/origin/lock/verify": {
      "post": {
        "summary": "Verify Origin Lock Live",
        "description": "Executes live cryptographic handshake probe to verify origin web server blocks direct IP access while allowing Nexren Shield edge proxy.",
        "tags": [
          "Origin & Lock"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Origin Lock verification result"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/tunnels": {
      "get": {
        "summary": "List Site Tunnels",
        "description": "Lists all Nexren Tunnels configured for zero-open-port reverse origin tunneling on this site.",
        "tags": [
          "Tunnels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of site tunnels"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "post": {
        "summary": "Create Nexren Tunnel",
        "description": "Provisions a new encrypted WebSocket/QUIC reverse tunnel token for exposing origins behind strict firewalls and NATs without public IPs.",
        "tags": [
          "Tunnels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Primary App Cluster"
                  },
                  "localTarget": {
                    "type": "string",
                    "example": "http://127.0.0.1:3000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tunnel created with secret token"
          },
          "400": {
            "description": "Bad request"
          }
        }
      }
    },
    "/v1/sites/{id}/tunnels/{tunnelId}": {
      "get": {
        "summary": "Get Site Tunnel Details",
        "description": "Retrieves live connection status, RTT latency, stream count, and credentials for a tunnel.",
        "tags": [
          "Tunnels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tunnelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tunnel details"
          },
          "404": {
            "description": "Tunnel not found"
          }
        }
      },
      "delete": {
        "summary": "Revoke Site Tunnel",
        "description": "Immediately revokes and disconnects an active Nexren Tunnel.",
        "tags": [
          "Tunnels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tunnelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tunnel revoked"
          },
          "404": {
            "description": "Tunnel not found"
          }
        }
      }
    },
    "/v1/sites/{id}/origin-kind": {
      "post": {
        "summary": "Switch Site Origin Kind",
        "description": "Switches origin routing mode between direct IP/URL ('direct') and reverse tunnel ('tunnel').",
        "tags": [
          "Tunnels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "originKind"
                ],
                "properties": {
                  "originKind": {
                    "type": "string",
                    "enum": [
                      "direct",
                      "tunnel"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Origin kind switched"
          },
          "400": {
            "description": "Invalid kind"
          }
        }
      }
    },
    "/v1/tunnels": {
      "get": {
        "summary": "List All User Tunnels",
        "description": "Lists all Nexren Tunnels across all accessible sites for the authenticated account.",
        "tags": [
          "Tunnels"
        ],
        "responses": {
          "200": {
            "description": "List of all user tunnels"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/sites/{id}/waf/exceptions": {
      "get": {
        "summary": "List Scoped WAF Exceptions",
        "description": "Returns scoped WAF exceptions in the legacy line format (`<ruleId> <path> [methods]`). Since v4.10.231 they are stored as custom rules (action `skip` with `wafRules`); this view lists custom rules that only have a glob path (+ methods) condition, plus any legacy line that could not be migrated.",
        "tags": [
          "WAF"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of WAF exceptions"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "put": {
        "summary": "Update Scoped WAF Exceptions",
        "description": "Replaces all scoped exceptions (<ruleId> <path> [methods]); written as custom rules (`skip` + `wafRules`), other custom rules are kept. Whole-site wildcards are strictly rejected for safety. Site manager role. POST /v1/sites/{id}/waf/exceptions appends one line the same way.",
        "tags": [
          "WAF"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "raw": {
                    "type": "string",
                    "example": "930120 /api/v1/upload POST\n941100 /admin/* *"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exceptions updated"
          },
          "400": {
            "description": "Syntax or wildcard violation"
          }
        }
      }
    },
    "/v1/sites/{id}/waf/exceptions/{ruleId}": {
      "delete": {
        "summary": "Delete WAF Exception Rule",
        "description": "Removes the rule ID from every scoped exception (custom rules with `wafRules`) of the site. Site manager role.",
        "tags": [
          "WAF"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Exception deleted"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/custom-rules": {
      "get": {
        "summary": "List Custom Rules",
        "description": "Ordered custom rules (first match wins; `log` records and continues; `override` and WAF-rule-scoped `skip` are non-final phases), the built-in templates and the per-site limit (100). Viewer role and policies:read or sites:read.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rules, templates and limits"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "put": {
        "summary": "Replace Custom Rules (ordered)",
        "description": "Replaces the whole ordered list. Site manager role and policies:write or sites:write. `allow` skips WAF, bot, rate limiting and verification — but NOT the global/site IP blacklist, country rule, maintenance, Zero Trust or kernel/DDoS flood protection. Relaxing rules (`allow`, `skip`) with a path condition are not applied to ambiguous paths (encoded separators, `..`, `;`). No user regex: paths match by prefix, exact or `*`-glob.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rules"
                ],
                "properties": {
                  "rules": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/CustomRule"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved rules (ids assigned)"
          },
          "400": {
            "description": "Validation error"
          },
          "404": {
            "description": "Site not found or role below manager"
          }
        }
      },
      "post": {
        "summary": "Add Custom Rule",
        "description": "Adds one rule at `position` (default: end). Site manager role.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rule": {
                    "$ref": "#/components/schemas/CustomRule"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Rule created"
          },
          "400": {
            "description": "Validation error or limit reached"
          }
        }
      }
    },
    "/v1/sites/{id}/custom-rules/order": {
      "post": {
        "summary": "Reorder Custom Rules",
        "description": "`ids` must list every rule id exactly once, in the new evaluation order. Site manager role.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reordered rules"
          },
          "400": {
            "description": "ids is not a permutation of the current rule ids"
          }
        }
      }
    },
    "/v1/sites/{id}/custom-rules/{ruleId}": {
      "get": {
        "summary": "Get Custom Rule",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rule"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "Update Custom Rule",
        "description": "Merges the given fields into the rule; its position is kept. Site manager role.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomRule"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated rule"
          },
          "400": {
            "description": "Validation error"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "put": {
        "summary": "Replace Custom Rule",
        "description": "Replaces the rule in place (position kept). Site manager role.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomRule"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated rule"
          },
          "400": {
            "description": "Validation error"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Delete Custom Rule",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/sites/{id}/exemptions": {
      "get": {
        "summary": "Unified Exemptions View",
        "description": "Every exemption source in one list — IP-excluded paths, Zero Trust excluded paths (with the implicit effect of scope `all`), the IP allow-list action, ZT session bypasses and custom rules (`allow`/`skip`, WAF-rule-scoped `skip`, relaxing `override`) — each with what it skips.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Exemption rows"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/explain": {
      "get": {
        "summary": "Why Was This Request Blocked?",
        "description": "Looks up a request id in this site's event log and replays today's policy step by step (blacklist, IP access, country, maintenance, Zero Trust, custom rules, exemptions, DDoS, override rule, WAF, bot), with a suggested fix (e.g. a custom-rule draft that skips only the matched WAF rule ids, or a `skip waf` draft). Only this site's events; the client IP is masked for the viewer role. Lookup is by request id only. Scope events:read, policies:read or sites:read.",
        "tags": [
          "Custom Rules"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requestId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{8,80}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event, network data and trace"
          },
          "400": {
            "description": "Malformed request id"
          },
          "404": {
            "description": "No event for this request id on this site"
          }
        }
      }
    },
    "/v1/sites/{id}/waf/exceptions/validate": {
      "post": {
        "summary": "Validate Scoped WAF Exceptions Syntax",
        "description": "Dry-run syntax validator for exception rules without persisting changes.",
        "tags": [
          "WAF"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "raw"
                ],
                "properties": {
                  "raw": {
                    "type": "string",
                    "example": "930120 /api/upload POST"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result"
          }
        }
      }
    },
    "/v1/sites/{id}/ddos": {
      "get": {
        "summary": "Get Rate Limiting & DDoS Mitigation Settings",
        "description": "Retrieves rate limiting RPS, burst headroom, challenge sensitivity, and live mitigation state.",
        "tags": [
          "DDoS & Rate Limiting"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "DDoS and rate limit configuration"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "put": {
        "summary": "Update Rate Limiting & DDoS Settings",
        "description": "Updates RPS limit, burst capacity, mitigation mode, and sensitivity threshold.",
        "tags": [
          "DDoS & Rate Limiting"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rateLimitRps": {
                    "type": "integer",
                    "example": 100
                  },
                  "rateLimitBurst": {
                    "type": "integer",
                    "example": 150
                  },
                  "ddosSensitivity": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high",
                      "paranoid"
                    ]
                  },
                  "challengeBotDDoS": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "DDoS settings updated"
          },
          "400": {
            "description": "Invalid parameters"
          }
        }
      }
    },
    "/v1/sites/{id}/ddos/mitigate": {
      "post": {
        "summary": "Trigger Immediate DDoS Mitigation",
        "description": "Forces site into emergency 'Under Attack' mode with mandatory JS PoW/CAPTCHA challenge.",
        "tags": [
          "DDoS & Rate Limiting"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mitigation engaged"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/ddos/reset": {
      "post": {
        "summary": "Reset Rate Limit Windows",
        "description": "Clears sliding-window rate counters and ban-lists for the site.",
        "tags": [
          "DDoS & Rate Limiting"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rate limits reset"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/notifications/channels": {
      "get": {
        "summary": "List Notification Channels",
        "description": "Returns every notification channel visible to the token owner. Each channel has a `scope`: `site` (one site; `siteId` set), `my_sites` (every site the token owner owns or holds the `notifications:edit` site permission on, resolved live at delivery time — never other tenants' sites) or `platform` (platform administrators only; `siteFilter` all_sites/selected_sites/exclude_sites). Targets are always masked (`targetPreview`). Site channels are readable with `notifications:view`; `my_sites` channels only by their creator.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "site",
                "my_sites",
                "platform"
              ],
              "default": "all"
            }
          },
          {
            "name": "siteId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only site channels of this site and multi-site channels that cover it"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channels": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NotificationChannel"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "post": {
        "summary": "Create Notification Channel",
        "description": "Creates a channel. `scope: site` (default) needs `siteId` and site role 'manager'. `scope: my_sites` covers every site you own or manage. `scope: platform` requires a platform administrator. Non-admin accounts may only bind their own credentials and send email only to their own account address.",
        "tags": [
          "Notifications"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationChannelInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "$ref": "#/components/schemas/NotificationChannel"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error"
          },
          "403": {
            "description": "Scope or recipient not allowed"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/notifications/channels/{id}": {
      "get": {
        "summary": "Get Notification Channel",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Channel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "channel": {
                      "$ref": "#/components/schemas/NotificationChannel"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "Update Notification Channel",
        "description": "Merges the given fields. `scope` can switch only between `my_sites` and `platform` (platform: administrators only); a site channel stays site-scoped — recreate it to widen its scope.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationChannelInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated channel"
          },
          "400": {
            "description": "Validation error"
          },
          "403": {
            "description": "Not allowed"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Delete Notification Channel",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/notifications/channels/{id}/test": {
      "post": {
        "summary": "Send Test Notification",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Test attempted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/sites/{id}/notifications/channels": {
      "get": {
        "summary": "List Channels Covering a Site",
        "description": "Site channels of the site plus the `my_sites`/`platform` channels visible to you that cover it.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "site",
                "my_sites",
                "platform"
              ],
              "default": "all"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Channel list"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "post": {
        "summary": "Create Site Channel",
        "description": "Creates a `site`-scoped channel on this site (site role 'manager').",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationChannelInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel created"
          },
          "403": {
            "description": "Insufficient role"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/notifications/owner-alerts": {
      "get": {
        "summary": "Get Owner Alert Defaults",
        "description": "When the site has no notification channel subscribed to a critical event (tunnel offline/online, certificate expiring/renewal failed, quota 80%/100%, origin lock broken, DDoS start), Shield emails the site owner's account address. Returns that setting, the optional daily summary flag and the list of covered events.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Owner alert settings"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "patch": {
        "summary": "Update Owner Alert Defaults",
        "description": "Turns the owner email fallback (default on) and the daily summary email (default off, sent around 09:00 Europe/Istanbul) on or off. Requires site role 'manager'.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ownerAlertsEnabled": {
                    "type": "boolean"
                  },
                  "ownerDigestEnabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated settings"
          },
          "400": {
            "description": "No boolean field given"
          },
          "403": {
            "description": "Insufficient site role"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/maintenance": {
      "get": {
        "summary": "Get Maintenance Mode Status",
        "description": "Retrieves maintenance mode toggle state, custom HTML template, ETA, and IP bypass list.",
        "tags": [
          "Maintenance Mode"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Maintenance configuration"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "put": {
        "summary": "Update Maintenance Mode Configuration",
        "description": "Updates maintenance mode enabled state, custom HTML page, bypass CIDR IPs, and estimated completion time.",
        "tags": [
          "Maintenance Mode"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "customHtml": {
                    "type": "string"
                  },
                  "bypassIps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "eta": {
                    "type": "string",
                    "description": "Estimated completion: UTC epoch ms or ISO 8601 with a timezone (e.g. 2026-10-09T18:00:00Z); null or empty clears it. Shown on the maintenance page in UTC and drives Retry-After."
                  },
                  "contact": {
                    "type": "string",
                    "description": "E-mail address or https URL shown on the maintenance page; null or empty clears it."
                  },
                  "autoEnd": {
                    "type": "boolean",
                    "description": "Turn maintenance off automatically when the ETA passes (default false)."
                  },
                  "statusCode": {
                    "type": "integer",
                    "default": 503
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Maintenance mode updated"
          },
          "400": {
            "description": "Bad request"
          }
        }
      }
    },
    "/v1/sites/{id}/maintenance/toggle": {
      "post": {
        "summary": "Toggle Maintenance Mode",
        "description": "Quick toggle to switch maintenance mode on or off.",
        "tags": [
          "Maintenance Mode"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Maintenance mode toggled"
          }
        }
      }
    },
    "/v1/sites/{id}/maintenance/preview": {
      "get": {
        "summary": "Preview Maintenance Page",
        "description": "Renders the maintenance HTML template as it will be served to visitors during maintenance.",
        "tags": [
          "Maintenance Mode"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "HTML maintenance page"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/zt/access-rules": {
      "get": {
        "summary": "List Zero Trust Access Rules",
        "description": "Returns path-based Zero Trust access rules protecting private routes and admin portals.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of access rules"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "post": {
        "summary": "Create Zero Trust Access Rule",
        "description": "Creates an access policy rule requiring TOTP 2FA, corporate email domain verification, or IP whitelist.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "pathPattern"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Admin Portal 2FA"
                  },
                  "pathPattern": {
                    "type": "string",
                    "example": "/admin/*"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "allow",
                      "block",
                      "authenticate"
                    ],
                    "default": "authenticate"
                  },
                  "require2fa": {
                    "type": "boolean",
                    "default": false
                  },
                  "allowedEmails": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "allowedIps": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Access rule created"
          },
          "400": {
            "description": "Bad request"
          }
        }
      }
    },
    "/v1/sites/{id}/zt/access-rules/{ruleId}": {
      "get": {
        "summary": "Get Zero Trust Access Rule",
        "description": "Retrieves single access policy rule by ID.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Access rule details"
          },
          "404": {
            "description": "Rule not found"
          }
        }
      },
      "put": {
        "summary": "Update Zero Trust Access Rule",
        "description": "Updates an existing Zero Trust access policy rule.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "pathPattern": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "allow",
                      "block",
                      "authenticate"
                    ]
                  },
                  "require2fa": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access rule updated"
          },
          "404": {
            "description": "Rule not found"
          }
        }
      },
      "delete": {
        "summary": "Delete Zero Trust Access Rule",
        "description": "Deletes a Zero Trust access policy rule.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Access rule deleted"
          },
          "404": {
            "description": "Rule not found"
          }
        }
      }
    },
    "/v1/sites/{id}/zt/users/{userId}/verify-2fa": {
      "post": {
        "summary": "Verify User 2FA Token",
        "description": "Validates a 6-digit TOTP code against enrolled Zero Trust user secret.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "example": "123456"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "2FA verification result"
          },
          "400": {
            "description": "Invalid token"
          }
        }
      }
    },
    "/v1/sites/{id}/zt/sessions": {
      "get": {
        "summary": "Get Zero Trust Sessions",
        "description": "Lists active authenticated Zero Trust sessions and timeout policies, including `excludedPaths` and `excludedPathsScope`.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Active sessions and policy"
          },
          "404": {
            "description": "Site not found"
          }
        }
      },
      "patch": {
        "summary": "Update Zero Trust Session & Exemption Policy",
        "description": "Site manager role. `excludedPathsScope`: `all` (default, historical) — a path excluded from Zero Trust is ALSO exempt from IP/country rules, verification, flood verification and bot decisions; `zt_only` — the path skips only Zero Trust. Same body is accepted on PUT and on /zt/exemptions.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "durationMinutes": {
                    "type": "integer"
                  },
                  "authMethod": {
                    "type": "string",
                    "enum": [
                      "email_only",
                      "totp_only",
                      "both"
                    ]
                  },
                  "exemptionScope": {
                    "type": "string",
                    "enum": [
                      "cookie_only",
                      "ip_only",
                      "hybrid"
                    ]
                  },
                  "bypassRateLimit": {
                    "type": "boolean"
                  },
                  "bypassWaf": {
                    "type": "boolean"
                  },
                  "ipWhitelist": {
                    "type": "string"
                  },
                  "excludedPaths": {
                    "type": "string",
                    "description": "One path pattern per line"
                  },
                  "excludedPathsScope": {
                    "type": "string",
                    "enum": [
                      "all",
                      "zt_only"
                    ]
                  },
                  "protectedPaths": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated policy"
          },
          "400": {
            "description": "Invalid value"
          },
          "404": {
            "description": "Site not found or role below manager"
          }
        }
      }
    },
    "/v1/sites/{id}/zt/sessions/revoke": {
      "post": {
        "summary": "Revoke Zero Trust Sessions",
        "description": "Revokes active Zero Trust sessions for a specific user email or all users across the site.",
        "tags": [
          "Zero Trust"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "example": "dev@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sessions revoked"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/sites/{id}/incidents": {
      "get": {
        "summary": "List Site DDoS Incidents",
        "description": "Returns recorded volumetric and L7 flood attack mitigation incidents for the site.",
        "tags": [
          "Threats & Incidents"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of incidents"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/incidents": {
      "get": {
        "summary": "List All DDoS Incidents",
        "description": "Returns recorded DDoS attack incidents across all sites accessible to the account.",
        "tags": [
          "Threats & Incidents"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of all DDoS incidents"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/threats/live": {
      "get": {
        "summary": "Real-Time Live Threat Feed",
        "description": "Returns live stream of blocked attack traffic, matched WAF rules, attacker IP masking, and geo-location.",
        "tags": [
          "Threats & Incidents"
        ],
        "parameters": [
          {
            "name": "siteId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Live threat feed"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/threats/summary": {
      "get": {
        "summary": "Threat Intelligence Summary",
        "description": "Returns aggregated attack distribution breakdown by category (SQLi, XSS, RCE, DDoS, Bot, Scanner).",
        "tags": [
          "Threats & Incidents"
        ],
        "parameters": [
          {
            "name": "siteId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1h",
                "24h",
                "7d",
                "30d"
              ],
              "default": "24h"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Threat summary metrics"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/sites/{id}/audit-logs": {
      "get": {
        "summary": "List Site Audit Logs",
        "description": "Returns security audit trail of configuration changes, key rotations, and actions on this site.",
        "tags": [
          "Audit Logs"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Site audit logs"
          },
          "404": {
            "description": "Site not found"
          }
        }
      }
    },
    "/v1/audit-logs": {
      "get": {
        "summary": "List Account Audit Logs",
        "description": "Returns security audit trail across all accessible sites and API activities.",
        "tags": [
          "Audit Logs"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Account audit logs"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/audit-logs/{logId}": {
      "get": {
        "summary": "Get Audit Log By ID",
        "description": "Retrieves complete audit record details including client IP, actor, diff, and timestamp.",
        "tags": [
          "Audit Logs"
        ],
        "parameters": [
          {
            "name": "logId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audit log entry"
          },
          "404": {
            "description": "Audit log not found"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "NotificationChannel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Site channels start with `nch_`, multi-site channels with `notif_rule_`"
          },
          "scope": {
            "type": "string",
            "enum": [
              "site",
              "my_sites",
              "platform"
            ]
          },
          "siteId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set only for `site` scope"
          },
          "siteFilter": {
            "type": "string",
            "enum": [
              "all_sites",
              "selected_sites",
              "exclude_sites"
            ],
            "description": "`platform` scope only"
          },
          "siteIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "`platform` scope with selected/exclude filter"
          },
          "coveredSiteCount": {
            "type": "integer",
            "description": "`my_sites` scope: sites currently covered"
          },
          "name": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "enum": [
              "email",
              "telegram",
              "discord",
              "slack",
              "webhook"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "targetPreview": {
            "type": "string",
            "description": "Masked destination; secrets are never returned"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "lastStatus": {
            "type": "string"
          },
          "lastSentAt": {
            "type": "integer"
          },
          "sentCount": {
            "type": "integer"
          },
          "createdAt": {
            "type": "integer"
          }
        }
      },
      "NotificationChannelInput": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "site",
              "my_sites",
              "platform"
            ],
            "default": "site"
          },
          "siteId": {
            "type": "string",
            "description": "Required for `site` scope (or use /v1/sites/{id}/notifications/channels)"
          },
          "siteFilter": {
            "type": "string",
            "enum": [
              "all_sites",
              "selected_sites",
              "exclude_sites"
            ],
            "default": "all_sites",
            "description": "`platform` scope only"
          },
          "siteIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "name": {
            "type": "string",
            "maxLength": 80
          },
          "platform": {
            "type": "string",
            "enum": [
              "email",
              "telegram",
              "discord",
              "slack",
              "webhook"
            ]
          },
          "credentialId": {
            "type": "string",
            "description": "Saved credential to deliver through (your own; multi-site scopes only)"
          },
          "target": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object"
              }
            ],
            "description": "Destination when no credential is given"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "minSeverity": {
            "type": "string",
            "enum": [
              "all",
              "high_critical",
              "critical_only"
            ],
            "description": "Multi-site scopes only"
          },
          "language": {
            "type": "string",
            "enum": [
              "tr",
              "en"
            ]
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "CustomRule": {
        "type": "object",
        "required": [
          "name",
          "action",
          "match"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^cr_[a-z]{6,24}$",
            "description": "Assigned by the server when missing or invalid"
          },
          "name": {
            "type": "string",
            "maxLength": 80
          },
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "action": {
            "type": "string",
            "enum": [
              "block",
              "challenge",
              "allow",
              "skip",
              "log",
              "override"
            ],
            "description": "`override` (replaces the former path rules) is a separate, non-final phase: the first matching override rule changes mode/WAF/bot/verification for the request and may add a per-rule rate limit; final rules are still evaluated."
          },
          "skip": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "waf",
                "bot",
                "rate_limit",
                "challenge"
              ]
            },
            "description": "Required (non-empty) when action is skip"
          },
          "wafRules": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^([A-Za-z0-9_.:-]{1,64}|\\*)$"
            },
            "description": "Only with action skip and skip = [\"waf\"]: skip only these WAF rule ids instead of the whole WAF (replaces the former scoped WAF exceptions). Not final — every matching rule of this kind accumulates."
          },
          "override": {
            "type": "object",
            "description": "Only with action override. Omitted fields inherit the site setting. Relaxing fields (waf/bot off, Off/Monitor mode, challenge never) are not applied on ambiguous paths when the rule has a path condition.",
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "Off",
                  "Monitor",
                  "Balanced",
                  "Under Attack"
                ]
              },
              "waf": {
                "type": "string",
                "enum": [
                  "on",
                  "off"
                ]
              },
              "botProtection": {
                "type": "string",
                "enum": [
                  "on",
                  "off"
                ]
              },
              "challenge": {
                "type": "string",
                "enum": [
                  "always",
                  "never"
                ]
              },
              "rateLimit": {
                "type": "object",
                "required": [
                  "requests",
                  "windowSec"
                ],
                "properties": {
                  "requests": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100000
                  },
                  "windowSec": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 3600
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "block",
                      "challenge"
                    ],
                    "default": "block"
                  }
                }
              }
            }
          },
          "match": {
            "type": "object",
            "description": "All given conditions must match (AND). At least one condition.",
            "properties": {
              "path": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "prefix",
                      "exact",
                      "glob"
                    ]
                  },
                  "values": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "methods": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "GET",
                    "HEAD",
                    "POST",
                    "PUT",
                    "PATCH",
                    "DELETE",
                    "OPTIONS",
                    "TRACE",
                    "CONNECT"
                  ]
                }
              },
              "userAgent": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "contains",
                      "not_contains"
                    ]
                  },
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "asn": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "in",
                      "not_in"
                    ]
                  },
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                }
              },
              "country": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "in",
                      "not_in"
                    ]
                  },
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[A-Z]{2}$"
                    }
                  }
                }
              },
              "network": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "is",
                      "is_not"
                    ]
                  },
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "datacenter",
                        "tor"
                      ]
                    }
                  }
                }
              },
              "headers": {
                "type": "array",
                "maxItems": 5,
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "op": {
                      "type": "string",
                      "enum": [
                        "contains",
                        "equals",
                        "present",
                        "absent"
                      ]
                    },
                    "value": {
                      "type": "string"
                    }
                  }
                }
              },
              "query": {
                "type": "array",
                "maxItems": 5,
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "\"*\" = any parameter (contains/equals only)"
                    },
                    "op": {
                      "type": "string",
                      "enum": [
                        "contains",
                        "equals",
                        "present",
                        "absent"
                      ]
                    },
                    "value": {
                      "type": "string"
                    }
                  }
                }
              },
              "ip": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "in",
                      "not_in"
                    ]
                  },
                  "values": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "type": "string",
                      "description": "IP or CIDR"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "nx_live_<entropy>",
        "description": "Standard HTTP Authorization header with Bearer token format"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Alternative custom header passing the raw API token secret"
      },
      "scopes": {
        "sites:read": "View websites and configuration",
        "sites:write": "Create and update websites and settings",
        "sites:delete": "Delete websites",
        "policies:read": "Read security policy and mode settings",
        "policies:write": "Modify security policy and mode settings",
        "analytics:read": "Read analytics and traffic metrics",
        "events:read": "Read security event logs",
        "tokens:read": "List API tokens",
        "tokens:write": "Create and revoke API tokens"
      }
    }
  }
}