{
  "openapi": "3.1.0",
  "info": {
    "title": "AccessDiff API",
    "version": "1.0.0",
    "summary": "Know what broke on your website's accessibility this week.",
    "description": "AccessDiff scans your sites every week with automated checks against WCAG 2.2 AA, shows you exactly what changed since last week, and sends a report you can forward to whoever owns the fix. The same scan is available to people in the web app and to agents through the API and MCP server.\n\n## Authentication\nSend an API key as `Authorization: Bearer <key>` (or `X-API-Key`). Keys are created on the account page (https://accessdiff.com/app/api-keys). Signed-in browser sessions work too.\n\n## Metering and pricing\nEvery successful `scan` call records one `site_scan` usage event and every `quick-scan` call one `quick_scan` event. Accounts on a plan use their monthly allowances (Starter: 40 site scans, 200 quick scans, 5 watched sites, $49/month; Growth: 200 site scans, 1,000 quick scans, 25 watched sites, $99/month; Agency: 2000 site scans, 5,000 quick scans, unlimited watched sites, $149/month). The weekly scans of watched sites are included and never count against the allowance. Past the allowance, API and agent callers on a paid account pay $2.00 per site scan and $0.05 per quick scan; the free tier is the instant scan only and gets HTTP 402 with a link to the pricing page.\nAgents should send `x-usage-source: agent` so their usage is attributed correctly; it does not change the price.\n\n## What a scan is\nAutomated checks against WCAG 2.2 AA (the axe-core rule set, or a reduced built-in rule set when axe-core is unavailable, which the result states). Automated checks find a portion of accessibility issues; a review by a person is needed to find the rest. Results are not legal advice.\n\n## Rate limits\nPer key or IP, published in the `X-RateLimit-*` response headers. A 429 carries `Retry-After`. A site scan takes one to four minutes; keep the HTTP client timeout at five minutes.\n\nSupport: support@tenhatch.com. Machine-readable product summary: https://accessdiff.com/.well-known/product.json",
    "contact": {
      "name": "AccessDiff support",
      "email": "support@tenhatch.com",
      "url": "https://accessdiff.com"
    },
    "termsOfService": "https://accessdiff.com/legal/terms",
    "x-pricing": {
      "currency": "usd",
      "perResultCents": 200,
      "eventName": "site_scan",
      "additionalEvents": [
        {
          "eventName": "quick_scan",
          "perResultCents": 5,
          "name": "Quick scan"
        }
      ],
      "freeResultsPerMonth": 0,
      "plans": [
        {
          "planId": "starter",
          "name": "Starter",
          "monthly": "$49/month",
          "annual": "$490/year",
          "annualPerMonth": "$40.83/month",
          "savingsPercent": 17,
          "savings": "$98",
          "resultsPerMonth": 40,
          "quickScansPerMonth": 200,
          "sites": 5
        },
        {
          "planId": "growth",
          "name": "Growth",
          "monthly": "$99/month",
          "annual": "$990/year",
          "annualPerMonth": "$82.50/month",
          "savingsPercent": 17,
          "savings": "$198",
          "resultsPerMonth": 200,
          "quickScansPerMonth": 1000,
          "sites": 25
        },
        {
          "planId": "agency",
          "name": "Agency",
          "monthly": "$149/month",
          "annual": "$1,490/year",
          "annualPerMonth": "$124.17/month",
          "savingsPercent": 17,
          "savings": "$298",
          "resultsPerMonth": 2000,
          "quickScansPerMonth": 5000,
          "sites": null
        }
      ],
      "refundWindowDays": 14
    }
  },
  "servers": [
    {
      "url": "https://accessdiff.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "feature",
      "description": "Site scan"
    },
    {
      "name": "sites",
      "description": "Watched sites (session only)"
    },
    {
      "name": "account",
      "description": "Account, usage and API keys"
    },
    {
      "name": "meta",
      "description": "Documents about the API itself"
    }
  ],
  "paths": {
    "/api/v1/scan": {
      "post": {
        "tags": [
          "feature"
        ],
        "operationId": "scan",
        "summary": "Site scan",
        "description": "Scans up to 25 pages of a website with automated accessibility checks against WCAG 2.2 AA, stores the snapshot, diffs it against the previous scan of that site and returns the summary with a link to the full report.\n\nInput: A JSON object with a `siteUrl` string (http or https) and an optional `maxPages` integer (1 to 25, default 25).\n\nOutput: A JSON object with the snapshot summary (score, counts by impact, what changed since the previous scan, pages scanned, top issues) and a signed `reportUrl` to the HTML report (append `/pdf` for the PDF).\n\nThe call is synchronous and takes one to four minutes. The snapshot is stored under the account's site record for that URL (created on first use, not watched weekly until you turn that on in the dashboard), so the next call diffs against it.",
        "parameters": [
          {
            "name": "x-usage-source",
            "in": "header",
            "required": false,
            "description": "Set to `agent` when the caller is an AI agent or MCP client.",
            "schema": {
              "type": "string",
              "enum": [
                "agent",
                "api"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FeatureInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scan summary, report links and how it was metered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (see `error.details`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Plan allowance exhausted and pay-as-you-go not available for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A scan of this site is already running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-price-per-result-cents": 200,
        "x-usage-event": "site_scan"
      }
    },
    "/api/v1/quick-scan": {
      "post": {
        "tags": [
          "feature"
        ],
        "operationId": "quickScan",
        "summary": "Quick scan of one page",
        "description": "One page, about 20 seconds: counts by impact, a page score and the top five problems grouped by rule, with an example element and the rule link for each. Nothing is stored. The same checks as the site scan, on one page.",
        "parameters": [
          {
            "name": "x-usage-source",
            "in": "header",
            "required": false,
            "description": "Set to `agent` when the caller is an AI agent or MCP client.",
            "schema": {
              "type": "string",
              "enum": [
                "agent",
                "api"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuickScanInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quick scan result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuickScanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (see `error.details`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Quick scan allowance exhausted and pay-as-you-go not available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-price-per-result-cents": 5,
        "x-usage-event": "quick_scan"
      }
    },
    "/api/v1/sites": {
      "get": {
        "tags": [
          "sites"
        ],
        "operationId": "listSites",
        "summary": "List the sites of the account with their latest scan",
        "responses": {
          "200": {
            "description": "Sites.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Site"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "sites"
        ],
        "operationId": "createSite",
        "summary": "Watch a site weekly (session only)",
        "security": [
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSiteInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The site; its first scan is queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Site"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The plan watches no more sites.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{id}": {
      "delete": {
        "tags": [
          "sites"
        ],
        "operationId": "deleteSite",
        "summary": "Stop watching a site (session only)",
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed."
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such site on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sites/{id}/scan": {
      "post": {
        "tags": [
          "sites"
        ],
        "operationId": "scanSiteNow",
        "summary": "Queue an on-demand scan of a watched site (session only)",
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued; the dashboard shows the result when the worker is done.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Site scan allowance exhausted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such site on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A scan is already queued or running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getUsage",
        "summary": "Current-period usage meters",
        "responses": {
          "200": {
            "description": "Usage this calendar month.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "getAccount",
        "summary": "The calling account, its plan and limits",
        "responses": {
          "200": {
            "description": "Account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys": {
      "get": {
        "tags": [
          "account"
        ],
        "operationId": "listApiKeys",
        "summary": "List API keys (session only)",
        "security": [
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Keys without secrets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ApiKeySummary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "account"
        ],
        "operationId": "createApiKey",
        "summary": "Create an API key (session only)",
        "security": [
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new key; the secret is shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateApiKeyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key limit for the plan reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys/{id}": {
      "delete": {
        "tags": [
          "account"
        ],
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key (session only)",
        "security": [
          {
            "session": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "401": {
            "description": "Missing or invalid API key / session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such key on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/openapi.json": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "getOpenApi",
        "summary": "This document",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key as a bearer token."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },
      "session": {
        "type": "apiKey",
        "in": "cookie",
        "name": "session",
        "description": "Browser session of a signed-in user."
      }
    },
    "schemas": {
      "FeatureInput": {
        "type": "object",
        "properties": {
          "siteUrl": {
            "type": "string",
            "minLength": 4,
            "maxLength": 2048,
            "description": "The site to scan, e.g. https://example.com. Public http(s) addresses only."
          },
          "maxPages": {
            "description": "Pages to scan, found through the sitemap and links (default 25).",
            "type": "integer",
            "minimum": 1,
            "maximum": 25
          }
        },
        "required": [
          "siteUrl"
        ],
        "description": "Input of the site scan."
      },
      "ScanResponse": {
        "type": "object",
        "properties": {
          "result": {
            "type": "object",
            "properties": {
              "siteUrl": {
                "type": "string"
              },
              "scannedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "durationMs": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "score": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Site score 0–100 (average of page scores); null when no page could be scanned."
              },
              "previousScore": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "scoreDelta": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "counts": {
                "type": "object",
                "properties": {
                  "critical": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "serious": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "moderate": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "minor": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "total": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "critical",
                  "serious",
                  "moderate",
                  "minor",
                  "total"
                ],
                "additionalProperties": false,
                "description": "Issue counts by impact."
              },
              "pages": {
                "type": "object",
                "properties": {
                  "scanned": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "errored": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "crawled": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "scanned",
                  "errored",
                  "crawled"
                ],
                "additionalProperties": false
              },
              "changes": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "previousScannedAt": {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                      },
                      "new": {
                        "type": "object",
                        "properties": {
                          "critical": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "serious": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "moderate": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "minor": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "critical",
                          "serious",
                          "moderate",
                          "minor",
                          "total"
                        ],
                        "additionalProperties": false,
                        "description": "Issue counts by impact."
                      },
                      "fixed": {
                        "type": "object",
                        "properties": {
                          "critical": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "serious": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "moderate": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "minor": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "critical",
                          "serious",
                          "moderate",
                          "minor",
                          "total"
                        ],
                        "additionalProperties": false,
                        "description": "Issue counts by impact."
                      },
                      "unchanged": {
                        "type": "object",
                        "properties": {
                          "critical": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "serious": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "moderate": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "minor": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "total": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "critical",
                          "serious",
                          "moderate",
                          "minor",
                          "total"
                        ],
                        "additionalProperties": false,
                        "description": "Issue counts by impact."
                      },
                      "unverified": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      },
                      "pagesAdded": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      },
                      "pagesRemoved": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 9007199254740991
                      }
                    },
                    "required": [
                      "previousScannedAt",
                      "new",
                      "fixed",
                      "unchanged",
                      "unverified",
                      "pagesAdded",
                      "pagesRemoved"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "What changed since the previous scan of this site; null on a first scan."
              },
              "topIssues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "ruleId": {
                      "type": "string"
                    },
                    "impact": {
                      "type": "string",
                      "enum": [
                        "critical",
                        "serious",
                        "moderate",
                        "minor"
                      ],
                      "description": "Impact level assigned by the axe-core rule set."
                    },
                    "help": {
                      "type": "string",
                      "description": "Short title of the rule, e.g. \"Images must have alternative text\"."
                    },
                    "helpUrl": {
                      "type": "string"
                    },
                    "wcagCriteria": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "bestPractice": {
                      "type": "boolean"
                    },
                    "occurrences": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Elements failing this rule across the pages."
                    },
                    "pages": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "newOccurrences": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "New since the previous scan (0 on a first scan)."
                    },
                    "example": {
                      "type": "object",
                      "properties": {
                        "pageUrl": {
                          "type": "string"
                        },
                        "selector": {
                          "type": "string"
                        },
                        "htmlSnippet": {
                          "type": "string"
                        },
                        "fixSummary": {
                          "type": "string",
                          "description": "How to fix, from the axe-core failure summary."
                        }
                      },
                      "required": [
                        "pageUrl",
                        "selector",
                        "htmlSnippet",
                        "fixSummary"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "ruleId",
                    "impact",
                    "help",
                    "helpUrl",
                    "wcagCriteria",
                    "bestPractice",
                    "occurrences",
                    "pages",
                    "newOccurrences",
                    "example"
                  ],
                  "additionalProperties": false,
                  "description": "Issues of one rule across the scanned pages."
                }
              },
              "engine": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "version": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "version"
                ],
                "additionalProperties": false
              },
              "errors": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Site-level problems, e.g. robots.txt disallowing the scan."
              },
              "disclaimer": {
                "type": "string"
              }
            },
            "required": [
              "siteUrl",
              "scannedAt",
              "durationMs",
              "score",
              "previousScore",
              "scoreDelta",
              "counts",
              "pages",
              "changes",
              "topIssues",
              "engine",
              "errors",
              "disclaimer"
            ],
            "additionalProperties": false,
            "description": "Summary of a stored site scan."
          },
          "siteId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "snapshotId": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "reportUrl": {
            "type": "string",
            "description": "Signed link to the HTML report (valid for 30 days)."
          },
          "pdfUrl": {
            "description": "Signed link to the PDF, or null when it could not be rendered.",
            "type": [
              "string",
              "null"
            ]
          },
          "usage": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "human",
                  "agent",
                  "api"
                ],
                "description": "Which door a request came through: human (web app), agent (MCP / agent frameworks), api (plain API calls)."
              },
              "chargedCents": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Cost of this call for pay-as-you-go callers; 0 when covered by a plan."
              },
              "remaining": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Site scans left in the current period after this call."
              }
            },
            "required": [
              "source",
              "chargedCents",
              "remaining"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "result",
          "siteId",
          "snapshotId",
          "reportUrl",
          "pdfUrl",
          "usage"
        ],
        "additionalProperties": false,
        "description": "The scan summary, the report links and how the call was metered."
      },
      "QuickScanInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 4,
            "maxLength": 2048,
            "description": "The one page to scan, e.g. https://example.com/pricing."
          }
        },
        "required": [
          "url"
        ],
        "description": "Input of the quick scan (one page, about 20 seconds)."
      },
      "QuickScanResponse": {
        "type": "object",
        "properties": {
          "result": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string"
              },
              "finalUrl": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "scannedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "durationMs": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "status": {
                "type": "string",
                "enum": [
                  "ok",
                  "error"
                ]
              },
              "error": {
                "type": "object",
                "properties": {
                  "stage": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                },
                "required": [
                  "stage",
                  "message"
                ],
                "additionalProperties": false
              },
              "counts": {
                "type": "object",
                "properties": {
                  "critical": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "serious": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "moderate": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "minor": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "total": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "critical",
                  "serious",
                  "moderate",
                  "minor",
                  "total"
                ],
                "additionalProperties": false,
                "description": "Issue counts by impact."
              },
              "topIssues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "ruleId": {
                      "type": "string"
                    },
                    "impact": {
                      "type": "string",
                      "enum": [
                        "critical",
                        "serious",
                        "moderate",
                        "minor"
                      ],
                      "description": "Impact level assigned by the axe-core rule set."
                    },
                    "help": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "helpUrl": {
                      "type": "string"
                    },
                    "wcagCriteria": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "bestPractice": {
                      "type": "boolean"
                    },
                    "count": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "example": {
                      "type": "object",
                      "properties": {
                        "selector": {
                          "type": "string"
                        },
                        "htmlSnippet": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "selector",
                        "htmlSnippet"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "ruleId",
                    "impact",
                    "help",
                    "description",
                    "helpUrl",
                    "wcagCriteria",
                    "bestPractice",
                    "count",
                    "example"
                  ],
                  "additionalProperties": false
                },
                "description": "Top five problems grouped by rule, most severe first."
              },
              "score": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "engine": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "version": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "version"
                ],
                "additionalProperties": false
              },
              "disclaimer": {
                "type": "string"
              }
            },
            "required": [
              "url",
              "finalUrl",
              "title",
              "scannedAt",
              "durationMs",
              "status",
              "counts",
              "topIssues",
              "score",
              "engine",
              "disclaimer"
            ],
            "additionalProperties": false,
            "description": "Result of the quick scan of one page."
          },
          "usage": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "human",
                  "agent",
                  "api"
                ],
                "description": "Which door a request came through: human (web app), agent (MCP / agent frameworks), api (plain API calls)."
              },
              "chargedCents": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "remaining": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Quick scans left in the current period after this call."
              }
            },
            "required": [
              "source",
              "chargedCents",
              "remaining"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "result",
          "usage"
        ],
        "additionalProperties": false,
        "description": "The quick scan result plus how it was metered."
      },
      "Site": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "url": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "watch": {
            "type": "boolean"
          },
          "maxPages": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
          },
          "lastScanAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "lastScore": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              {
                "type": "null"
              }
            ]
          },
          "lastScanStatus": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "ok",
                  "incomplete",
                  "failed"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "lastNewCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "lastFixedCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "lastNewCriticalCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "nextScanAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "activeJob": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "queued",
                      "running"
                    ]
                  }
                },
                "required": [
                  "id",
                  "status"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "url",
          "name",
          "watch",
          "maxPages",
          "createdAt",
          "lastScanAt",
          "lastScore",
          "lastScanStatus",
          "lastNewCount",
          "lastFixedCount",
          "lastNewCriticalCount",
          "nextScanAt",
          "activeJob"
        ],
        "additionalProperties": false,
        "description": "A watched site and its latest scan."
      },
      "CreateSiteInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 4,
            "maxLength": 2048,
            "description": "The site to watch, e.g. https://example.com."
          },
          "name": {
            "description": "Name on reports (default: the host).",
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          }
        },
        "required": [
          "url"
        ],
        "description": "Input for adding a watched site."
      },
      "UsageReport": {
        "type": "object",
        "properties": {
          "siteScans": {
            "type": "object",
            "properties": {
              "used": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "limit": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "remaining": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "percent": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "exhausted": {
                "type": "boolean"
              },
              "period": {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  }
                },
                "required": [
                  "start",
                  "end"
                ],
                "additionalProperties": false
              },
              "bySource": {
                "type": "object",
                "properties": {
                  "human": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "agent": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "api": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "human",
                  "agent",
                  "api"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "used",
              "limit",
              "remaining",
              "percent",
              "exhausted",
              "period",
              "bySource"
            ],
            "additionalProperties": false,
            "description": "Results used in the current calendar month (UTC) against the plan limit, split by door."
          },
          "quickScans": {
            "type": "object",
            "properties": {
              "used": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "limit": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "remaining": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "percent": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "exhausted": {
                "type": "boolean"
              },
              "period": {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  }
                },
                "required": [
                  "start",
                  "end"
                ],
                "additionalProperties": false
              },
              "bySource": {
                "type": "object",
                "properties": {
                  "human": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "agent": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "api": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  }
                },
                "required": [
                  "human",
                  "agent",
                  "api"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "used",
              "limit",
              "remaining",
              "percent",
              "exhausted",
              "period",
              "bySource"
            ],
            "additionalProperties": false,
            "description": "Results used in the current calendar month (UTC) against the plan limit, split by door."
          },
          "sites": {
            "type": "object",
            "properties": {
              "used": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "limit": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "null = unlimited (fair use)."
              }
            },
            "required": [
              "used",
              "limit"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "siteScans",
          "quickScans",
          "sites"
        ],
        "additionalProperties": false,
        "description": "The three meters of an account: site scans, quick scans, watched sites."
      },
      "Account": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "email": {
            "type": "string",
            "format": "email",
            "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
          },
          "plan": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ],
            "additionalProperties": false,
            "description": "The free tier has id \"free\"."
          },
          "limits": {
            "type": "object",
            "properties": {
              "resultsPerMonth": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991,
                "description": "Site scans included per month."
              },
              "apiKeys": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "seats": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "sites": {
                "anyOf": [
                  {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "quickScansPerMonth": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "pagesPerSite": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991
              },
              "whiteLabel": {
                "type": "boolean"
              }
            },
            "required": [
              "resultsPerMonth",
              "apiKeys",
              "seats",
              "sites",
              "quickScansPerMonth",
              "pagesPerSite",
              "whiteLabel"
            ],
            "additionalProperties": false
          },
          "subscription": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string"
                  },
                  "interval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "annual"
                    ]
                  },
                  "currentPeriodEnd": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "date-time",
                        "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "cancelAtPeriodEnd": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "status",
                  "interval",
                  "currentPeriodEnd",
                  "cancelAtPeriodEnd"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "email",
          "plan",
          "limits",
          "subscription"
        ],
        "additionalProperties": false,
        "description": "The calling account."
      },
      "ApiKeySummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "First characters of the key, for recognising it. The full key is only returned once, on creation."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
          },
          "lastUsedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "revokedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "prefix",
          "createdAt",
          "lastUsedAt",
          "revokedAt"
        ],
        "additionalProperties": false,
        "description": "An API key without its secret."
      },
      "CreateApiKeyInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "description": "A label such as \"CI\" or \"Claude Desktop\"."
          }
        },
        "required": [
          "name"
        ],
        "description": "Input for creating an API key."
      },
      "CreateApiKeyResponse": {
        "type": "object",
        "properties": {
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "name": {
                "type": "string"
              },
              "prefix": {
                "type": "string",
                "description": "First characters of the key, for recognising it. The full key is only returned once, on creation."
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "lastUsedAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "revokedAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "id",
              "name",
              "prefix",
              "createdAt",
              "lastUsedAt",
              "revokedAt"
            ],
            "additionalProperties": false,
            "description": "An API key without its secret."
          },
          "secret": {
            "type": "string",
            "description": "The full API key. Shown once; store it now."
          }
        },
        "required": [
          "key",
          "secret"
        ],
        "additionalProperties": false,
        "description": "A freshly created API key."
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "invalid_input",
                  "quota_exhausted",
                  "rate_limited",
                  "payment_required",
                  "conflict",
                  "unavailable",
                  "not_open",
                  "internal"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {}
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "description": "Error envelope returned with every non-2xx status."
      }
    }
  }
}