Scanning API v2

The v2 scanning API runs a multi-page crawl in every consent state (none / necessary / functional / analytics / marketing / all) and emits a richly structured result — cookies per state, per-page network requests, storage writes, violations, score breakdown, and a diff vs your previous scan.

Use v2 when you need compliance evidence across consent states (e.g. “do analytics cookies actually stop firing when the user rejects analytics?”). For a simpler single-shot scan, use v1.

v2 scans are asynchronous. A full 12-page × 6-state scan typically takes 2–5 minutes. Either poll GET /api/v2/scan/{scanId} or register a webhook.

Authentication

All v2 endpoints require an API key (X-API-Key: cb_live_…). Available on Pro, Business, and Enterprise plans. See Authentication for details.

Default quota: 10 scans per hour per API key. Requests that exceed the quota return 429.

Triggering a Scan

POST /api/v2/scan Start an async multi-consent-state scan

Request body:

{
  "domain": "lyse.no",
  "consentStates": ["none", "necessary", "functional", "analytics", "marketing", "all"],
  "maxPages": 12,
  "maxDepth": 3,
  "callbackUrl": "https://example.com/webhooks/cookieboss",
  "callbackSecret": "your-shared-secret-32-chars-min"
}

Fields:

  • domain — apex or subdomain to scan. IPs and private/internal hosts are rejected.
  • consentStates — optional, defaults to all six states. Any subset is valid.
  • maxPages — optional (default 10, max 50). Caps total pages crawled.
  • maxDepth — optional (default 3, max 5). Caps BFS depth from the homepage.
  • callbackUrl — optional HTTPS URL that receives a signed scan.completed webhook. Private/internal hosts are rejected.
  • callbackSecret — optional shared secret used to HMAC-sign the webhook payload. If omitted and callbackUrl is provided, CookieBoss generates one. Min 16 chars.

Returns 202 Accepted with the scanId. estimatedDuration is in seconds.

Response

{
"scanId": "scn_01HKQ...",
"status": "queued",
"estimatedDuration": 420
}

Fetching Results

GET /api/v2/scan/:scanId Fetch scan status or completed result

Three possible responses:

  • 202 Accepted — scan still running:
    { "scanId": "scn_...", "status": "running", "startedAt": "2026-04-18T10:15:00Z" }
  • 200 OK with full payload — scan completed. See Result Shape.
  • 200 OK with { "status": "failed", "errorMessage": "..." } — scan failed.
  • 404 Not Found — unknown scanId, or scan belongs to a different customer.

Payloads are retained for 12 months after completion.

Webhook Delivery

When you pass a callbackUrl, CookieBoss POSTs a signed notification when the scan completes.

POST <callbackUrl>
Content-Type: application/json
X-CookieBoss-Signature: sha256=<hex>
X-CookieBoss-Event: scan.completed
X-CookieBoss-Delivery: <ulid>

{
  "event": "scan.completed",
  "scanId": "scn_...",
  "domain": "lyse.no",
  "status": "completed",
  "completedAt": "2026-04-18T10:22:31Z",
  "score": { "overall": 62, "grade": "D" }
}

Signature verification: Compute HMAC-SHA256 of the raw request body using your callbackSecret, hex-encode, and compare to the value after sha256= in X-CookieBoss-Signature.

Retry policy: On any non-2xx response or network error, delivery is retried up to 3 times with exponential backoff (5s, 30s, 5min). After exhausting retries, callback_status on the scan is marked failed.

The webhook only contains the summary. Call GET /api/v2/scan/{scanId} to fetch the full result.

Result Shape

