{
  "openapi": "3.1.0",
  "info": {
    "title": "PrivacyPill API",
    "version": "1.0.0",
    "summary": "Create cookie banners, configure them, get the embed snippet and run compliance scans.",
    "description": "GDPR / ePrivacy cookie banner with Google Consent Mode v2. `/pre-consent-scan` is free and needs no key (10 checks per hour per visitor). Everything else uses an API key from your dashboard (Developers), sent as `Authorization: Bearer ppk_…`; limits 1,200 requests per key and 30 scans per account per hour. AI assistants can use the MCP server at https://privacy-pill-consent-studio.lovable.app/mcp instead; discovery: https://privacy-pill-consent-studio.lovable.app/.well-known/api-catalog.",
    "contact": {
      "name": "PrivacyPill",
      "url": "https://privacy-pill-consent-studio.lovable.app/developers"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://privacy-pill-consent-studio.lovable.app/terms"
    }
  },
  "servers": [
    {
      "url": "https://privacy-pill-consent-studio.lovable.app/api/public/v1"
    }
  ],
  "externalDocs": {
    "description": "Developer docs",
    "url": "https://privacy-pill-consent-studio.lovable.app/developers"
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Free tools",
      "description": "No API key needed; rate limited per visitor"
    },
    {
      "name": "Sites",
      "description": "Domains and their cookie banners"
    },
    {
      "name": "Scans",
      "description": "Tracker and cookie compliance scans"
    },
    {
      "name": "Account"
    }
  ],
  "paths": {
    "/pre-consent-scan": {
      "get": {
        "tags": [
          "Free tools"
        ],
        "operationId": "preConsentScan",
        "summary": "Which cookies and trackers load before consent (free, no key)",
        "description": "Fetches the page once as a first-time visitor and reports every tracker and cookie that loads before consent, which ones wait (blocked, or held by Consent Mode), and fixes. 10 checks per hour per visitor.",
        "security": [],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 2048
            },
            "examples": {
              "site": {
                "value": "https://example.com"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Report",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Checks allowed per hour per visitor.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreConsentReport"
                }
              }
            }
          },
          "400": {
            "description": "Missing, invalid or private URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The page couldn't be fetched (timeout, HTTP error, not HTML)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Hourly free quota used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the next hourly window.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "502": {
            "description": "Unexpected scan failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limiter unavailable — retry shortly",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the next hourly window.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Free tools"
        ],
        "operationId": "preConsentScanPost",
        "summary": "Same check with a JSON body",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 2048,
                    "examples": [
                      "https://example.com"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Report",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Checks allowed per hour per visitor.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreConsentReport"
                }
              }
            }
          },
          "400": {
            "description": "Missing, invalid or private URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The page couldn't be fetched (timeout, HTTP error, not HTML)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Hourly free quota used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the next hourly window.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "502": {
            "description": "Unexpected scan failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limiter unavailable — retry shortly",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until the next hourly window.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Your account, licenses and limits",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sites": {
      "get": {
        "tags": [
          "Sites"
        ],
        "operationId": "listSites",
        "summary": "List your sites",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sites": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Site"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Sites"
        ],
        "operationId": "createSite",
        "summary": "Create a site",
        "description": "The free plan includes one site per account. Attach a Pro or Lifetime license (license_id) for more.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "domain"
                ],
                "properties": {
                  "domain": {
                    "type": "string",
                    "examples": [
                      "example.com"
                    ]
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "design": {
                    "$ref": "#/components/schemas/DesignPatch"
                  },
                  "license_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "$ref": "#/components/schemas/Site"
                    },
                    "design": {
                      "type": "object"
                    },
                    "embed": {
                      "$ref": "#/components/schemas/Embed"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Plan limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Domain already added",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sites/{siteId}": {
      "parameters": [
        {
          "name": "siteId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Sites"
        ],
        "operationId": "getSite",
        "summary": "Get a site and its banner settings",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "$ref": "#/components/schemas/Site"
                    },
                    "design": {
                      "type": "object",
                      "description": "Full current banner settings."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Sites"
        ],
        "operationId": "updateSite",
        "summary": "Update a site or its banner",
        "description": "Changing which categories are offered bumps the revision, so returning visitors are asked again.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "domain": {
                    "type": "string"
                  },
                  "design": {
                    "$ref": "#/components/schemas/DesignPatch"
                  },
                  "geo_targeting": {
                    "type": "boolean",
                    "description": "Pro: show the banner only where consent law applies."
                  },
                  "monthly_rescan": {
                    "type": "boolean",
                    "description": "Pro: rescan the site every month."
                  },
                  "license_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "site": {
                      "$ref": "#/components/schemas/Site"
                    },
                    "design": {
                      "type": "object",
                      "description": "Full current banner settings."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Plan limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Sites"
        ],
        "operationId": "deleteSite",
        "summary": "Delete a site",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sites/{siteId}/embed": {
      "parameters": [
        {
          "name": "siteId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Sites"
        ],
        "operationId": "getEmbed",
        "summary": "Embed snippet and install steps",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Embed"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sites/{siteId}/scans": {
      "parameters": [
        {
          "name": "siteId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Scans"
        ],
        "operationId": "listSiteScans",
        "summary": "Recent scans of a site",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "scans": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "score": {
                            "type": "integer"
                          },
                          "source": {
                            "type": "string",
                            "enum": [
                              "manual",
                              "scheduled",
                              "api"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Scans"
        ],
        "operationId": "scanSite",
        "summary": "Scan the site now",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "path": {
                    "type": "string",
                    "description": "Page to scan, e.g. /pricing (default: homepage)"
                  },
                  "apply": {
                    "type": "boolean",
                    "description": "Add detected services and cookies to the banner."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scanned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Page couldn't be scanned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/scans": {
      "post": {
        "tags": [
          "Scans"
        ],
        "operationId": "scanUrl",
        "summary": "Scan any public URL",
        "description": "Detects GA4, Google Ads, GTM containers, Meta Pixel, Hotjar, TikTok, LinkedIn and more, and grades the page. Scans of your own domains are saved to that site.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "examples": [
                      "https://example.com"
                    ]
                  },
                  "site_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "apply": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scanned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Page couldn't be scanned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/presets": {
      "get": {
        "tags": [
          "Sites"
        ],
        "operationId": "listPresets",
        "summary": "Colour presets and allowed values",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ppk_ + 32 characters",
        "description": "Create keys in Dashboard → Developers."
      }
    },
    "schemas": {
      "PreConsentReport": {
        "type": "object",
        "description": "What a first-time visitor's browser loads before answering the consent banner. Static check of the HTML, response headers and Google Tag Manager containers; no JavaScript runs.",
        "required": [
          "url",
          "final_url",
          "checked_at",
          "verdict",
          "summary",
          "counts",
          "trackers",
          "cookies",
          "fixes"
        ],
        "properties": {
          "url": {
            "type": "string"
          },
          "final_url": {
            "type": "string",
            "description": "After redirects."
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "verdict": {
            "type": "string",
            "enum": [
              "pass",
              "warning",
              "fail"
            ],
            "description": "fail: something that needs consent loads (or a cookie is set) before the visitor chooses. warning: some trackers couldn't be confirmed statically. pass: nothing that needs consent loads early."
          },
          "summary": {
            "type": "string"
          },
          "counts": {
            "type": "object",
            "properties": {
              "trackers_before_consent": {
                "type": "integer"
              },
              "cookies_before_consent": {
                "type": "integer"
              },
              "waiting_for_consent": {
                "type": "integer"
              },
              "unclear": {
                "type": "integer"
              },
              "unidentified_cookies": {
                "type": "integer",
                "description": "Persistent server cookies we couldn't identify."
              },
              "third_party_hosts": {
                "type": "integer",
                "description": "Other sites the page contacts before consent."
              }
            }
          },
          "consent_banner": {
            "type": "object",
            "properties": {
              "detected": {
                "type": "boolean"
              },
              "tools": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "examples": [
                  [
                    "Cookiebot"
                  ]
                ]
              },
              "auto_blocking": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "consent_mode": {
            "type": "object",
            "properties": {
              "default_denied_found": {
                "type": "boolean"
              },
              "set_before_google_tags": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "null when the page has no Google tags."
              },
              "v2": {
                "type": "boolean"
              }
            }
          },
          "trackers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "examples": [
                    "ga4",
                    "meta-pixel"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "vendor": {
                  "type": "string"
                },
                "category": {
                  "type": "string",
                  "enum": [
                    "necessary",
                    "analytics",
                    "marketing",
                    "functional"
                  ]
                },
                "needs_consent": {
                  "type": "boolean"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "loads_before_consent",
                    "restricted_by_consent_mode",
                    "blocked_until_consent",
                    "may_be_gated_by_cmp",
                    "via_tag_manager",
                    "unclear",
                    "no_consent_needed"
                  ]
                },
                "found_in": {
                  "type": "string",
                  "enum": [
                    "page",
                    "tag_manager",
                    "page_and_tag_manager"
                  ]
                },
                "ids": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "evidence": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                },
                "cookies": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Cookies the tag typically sets (* = prefix)."
                }
              }
            }
          },
          "cookies": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "category": {
                  "type": "string",
                  "enum": [
                    "necessary",
                    "analytics",
                    "marketing",
                    "functional"
                  ]
                },
                "provider": {
                  "type": "string"
                },
                "duration": {
                  "type": "string"
                },
                "purpose": {
                  "type": "string"
                },
                "needs_consent": {
                  "type": "boolean"
                },
                "observed": {
                  "type": "boolean",
                  "description": "true: seen in a Set-Cookie header; false: set by a tag that runs before consent."
                },
                "identified": {
                  "type": "boolean",
                  "description": "false: an unrecognised server cookie — check it's strictly necessary."
                },
                "set_by": {
                  "type": "string"
                },
                "third_party": {
                  "type": "boolean"
                }
              }
            }
          },
          "third_party": {
            "type": "array",
            "description": "Other sites the page's HTML makes the browser contact before consent (each receives the visitor's IP address).",
            "items": {
              "type": "object",
              "properties": {
                "host": {
                  "type": "string",
                  "examples": [
                    "fonts.googleapis.com"
                  ]
                },
                "kinds": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "examples": [
                      "script",
                      "stylesheet",
                      "font",
                      "preconnect",
                      "iframe",
                      "image",
                      "media",
                      "icon"
                    ]
                  }
                },
                "service": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "examples": [
                    "Google Fonts"
                  ]
                },
                "needs_attention": {
                  "type": "boolean"
                },
                "note": {
                  "type": "string"
                },
                "example": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "fixes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "method": {
            "type": "string"
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "properties": {
              "full_scan": {
                "type": "string",
                "format": "uri"
              },
              "customizer": {
                "type": "string",
                "format": "uri"
              },
              "docs": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "Site": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "domain": {
            "type": "string",
            "examples": [
              "example.com"
            ]
          },
          "site_key": {
            "type": "string",
            "examples": [
              "pp_1a2b3c4d5e6f7a8b"
            ]
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "pro",
              "lifetime"
            ]
          },
          "license_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "geo_targeting": {
            "type": "boolean"
          },
          "monthly_rescan": {
            "type": "boolean"
          },
          "revision": {
            "type": "integer",
            "description": "Visitors are asked again when this increases."
          },
          "last_scan_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_scan_score": {
            "type": [
              "integer",
              "null"
            ]
          },
          "script_url": {
            "type": "string",
            "format": "uri"
          },
          "dashboard_url": {
            "type": "string",
            "format": "uri"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Embed": {
        "type": "object",
        "properties": {
          "site_key": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "script_url": {
            "type": "string",
            "format": "uri"
          },
          "script_tag": {
            "type": "string"
          },
          "fallback_snippet": {
            "type": "string",
            "description": "Consent Mode defaults (denied) for when the script is blocked."
          },
          "snippet": {
            "type": "string",
            "description": "Paste this first thing inside <head>."
          },
          "placement": {
            "type": "string"
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "html",
                    "gtm",
                    "wordpress",
                    "shopify",
                    "webflow"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "steps": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "code": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "guides_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "DesignPatch": {
        "type": "object",
        "additionalProperties": false,
        "description": "Every field is optional. Nested objects (theme, categories, gcm, overrides) merge into the current settings.",
        "properties": {
          "preset": {
            "type": "string",
            "enum": [
              "mint",
              "slate",
              "paper",
              "lagoon",
              "sand",
              "contrast"
            ],
            "description": "Apply a built-in colour preset."
          },
          "layout": {
            "type": "string",
            "enum": [
              "pill",
              "bar",
              "modal",
              "corner"
            ]
          },
          "side": {
            "type": "string",
            "enum": [
              "left",
              "right"
            ]
          },
          "theme": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "bg": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$",
                "examples": [
                  "#0f766e"
                ]
              },
              "fg": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$",
                "examples": [
                  "#0f766e"
                ]
              },
              "accent": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$",
                "examples": [
                  "#0f766e"
                ]
              },
              "accentFg": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$",
                "examples": [
                  "#0f766e"
                ]
              },
              "border": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$",
                "examples": [
                  "#0f766e"
                ]
              },
              "radius": {
                "type": "number",
                "minimum": 0,
                "maximum": 40
              },
              "btnRadius": {
                "type": "number",
                "minimum": 0,
                "maximum": 999
              },
              "font": {
                "type": "string",
                "maxLength": 160,
                "examples": [
                  "inherit",
                  "Inter, sans-serif"
                ]
              },
              "size": {
                "type": "number",
                "minimum": 12,
                "maximum": 18
              },
              "shadow": {
                "type": "boolean"
              },
              "buttons": {
                "type": "string",
                "enum": [
                  "outline",
                  "soft",
                  "text"
                ]
              }
            }
          },
          "equal": {
            "type": "boolean",
            "description": "Accept and Reject get equal visual weight (recommended)."
          },
          "reopen": {
            "type": "boolean",
            "description": "Show a small button so visitors can change their choice."
          },
          "categories": {
            "type": "object",
            "additionalProperties": false,
            "description": "Which optional categories the banner offers. Strictly necessary is always on.",
            "properties": {
              "analytics": {
                "type": "boolean"
              },
              "marketing": {
                "type": "boolean"
              },
              "functional": {
                "type": "boolean"
              }
            }
          },
          "services": {
            "type": "object",
            "description": "Service names listed under each category, e.g. { analytics: ['Google Analytics 4'] }.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 120
              },
              "maxItems": 40
            }
          },
          "gcm": {
            "type": "object",
            "additionalProperties": false,
            "description": "Google Consent Mode v2.",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "wait": {
                "type": "number",
                "minimum": 0,
                "maximum": 5000,
                "description": "wait_for_update in ms"
              },
              "redact": {
                "type": "boolean",
                "description": "ads_data_redaction while ad_storage is denied"
              },
              "passthrough": {
                "type": "boolean",
                "description": "url_passthrough"
              }
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "en",
                "de",
                "fr",
                "nl",
                "es",
                "it"
              ]
            },
            "minItems": 1
          },
          "defaultLang": {
            "type": "string",
            "enum": [
              "en",
              "de",
              "fr",
              "nl",
              "es",
              "it"
            ]
          },
          "autoLang": {
            "type": "boolean",
            "description": "Pick the visitor's browser language when available."
          },
          "overrides": {
            "type": "object",
            "description": "Custom wording per language.",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "title": {
                  "type": "string",
                  "maxLength": 120
                },
                "body": {
                  "type": "string",
                  "maxLength": 600
                },
                "accept": {
                  "type": "string",
                  "maxLength": 40
                },
                "reject": {
                  "type": "string",
                  "maxLength": 40
                },
                "customize": {
                  "type": "string",
                  "maxLength": 40
                }
              }
            }
          },
          "policyUrl": {
            "type": "string",
            "maxLength": 500,
            "description": "Full https:// link or a path starting with /"
          },
          "expiryDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 395,
            "description": "How long a choice is remembered."
          },
          "reloadOnWithdraw": {
            "type": "boolean"
          }
        }
      },
      "ScanResult": {
        "type": "object",
        "properties": {
          "scan_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "site_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "applied": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "services": {
                "type": "integer"
              },
              "cookies": {
                "type": "integer"
              }
            }
          },
          "report_url": {
            "type": "string",
            "format": "uri"
          },
          "result": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string"
              },
              "final_url": {
                "type": "string"
              },
              "status": {
                "type": "integer"
              },
              "https": {
                "type": "boolean"
              },
              "score": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "grade": {
                "type": "string",
                "enum": [
                  "A",
                  "B",
                  "C",
                  "D",
                  "E"
                ]
              },
              "counts": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "trackers": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "vendor": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "necessary",
                        "analytics",
                        "marketing",
                        "functional"
                      ]
                    },
                    "cookieless": {
                      "type": "boolean"
                    },
                    "evidence": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "html",
                          "gtm",
                          "set-cookie"
                        ]
                      }
                    }
                  }
                }
              },
              "cookies": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "duration": {
                      "type": "string"
                    },
                    "third_party": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "consent_tools": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "consent_mode": {
                "type": "object",
                "properties": {
                  "default_found": {
                    "type": "boolean"
                  },
                  "v2": {
                    "type": "boolean"
                  },
                  "update_found": {
                    "type": "boolean"
                  }
                }
              },
              "policy_link": {
                "type": "boolean"
              },
              "google_fonts": {
                "type": "boolean"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "level": {
                      "type": "string",
                      "enum": [
                        "high",
                        "medium",
                        "low"
                      ]
                    },
                    "title": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "unauthorized",
                  "not_found",
                  "invalid_request",
                  "plan_limit",
                  "rate_limited",
                  "invalid_url",
                  "timeout"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {}
            },
            "required": [
              "code",
              "message"
            ]
          }
        }
      }
    }
  }
}