{
  "openapi": "3.0.3",
  "info": {
    "title": "SunPilot API",
    "version": "0.4.0",
    "description": "Public API for the SunPilot TPO compliance platform. SunPilot is the platform; each Customer (tenant, e.g. Brighthouse LLC) writes its records INTO SunPilot. Ingest endpoints (add project / import) accept a session cookie or a customer-scoped API key (Authorization: Bearer sk_live_… or X-API-Key); records written with a key are tagged to that customer. See docs/ for the full route list."
  },
  "servers": [{ "url": "https://sunpilot.work" }],
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "http", "scheme": "bearer", "bearerFormat": "sk_live_…", "description": "Issued by a SuperAdmin via POST /api/api-keys. Acts as its role on data routes only." },
      "session": { "type": "apiKey", "in": "cookie", "name": "sp_session" }
    },
    "schemas": {
      "ProjectRecord": {
        "type": "object",
        "required": ["customer", "address", "utility", "system", "contract"],
        "properties": {
          "system_id": { "type": "string", "pattern": "^BH-\\d{4}-\\d{4}$", "description": "Optional; auto-assigned if omitted." },
          "customer": { "type": "string" },
          "address": {
            "type": "object",
            "required": ["street", "city", "state", "zip"],
            "properties": {
              "street": { "type": "string" }, "city": { "type": "string" },
              "state": { "type": "string", "example": "CA" }, "zip": { "type": "string", "pattern": "^\\d{5}$" }
            }
          },
          "utility": { "type": "string", "enum": ["PG&E", "SCE", "SDG&E", "other"] },
          "system": {
            "type": "object", "required": ["kw_dc", "kw_ac"],
            "properties": {
              "kw_dc": { "type": "number", "maximum": 1000 }, "kw_ac": { "type": "number" }, "battery_kwh": { "type": "number" }
            }
          },
          "contract": {
            "type": "object", "required": ["rate_per_kwh", "escalator_pct"],
            "properties": {
              "type": { "type": "string", "default": "LEASE_TPO" },
              "rate_per_kwh": { "type": "number" },
              "escalator_pct": { "type": "number", "description": "HARD_BLOCK if over the escalator cap." },
              "term_yrs": { "type": "integer" }
            }
          },
          "basis": { "type": "object", "properties": { "basis_cents": { "type": "integer" }, "per_system_cost_cents": { "type": "integer" } } },
          "evidence": { "type": "array", "items": { "type": "object", "properties": { "stage": { "type": "integer" }, "slot": { "type": "string" }, "status": { "type": "string", "enum": ["GAP", "PENDING", "PROVIDED"] } } } }
        }
      },
      "IngestResult": {
        "type": "object",
        "properties": {
          "system_id": { "type": "string" },
          "action": { "type": "string", "enum": ["created", "updated", "rejected"] },
          "errors": { "type": "array", "items": { "type": "string" } },
          "flags": { "type": "array", "items": { "type": "object" }, "description": "OUT_OF_BAND variance flags (imported, not rejected)." }
        }
      }
    }
  },
  "paths": {
    "/api/health": { "get": { "summary": "Service health (public)", "security": [], "responses": { "200": { "description": "ok" } } } },
    "/api/auth/login": {
      "post": {
        "summary": "Log in, receive an HttpOnly session cookie", "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string" }, "password": { "type": "string" } } } } } },
        "responses": { "200": { "description": "session cookie set" }, "401": { "description": "invalid credentials" } }
      }
    },
    "/api/projects": {
      "post": {
        "summary": "Add a single project (the bri0 ingest path)",
        "description": "Maps a canonical project record to a passport. Validation tiers: schema errors reject; HARD_BLOCK variances reject (422); OUT_OF_BAND variances import with a flag.",
        "security": [{ "apiKey": [] }, { "session": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectRecord" } } } },
        "responses": { "201": { "description": "created/updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IngestResult" } } } }, "401": { "description": "unauthenticated" }, "403": { "description": "role below Admin" }, "422": { "description": "rejected" } }
      }
    },
    "/api/import/systems": {
      "post": {
        "summary": "Bulk import projects (array or {systems:[…]})",
        "security": [{ "apiKey": [] }, { "session": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [{ "type": "array", "items": { "$ref": "#/components/schemas/ProjectRecord" } }, { "type": "object", "properties": { "systems": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectRecord" } } } }] } } } },
        "responses": { "200": { "description": "summary + per-row results" }, "422": { "description": "all rows rejected" } }
      }
    },
    "/api/systems": {
      "get": { "summary": "List systems (with gap/pending counts)", "security": [{ "apiKey": [] }, { "session": [] }], "responses": { "200": { "description": "systems" } } }
    },
    "/api/systems/{id}": {
      "get": { "summary": "Passport detail + evidence + gate verdict", "security": [{ "apiKey": [] }, { "session": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "passport" }, "404": { "description": "not found" } } }
    },
    "/api/systems/{id}/advance": {
      "post": { "summary": "Advance a stage (gate-checked; 409 if blocked)", "security": [{ "apiKey": [] }, { "session": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "advanced" }, "409": { "description": "blocked by open gap" } } }
    }
  }
}
