{
  "openapi": "3.1.1",
  "info": {
    "title": "Hightower Sites public website scan API",
    "version": "1.1.2",
    "description": "Public technical checks of one website page and retrieval of existing Is Agentic reports. No ordering or customer mutation API. No GraphQL. Keys are not required. Respect advertised IP quotas.",
    "contact": {
      "name": "Hightower Sites / Marko Rampen",
      "email": "website@hightowersites.nl",
      "url": "https://hightowersites.nl/contact.html"
    }
  },
  "servers": [
    {
      "url": "https://hightowersites.nl",
      "description": "Public production website"
    }
  ],
  "externalDocs": {
    "url": "https://hightowersites.nl/docs",
    "description": "Authentication, limits and examples"
  },
  "security": [],
  "tags": [
    {
      "name": "Website scans",
      "description": "Technical first impressions of public HTML pages"
    },
    {
      "name": "Agent readiness",
      "description": "Existing public Is Agentic / Ora reports; no new scan"
    }
  ],
  "paths": {
    "/api/website-scan.php": {
      "post": {
        "operationId": "scanPublicWebsite",
        "tags": [
          "Website scans"
        ],
        "summary": "Check one public website page",
        "description": "Runs core HTML/SEO/security checks or Google PageSpeed. core and psi each consume one quota unit on a cache miss. Cached results last 30 minutes. A provider failure may leave a partial result. Only public HTTP(S) targets on port 80 or 443 are permitted.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000,
                    "description": "Public website page; no credentials or private addresses"
                  },
                  "part": {
                    "type": "string",
                    "enum": [
                      "core",
                      "psi"
                    ],
                    "default": "core",
                    "description": "core checks HTML; psi calls PageSpeed"
                  },
                  "strategy": {
                    "type": "string",
                    "enum": [
                      "mobile",
                      "desktop"
                    ],
                    "default": "mobile",
                    "description": "PageSpeed device profile"
                  },
                  "refresh": {
                    "type": "boolean",
                    "default": false,
                    "description": "Bypass cached measurements; consumes quota"
                  }
                }
              },
              "example": {
                "url": "https://example.com/",
                "part": "core"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Complete or partial technical result",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/ScanResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL or body / target blocked",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Use POST",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "Supported methods",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Processing or storage unavailable",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "502": {
            "description": "Target or provider cannot be reached",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "504": {
            "description": "Target timeout",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent-readiness.php": {
      "get": {
        "operationId": "getAgentReadinessReport",
        "tags": [
          "Agent readiness"
        ],
        "summary": "Read an existing Is Agentic report",
        "description": "Calls the supported free read-only Is Agentic v1 API via this server. Never launches a new scan. available includes the original score and timestamp; not_found is an explicit absence without a zero score. Public URLs only, without query strings, fragments or credentials. Cache TTL: 300 seconds; 30 uncached requests per IP per 60 seconds.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "URL-encoded public HTTP(S) page, no private data, credentials, query or fragment",
            "schema": {
              "type": "string",
              "format": "uri",
              "maxLength": 2000
            },
            "example": "https://example.com/"
          }
        ],
        "responses": {
          "200": {
            "description": "Existing report or explicit report absence",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/AgentResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid, nonpublic or sensitive URL",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Use GET or HEAD",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              },
              "Allow": {
                "description": "Supported methods",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Processing or storage unavailable",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Is Agentic unavailable or invalid upstream data",
            "headers": {
              "RateLimit-Policy": {
                "description": "IETF structured-field quota policy, for example \"scan\";q=10;w=600",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF remaining quota and seconds to reset, for example \"scan\";r=8;t=580",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum uncached operations in this IP window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Remaining uncached operations",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until quota next becomes available",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "instance",
          "code",
          "resolution",
          "ok",
          "error"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string",
            "format": "uri-reference"
          },
          "code": {
            "type": "string"
          },
          "resolution": {
            "type": "string"
          },
          "documentation_url": {
            "type": "string",
            "format": "uri"
          },
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "AgentIssue": {
        "type": "object",
        "required": [
          "id",
          "name",
          "tier",
          "result",
          "details",
          "recommendation"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tier": {
            "type": "string",
            "enum": [
              "essential",
              "recommended",
              "bonus"
            ]
          },
          "result": {
            "type": "string",
            "enum": [
              "failed",
              "partial"
            ]
          },
          "details": {
            "type": "string"
          },
          "recommendation": {
            "type": "string"
          }
        }
      },
      "AgentResult": {
        "type": "object",
        "required": [
          "state",
          "message"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "available",
              "not_found"
            ]
          },
          "message": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "target": {
            "type": "string",
            "format": "uri"
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time"
          },
          "reportUrl": {
            "type": "string",
            "format": "uri"
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentIssue"
            }
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "state": {
                  "const": "available"
                }
              }
            },
            "then": {
              "required": [
                "score",
                "provider",
                "target",
                "scannedAt",
                "reportUrl",
                "issues"
              ]
            }
          }
        ]
      },
      "Check": {
        "type": "object",
        "required": [
          "label",
          "status"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "warn",
              "fail",
              "info"
            ]
          },
          "waarde": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "uitleg": {
            "type": "string"
          },
          "advies": {
            "type": "string"
          }
        }
      },
      "Module": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "titel": {
            "type": "string"
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "status": {
            "type": "string"
          },
          "statusLabel": {
            "type": "string"
          },
          "samenvatting": {
            "type": "string"
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            }
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Priority": {
        "type": "object",
        "properties": {
          "severity": {
            "type": "string"
          },
          "impact": {
            "type": "string"
          },
          "confidence": {
            "type": "string"
          },
          "titel": {
            "type": "string"
          },
          "uitleg": {
            "type": "string"
          },
          "waarom": {
            "type": "string"
          },
          "advies": {
            "type": "string"
          },
          "bewijs": {
            "type": "string"
          }
        }
      },
      "ScanResult": {
        "type": "object",
        "required": [
          "scanId",
          "inputUrl",
          "finalUrl",
          "scannedAt",
          "compleet",
          "modules",
          "providerErrors"
        ],
        "properties": {
          "scanId": {
            "type": "string"
          },
          "versie": {
            "type": "string"
          },
          "inputUrl": {
            "type": "string",
            "format": "uri"
          },
          "finalUrl": {
            "type": "string",
            "format": "uri"
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time"
          },
          "uitCache": {
            "type": "boolean"
          },
          "compleet": {
            "type": "boolean"
          },
          "overallVolledig": {
            "type": "boolean"
          },
          "overallScore": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "overallStatus": {
            "type": "string"
          },
          "overallLabel": {
            "type": "string"
          },
          "samenvatting": {
            "type": "string"
          },
          "wachtOp": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "modules": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Module"
            }
          },
          "prioriteiten": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Priority"
            }
          },
          "providerErrors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "platform": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        }
      }
    }
  }
}