{
  "openapi": "3.1.0",
  "info": {
    "title": "POSITRONIA API publique",
    "version": "1.0.0",
    "description": "API publique v1 d'EuTrustedIA — lancez vos audits de conformité SaaS (RGPD + AI Act) POSITRONIA depuis vos outils : scripts, CI, agents IA, serveurs MCP. Authentification par clé API (créée dans votre Espace Client, page « API publique »). Les quotas d'audits de votre plan s'appliquent à l'identique du web. Les codes d'erreur (`code`) sont contractuels ; les messages peuvent évoluer.",
    "contact": { "url": "https://eutrustedia.eu" }
  },
  "servers": [{ "url": "https://app.eutrustedia.eu" }],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/api/v1/audits/saas": {
      "post": {
        "operationId": "runSaasAudit",
        "summary": "Lancer un audit de conformité d'un SaaS tiers",
        "description": "Audit RGPD + AI Act privé d'un SaaS/LLM/agent IA tiers à partir de son URL. Le rapport est scellé (SHA-256 chaîné + Ed25519). Cache-first : si un audit du même domaine existe déjà sur votre compte et que `force` est absent/false, la réponse est `ALREADY_AUDITED` (zéro quota consommé). Consomme 1 audit du quota de votre plan sinon.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RunSaasAuditRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Audit créé (id, verdict, sealedHash) OU audit existant (code=ALREADY_AUDITED).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/RunSaasAuditCreated" },
                    { "$ref": "#/components/schemas/AlreadyAudited" }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "502": { "$ref": "#/components/responses/SidecarError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      },
      "get": {
        "operationId": "listSaasAudits",
        "summary": "Lister vos audits SaaS (métadonnées)",
        "description": "Les 50 audits les plus récents du compte — métadonnées seules, jamais le contenu du rapport (récupérable audit par audit via GET /api/v1/audits/saas/{id}).",
        "responses": {
          "200": {
            "description": "Liste des audits.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SaasAuditList" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/v1/audits/saas/{id}": {
      "get": {
        "operationId": "getSaasAudit",
        "summary": "Détail d'un audit SaaS (rapport complet)",
        "description": "Rapport markdown complet + métadonnées + attestations. 404 si l'audit n'existe pas OU n'appartient pas à votre compte.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Détail de l'audit.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SaasAuditDetail" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clé API au format `etia_…`, créée dans l'Espace Client (page « API publique »). En-tête : `Authorization: Bearer etia_…`. Le secret n'est affiché qu'une fois à la création — révoquez et recréez en cas de perte."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Corps invalide (code=BAD_REQUEST).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Clé API absente, invalide ou révoquée (code=UNAUTHORIZED).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "PaymentRequired": {
        "description": "Aucun abonnement actif sur le compte (code=PAYMENT_REQUIRED).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Ressource introuvable sur ce compte (code=NOT_FOUND).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "TooManyRequests": {
        "description": "Rate-limit par clé (code=RATE_LIMITED, 5/min) OU quota d'audits du plan épuisé (code=QUOTA_EXCEEDED, réinitialisé à chaque cycle de facturation).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "SidecarError": {
        "description": "Échec du service d'audit amont (code=SIDECAR_ERROR).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ServiceUnavailable": {
        "description": "Service d'audit non configuré ou indisponible (code=SIDECAR_UNCONFIGURED | SIDECAR_UNAVAILABLE).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error", "code"],
        "properties": {
          "error": { "type": "string", "description": "Message lisible (peut évoluer)." },
          "code": {
            "type": "string",
            "description": "Code machine CONTRACTUEL.",
            "enum": [
              "BAD_REQUEST",
              "UNAUTHORIZED",
              "PAYMENT_REQUIRED",
              "NOT_FOUND",
              "RATE_LIMITED",
              "QUOTA_EXCEEDED",
              "SIDECAR_UNCONFIGURED",
              "SIDECAR_UNAVAILABLE",
              "SIDECAR_ERROR",
              "INTERNAL"
            ]
          },
          "details": { "description": "Détails de validation (Zod flatten) si BAD_REQUEST." }
        }
      },
      "RunSaasAuditRequest": {
        "type": "object",
        "required": ["url", "useCase", "dataSensitivity", "position"],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "URL du SaaS/LLM/agent IA tiers à auditer."
          },
          "useCase": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000,
            "description": "Votre cas d'usage de ce SaaS (contexte de l'analyse). Sans caractères de contrôle ni chevrons."
          },
          "dataSensitivity": {
            "type": "string",
            "enum": ["public", "professional", "rgpd_art9", "oiv"],
            "description": "Sensibilité des données que vous confiez à ce SaaS."
          },
          "position": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Votre position RGPD vis-à-vis de ce SaaS (ex. responsable de traitement, sous-traitant)."
          },
          "computeRegions": {
            "type": "array",
            "items": { "type": "string", "maxLength": 128 },
            "maxItems": 20,
            "description": "Déclaratif : régions d'inférence/calcul connues (Axe provenance du scoring)."
          },
          "force": {
            "type": "boolean",
            "default": false,
            "description": "true = relancer une analyse à jour même si un audit du domaine existe déjà (consomme 1 quota)."
          }
        }
      },
      "RunSaasAuditCreated": {
        "type": "object",
        "required": ["id", "verdict", "sealedHash"],
        "properties": {
          "id": { "type": "string" },
          "verdict": { "type": "string" },
          "sealedHash": { "type": "string", "description": "Hash de scellement (audit-trail chaîné)." }
        }
      },
      "AlreadyAudited": {
        "type": "object",
        "required": ["code", "existingId", "auditedAt", "domain"],
        "properties": {
          "code": { "type": "string", "const": "ALREADY_AUDITED" },
          "existingId": { "type": "string" },
          "auditedAt": { "type": "string", "format": "date-time" },
          "domain": { "type": "string" },
          "severity": { "type": ["string", "null"] }
        }
      },
      "SaasAuditList": {
        "type": "object",
        "required": ["audits"],
        "properties": {
          "audits": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "domain", "createdAt", "dataSensitivity", "state"],
              "properties": {
                "id": { "type": "string" },
                "domain": { "type": "string" },
                "createdAt": { "type": "string", "format": "date-time" },
                "dataSensitivity": { "type": "string" },
                "severity": { "type": ["string", "null"], "enum": ["info", "low", "medium", "high", "critical", null] },
                "state": { "type": "string" }
              }
            }
          }
        }
      },
      "SaasAuditDetail": {
        "type": "object",
        "required": ["id", "domain", "createdAt", "dataSensitivity", "state"],
        "properties": {
          "id": { "type": "string" },
          "domain": { "type": "string" },
          "name": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "useCase": { "type": ["string", "null"] },
          "dataSensitivity": { "type": "string" },
          "position": { "type": ["string", "null"] },
          "verdictScore": { "type": ["integer", "null"], "minimum": 0, "maximum": 100 },
          "severity": { "type": ["string", "null"] },
          "markdown": { "type": ["string", "null"], "description": "Rapport complet (markdown). Les recommandations LLM-aided portent le marquage AI Act Art. 50." },
          "state": { "type": "string" },
          "sealedHash": { "type": ["string", "null"] },
          "sealedAt": { "type": ["string", "null"], "format": "date-time" },
          "attestations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "signerName": { "type": "string" },
                "signerRole": { "type": "string" },
                "attestedAt": { "type": "string", "format": "date-time" }
              }
            }
          }
        }
      }
    }
  }
}
