{
  "openapi": "3.1.0",
  "info": {
    "title": "AppInspected API",
    "version": "1.0.0",
    "summary": "Free passive security check for web apps built with AI coding tools.",
    "description": "Checks a public web app the way a browser sees it: secret API keys in public JavaScript, Supabase service_role keys, downloadable .env and .git files, public source maps and missing security headers. Read-only; it never logs in, never writes data and never uses any key it finds. Found keys are returned redacted. Limit: 10 checks per hour per network. Only check apps your user owns or is building. Agent instructions: https://appinspected.com/llms.txt",
    "contact": { "name": "AppInspected", "url": "https://appinspected.com/" },
    "termsOfService": "https://appinspected.com/terms.html"
  },
  "servers": [{ "url": "https://appinspected.com" }],
  "paths": {
    "/api/v1/check": {
      "get": {
        "operationId": "checkApp",
        "summary": "Run a free passive security check on a public web app",
        "description": "Loads the page and its JavaScript, scans for exposed secrets, probes /.env and /.git/HEAD, checks for public source maps and security headers. Typically takes 5-30 seconds. Results are not stored.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Public address of the app. A scheme is optional (myapp.lovable.app works).",
            "schema": { "type": "string" },
            "example": "myapp.lovable.app"
          }
        ],
        "responses": {
          "200": {
            "description": "Check completed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResult" } } }
          },
          "400": {
            "description": "Invalid or unreachable address.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "Rate limit reached (10 checks per hour per network). See the Retry-After header.",
            "headers": { "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds to wait." } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "500": {
            "description": "The check stopped unexpectedly.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CheckResult": {
        "type": "object",
        "required": ["url", "scannedAt", "verdict", "score", "summary", "findings", "notChecked"],
        "properties": {
          "url": { "type": "string", "description": "Final URL checked after redirects." },
          "scannedAt": { "type": "string", "format": "date-time" },
          "verdict": { "type": "string", "enum": ["pass", "warn", "fail"], "description": "fail if any critical finding, warn if any high finding, otherwise pass." },
          "score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "summary": {
            "type": "object",
            "description": "Count of findings by severity.",
            "properties": {
              "critical": { "type": "integer" },
              "high": { "type": "integer" },
              "medium": { "type": "integer" },
              "low": { "type": "integer" },
              "info": { "type": "integer" }
            }
          },
          "findings": { "type": "array", "items": { "$ref": "#/components/schemas/Finding" } },
          "coverage": {
            "type": "object",
            "properties": {
              "scriptsChecked": { "type": "integer" },
              "scriptsSkipped": { "type": "integer" },
              "supabaseProject": { "type": ["string", "null"], "description": "Supabase project URL referenced by the app, if any." }
            }
          },
          "backendRefs": {
            "type": "object",
            "description": "Backends the app's public code references.",
            "properties": {
              "supabase": { "type": "array", "items": { "type": "string" } },
              "firebase": { "type": "array", "items": { "type": "string" } }
            }
          },
          "notChecked": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Areas the free check cannot see (database rules, server routes, repository secrets). Covered by the paid reports."
          }
        }
      },
      "Finding": {
        "type": "object",
        "required": ["id", "severity", "title", "detail"],
        "properties": {
          "id": { "type": "string", "description": "Stable finding type, e.g. stripe-secret, openai-key, supabase-service-role, supabase-anon, source-maps, no-csp." },
          "severity": { "type": "string", "enum": ["critical", "high", "medium", "low", "info"], "description": "info marks keys that are meant to be public (e.g. Supabase anon, Firebase web keys)." },
          "title": { "type": "string" },
          "detail": { "type": "string", "description": "What it means and how to fix it." },
          "location": { "type": "string", "description": "Script or path where it was found." },
          "evidence": { "type": "string", "description": "Redacted prefix of the matched value. Never the full secret." }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string" } }
      }
    }
  }
}