{
  "scanId": "scn_01HKQ...",
  "status": "completed",
  "domain": "lyse.no",
  "startedAt": "2026-04-18T10:15:00Z",
  "completedAt": "2026-04-18T10:22:31Z",
  "durationMs": 451234,
  "pagesScanned": ["https://lyse.no/", "https://lyse.no/strom", "..."],
  "consentStates": ["none", "necessary", "functional", "analytics", "marketing", "all"],
  "perConsentState": [
    {
      "state": "marketing",
      "cookies": [
        {
          "name": "_hjSession_5844",
          "domain": ".lyse.no",
          "category": "analytics",
          "vendor": "Hotjar",
          "expiryDays": 30,
          "valueHash": "a1b2c3d4e5f6g7h8",
          "sameSite": "Lax",
          "httpOnly": false,
          "secure": true,
          "isFirstParty": true,
          "setBeforeConsent": false
        }
      ],
      "storage": [
        { "kind": "localStorage", "key": "hjClosedSurveyInvites", "valueHash": null, "page": "https://lyse.no/strom" }
      ],
      "requests": [
        {
          "url": "https://static.hotjar.com/c/hotjar-12345.js",
          "method": "GET",
          "resourceType": "script",
          "destinationDomain": "static.hotjar.com",
          "page": "https://lyse.no/strom",
          "isThirdParty": true
        }
      ],
      "violations": [
        {
          "type": "category_leak",
          "severity": "high",
          "consentedCategory": "marketing",
          "actualCategory": "analytics",
          "asset": { "kind": "cookie", "name": "_hjSession_5844", "vendor": "Hotjar", "category": "analytics" },
          "page": "",
          "evidence": "_hjSession_5844 fired in marketing consent state but requires analytics consent"
        }
      ]
    }
  ],
  "cookieInventory": [
    {
      "name": "_hjSession_5844",
      "domain": ".lyse.no",
      "category": "analytics",
      "vendor": "Hotjar",
      "expiryDays": 30,
      "firstSeenInState": "analytics",
      "firstSeenOnPage": "",
      "preConsent": false
    }
  ],
  "score": {
    "overall": 62,
    "grade": "D",
    "version": "1.0",
    "breakdown": [
      { "dimension": "all", "deduction": 15, "description": "3 non-necessary cookie(s) set before consent" },
      { "dimension": "all", "deduction": 10, "description": "7 third-party tracking request(s) before consent" }
    ]
  },
  "changes": {
    "previousScanId": "scn_01HJX...",
    "newCookies": [],
    "removedCookies": [],
    "newViolations": [],
    "resolvedViolations": []
  }
}

Cookie values are never stored. valueHash is a 16-char prefix of sha256(value) — enough to detect “same cookie, different value” across scans without holding pseudonymous identifiers.

Violation Types

TypeDescriptionSeverity
pre_consentNon-necessary cookie set before any consenthigh/medium
category_leakCookie fires in a state where its category is disabledhigh
uncategorizedCookie observed but not classifiedlow
miscategorizedCMP declaration differs from CookieBoss classificationmedium
third_party_request_before_consentThird-party request before consent grantedhigh
excessive_lifetimeCookie lifetime exceeds 12 months (Datatilsynet guidance)medium
fingerprinting_without_consentFingerprinting API used without consenthigh

CMP Support

Tier-1 support (tested, reliable): Cookiebot.

Best-effort (CMP detected and consent cookies injected; validate output per site): OneTrust, Cookie Information, Didomi, Usercentrics, Quantcast, Sourcepoint, CookieYes, Complianz, Termly, Iubenda, TrustArc, and 15+ others.

If no CMP is detected on your site, CookieBoss still runs the none and all states — but per-category states may return identical results.

Scoring

The compliance score is a fixed algorithm, versioned as scoreVersion: "1.0" in the response. The algorithm is not silently retuned between releases — a new version is cut if the weights change, and consumers can filter historical scans by version.

Deductions are returned in score.breakdown with per-jurisdiction attribution (gdpr, ccpa, eprivacy, or all).

Example: End-to-end Flow

# 1. Trigger scan
curl -X POST https://api.cookieboss.io/api/v2/scan \
  -H "X-API-Key: cb_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "lyse.no",
    "callbackUrl": "https://example.com/webhook",
    "callbackSecret": "my-shared-secret-32-chars-min"
  }'
# → { "scanId": "scn_01HKQ...", "status": "queued", "estimatedDuration": 420 }

# 2. Wait for webhook, OR poll:
curl https://api.cookieboss.io/api/v2/scan/scn_01HKQ... \
  -H "X-API-Key: cb_live_..."

# 3. When status=completed, the same GET returns the full result payload.

Rate Limits & Quotas

PlanScans/hour per keymaxPages cap
Pro1025
Business1050
EnterpriseOn request50

Rate limits are tracked per API key in hourly buckets. Exceeding returns 429 with:

{ "error": "rate_limit_exceeded", "limit": 10, "windowStart": "2026-04-18T10:00:00.000Z" }

Error Responses

  • 400 validation_error — request body failed validation (invalid domain, callback URL, etc.)
  • 401 unauthorized — missing or invalid API key
  • 403 forbidden — API key’s plan doesn’t include v2 scanning
  • 404 not_found — unknown scanId or scan belongs to a different customer
  • 429 rate_limit_exceeded — hourly quota exceeded
  • 500 internal_error — transient failure; safe to retry with a new scanId