{
  "openapi": "3.1.0",
  "info": {
    "title": "CyberSentriq AI Governance — Public API",
    "version": "1.0.0",
    "description": "Public endpoints behind lab.cybersentriq.com. The flagship surface is the MSP AI-governance self-assessment: the questions and scoring are published at /assessment.json, and a completed assessment is submitted here. Scoring is derived server-side from the answers using the public rubric — the client's claimed score is ignored.",
    "contact": { "name": "CyberSentriq", "url": "https://www.cybersentriq.com" }
  },
  "servers": [{ "url": "https://lab.cybersentriq.com" }],
  "paths": {
    "/health": {
      "get": {
        "summary": "Liveness probe",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Service healthy",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "example": "healthy" } } } } }
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "summary": "Current identity",
        "description": "Returns the caller's identity if authenticated, or an anonymous identity. The public site is anonymous; this is used by the SPA to gate views.",
        "operationId": "getMe",
        "responses": {
          "200": {
            "description": "Identity (authenticated=false for anonymous public callers)",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "authenticated": { "type": "boolean" },
                "email": { "type": "string" },
                "name": { "type": "string" },
                "roles": { "type": "array", "items": { "type": "string" } }
              }
            } } }
          }
        }
      }
    },
    "/api/survey-results": {
      "post": {
        "summary": "Submit a completed self-assessment",
        "description": "Anonymous submission is allowed. Provide the answers map (question id -> 0..3, per /assessment.json). The server derives the score, tier, and launch verdict from the answers using the public rubric; any client-supplied score is ignored. An optional valid submitterEmail triggers a completion email and certificate.",
        "operationId": "submitSurvey",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveySubmission" } } }
        },
        "responses": {
          "201": {
            "description": "Result created, with server-derived score/tier/verdict",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveyResult" } } }
          },
          "400": {
            "description": "Invalid body, missing answers, or invalid submitterEmail",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/survey-results/{id}/resend": {
      "post": {
        "summary": "Resend the result certificate email",
        "description": "Re-sends the certificate-of-assessment email for an existing result to the email recorded at submission. Rate-limited per IP.",
        "operationId": "resendSurveyResult",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "Certificate email resent" },
          "404": { "description": "Unknown result id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/api/certification/verify/{id}": {
      "get": {
        "summary": "Verify a certificate (durable public contract)",
        "description": "The canonical, durable certificate-verification endpoint — the URL baked into every issued certificate (printed + QR), served on the stable alias https://api.cybersentriq.com so it keeps resolving regardless of where the app is hosted. Content-negotiated: with Accept: application/json (or ?format=json) it returns the verification record (no PII); a browser request is 302-redirected to the human verification page. Prefer this over /api/survey-results/{id}/verify, which is the legacy alias.",
        "operationId": "verifyCertification",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "Verification result (Accept: application/json)" },
          "302": { "description": "Redirect to the human verification page (browser request)" },
          "400": { "description": "Invalid id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/survey-results/{id}/verify": {
      "get": {
        "summary": "Verify a certificate (legacy alias)",
        "description": "Minimal public verification of a signed assessment certificate. Returns no personal data. Retained for certificates issued before the durable /api/certification/verify/{id} contract; new integrations should use that endpoint.",
        "operationId": "verifySurvey",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "Verification result" },
          "400": { "description": "Invalid id", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SurveySubmission": {
        "type": "object",
        "required": ["answers"],
        "properties": {
          "answers": {
            "type": "object",
            "description": "Map of question id (see /assessment.json) to an integer 0–3 (None/Partial/Mostly/Yes).",
            "additionalProperties": { "type": "integer", "minimum": 0, "maximum": 3 },
            "example": { "s1": 2, "t2": 1, "m4": 3 }
          },
          "mode": { "type": "string", "enum": ["msp-self", "client-on-behalf"], "default": "msp-self" },
          "submitterEmail": { "type": "string", "format": "email", "description": "Optional. If valid, triggers the completion email + certificate." },
          "notes": { "type": "string" }
        }
      },
      "SurveyResult": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "createdAt": { "type": "string", "format": "date-time" },
          "mode": { "type": "string" },
          "overallScore": { "type": "integer", "description": "Overall posture, 0–100." },
          "tier": { "type": "string", "enum": ["Critical", "Developing", "Progressing", "Ready"] },
          "launchAllowed": { "type": "boolean" },
          "launchReason": { "type": "string" },
          "domainScores": { "type": "array", "items": { "type": "object" } },
          "remediation": { "type": "array", "items": { "type": "object" } }
        }
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      }
    }
  }
}
