{
  "openapi": "3.1.1",
  "info": {
    "title": "HeaderLens Public API",
    "version": "1.0.0",
    "summary": "Deterministic DNS, TLS, HTTP and security-header observations for public websites.",
    "description": "Every human page on HeaderLens has a JSON equivalent here. A target being down is a successful measurement: the API returns 200 with observation.classification = \"down\". Status semantics: https://headerlens.usefulweb.site/methodology",
    "contact": {
      "url": "https://headerlens.usefulweb.site/about"
    },
    "license": {
      "name": "Observations: CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://headerlens.usefulweb.site"
    }
  ],
  "tags": [
    {
      "name": "status"
    },
    {
      "name": "meta"
    }
  ],
  "paths": {
    "/api/v1/check": {
      "post": {
        "operationId": "runWebsiteCheck",
        "tags": [
          "status"
        ],
        "summary": "Run a fresh observation against a public hostname",
        "description": "Rate-limited. If the same host was observed in the last 30 seconds that observation is returned (X-HeaderLens-Cache: hit).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "target"
                ],
                "properties": {
                  "target": {
                    "type": "string",
                    "maxLength": 2048,
                    "examples": [
                      "example.com"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Observation completed (the target may be up, down, restricted, ...).",
            "headers": {
              "X-HeaderLens-Cache": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "hit",
                    "miss"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Target is invalid or resolves to a prohibited (non-public) destination.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Caller rate limit exceeded. See Retry-After.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Probe infrastructure could not take a measurement. See Retry-After.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/status/{hostname}": {
      "get": {
        "operationId": "getLatestStatus",
        "tags": [
          "status"
        ],
        "summary": "Get the latest stored observation for a hostname",
        "parameters": [
          {
            "in": "path",
            "name": "hostname",
            "required": true,
            "description": "Public hostname, e.g. example.com (Unicode or punycode).",
            "schema": {
              "type": "string",
              "examples": [
                "example.com"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest observation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResult"
                }
              }
            }
          },
          "404": {
            "description": "No observation exists yet. POST /api/v1/check to create one.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid hostname.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/status/{hostname}/history": {
      "get": {
        "operationId": "getStatusHistory",
        "tags": [
          "status"
        ],
        "summary": "Historical observations and availability rollups",
        "parameters": [
          {
            "in": "path",
            "name": "hostname",
            "required": true,
            "description": "Public hostname, e.g. example.com (Unicode or punycode).",
            "schema": {
              "type": "string",
              "examples": [
                "example.com"
              ]
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "in": "query",
            "name": "before",
            "description": "Cursor: return observations strictly older than this timestamp.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "History page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/History"
                }
              }
            }
          },
          "404": {
            "description": "Unknown hostname.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid hostname or parameters.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/recent": {
      "get": {
        "operationId": "listRecentChecks",
        "tags": [
          "status"
        ],
        "summary": "Recently checked hostnames (hostnames only, never paths)",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent checks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecentList"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/methodology": {
      "get": {
        "operationId": "getMethodology",
        "tags": [
          "meta"
        ],
        "summary": "Machine-readable status semantics",
        "responses": {
          "200": {
            "description": "Methodology.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Methodology"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/schema": {
      "get": {
        "operationId": "getSchemaInfo",
        "tags": [
          "meta"
        ],
        "summary": "Schema and API version information",
        "responses": {
          "200": {
            "description": "Schema info.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchemaInfo"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "meta"
        ],
        "summary": "Service health (of HeaderLens itself, not of any target)",
        "responses": {
          "200": {
            "description": "Healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "503": {
            "description": "Unhealthy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Target": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "hostname",
          "unicodeHostname",
          "canonicalUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier for the hostname.",
            "examples": [
              "target_example.com"
            ]
          },
          "hostname": {
            "type": "string",
            "description": "Canonical ASCII (punycode) hostname."
          },
          "unicodeHostname": {
            "type": "string",
            "description": "Human-readable Unicode form of the hostname."
          },
          "canonicalUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "HttpHop": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "status",
          "durationMs",
          "location"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "status": {
            "type": "integer",
            "minimum": 100,
            "maximum": 599
          },
          "durationMs": {
            "type": "integer",
            "minimum": 0
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "HeaderFinding": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "header",
          "present",
          "value",
          "severity",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "header": {
            "type": "string"
          },
          "present": {
            "type": "boolean"
          },
          "value": {
            "type": [
              "string",
              "null"
            ]
          },
          "severity": {
            "type": "string",
            "enum": [
              "good",
              "info",
              "warning"
            ]
          },
          "summary": {
            "type": "string"
          }
        }
      },
      "Observation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "observedAt",
          "vantage",
          "reachability",
          "classification",
          "reason",
          "summary",
          "dns",
          "tls",
          "http",
          "headers"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Time-sortable observation identifier."
          },
          "observedAt": {
            "type": "string",
            "format": "date-time"
          },
          "vantage": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "region",
              "scope"
            ],
            "properties": {
              "region": {
                "type": "string"
              },
              "scope": {
                "type": "string",
                "const": "single-server-probe"
              }
            }
          },
          "reachability": {
            "type": "string",
            "enum": [
              "reachable",
              "unreachable",
              "indeterminate"
            ]
          },
          "classification": {
            "type": "string",
            "enum": [
              "up",
              "reachable_restricted",
              "degraded",
              "down",
              "inconclusive"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "http_success",
              "http_only",
              "slow_response",
              "tls_invalid",
              "too_many_redirects",
              "redirect_blocked",
              "http_client_error",
              "http_server_error",
              "authentication_required",
              "forbidden",
              "rate_limited",
              "bot_challenge",
              "dns_nxdomain",
              "dns_error",
              "connection_refused",
              "connection_timeout",
              "connection_failed"
            ]
          },
          "summary": {
            "type": "string",
            "description": "Deterministic plain-English sentence derived from classification + reason."
          },
          "dns": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "resolved",
              "durationMs",
              "addresses",
              "error"
            ],
            "properties": {
              "resolved": {
                "type": "boolean"
              },
              "durationMs": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "addresses": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "tls": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "attempted",
              "valid",
              "protocol",
              "issuer",
              "validTo",
              "daysRemaining",
              "error"
            ],
            "properties": {
              "attempted": {
                "type": "boolean"
              },
              "valid": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "protocol": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issuer": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "validTo": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "daysRemaining": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "http": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "status",
              "durationMs",
              "redirectCount",
              "finalUrl",
              "access",
              "httpVersion",
              "hops",
              "error"
            ],
            "properties": {
              "method": {
                "type": "string",
                "const": "GET"
              },
              "status": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "durationMs": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "redirectCount": {
                "type": "integer",
                "minimum": 0
              },
              "finalUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "access": {
                "type": "string",
                "enum": [
                  "normal",
                  "authentication_required",
                  "forbidden",
                  "rate_limited",
                  "automated_client_restricted",
                  "not_applicable"
                ]
              },
              "httpVersion": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "hops": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/HttpHop"
                }
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "headers": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "response",
              "findings"
            ],
            "properties": {
              "response": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "findings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/HeaderFinding"
                }
              }
            }
          }
        }
      },
      "Consensus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "classification",
          "reachability",
          "reason",
          "summary",
          "regionsConfigured",
          "regionsReporting",
          "unavailableRegions",
          "counts",
          "regions"
        ],
        "properties": {
          "classification": {
            "type": "string",
            "enum": [
              "up",
              "reachable_restricted",
              "degraded",
              "down",
              "inconclusive"
            ],
            "description": "Headline status aggregated over all reporting regions."
          },
          "reachability": {
            "type": "string",
            "enum": [
              "reachable",
              "unreachable",
              "indeterminate"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "single_region",
              "unanimous",
              "majority",
              "partial_outage",
              "no_conclusive_data"
            ]
          },
          "summary": {
            "type": "string"
          },
          "regionsConfigured": {
            "type": "integer",
            "minimum": 1
          },
          "regionsReporting": {
            "type": "integer",
            "minimum": 0
          },
          "unavailableRegions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "counts": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "up",
              "reachable_restricted",
              "degraded",
              "down",
              "inconclusive"
            ],
            "properties": {
              "up": {
                "type": "integer",
                "minimum": 0
              },
              "reachable_restricted": {
                "type": "integer",
                "minimum": 0
              },
              "degraded": {
                "type": "integer",
                "minimum": 0
              },
              "down": {
                "type": "integer",
                "minimum": 0
              },
              "inconclusive": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "regions": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "region",
                "classification",
                "reason",
                "httpStatus",
                "durationMs"
              ],
              "properties": {
                "region": {
                  "type": "string"
                },
                "classification": {
                  "type": "string",
                  "enum": [
                    "up",
                    "reachable_restricted",
                    "degraded",
                    "down",
                    "inconclusive"
                  ]
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "http_success",
                    "http_only",
                    "slow_response",
                    "tls_invalid",
                    "too_many_redirects",
                    "redirect_blocked",
                    "http_client_error",
                    "http_server_error",
                    "authentication_required",
                    "forbidden",
                    "rate_limited",
                    "bot_challenge",
                    "dns_nxdomain",
                    "dns_error",
                    "connection_refused",
                    "connection_timeout",
                    "connection_failed"
                  ]
                },
                "httpStatus": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "durationMs": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "target",
          "checkId",
          "consensus",
          "observation",
          "regions",
          "provenance",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.1"
          },
          "target": {
            "$ref": "#/components/schemas/Target"
          },
          "checkId": {
            "type": "string",
            "description": "Identifier of this multi-region check."
          },
          "consensus": {
            "$ref": "#/components/schemas/Consensus"
          },
          "observation": {
            "$ref": "#/components/schemas/Observation"
          },
          "regions": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Observation"
            },
            "description": "One observation per reporting region."
          },
          "provenance": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "checkerVersion",
              "methodologyVersion",
              "methodology"
            ],
            "properties": {
              "checkerVersion": {
                "type": "string"
              },
              "methodologyVersion": {
                "type": "string"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "self",
              "human",
              "history",
              "methodology"
            ],
            "properties": {
              "self": {
                "type": "string",
                "format": "uri"
              },
              "human": {
                "type": "string",
                "format": "uri"
              },
              "history": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "Rollup": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "window",
          "since",
          "observations",
          "counts",
          "availability"
        ],
        "properties": {
          "window": {
            "type": "string",
            "enum": [
              "24h",
              "7d",
              "30d"
            ]
          },
          "since": {
            "type": "string",
            "format": "date-time"
          },
          "observations": {
            "type": "integer",
            "minimum": 0
          },
          "counts": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "up",
              "reachable_restricted",
              "degraded",
              "down",
              "inconclusive"
            ],
            "properties": {
              "up": {
                "type": "integer",
                "minimum": 0
              },
              "reachable_restricted": {
                "type": "integer",
                "minimum": 0
              },
              "degraded": {
                "type": "integer",
                "minimum": 0
              },
              "down": {
                "type": "integer",
                "minimum": 0
              },
              "inconclusive": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "availability": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1,
            "description": "(up + reachable_restricted + degraded) / (observations - inconclusive)"
          }
        }
      },
      "History": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "target",
          "rollups",
          "checks",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.1"
          },
          "target": {
            "$ref": "#/components/schemas/Target"
          },
          "rollups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Rollup"
            }
          },
          "checks": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "checkId",
                "observedAt",
                "consensus",
                "regions"
              ],
              "properties": {
                "checkId": {
                  "type": "string"
                },
                "observedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "consensus": {
                  "$ref": "#/components/schemas/Consensus"
                },
                "regions": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Observation"
                  }
                }
              }
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "self",
              "human",
              "next"
            ],
            "properties": {
              "self": {
                "type": "string",
                "format": "uri"
              },
              "human": {
                "type": "string",
                "format": "uri"
              },
              "next": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              }
            }
          }
        }
      },
      "RecentList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "generatedAt",
          "items"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.1"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "hostname",
                "unicodeHostname",
                "classification",
                "observedAt",
                "links"
              ],
              "properties": {
                "hostname": {
                  "type": "string"
                },
                "unicodeHostname": {
                  "type": "string"
                },
                "classification": {
                  "type": "string",
                  "enum": [
                    "up",
                    "reachable_restricted",
                    "degraded",
                    "down",
                    "inconclusive"
                  ]
                },
                "observedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "links": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "human",
                    "self"
                  ],
                  "properties": {
                    "human": {
                      "type": "string",
                      "format": "uri"
                    },
                    "self": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "checkerVersion",
          "database",
          "time"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ]
          },
          "checkerVersion": {
            "type": "string"
          },
          "database": {
            "type": "string",
            "enum": [
              "ok",
              "error"
            ]
          },
          "time": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Problem": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        },
        "description": "RFC 9457 problem details."
      },
      "Methodology": {
        "type": "object",
        "description": "Machine-readable status semantics; same source as /methodology."
      },
      "SchemaInfo": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "apiVersion",
          "openapi",
          "schemas"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.1"
          },
          "apiVersion": {
            "type": "string"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "schemas": {
            "type": "object"
          }
        }
      }
    }
  }
}