{
  "openapi": "3.1.0",
  "info": {
    "title": "NodeGrade Public API",
    "version": "1.0.0",
    "description": "Read-only agent-readiness scoring API. No account required for /api/scans and /api/reports.",
    "contact": {
      "name": "NodeGrade",
      "email": "hello@nodegrade.com",
      "url": "https://nodegrade.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://nodegrade.com/terms"
    }
  },
  "servers": [
    { "url": "https://nodegrade.com", "description": "Production" }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "summary": "Liveness check",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" }
              }
            }
          }
        }
      }
    },
    "/api/scans": {
      "post": {
        "summary": "Score a domain",
        "description": "Scans publicly reachable files on the submitted domain. Rate-limited to 10 requests/minute/IP. Returns a locked report; remediation text is null until the report is unlocked via /api/leads.",
        "operationId": "createScan",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domain"],
                "properties": {
                  "domain": { "type": "string", "example": "example.com" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scan succeeded",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScanReport" }
              }
            }
          },
          "400": { "description": "Invalid domain" },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/api/reports/{token}": {
      "get": {
        "summary": "Fetch a scan report by token",
        "operationId": "getReport",
        "parameters": [
          { "name": "token", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Report found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScanReport" }
              }
            }
          },
          "404": { "description": "Report not found" }
        }
      }
    },
    "/api/leads": {
      "post": {
        "summary": "Unlock a report by capturing an email lead",
        "description": "Rate-limited to 15/min/IP.",
        "operationId": "captureLead",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["reportToken", "email"],
                "properties": {
                  "reportToken": { "type": "string" },
                  "email": { "type": "string", "format": "email" },
                  "name": { "type": "string" },
                  "role": { "type": "string", "default": "Website owner" },
                  "answers": { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Report unlocked" },
          "400": { "description": "Invalid email or token" },
          "503": { "description": "Report could not be unlocked" }
        }
      }
    },
    "/api/events": {
      "post": {
        "summary": "Ingest a first-party analytics event",
        "operationId": "trackEvent",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": { "202": { "description": "Accepted" } }
      }
    },
    "/api/unsubscribe": {
      "get": {
        "summary": "Honor a one-click unsubscribe",
        "operationId": "unsubscribe",
        "parameters": [
          { "name": "token", "in": "query", "required": true, "schema": { "type": "string" }, "description": "HMAC-signed base64url token minted at send-time." }
        ],
        "responses": {
          "200": { "description": "Unsubscribed", "content": { "text/html": {} } },
          "400": { "description": "Invalid or expired token", "content": { "text/html": {} } }
        }
      }
    },
    "/api/stripe/fix-kit/checkout": {
      "post": {
        "summary": "Create a Stripe checkout session for the $99 Fix-Kit",
        "operationId": "createFixKitCheckout",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["reportToken", "email"],
                "properties": {
                  "reportToken": { "type": "string" },
                  "email": { "type": "string", "format": "email" },
                  "domain": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": { "type": "string", "format": "uri" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Health": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "app": { "type": "string" },
          "scanner": { "type": "string" },
          "budgetMs": { "type": "integer" }
        }
      },
      "Finding": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "status": { "type": "string", "enum": ["PASS", "WARN", "FAIL"] },
          "pillar": { "type": "string", "enum": ["readability", "discoverability", "callability", "monetization", "trust"] },
          "remediation": { "type": ["string", "null"], "description": "Null until the report is unlocked via /api/leads" },
          "source_url": { "type": "string", "format": "uri" }
        }
      },
      "ScanResult": {
        "type": "object",
        "properties": {
          "score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "tier": { "type": "string", "enum": ["AGENT_READY", "PARTIALLY_READY", "INVISIBLE", "BLOCKED_UNMONETIZED"] },
          "pillars": { "type": "object", "additionalProperties": { "type": "integer" } },
          "findings": { "type": "array", "items": { "$ref": "#/components/schemas/Finding" } },
          "topFindings": { "type": "array", "items": { "$ref": "#/components/schemas/Finding" } }
        }
      },
      "ScanReport": {
        "type": "object",
        "properties": {
          "reportToken": { "type": "string" },
          "unlocked": { "type": "boolean" },
          "domain": { "type": "string" },
          "result": { "$ref": "#/components/schemas/ScanResult" },
          "evidence": { "type": "object", "additionalProperties": true }
        }
      }
    }
  }
}
