{
  "openapi": "3.1.0",
  "info": {
    "title": "HackproofHacks API",
    "version": "1.0.0",
    "description": "Public HTTP endpoints for hackproofhacks.com: free reconnaissance tools (header, TLS, and subdomain checks), a live CVE-intelligence feed, and the site's contact/newsletter intake forms. Every response is JSON, including errors — see the ApiError schema. Rate-limited endpoints return RateLimit-* headers (RFC 9239 draft) on every response so a caller can self-throttle, plus Retry-After once a limit is hit.",
    "contact": {
      "email": "connect@hackproofhacks.com",
      "url": "https://hackproofhacks.com/developers"
    },
    "license": {
      "name": "All rights reserved"
    }
  },
  "servers": [
    {
      "url": "https://hackproofhacks.com"
    }
  ],
  "tags": [
    {
      "name": "intelligence",
      "description": "Read-only, unauthenticated data feeds."
    },
    {
      "name": "tools",
      "description": "Free reconnaissance tools. Browser + Turnstile gated."
    },
    {
      "name": "intake",
      "description": "Contact and newsletter form submission. Browser + Turnstile gated."
    }
  ],
  "paths": {
    "/api/cve-stats": {
      "get": {
        "operationId": "getCveStats",
        "tags": [
          "intelligence"
        ],
        "summary": "Recent CVE statistics",
        "description": "Rolling 7-day count of newly published CVEs by severity, plus an average CVSS score, sourced from the NIST NVD API and cached at the edge for up to an hour. No authentication, no rate limiting, no request body. Falls back to a committed snapshot (with \"stale\": true) if NVD is unreachable, so this endpoint never errors under normal conditions.",
        "responses": {
          "200": {
            "description": "CVE statistics for the last 7 days.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "critical": {
                      "type": "integer",
                      "description": "CRITICAL-severity CVEs published in the last 7 days."
                    },
                    "high": {
                      "type": "integer",
                      "description": "HIGH-severity CVEs published in the last 7 days."
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total CVEs published in the last 7 days."
                    },
                    "avgCvss": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Mean CVSS base score across a sample of the window."
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "stale": {
                      "type": "boolean",
                      "description": "Present and true only when NVD was unreachable and this is the fallback snapshot."
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/header-check": {
      "post": {
        "operationId": "checkSecurityHeaders",
        "tags": [
          "tools"
        ],
        "summary": "Analyse a URL's HTTP security headers",
        "description": "Fetches the given URL, follows redirects, and grades the security headers it serves (CSP, HSTS, X-Frame-Options, etc.) with copy-paste fix snippets per platform. This endpoint is intended to be called from the hackproofhacks.com frontend in a real browser. It requires the X-Requested-With: XMLHttpRequest header and, where noted, a Cloudflare Turnstile response token obtained by rendering the Turnstile widget — there is no server-side or headless-agent path to a valid token. An agent calling it directly will receive a 403 FORBIDDEN with a machine-readable hint (see the ApiError schema).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "A public http(s) URL, e.g. https://example.com. Internal/private hosts are rejected."
                  },
                  "cf-turnstile-response": {
                    "type": "string",
                    "description": "Cloudflare Turnstile response token."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Header report.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per rolling window, per client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing/failed CSRF or Turnstile check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (30 requests/hour/IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "502": {
            "description": "Target host unreachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/ssl-check": {
      "post": {
        "operationId": "checkTlsCertificate",
        "tags": [
          "tools"
        ],
        "summary": "Inspect the TLS certificate served by a host",
        "description": "Performs a TLS handshake against the given host on port 443 and reports the served leaf certificate (issuer, validity window, SANs), falling back to Certificate Transparency logs if a direct handshake fails. This endpoint is intended to be called from the hackproofhacks.com frontend in a real browser. It requires the X-Requested-With: XMLHttpRequest header and, where noted, a Cloudflare Turnstile response token obtained by rendering the Turnstile widget — there is no server-side or headless-agent path to a valid token. An agent calling it directly will receive a 403 FORBIDDEN with a machine-readable hint (see the ApiError schema).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain"
                ],
                "properties": {
                  "domain": {
                    "type": "string",
                    "description": "Bare hostname, no scheme/port/path, e.g. example.com."
                  },
                  "cf-turnstile-response": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Certificate report.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per rolling window, per client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing/failed CSRF or Turnstile check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (30 requests/hour/IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "502": {
            "description": "Could not read a certificate for that host.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/subdomain-finder": {
      "post": {
        "operationId": "findSubdomains",
        "tags": [
          "tools"
        ],
        "summary": "Passive subdomain discovery",
        "description": "Aggregates subdomains for a domain from Certificate Transparency logs (crt.sh, CertSpotter) and a passive-DNS index (HackerTarget), then resolves a common-name wordlist via DNS-over-HTTPS. Never contacts the target's own infrastructure. This endpoint is intended to be called from the hackproofhacks.com frontend in a real browser. It requires the X-Requested-With: XMLHttpRequest header and, where noted, a Cloudflare Turnstile response token obtained by rendering the Turnstile widget — there is no server-side or headless-agent path to a valid token. An agent calling it directly will receive a 403 FORBIDDEN with a machine-readable hint (see the ApiError schema).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain"
                ],
                "properties": {
                  "domain": {
                    "type": "string",
                    "description": "Bare apex domain, e.g. example.com."
                  },
                  "cf-turnstile-response": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Discovered subdomains with resolution stats.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "domain": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per rolling window, per client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing/failed CSRF or Turnstile check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (15 requests/hour/IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/tools/subdomain-check": {
      "post": {
        "operationId": "checkSubdomainLiveness",
        "tags": [
          "tools"
        ],
        "summary": "Active liveness probe for a single host",
        "description": "Opt-in active check: connects to the given host once over HTTPS then HTTP (no redirects followed, body never read) and reports whether it is alive, its status code, server header, and redirect target host. Requires an explicit \"authorized\" acknowledgement in the request body. Same-origin browser request required (no Turnstile on this one).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "host",
                  "authorized"
                ],
                "properties": {
                  "host": {
                    "type": "string",
                    "description": "Bare fully-qualified hostname."
                  },
                  "authorized": {
                    "type": "boolean",
                    "description": "Must be true — an explicit acknowledgement that the caller is authorised to probe this host."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Liveness result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "host": {
                      "type": "string"
                    },
                    "alive": {
                      "type": "boolean"
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per rolling window, per client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid host, or authorized was not true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing X-Requested-With header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/hour/IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "submitContactForm",
        "tags": [
          "intake"
        ],
        "summary": "Submit a contact, assessment, training, or workshop enquiry",
        "description": "One endpoint for five form types, selected by the \"formType\" field: contact, assessment, training, training-apply, workshop. This endpoint is intended to be called from the hackproofhacks.com frontend in a real browser. It requires the X-Requested-With: XMLHttpRequest header and, where noted, a Cloudflare Turnstile response token obtained by rendering the Turnstile widget — there is no server-side or headless-agent path to a valid token. An agent calling it directly will receive a 403 FORBIDDEN with a machine-readable hint (see the ApiError schema).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "formType",
                  "name",
                  "email"
                ],
                "properties": {
                  "formType": {
                    "type": "string",
                    "enum": [
                      "contact",
                      "assessment",
                      "training",
                      "training-apply",
                      "workshop"
                    ]
                  },
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "cf-turnstile-response": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submission accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per rolling window, per client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed — see the error field for the specific message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing/failed CSRF or Turnstile check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (10 requests/hour/IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Submission could not be stored — safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/newsletter": {
      "post": {
        "operationId": "subscribeToNewsletter",
        "tags": [
          "intake"
        ],
        "summary": "Subscribe an email address to the weekly newsletter",
        "description": "Single opt-in — the address becomes a live subscriber immediately, no confirmation link. This endpoint is intended to be called from the hackproofhacks.com frontend in a real browser. It requires the X-Requested-With: XMLHttpRequest header and, where noted, a Cloudflare Turnstile response token obtained by rendering the Turnstile widget — there is no server-side or headless-agent path to a valid token. An agent calling it directly will receive a 403 FORBIDDEN with a machine-readable hint (see the ApiError schema).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "cf-turnstile-response": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscribed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests allowed per rolling window, per client IP.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid email address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing/failed CSRF or Turnstile check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (3 requests/hour/IP).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiError": {
        "type": "object",
        "description": "Every error response on this API, from every endpoint, uses this shape.",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Human-readable message, safe to display to an end user."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code, e.g. VALIDATION_ERROR, FORBIDDEN, RATE_LIMITED."
          },
          "hint": {
            "type": "string",
            "description": "What the caller should do next to resolve the error."
          }
        },
        "required": [
          "ok",
          "error",
          "code",
          "hint"
        ]
      }
    }
  }
}