{
  "openapi": "3.1.0",
  "info": {
    "title": "ARD Readiness Checker · API",
    "version": "0.3.0",
    "summary": "Comprueba si un dominio publica un manifest ARD / AI Catalog válido.",
    "description": "Descarga https://{dominio}/.well-known/ai-catalog.json con límites de tamaño y tiempo, y lo evalúa: accesibilidad, Content-Type, CORS, estructura, identifiers urn:air y metadata semántica. Las herramientas OpenAPI que declare el dominio se detectan, pero no se invocan. No persiste nada. Límites: 10 peticiones por minuto y 30 cada 10 minutos por IP, 20 cada 10 minutos por dominio analizado; resultados en caché 15 minutos. Sin CORS abierto. Catálogo: https://ard.apwebai.es/.well-known/ai-catalog.json",
    "contact": {
      "name": "apWebAI",
      "url": "https://apwebai.es",
      "email": "info@apwebai.es"
    },
    "license": {
      "name": "Propietario - apWebAI"
    }
  },
  "servers": [
    {
      "url": "https://ard.apwebai.es",
      "description": "Cloudflare Pages (producción)"
    }
  ],
  "paths": {
    "/api/check": {
      "post": {
        "operationId": "comprobarManifestArd",
        "summary": "Evalúa el manifest ARD público de un dominio",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckInput"
              },
              "examples": {
                "apwebai": {
                  "summary": "Dominio con perfil apWebAI automático",
                  "value": {
                    "domain": "apwebai.es",
                    "profile": "auto"
                  }
                },
                "estandar": {
                  "summary": "Cualquier dominio, perfil estándar",
                  "value": {
                    "domain": "huggingface.co"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Análisis completado. Un dominio inválido también responde 200 con status \"error\" y la explicación en summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResult"
                }
              }
            }
          },
          "400": {
            "description": "Body JSON inválido o sin el campo domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Método distinto de POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Body mayor de 4 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type distinto de application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Límite de uso alcanzado; ver la cabecera Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Error interno; nunca expone detalles.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CheckInput": {
        "type": "object",
        "required": [
          "domain"
        ],
        "properties": {
          "domain": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "description": "Dominio público, con o sin https://. Sin rutas, parámetros, puertos ni direcciones IP."
          },
          "profile": {
            "type": "string",
            "enum": [
              "standard",
              "apwebai",
              "auto"
            ],
            "default": "standard",
            "description": "auto aplica el perfil apWebAI solo a apwebai.es."
          },
          "liveCalls": {
            "type": "boolean",
            "description": "Se ignora: el servicio gratuito nunca invoca endpoints de terceros."
          }
        }
      },
      "Check": {
        "type": "object",
        "required": [
          "id",
          "group",
          "label",
          "status",
          "message"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "group": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "info"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Recommendation": {
        "type": "object",
        "required": [
          "severity",
          "message"
        ],
        "properties": {
          "severity": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "info"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "required": [
          "domain",
          "profile",
          "generatedAt",
          "status",
          "score",
          "summary",
          "checks",
          "recommendations"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "manifestUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "profile": {
            "type": "string",
            "enum": [
              "standard",
              "apwebai"
            ]
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "conforme",
              "parcial",
              "no_conforme",
              "no_detectado",
              "error"
            ]
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "summary": {
            "type": "string"
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            }
          },
          "findings": {
            "type": [
              "object",
              "null"
            ]
          },
          "recommendations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recommendation"
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "status",
          "summary"
        ],
        "properties": {
          "status": {
            "const": "error"
          },
          "summary": {
            "type": "string"
          }
        }
      }
    }
  }
}
