{
  "openapi": "3.1.0",
  "info": {
    "title": "AiDev Website Public API",
    "version": "1.0.0",
    "description": "Publika endpoints för AiDevs AI-beredskapstest och kontaktformulär."
  },
  "servers": [
    {
      "url": "https://aidev.se"
    }
  ],
  "paths": {
    "/api/ai-readiness-scan": {
      "post": {
        "operationId": "scanAiReadiness",
        "summary": "Kör en indikativ AI-beredskapsskanning för en publik URL",
        "description": "Hämtar begränsade publika resurser med DNS/IP-validering, blockering av privata nätverk, redirect-gränser, tidsgränser och begränsad svarsstorlek.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url"],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Publik webbadress som ska skannas."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Indikativt skannerresultat.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiReadinessResult"
                }
              }
            }
          },
          "400": {
            "description": "Ogiltigt, blockerat eller oåtkomligt mål."
          },
          "413": {
            "description": "Förfrågans body är för stor."
          },
          "415": {
            "description": "Medietypen stöds inte."
          },
          "429": {
            "description": "För många förfrågningar."
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "submitContactRequest",
        "summary": "Skicka en kontaktförfrågan till AiDev",
        "description": "Validerar en kontaktförfrågan och levererar den till konfigurerad produktionswebhook, eller returnerar en mailto-fallback när webhook saknas.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Förfrågan levererad.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactSuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Valideringen misslyckades.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Förfrågans body är för stor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "415": {
            "description": "Medietypen stöds inte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "För många förfrågningar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Produktionswebhooken nekade kontaktförfrågan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Ingen produktionswebhook är konfigurerad; svaret innehåller en mailto-fallback.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactFallbackResponse"
                }
              }
            }
          },
          "504": {
            "description": "Produktionswebhooken svarade inte i tid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AiReadinessResult": {
        "type": "object",
        "required": ["ok", "scannerVersion", "requestedUrl", "finalUrl", "scannedAt", "httpStatus", "score", "grade", "confidence", "status", "summary", "categories", "quickWins", "scoring", "recommendations", "technical"],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "scannerVersion": {
            "type": "string",
            "description": "Version av den publika indikativa skannerns poängmodell."
          },
          "requestedUrl": {
            "type": "string",
            "format": "uri"
          },
          "finalUrl": {
            "type": "string",
            "format": "uri"
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time",
            "description": "UTC-tidpunkt då skannerresultatet skapades."
          },
          "httpStatus": {
            "type": "integer",
            "description": "HTTP-status från den slutligt skannade sidan."
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "grade": {
            "type": "string",
            "enum": ["A+", "A", "B", "C", "D", "F"]
          },
          "confidence": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "status": {
            "type": "string",
            "enum": ["Redo", "På väg", "Behöver åtgärdas"]
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AiReadinessCategory"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/AiReadinessSummary"
          },
          "quickWins": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AiReadinessQuickWin"
            }
          },
          "scoring": {
            "$ref": "#/components/schemas/AiReadinessScoring"
          },
          "recommendations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AiReadinessRecommendation"
            }
          },
          "technical": {
            "$ref": "#/components/schemas/AiReadinessTechnical"
          }
        }
      },
      "AiReadinessCategory": {
        "type": "object",
        "required": ["id", "label", "weight", "points", "maxPoints", "score", "weightedContribution", "checks"],
        "properties": {
          "id": {
            "type": "string",
            "enum": ["discovery", "search", "semantics", "protocols", "trust"]
          },
          "label": {
            "type": "string"
          },
          "weight": {
            "type": "number"
          },
          "points": {
            "type": "integer"
          },
          "maxPoints": {
            "type": "integer"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "weightedContribution": {
            "type": "integer"
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AiReadinessCheck"
            }
          }
        }
      },
      "AiReadinessCheck": {
        "type": "object",
        "required": ["id", "title", "state", "points", "maxPoints", "evidence", "fix"],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": ["pass", "partial", "fail"]
          },
          "evidence": {
            "type": "string"
          },
          "points": {
            "type": "integer"
          },
          "maxPoints": {
            "type": "integer"
          },
          "fix": {
            "type": "string"
          }
        }
      },
      "AiReadinessSummary": {
        "type": "object",
        "required": ["passed", "partial", "failed", "bestCategory", "weakestCategory"],
        "properties": {
          "passed": {
            "type": "integer"
          },
          "partial": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "bestCategory": {
            "type": "string"
          },
          "weakestCategory": {
            "type": "string"
          }
        }
      },
      "AiReadinessQuickWin": {
        "type": "object",
        "required": ["id", "title", "category", "categoryId", "state", "pointsPossible", "overallPotential", "evidence", "fix"],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "categoryId": {
            "type": "string",
            "enum": ["discovery", "search", "semantics", "protocols", "trust"]
          },
          "state": {
            "type": "string",
            "enum": ["pass", "partial", "fail"]
          },
          "pointsPossible": {
            "type": "integer"
          },
          "overallPotential": {
            "type": "integer"
          },
          "evidence": {
            "type": "string"
          },
          "fix": {
            "type": "string"
          }
        }
      },
      "AiReadinessRecommendation": {
        "type": "object",
        "required": ["category", "title", "text"],
        "properties": {
          "category": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "text": {
            "type": "string"
          }
        }
      },
      "AiReadinessScoring": {
        "type": "object",
        "required": ["explanation", "weights"],
        "properties": {
          "explanation": {
            "type": "string"
          },
          "weights": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AiReadinessScoringWeight"
            }
          }
        }
      },
      "AiReadinessScoringWeight": {
        "type": "object",
        "required": ["id", "label", "weight"],
        "properties": {
          "id": {
            "type": "string",
            "enum": ["discovery", "search", "semantics", "protocols", "trust"]
          },
          "label": {
            "type": "string"
          },
          "weight": {
            "type": "number"
          }
        }
      },
      "AiReadinessTechnical": {
        "type": "object",
        "required": [
          "title",
          "description",
          "canonical",
          "h1Count",
          "jsonLdTypes",
          "htmlKilobytes",
          "bytesRead",
          "htmlTruncated",
          "fetchBudget",
          "headers"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "canonical": {
            "type": "string"
          },
          "h1Count": {
            "type": "integer"
          },
          "jsonLdTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "htmlKilobytes": {
            "type": "integer"
          },
          "bytesRead": {
            "type": "integer"
          },
          "htmlTruncated": {
            "type": "boolean"
          },
          "fetchBudget": {
            "type": "object",
            "required": ["mainBytes", "auxiliaryBytes", "mainTimeoutMs", "auxiliaryTimeoutMs", "maxRedirects"],
            "properties": {
              "mainBytes": {
                "type": "integer"
              },
              "auxiliaryBytes": {
                "type": "integer"
              },
              "mainTimeoutMs": {
                "type": "integer"
              },
              "auxiliaryTimeoutMs": {
                "type": "integer"
              },
              "maxRedirects": {
                "type": "integer"
              }
            }
          },
          "headers": {
            "$ref": "#/components/schemas/AiReadinessHeaders"
          }
        }
      },
      "AiReadinessHeaders": {
        "type": "object",
        "required": ["hsts", "csp", "contentTypeOptions", "frameOptions", "referrerPolicy"],
        "properties": {
          "hsts": {
            "type": ["string", "null"]
          },
          "csp": {
            "type": ["string", "null"]
          },
          "contentTypeOptions": {
            "type": ["string", "null"]
          },
          "frameOptions": {
            "type": ["string", "null"]
          },
          "referrerPolicy": {
            "type": ["string", "null"]
          }
        }
      },
      "ContactRequest": {
        "type": "object",
        "required": ["name", "email", "message", "consent"],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "website": {
            "type": "string"
          },
          "service": {
            "type": "string"
          },
          "message": {
            "type": "string",
            "minLength": 20
          },
          "consent": {
            "type": "boolean",
            "const": true
          },
          "scannerUrl": {
            "type": "string"
          },
          "scannerScore": {
            "type": "string"
          },
          "scannerStatus": {
            "type": "string"
          },
          "companyWebsite": {
            "type": "string",
            "description": "Honeypot-fält; ska vara tomt."
          }
        },
        "additionalProperties": false
      },
      "ContactSuccessResponse": {
        "type": "object",
        "required": ["ok", "message"],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "message": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ContactFallbackResponse": {
        "type": "object",
        "required": ["ok", "fallback", "mailto", "error"],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "fallback": {
            "type": "boolean",
            "const": true
          },
          "mailto": {
            "type": "string",
            "format": "uri"
          },
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ApiErrorResponse": {
        "type": "object",
        "required": ["ok", "error"],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    }
  }
}
