## GET /api/v2/cdn/security-insights

**Get the 24-hour CDN security report**

Account-level security report covering every CDN zone the caller owns: how much automated traffic the audience filter stopped (challenged requests that never completed verification, plus outright rejections), how many real visitors outside the audience passed verification, and how much trusted automated traffic (verified bots such as Google's indexer, payment webhooks, allowlisted services) was let through. Broken down per zone with top countries, challenged request paths, source IP addresses, trusted-traffic paths, the named services let through (search engines, AI crawlers, monitors) and hourly activity, correlated with each zone's configured audience filter. Includes a structured AI explanation in Swedish and English.

Reports are generated at most once per 24 hours per account and served from cache in between (`status: "ready"`). When the cached report is older than 24 hours it is returned immediately as `status: "stale"` and one background refresh starts; poll again shortly and `refreshPending` turns `false` when the new report is ready. `pending` means the first report is still being generated. A high challenged count is usually the expected result of a narrow audience filter, not an attack; `totals.challengePassRatePercent` shows how little of the challenged traffic was human. When `truncated` is `true` the counts are minimums.

### Related Endpoints

- `GET /api/v2/cdn/zones`: List CDN zones
- `GET /api/v2/cdn/zones/{id}`: Get CDN zone details
- `PATCH /api/v2/cdn/zones/{id}`: Update CDN zone settings

### Headers

- `Accept`: application/json
- `Authorization`: Bearer YOUR_API_KEY
- Required API scopes: `read:cdn`, `read:domains`

### Request Example

```bash
curl -X GET "https://cloud.hostup.se/api/v2/cdn/security-insights" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
```

### Response Schema

- `status` (string, required): ready = report younger than 24h; stale = older report returned while a refresh runs; pending = first report generating; failed = no report and the last attempt failed; none = no eligible zones; unsupported = the CDN integration is unavailable.
  Allowed values: ready, stale, pending, failed, none, unsupported
- `refreshPending` (boolean, required): True while a newer report is being generated in the background.
- `report` (object, required, nullable): The newest completed report, or null when none exists yet.

### Responses

#### 200 - The current security report state for the account.
```json
{
  "status": "ready",
  "refreshPending": false,
  "report": {
    "windowStart": "2026-07-28T06:00:00.000Z",
    "windowEnd": "2026-07-29T06:00:00.000Z",
    "generatedAt": "2026-07-29T06:00:05.000Z",
    "truncated": false,
    "totals": {
      "securityActionCount": 2398,
      "managedChallengeCount": 2398,
      "jsChallengeCount": 0,
      "interactiveChallengeCount": 0,
      "hardBlockCount": 0,
      "solvedChallengeCount": 41,
      "bypassedChallengeCount": 3,
      "trustedSkipCount": 356,
      "otherActionCount": 0,
      "challengeCount": 2398,
      "challengePassedCount": 44,
      "stoppedRequestCount": 2354,
      "challengePassRatePercent": 1.8
    },
    "previousSecurityActionCount": 2101,
    "trendPercent": 14,
    "topCountries": [
      {
        "countryCode": "US",
        "count": 981
      },
      {
        "countryCode": "DE",
        "count": 512
      }
    ],
    "topPaths": [
      {
        "path": "/wp-login",
        "count": 611
      },
      {
        "path": "/xmlrpc",
        "count": 402
      }
    ],
    "topIps": [
      {
        "ip": "203.0.113.7",
        "count": 188
      }
    ],
    "actionCounts": [
      {
        "action": "managed_challenge",
        "count": 2398
      }
    ],
    "sourceCounts": [
      {
        "source": "firewallCustom",
        "count": 2398
      }
    ],
    "hourly": [
      {
        "hourStart": "2026-07-28T06:00:00.000Z",
        "count": 96
      }
    ],
    "zones": [
      {
        "zoneId": "cdn_01hxa3b4c5d6e7f8g9h0j1k2m3",
        "domain": "example.com",
        "policy": {
          "visitorProfileMode": "challenge",
          "audienceCountries": [
            "SE"
          ]
        },
        "totals": {
          "securityActionCount": 2398,
          "managedChallengeCount": 2398,
          "jsChallengeCount": 0,
          "interactiveChallengeCount": 0,
          "hardBlockCount": 0,
          "solvedChallengeCount": 41,
          "bypassedChallengeCount": 3,
          "trustedSkipCount": 356,
          "otherActionCount": 0,
          "challengeCount": 2398,
          "challengePassedCount": 44,
          "stoppedRequestCount": 2354,
          "challengePassRatePercent": 1.8
        },
        "trendPercent": 14,
        "topCountries": [
          {
            "countryCode": "US",
            "count": 981
          }
        ],
        "topPaths": [
          {
            "path": "/wp-login",
            "count": 611
          }
        ],
        "topIps": [
          {
            "ip": "203.0.113.7",
            "count": 188
          }
        ],
        "actionCounts": [
          {
            "action": "managed_challenge",
            "count": 2398
          }
        ],
        "sourceCounts": [
          {
            "source": "firewallCustom",
            "count": 2398
          }
        ],
        "hourly": [
          {
            "hourStart": "2026-07-28T06:00:00.000Z",
            "count": 96
          }
        ],
        "truncated": false,
        "topTrustedPaths": [
          {
            "path": "/wp-json/wc/v3/webhooks",
            "count": 214
          }
        ],
        "topTrustedBots": [
          {
            "bot": "Googlebot",
            "category": "search",
            "count": 148
          },
          {
            "bot": "ClaudeBot",
            "category": "ai",
            "count": 25
          },
          {
            "bot": "GPTBot",
            "category": "ai",
            "count": 11
          }
        ]
      }
    ],
    "zoneFailures": [],
    "ai": {
      "status": "completed",
      "severity": "normal",
      "confidence": "high",
      "languages": {
        "sv": {
          "headline": "2 354 automatiska besöksförsök stoppades – bara 1,8 % klarade verifieringen",
          "summary": "Ditt målgruppsfilter för Sverige utmanade 2 398 besök utanför målgruppen. Endast 44 klarade verifieringen – riktiga besökare som släpptes in på ett par sekunder. Resten var automatiserad trafik som aldrig nådde din webbplats. Betrodda tjänster som Googles indexeringsbot släpptes förbi automatiskt.",
          "observations": [
            "Botarna försökte främst nå inloggningssidor, ett tecken på automatiska intrångsförsök som stoppades"
          ],
          "recommendations": []
        },
        "en": {
          "headline": "2,354 automated visit attempts were stopped — only 1.8% passed verification",
          "summary": "Your Sweden audience filter challenged 2,398 visits from outside the audience. Only 44 passed verification — real visitors who were let in within a couple of seconds. The rest was automated traffic that never reached your site. Trusted services such as Google's indexing bot were let through automatically.",
          "observations": [
            "Bots mostly targeted login pages, a sign of automated intrusion attempts that were stopped"
          ],
          "recommendations": []
        }
      }
    },
    "topTrustedPaths": [
      {
        "path": "/wp-json/wc/v3/webhooks",
        "count": 214
      },
      {
        "path": "/sitemap-index",
        "count": 88
      }
    ],
    "topTrustedBots": [
      {
        "bot": "Googlebot",
        "category": "search",
        "count": 148
      },
      {
        "bot": "ClaudeBot",
        "category": "ai",
        "count": 25
      },
      {
        "bot": "GPTBot",
        "category": "ai",
        "count": 11
      },
      {
        "bot": "OAI-SearchBot",
        "category": "ai",
        "count": 4
      },
      {
        "bot": "UptimeRobot",
        "category": "monitoring",
        "count": 96
      }
    ]
  }
}
```

#### 400 - Invalid request. The response body is an RFC 7807 Problem Details document.
```json
{
  "type": "https://developer.hostup.se/errors/invalid_request",
  "title": "Invalid request",
  "status": 400,
  "detail": "The request body failed validation.",
  "code": "invalid_request",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z",
  "errors": [
    {
      "pointer": "/items/0/domainName",
      "detail": "`domainName` is required.",
      "code": "invalid_request"
    }
  ]
}
```

#### 401 - Unauthorized. Authentication is required.
```json
{
  "type": "https://developer.hostup.se/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication is required.",
  "code": "unauthorized",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 403 - Forbidden. The caller lacks a required scope or does not own the resource.
```json
{
  "type": "https://developer.hostup.se/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "The caller lacks a required scope or does not own the resource.",
  "code": "forbidden",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 404 - Not found. The resource does not exist or is not owned by the caller.
```json
{
  "type": "https://developer.hostup.se/errors/not_found",
  "title": "Not found",
  "status": 404,
  "detail": "The requested resource could not be found.",
  "code": "not_found",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 429 - Rate limited. Retry after the limit resets. 429 responses include `Retry-After` seconds plus `X-RateLimit-*` headers.
```json
{
  "type": "https://developer.hostup.se/errors/rate_limit_exceeded",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests. Retry after the limit resets.",
  "code": "rate_limit_exceeded",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 500 - Internal error. Retry later or contact support if the issue persists.
```json
{
  "type": "https://developer.hostup.se/errors/internal_error",
  "title": "Internal server error",
  "status": 500,
  "detail": "An unexpected error occurred. Retry later or contact support if the issue persists.",
  "code": "internal_error",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```
