{
 "components": {
  "schemas": {
   "A2AIn": {
    "description": "Un message d'agent à agent à envoyer.",
    "properties": {
     "conversation_id": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Conversation à laquelle appartient le message (200 caractères au plus). Une nouvelle est générée si omise.",
      "title": "Conversation Id"
     },
     "from": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Expéditeur : `agent:<nom>` ou `run:<id>`. Par défaut : le principal appelant (`principal:<id>`).",
      "title": "From"
     },
     "in_reply_to": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant du message auquel celui-ci répond.",
      "title": "In Reply To"
     },
     "payload": {
      "description": "Contenu du message, toute valeur JSON. Filtré contre l'injection de prompt avant remise.",
      "title": "Payload"
     },
     "to": {
      "description": "Destinataire : `agent:<nom>` ou `run:<id>` (3 à 300 caractères).",
      "maxLength": 300,
      "minLength": 3,
      "title": "To",
      "type": "string"
     },
     "type": {
      "description": "Type de message : `request`, `response` ou `event`.",
      "enum": [
       "request",
       "response",
       "event"
      ],
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "to",
     "type"
    ],
    "title": "A2AIn",
    "type": "object"
   },
   "AccessIn": {
    "description": "Appelants permis sur un serveur MCP.",
    "properties": {
     "principals": {
      "description": "Utilisateurs et agents (identifiants de principaux, 200 au plus) autorisés à appeler le serveur ; vide : tout membre de son projet.",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Principals",
      "type": "array"
     }
    },
    "title": "AccessIn",
    "type": "object"
   },
   "Acl": {
    "description": "Liste d'accès d'une base. Les entrées sont des identités : `*`, `user:<id>`, `agent:<id>`, `service:<id>`, `role:<rôle>`, `group:<groupe>` ou un identifiant de principal seul.",
    "properties": {
     "readers": {
      "description": "Identités autorisées à lire et interroger. Par défaut `[\"*\"]` (tout principal du tenant).",
      "items": {
       "type": "string"
      },
      "title": "Readers",
      "type": "array"
     },
     "writers": {
      "description": "Identités autorisées à ajouter et supprimer des documents. Vide (par défaut) : `tenant_admin`, `developer` et `agent`.",
      "items": {
       "type": "string"
      },
      "title": "Writers",
      "type": "array"
     }
    },
    "title": "Acl",
    "type": "object"
   },
   "AclIn": {
    "description": "Nouvelle ACL d'une base.",
    "properties": {
     "apply_to_documents": {
      "default": true,
      "description": "Si vrai (par défaut), les documents qui héritent de l'ACL de la base reçoivent les nouveaux lecteurs.",
      "title": "Apply To Documents",
      "type": "boolean"
     },
     "readers": {
      "description": "Identités autorisées à lire et interroger. Par défaut `[\"*\"]`.",
      "items": {
       "type": "string"
      },
      "title": "Readers",
      "type": "array"
     },
     "writers": {
      "description": "Identités autorisées à écrire. Vide : `tenant_admin`, `developer` et `agent`.",
      "items": {
       "type": "string"
      },
      "title": "Writers",
      "type": "array"
     }
    },
    "title": "AclIn",
    "type": "object"
   },
   "ActionIn": {
    "description": "Action de confinement sur un incident.",
    "properties": {
     "action": {
      "description": "`suspend_agent`, `resume_agent`, `kill_agent`, `suspend_model`, `disable_model`, `disable_provider`, `rotate_key` ou `revoke_key`.",
      "title": "Action",
      "type": "string"
     },
     "reason": {
      "default": "",
      "description": "Raison de l'action, transmise au domaine propriétaire et conservée dans le journal. Par défaut : `incident action <action>`.",
      "title": "Reason",
      "type": "string"
     },
     "target": {
      "additionalProperties": true,
      "description": "Cible de l'action : `id` ou `name` (ou `model_id`), plus `scope`, `kind` ou `grace_seconds` selon l'action.",
      "title": "Target",
      "type": "object"
     }
    },
    "required": [
     "action",
     "target"
    ],
    "title": "ActionIn",
    "type": "object"
   },
   "AdvanceIn": {
    "description": "Passage d'un changement à l'étape suivante.",
    "properties": {
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Demande approuvée pour une étape qui exige une approbation (second appel).",
      "title": "Approval Id"
     },
     "evidence": {
      "additionalProperties": true,
      "description": "Résultats ajoutés aux preuves du changement, par clé : `tests`, `staging_smoke`, `production_smoke`, chacun `{passed, failed, errors}`.",
      "title": "Evidence",
      "type": "object"
     }
    },
    "title": "AdvanceIn",
    "type": "object"
   },
   "AgentMemoryPolicyIn": {
    "description": "Choix du moteur et politique de mémoire d'un agent.",
    "properties": {
     "cognee_instance": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instance Cognee à utiliser (par défaut `cognee`).",
      "title": "Cognee Instance"
     },
     "engine": {
      "default": "builtin",
      "description": "`builtin` (par défaut, magasin du Memory Manager), `mem0` (faits et mémoires persistants), `cognee` (connaissances et relations) ou `hybrid` (mémoires dans Mem0, connaissances dans Cognee, fusionnées au rappel). La mémoire de session reste toujours builtin.",
      "enum": [
       "builtin",
       "mem0",
       "cognee",
       "hybrid"
      ],
      "title": "Engine",
      "type": "string"
     },
     "mem0_instance": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instance Mem0 à utiliser (par défaut `mem0`, l'instance de plateforme du Navin Manager), par exemple une instance dédiée.",
      "title": "Mem0 Instance"
     },
     "policy": {
      "$ref": "#/components/schemas/MemoryPolicy",
      "description": "Modes, portées, limites de mémorisation, conservation, partage et isolation exigée."
     }
    },
    "title": "AgentMemoryPolicyIn",
    "type": "object"
   },
   "AgentPatch": {
    "description": "Champs de registre d'un agent à modifier.",
    "properties": {
     "description": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description libre de l'agent.",
      "title": "Description"
     },
     "labels": {
      "anyOf": [
       {
        "additionalProperties": {
         "type": "string"
        },
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Libellés, clés et valeurs textuelles ; remplace les libellés actuels.",
      "title": "Labels"
     },
     "owner": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 3,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire de l'agent (3 à 200 caractères).",
      "title": "Owner"
     },
     "risk_level": {
      "anyOf": [
       {
        "enum": [
         "minimal",
         "limited",
         "high"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Niveau de risque : `minimal`, `limited` ou `high`.",
      "title": "Risk Level"
     }
    },
    "title": "AgentPatch",
    "type": "object"
   },
   "AlertIn": {
    "description": "Une alerte Alertmanager.",
    "properties": {
     "annotations": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Annotations de l'alerte ; `summary` et `description` alimentent l'incident.",
      "title": "Annotations",
      "type": "object"
     },
     "endsAt": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fin de l'alerte (RFC 3339), pour une alerte résolue.",
      "title": "Endsat"
     },
     "fingerprint": {
      "description": "Empreinte Alertmanager, 1 à 200 caractères, qui relie l'alerte à son incident.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Fingerprint",
      "type": "string"
     },
     "generatorURL": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Lien vers l'expression qui a produit l'alerte.",
      "title": "Generatorurl"
     },
     "labels": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Labels de l'alerte, comme `alertname`, `severity` et éventuellement `tenant`.",
      "title": "Labels",
      "type": "object"
     },
     "startsAt": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Début du déclenchement de l'alerte (RFC 3339).",
      "title": "Startsat"
     },
     "status": {
      "default": "firing",
      "description": "`firing` (par défaut) ou `resolved`.",
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "fingerprint"
    ],
    "title": "AlertIn",
    "type": "object"
   },
   "AlertmanagerIn": {
    "description": "Charge utile du webhook Alertmanager.",
    "properties": {
     "alerts": {
      "description": "Alertes du groupe, 500 au plus.",
      "items": {
       "$ref": "#/components/schemas/AlertIn"
      },
      "maxItems": 500,
      "title": "Alerts",
      "type": "array"
     },
     "groupKey": {
      "default": "",
      "description": "Clé du groupe d'alertes dans Alertmanager.",
      "title": "Groupkey",
      "type": "string"
     },
     "receiver": {
      "default": "",
      "description": "Nom du récepteur Alertmanager.",
      "title": "Receiver",
      "type": "string"
     },
     "status": {
      "default": "firing",
      "description": "Statut du groupe, `firing` (par défaut) ou `resolved` ; c'est le statut de chaque alerte qui est utilisé.",
      "title": "Status",
      "type": "string"
     },
     "version": {
      "default": "4",
      "description": "Version de la charge utile, `4` par défaut.",
      "title": "Version",
      "type": "string"
     }
    },
    "title": "AlertmanagerIn",
    "type": "object"
   },
   "AllocationPolicyIn": {
    "description": "Politique de répartition du coût GPU.",
    "properties": {
     "currency": {
      "default": "USD",
      "description": "Devise ISO 4217 des prix, `USD` par défaut.",
      "pattern": "^[A-Z]{3}$",
      "title": "Currency",
      "type": "string"
     },
     "deployment_pattern": {
      "default": "*",
      "description": "Motif glob des identifiants de déploiement auxquels la politique s'applique, `*` par défaut ; le motif le plus précis l'emporte.",
      "title": "Deployment Pattern",
      "type": "string"
     },
     "effective_from": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Début de validité, ISO 8601.",
      "title": "Effective From"
     },
     "effective_to": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fin de validité, ISO 8601 ; null pour aucune fin.",
      "title": "Effective To"
     },
     "enabled": {
      "default": true,
      "description": "Indique si la politique est utilisée, vrai par défaut.",
      "title": "Enabled",
      "type": "boolean"
     },
     "energy_kwh_price": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Prix d'un kWh d'énergie, au moins 0, 0 par défaut.",
      "title": "Energy Kwh Price"
     },
     "gpu_hourly_cost": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Coût d'une heure de GPU, au moins 0, 0 par défaut.",
      "title": "Gpu Hourly Cost"
     },
     "gpu_model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle de GPU auquel la politique s'applique ; null pour tout modèle.",
      "title": "Gpu Model"
     },
     "name": {
      "description": "Nom de la politique (au moins 1 caractère).",
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "overhead_pct": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Frais généraux ajoutés au total, en pour cent, au moins 0, 0 par défaut.",
      "title": "Overhead Pct"
     },
     "pue": {
      "anyOf": [
       {
        "minimum": 1.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "1",
      "description": "Indicateur d'efficacité énergétique (PUE) du centre de données, au moins 1, 1 par défaut.",
      "title": "Pue"
     },
     "storage_gb": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Stockage du déploiement en Go, au moins 0, 0 par défaut.",
      "title": "Storage Gb"
     },
     "storage_gb_month_price": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Prix d'un Go de stockage par mois, au moins 0, 0 par défaut.",
      "title": "Storage Gb Month Price"
     }
    },
    "required": [
     "name"
    ],
    "title": "AllocationPolicyIn",
    "type": "object"
   },
   "ApiKeyIn": {
    "description": "Nouvelle clé d'API : nom, droits, rattachement facultatif à un projet et à un environnement, expiration.",
    "properties": {
     "clearance": {
      "default": "internal",
      "description": "Classification de données la plus haute que l'identifiant peut lire : `public`, `internal` (par défaut), `confidential`, `restricted` ou `secret`.",
      "enum": [
       "public",
       "internal",
       "confidential",
       "restricted",
       "secret"
      ],
      "title": "Clearance",
      "type": "string"
     },
     "environment": {
      "anyOf": [
       {
        "enum": [
         "dev",
         "test",
         "staging",
         "prod"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Environnement auquel la clé est rattachée : `dev`, `test`, `staging` ou `prod`. Quand il est null, l'en-tête `X-Environment` décide.",
      "title": "Environment"
     },
     "expires_at": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Date et heure (ISO 8601) après lesquelles la clé répond 401 `api_key_expired`. Null sans `expires_in_days` : la clé n'expire jamais.",
      "title": "Expires At"
     },
     "expires_in_days": {
      "anyOf": [
       {
        "maximum": 3650.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie en jours, de 1 à 3650. Utilisée seulement quand `expires_at` n'est pas donné.",
      "title": "Expires In Days"
     },
     "name": {
      "description": "Nom affiché de la clé, 1 à 200 caractères, par exemple la chaîne qui l'utilise.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "project": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Projet auquel la clé est rattachée. Quand il est null, l'appelant choisit le projet avec l'en-tête `X-Project`.",
      "title": "Project"
     },
     "roles": {
      "description": "Rôles accordés à l'identifiant. Vous ne pouvez accorder que des rôles que vous détenez, sauf si vous êtes `tenant_admin` ; seul un administrateur de plateforme peut accorder `platform_admin`.",
      "items": {
       "enum": [
        "platform_admin",
        "tenant_admin",
        "security_officer",
        "finops",
        "developer",
        "auditor",
        "agent",
        "viewer",
        "soc_analyst",
        "ai_governance_officer",
        "data_steward",
        "application_owner",
        "ai_platform_engineer",
        "business_owner",
        "support_engineer"
       ],
       "type": "string"
      },
      "title": "Roles",
      "type": "array"
     },
     "scopes": {
      "description": "Portées accordées à l'identifiant, sous forme de motifs (`models:invoke`, `admin:*`). Chaque portée doit être détenue par l'appelant (403 `forbidden`, raison `scope_not_held`). Liste vide par défaut.",
      "items": {
       "type": "string"
      },
      "title": "Scopes",
      "type": "array"
     }
    },
    "required": [
     "name"
    ],
    "title": "ApiKeyIn",
    "type": "object"
   },
   "AppEventIn": {
    "additionalProperties": false,
    "description": "Un événement d'audit applicatif. L'acteur, la séquence, les empreintes et `occurred_at` sont toujours fixés par le serveur ; tout autre champ est refusé, et l'action vit dans l'espace de noms `app.`, à part des événements de la plateforme.",
    "properties": {
     "action": {
      "description": "Nom de l'action dans l'espace de noms `app.`, en minuscules, 128 caractères au plus, par exemple `app.invoice.approved`.",
      "maxLength": 128,
      "pattern": "^app\\.[a-z0-9]+(?:[._-][a-z0-9]+)*$",
      "title": "Action",
      "type": "string"
     },
     "classification": {
      "anyOf": [
       {
        "enum": [
         "public",
         "internal",
         "confidential",
         "restricted",
         "secret"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification des données de l'événement : `public`, `internal`, `confidential`, `restricted` ou `secret`.",
      "title": "Classification"
     },
     "details": {
      "additionalProperties": true,
      "description": "Attributs libres de l'événement, 16384 octets au plus une fois sérialisés.",
      "title": "Details",
      "type": "object"
     },
     "event_time": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Moment de l'action selon l'horloge du client (ISO 8601), au plus le décalage d'horloge autorisé en avance sur le serveur. Par défaut : l'heure du serveur.",
      "title": "Event Time"
     },
     "outcome": {
      "default": "success",
      "description": "`success` (par défaut), `failure` ou `error`.",
      "enum": [
       "success",
       "failure",
       "error"
      ],
      "title": "Outcome",
      "type": "string"
     },
     "resource": {
      "$ref": "#/components/schemas/AppResource",
      "description": "Ressource concernée par l'événement."
     },
     "trace_id": {
      "anyOf": [
       {
        "pattern": "^[a-f0-9]{32}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de trace W3C, 32 caractères hexadécimaux en minuscules, pour relier l'événement à une trace.",
      "title": "Trace Id"
     }
    },
    "required": [
     "action",
     "resource"
    ],
    "title": "AppEventIn",
    "type": "object"
   },
   "AppResource": {
    "additionalProperties": false,
    "description": "Ressource d'un événement d'audit applicatif.",
    "properties": {
     "id": {
      "description": "Identifiant de la ressource, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Id",
      "type": "string"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom lisible de la ressource, 200 caractères au plus, facultatif.",
      "title": "Name"
     },
     "type": {
      "description": "Type de ressource, minuscules, chiffres et tirets bas, commençant par une lettre, 64 caractères au plus.",
      "pattern": "^[a-z][a-z0-9_]{0,63}$",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type",
     "id"
    ],
    "title": "AppResource",
    "type": "object"
   },
   "ApplicationIn": {
    "description": "Une application à enregistrer.",
    "properties": {
     "agents": {
      "description": "Agents accordés à l'application (noms ou motifs).",
      "items": {
       "type": "string"
      },
      "title": "Agents",
      "type": "array"
     },
     "environments": {
      "description": "Environnements où elle peut être déployée (1 à 10). Par défaut `[\"dev\"]`.",
      "items": {
       "type": "string"
      },
      "maxItems": 10,
      "minItems": 1,
      "title": "Environments",
      "type": "array"
     },
     "kind": {
      "description": "`agent_app`, `assistant`, `workflow` ou `external_agent`.",
      "enum": [
       "agent_app",
       "assistant",
       "workflow",
       "external_agent"
      ],
      "title": "Kind",
      "type": "string"
     },
     "mcp_servers": {
      "description": "Serveurs MCP que ses agents externes peuvent appeler (noms ou motifs).",
      "items": {
       "type": "string"
      },
      "title": "Mcp Servers",
      "type": "array"
     },
     "models": {
      "description": "Modèles que ses agents externes peuvent utiliser (identifiants ou motifs).",
      "items": {
       "type": "string"
      },
      "title": "Models",
      "type": "array"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres, `.`, `_`, `-`, 1 à 128 caractères.",
      "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
      "title": "Name",
      "type": "string"
     },
     "origin": {
      "default": "internal",
      "description": "`internal` (par défaut) ou `third_party` ; forcé à `third_party` pour un `external_agent`.",
      "enum": [
       "internal",
       "third_party"
      ],
      "title": "Origin",
      "type": "string"
     },
     "owner": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire de l'application. Par défaut : l'appelant.",
      "title": "Owner"
     },
     "plugins": {
      "description": "Plugins accordés à l'application (noms ou motifs).",
      "items": {
       "type": "string"
      },
      "title": "Plugins",
      "type": "array"
     },
     "project": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Projet de l'application. Par défaut : l'en-tête `X-Project` de la requête.",
      "title": "Project"
     },
     "risk_level": {
      "default": "medium",
      "description": "`low`, `medium` (par défaut) ou `high`.",
      "enum": [
       "low",
       "medium",
       "high"
      ],
      "title": "Risk Level",
      "type": "string"
     },
     "tools": {
      "description": "Outils que ses agents externes peuvent invoquer (noms ou motifs comme `crm.*`).",
      "items": {
       "type": "string"
      },
      "title": "Tools",
      "type": "array"
     }
    },
    "required": [
     "name",
     "kind"
    ],
    "title": "ApplicationIn",
    "type": "object"
   },
   "ApprovalIn": {
    "description": "Nouvelle demande de validation.",
    "properties": {
     "action": {
      "description": "Action à valider, 1 à 200 caractères, par exemple `tool.call`, `promotion`, `break_glass` ou `mandate`.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Action",
      "type": "string"
     },
     "approver_roles": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Rôles autorisés à décider. Par défaut : `tenant_admin`, `security_officer`, `developer`, `business_owner`.",
      "title": "Approver Roles"
     },
     "expires_in_seconds": {
      "anyOf": [
       {
        "maximum": 2592000.0,
        "minimum": 60.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie de la demande en secondes, de 60 à 2592000 (30 jours). Par défaut : le réglage de la plateforme, 86400 (24 heures).",
      "title": "Expires In Seconds"
     },
     "payload": {
      "additionalProperties": true,
      "description": "Détails de l'action montrés aux valideurs et vérifiés quand la validation est utilisée. `excluded_approvers` liste les sujets qui ne peuvent pas valider.",
      "title": "Payload",
      "type": "object"
     },
     "quorum": {
      "default": 1,
      "description": "Nombre de décisions `approve` nécessaires, de 1 à 10 (1 par défaut).",
      "maximum": 10.0,
      "minimum": 1.0,
      "title": "Quorum",
      "type": "integer"
     },
     "reason": {
      "default": "",
      "description": "Raison de la demande, montrée aux valideurs (vide par défaut).",
      "title": "Reason",
      "type": "string"
     },
     "resource_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de la ressource concernée, facultatif.",
      "title": "Resource Id"
     },
     "resource_type": {
      "description": "Type de la ressource concernée, 1 à 100 caractères, par exemple `tool` ou `model`.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Resource Type",
      "type": "string"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Exécution d'agent qui attend la décision, facultative. Elle reprend sur approbation et échoue sur refus, expiration ou annulation.",
      "title": "Run Id"
     }
    },
    "required": [
     "action",
     "resource_type"
    ],
    "title": "ApprovalIn",
    "type": "object"
   },
   "AssessmentIn": {
    "description": "Nouvelle évaluation de risque au titre de l'AI Act.",
    "properties": {
     "answers": {
      "additionalProperties": true,
      "description": "Réponses au questionnaire ; les clés à vrai comptent comme des cas d'usage pour la classification.",
      "title": "Answers",
      "type": "object"
     },
     "evidence": {
      "description": "Preuves jointes à l'évaluation.",
      "items": {
       "$ref": "#/components/schemas/eai__domains__risk__EvidenceIn"
      },
      "title": "Evidence",
      "type": "array"
     },
     "owner": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sujet responsable du système. Par défaut : l'appelant.",
      "title": "Owner"
     },
     "status": {
      "default": "submitted",
      "description": "`draft`, `submitted` (par défaut), `approved` ou `rejected`.",
      "title": "Status",
      "type": "string"
     },
     "target": {
      "$ref": "#/components/schemas/eai__domains__risk__Target",
      "description": "Système d'IA évalué."
     },
     "use_cases": {
      "description": "Cas d'usage déclarés, par exemple `employment`, `credit_scoring`, `interacts_with_humans` ou `social_scoring`.",
      "items": {
       "type": "string"
      },
      "title": "Use Cases",
      "type": "array"
     }
    },
    "required": [
     "target"
    ],
    "title": "AssessmentIn",
    "type": "object"
   },
   "AssumeIn": {
    "additionalProperties": false,
    "description": "Demande d'un ExecutionContext signé agissant en tant qu'identité de service.",
    "properties": {
     "audience": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 10,
        "minItems": 1,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Services autorisés à accepter le contexte, 1 à 10 audiences connues. Null : l'audience par défaut de l'autorité de contexte.",
      "title": "Audience"
     },
     "purpose": {
      "description": "Raison de l'endossement, 3 à 500 caractères. Inscrite dans le journal d'audit et dans le contexte signé.",
      "maxLength": 500,
      "minLength": 3,
      "title": "Purpose",
      "type": "string"
     },
     "scopes": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 50,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Portées demandées, 50 au plus. Par défaut : les portées de l'identité. Chacune doit être accordée par l'identité et détenue par l'appelant.",
      "title": "Scopes"
     },
     "ttl_seconds": {
      "default": 900,
      "description": "Durée de vie du contexte en secondes, de 30 à 3600 (900 par défaut).",
      "maximum": 3600.0,
      "minimum": 30.0,
      "title": "Ttl Seconds",
      "type": "integer"
     }
    },
    "required": [
     "purpose"
    ],
    "title": "AssumeIn",
    "type": "object"
   },
   "BreakGlassContextIn": {
    "description": "Options du contexte d'exécution d'un accès d'urgence.",
    "properties": {
     "audience": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 10,
        "minItems": 1,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Services autorisés à accepter le contexte, 1 à 10 audiences connues. Null : l'audience par défaut.",
      "title": "Audience"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "maximum": 86400.0,
        "minimum": 30.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie du contexte en secondes, de 30 à 86400, jamais au-delà de l'expiration de l'accès. Null : la durée par défaut.",
      "title": "Ttl Seconds"
     }
    },
    "title": "BreakGlassContextIn",
    "type": "object"
   },
   "BreakGlassIn": {
    "description": "Demande d'accès d'urgence.",
    "properties": {
     "beneficiary": {
      "anyOf": [
       {
        "maxLength": 300,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sujet qui reçoit l'élévation. Par défaut : l'appelant.",
      "title": "Beneficiary"
     },
     "duration_seconds": {
      "description": "Durée de l'élévation une fois approuvée, de 60 secondes au maximum de la plateforme (3600 par défaut).",
      "minimum": 60.0,
      "title": "Duration Seconds",
      "type": "integer"
     },
     "incident_ref": {
      "anyOf": [
       {
        "maxLength": 300,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de l'incident qui justifie la demande, facultative.",
      "title": "Incident Ref"
     },
     "reason": {
      "description": "Justification, 10 à 4000 caractères, montrée aux valideurs.",
      "maxLength": 4000,
      "minLength": 10,
      "title": "Reason",
      "type": "string"
     },
     "roles": {
      "description": "Rôles du tenant accordés, 8 au plus, jamais `platform_admin`. Des rôles ou des portées sont obligatoires.",
      "items": {
       "type": "string"
      },
      "maxItems": 8,
      "title": "Roles",
      "type": "array"
     },
     "scopes": {
      "description": "Portées accordées, 100 motifs au plus.",
      "items": {
       "type": "string"
      },
      "maxItems": 100,
      "title": "Scopes",
      "type": "array"
     }
    },
    "required": [
     "reason",
     "duration_seconds"
    ],
    "title": "BreakGlassIn",
    "type": "object"
   },
   "BundleIn": {
    "description": "Options de publication d'un bundle OPA.",
    "properties": {
     "data": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Contenu du fichier `data.json` du bundle, sous la racine `eai` : `{\"eai\": {...}}`.",
      "title": "Data"
     },
     "revision": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9._-]{1,100}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de révision, 1 à 100 lettres, chiffres, `.`, `_` ou `-`. Par défaut : généré.",
      "title": "Revision"
     }
    },
    "title": "BundleIn",
    "type": "object"
   },
   "BusinessUnitIn": {
    "description": "Nouvelle unité métier.",
    "properties": {
     "description": {
      "default": "",
      "description": "Description, 2000 caractères au plus (vide par défaut).",
      "maxLength": 2000,
      "title": "Description",
      "type": "string"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Paires clé-valeur libres enregistrées avec l'objet. Contrôlées par la base : un objet refusé répond 422 `invalid_metadata`.",
      "title": "Metadata",
      "type": "object"
     },
     "name": {
      "description": "Nom affiché, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "organization_id": {
      "description": "Organisation qui contient l'unité. Obligatoire.",
      "title": "Organization Id",
      "type": "string"
     },
     "parent_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Unité parente dans la même organisation ; null pour une unité de premier niveau. Ne peut plus être modifiée.",
      "title": "Parent Id"
     },
     "slug": {
      "description": "Identifiant court en minuscules, chiffres et tirets, commençant par une lettre ou un chiffre, 63 caractères au plus. Unique, il ne peut plus être modifié.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
      "title": "Slug",
      "type": "string"
     }
    },
    "required": [
     "organization_id",
     "slug",
     "name"
    ],
    "title": "BusinessUnitIn",
    "type": "object"
   },
   "BusinessUnitPatch": {
    "description": "Modifications d'une unité métier ; les champs omis gardent leur valeur.",
    "properties": {
     "description": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle description, 2000 caractères au plus.",
      "title": "Description"
     },
     "expected_version": {
      "anyOf": [
       {
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version lue en dernier par l'appelant. Une version périmée répond 409 `version_conflict` (concurrence optimiste).",
      "title": "Expected Version"
     },
     "metadata": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Paires clé-valeur libres enregistrées avec l'objet. Contrôlées par la base : un objet refusé répond 422 `invalid_metadata`.",
      "title": "Metadata"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom affiché, 1 à 200 caractères.",
      "title": "Name"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "archived"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`active` ou `archived`.",
      "title": "Status"
     }
    },
    "title": "BusinessUnitPatch",
    "type": "object"
   },
   "ChangeIn": {
    "description": "Une demande de changement à ouvrir.",
    "properties": {
     "kind": {
      "description": "Type d'objet : `prompt`, `skill`, `connector`, `rule`, `model`, `policy`, `tool`, `agent` ou `pack`.",
      "enum": [
       "prompt",
       "skill",
       "connector",
       "rule",
       "model",
       "policy",
       "tool",
       "agent",
       "pack"
      ],
      "title": "Kind",
      "type": "string"
     },
     "ref": {
      "description": "Nom de l'objet : minuscules, chiffres, `.`, `_` et `-`, 1 à 128 caractères, commençant et finissant par une lettre ou un chiffre.",
      "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
      "title": "Ref",
      "type": "string"
     },
     "title": {
      "default": "",
      "description": "Titre du changement (300 caractères au plus).",
      "maxLength": 300,
      "title": "Title",
      "type": "string"
     },
     "version": {
      "description": "Version de l'objet apportée par le changement (1 à 64 caractères).",
      "maxLength": 64,
      "minLength": 1,
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "kind",
     "ref",
     "version"
    ],
    "title": "ChangeIn",
    "type": "object"
   },
   "ChannelAuth": {
    "description": "Authentification des utilisateurs finaux d'un canal.",
    "properties": {
     "secret_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret (clé, secret de signature de webhook), `openbao://...` ou `env://...`.",
      "title": "Secret Ref"
     },
     "type": {
      "description": "`oidc`, `api_key`, `signed_webhook` ou `anonymous`.",
      "enum": [
       "oidc",
       "api_key",
       "signed_webhook",
       "anonymous"
      ],
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type"
    ],
    "title": "ChannelAuth",
    "type": "object"
   },
   "ChannelInstallIn": {
    "description": "Installation d'un canal à partir d'un adaptateur de canal Navin.",
    "properties": {
     "agent_ref": {
      "anyOf": [
       {
        "pattern": "^[a-z]+:[A-Za-z0-9._/@:-]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent qui répond sur le canal, sous la forme `<type>:<nom>`, par exemple `agent:support`.",
      "title": "Agent Ref"
     },
     "auth": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/ChannelAuth"
       },
       {
        "type": "null"
       }
      ],
      "description": "Authentification des utilisateurs finaux. `api_key` par défaut."
     },
     "dlp": {
      "anyOf": [
       {
        "additionalProperties": {
         "enum": [
          "off",
          "detect",
          "mask",
          "block"
         ],
         "type": "string"
        },
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mode DLP par sens, chacun `off`, `detect`, `mask` ou `block`. Par défaut `{inbound: detect, outbound: mask}`.",
      "title": "Dlp"
     },
     "limits": {
      "anyOf": [
       {
        "additionalProperties": {
         "type": "integer"
        },
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limites entières. Par défaut `{max_message_bytes: 65536, rate_per_minute: 60}`.",
      "title": "Limits"
     },
     "name": {
      "anyOf": [
       {
        "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom du canal (par défaut `<canal>-<slug du projet>`) ; minuscules, chiffres, `.`, `_`, `-`, 128 caractères au plus.",
      "title": "Name"
     },
     "secret_refs": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Champs secrets du contrat sous forme de références `openbao://` ou `env://`, par nom de champ.",
      "title": "Secret Refs",
      "type": "object"
     },
     "settings": {
      "additionalProperties": true,
      "description": "Champs non secrets du contrat de canal, chacun vérifié selon son type (texte de 2000 caractères au plus, entier, booléen, liste, énumération).",
      "title": "Settings",
      "type": "object"
     }
    },
    "title": "ChannelInstallIn",
    "type": "object"
   },
   "ChannelPatch": {
    "description": "Champs d'un canal à modifier.",
    "properties": {
     "owner": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 3,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire du canal (3 à 200 caractères).",
      "title": "Owner"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "disabled"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`active` ou `disabled`.",
      "title": "Status"
     }
    },
    "title": "ChannelPatch",
    "type": "object"
   },
   "CheckIn": {
    "description": "Demande de vérification d'une relation.",
    "properties": {
     "contextual_tuples": {
      "description": "Jusqu'à 20 tuples `{subject, relation, object}` tenus pour vrais pour cette seule vérification, jamais enregistrés.",
      "items": {
       "additionalProperties": {
        "type": "string"
       },
       "type": "object"
      },
      "maxItems": 20,
      "title": "Contextual Tuples",
      "type": "array"
     },
     "object": {
      "description": "Objet vérifié, `<type>:<id>`, par exemple `project:42`.",
      "pattern": "^[a-z_][a-z0-9_]*:[^\\s]+$",
      "title": "Object",
      "type": "string"
     },
     "relation": {
      "description": "Nom de la relation, minuscules, chiffres et tirets bas, par exemple `viewer`.",
      "pattern": "^[a-z_][a-z0-9_]*$",
      "title": "Relation",
      "type": "string"
     },
     "subject": {
      "description": "Sujet vérifié, `<type>:<id>` : `user:alice`, `group:fin#member` ou `user:*`.",
      "pattern": "^[a-z_][a-z0-9_]*:[^\\s]+$",
      "title": "Subject",
      "type": "string"
     }
    },
    "required": [
     "subject",
     "relation",
     "object"
    ],
    "title": "CheckIn",
    "type": "object"
   },
   "CheckpointIn": {
    "description": "Un point de reprise à enregistrer sur un run.",
    "properties": {
     "label": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Libellé du point de reprise (200 caractères au plus).",
      "title": "Label"
     },
     "state": {
      "description": "État à enregistrer, toute valeur JSON. Par défaut : objet vide.",
      "title": "State"
     }
    },
    "title": "CheckpointIn",
    "type": "object"
   },
   "ClassifyIn": {
    "description": "Demande de classification : un texte ou un média.",
    "properties": {
     "language": {
      "default": "en",
      "description": "Langue du texte pour les détecteurs, par exemple `en` (par défaut) ou `fr`.",
      "title": "Language",
      "type": "string"
     },
     "media_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant d'un média téléversé dont la classification enregistrée est renvoyée.",
      "title": "Media Id"
     },
     "text": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Texte à classer.",
      "title": "Text"
     }
    },
    "title": "ClassifyIn",
    "type": "object"
   },
   "ClientCertificateIn": {
    "additionalProperties": false,
    "description": "Certificat client et clé privée (PEM) ajoutés à la méthode pour le TLS mutuel. Les champs inconnus sont refusés.",
    "properties": {
     "client_cert": {
      "anyOf": [
       {
        "maxLength": 65536,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Certificat client, PEM, 65536 caractères au plus.",
      "title": "Client Cert"
     },
     "client_key": {
      "anyOf": [
       {
        "maxLength": 65536,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Clé privée du certificat client, PEM, 65536 caractères au plus. Relayée au Manager, jamais auditée.",
      "title": "Client Key"
     }
    },
    "title": "ClientCertificateIn",
    "type": "object"
   },
   "ClusterIn": {
    "description": "Nouveau cluster (environnement) du Manager.",
    "properties": {
     "description": {
      "default": "",
      "description": "Description libre, 500 caractères au plus.",
      "maxLength": 500,
      "title": "Description",
      "type": "string"
     },
     "mode": {
      "default": "production",
      "description": "`production` (par défaut) ou `sandbox`.",
      "pattern": "^(production|sandbox)$",
      "title": "Mode",
      "type": "string"
     },
     "name": {
      "description": "Nom du cluster, de 1 à 80 caractères.",
      "maxLength": 80,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "profile": {
      "anyOf": [
       {
        "pattern": "^(development|standard|banking-secure|air-gapped|hybrid)$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Profil de déploiement : `development`, `standard`, `banking-secure`, `air-gapped` ou `hybrid` ; omis pour la valeur par défaut du Manager.",
      "title": "Profile"
     }
    },
    "required": [
     "name"
    ],
    "title": "ClusterIn",
    "type": "object"
   },
   "CommitIn": {
    "description": "Montant mesuré qui remplace le montant réservé.",
    "properties": {
     "cost": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Coût mesuré dans la devise de la réservation, au moins 0, 0 par défaut.",
      "title": "Cost"
     },
     "tokens": {
      "default": 0,
      "description": "Jetons mesurés, au moins 0, 0 par défaut.",
      "minimum": 0.0,
      "title": "Tokens",
      "type": "integer"
     }
    },
    "title": "CommitIn",
    "type": "object"
   },
   "ComplianceBrandingIn": {
    "additionalProperties": false,
    "description": "Champs de l'image de marque à écrire ; un champ absent est conservé, null le ramène à la valeur par défaut du runtime.",
    "properties": {
     "agent_name": {
      "anyOf": [
       {
        "maxLength": 80,
        "minLength": 1,
        "pattern": "^[^<>\\x00-\\x1f\\x7f]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'agent affiché aux utilisateurs, 1 à 80 caractères, sans `<`, `>` ni caractère de contrôle.",
      "title": "Agent Name"
     },
     "app_url": {
      "anyOf": [
       {
        "maxLength": 2048,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URL de l'application web de l'instance une fois connue, https uniquement, sans identifiants (2048 caractères au plus).",
      "title": "App Url"
     },
     "expected_version": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de l'image de marque sur laquelle porte la modification ; une autre version courante répond 409 `compliance_branding_version_conflict`.",
      "title": "Expected Version"
     },
     "navbar_color": {
      "anyOf": [
       {
        "pattern": "^#[0-9A-Fa-f]{6}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Couleur de la barre supérieure, `#RRGGBB` ; le texte dessus est noir ou blanc avec un contraste de 4,5:1 ou plus.",
      "title": "Navbar Color"
     },
     "sidebar_color": {
      "anyOf": [
       {
        "pattern": "^#[0-9A-Fa-f]{6}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Couleur de la barre latérale, `#RRGGBB` ; le texte dessus est noir ou blanc avec un contraste de 4,5:1 ou plus.",
      "title": "Sidebar Color"
     }
    },
    "title": "ComplianceBrandingIn",
    "type": "object"
   },
   "ComplianceInstanceIn": {
    "description": "Une instance de l'agent de conformité et d'audit à créer en brouillon.",
    "properties": {
     "business_unit_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Unité métier de l'instance (UUID), contrôlée à la validation.",
      "title": "Business Unit Id"
     },
     "deployment_model": {
      "description": "`shared` (le runtime partagé existant) ou `dedicated` (un runtime installé par le Navin Manager).",
      "enum": [
       "shared",
       "dedicated"
      ],
      "title": "Deployment Model",
      "type": "string"
     },
     "description": {
      "default": "",
      "description": "Description libre (2000 caractères au plus).",
      "maxLength": 2000,
      "title": "Description",
      "type": "string"
     },
     "embedding_model": {
      "description": "Modèle d'embeddings du registre (capacité `embeddings`), utilisé par la base de connaissances de l'instance.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Embedding Model",
      "type": "string"
     },
     "generation_model": {
      "description": "Modèle de génération du registre (capacité `chat`) utilisé par le copilote.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Generation Model",
      "type": "string"
     },
     "id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant immuable (UUID) de l'instance ; généré s'il est absent.",
      "title": "Id"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 63 caractères.",
      "pattern": "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$",
      "title": "Name",
      "type": "string"
     },
     "organization_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Organisation de l'instance (UUID), contrôlée à la validation.",
      "title": "Organization Id"
     },
     "pack": {
      "anyOf": [
       {
        "enum": [
         "banking",
         "insurance",
         "industry"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Pack métier chargé au-dessus du noyau générique : `banking`, `insurance` ou `industry` ; null pour le noyau seul.",
      "title": "Pack"
     },
     "project": {
      "description": "Projet de l'instance (identifiant ou slug) ; l'appelant doit en être propriétaire ou être administrateur du tenant.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Project",
      "type": "string"
     },
     "sector": {
      "default": "generic",
      "description": "Secteur de l'instance : `generic` (par défaut), `banking`, `insurance` ou `industry`.",
      "enum": [
       "generic",
       "banking",
       "insurance",
       "industry"
      ],
      "title": "Sector",
      "type": "string"
     },
     "version": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version à déployer ; par défaut : la version que le runtime annonce dans son document OpenAPI à l'étape smoke.",
      "title": "Version"
     }
    },
    "required": [
     "name",
     "project",
     "deployment_model",
     "generation_model",
     "embedding_model"
    ],
    "title": "ComplianceInstanceIn",
    "type": "object"
   },
   "ComplianceRestoreIn": {
    "description": "Une demande de restauration.",
    "properties": {
     "backup_id": {
      "description": "Identifiant de la sauvegarde à restaurer.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Backup Id",
      "type": "string"
     }
    },
    "required": [
     "backup_id"
    ],
    "title": "ComplianceRestoreIn",
    "type": "object"
   },
   "ComplianceUpgradeIn": {
    "description": "Une demande de mise à niveau.",
    "properties": {
     "version": {
      "description": "Version à déployer (lettres, chiffres, `.`, `_`, `+`, `-`, 64 caractères au plus).",
      "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$",
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "version"
    ],
    "title": "ComplianceUpgradeIn",
    "type": "object"
   },
   "ComponentIn": {
    "description": "Un composant utilisé par une exécution.",
    "properties": {
     "attributes": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Attributs libres du composant, comme le fournisseur d'un modèle.",
      "title": "Attributes"
     },
     "digest": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Empreinte du composant, par exemple `sha256:<hex>`, 200 caractères au plus.",
      "title": "Digest"
     },
     "kind": {
      "description": "`model`, `prompt`, `tool`, `skill` ou `plugin`.",
      "enum": [
       "model",
       "prompt",
       "tool",
       "skill",
       "plugin"
      ],
      "title": "Kind",
      "type": "string"
     },
     "name": {
      "description": "Nom du composant, 1 à 300 caractères.",
      "maxLength": 300,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "version": {
      "description": "Version du composant, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "kind",
     "name",
     "version"
    ],
    "title": "ComponentIn",
    "type": "object"
   },
   "ComponentsIn": {
    "description": "Composants déclarés pour une exécution ou l'une de ses étapes.",
    "properties": {
     "components": {
      "description": "Composants utilisés, de 1 à 100.",
      "items": {
       "$ref": "#/components/schemas/ComponentIn"
      },
      "maxItems": 100,
      "minItems": 1,
      "title": "Components",
      "type": "array"
     },
     "step_seq": {
      "anyOf": [
       {
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro de séquence de l'étape (1 ou plus) ; omettez-le pour l'exécution entière.",
      "title": "Step Seq"
     }
    },
    "required": [
     "components"
    ],
    "title": "ComponentsIn",
    "type": "object"
   },
   "ConnectAuthIn": {
    "additionalProperties": false,
    "description": "Méthode d'authentification parmi celles du descripteur (`connect.auth`) et ses champs. Les champs secrets sont relayés au Manager, qui les vérifie sur le service et les scelle ; ils ne sont jamais écrits dans l'audit. Les champs inconnus sont refusés.",
    "properties": {
     "fields": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Champs de la méthode (par exemple utilisateur et mot de passe), jusqu'à 16 valeurs texte.",
      "maxProperties": 16,
      "title": "Fields",
      "type": "object"
     },
     "method": {
      "anyOf": [
       {
        "pattern": "^[a-z][a-z0-9_]{1,39}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de la méthode : une lettre minuscule puis 1 à 39 lettres minuscules, chiffres ou soulignés.",
      "title": "Method"
     }
    },
    "title": "ConnectAuthIn",
    "type": "object"
   },
   "ConnectIn": {
    "description": "Configurer et connecter : un service existant du client (point d'accès, méthode d'authentification et ses champs, TLS avec une autorité de certification privée facultative, champs du descripteur).",
    "properties": {
     "auth": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/ConnectAuthIn"
       },
       {
        "type": "null"
       }
      ],
      "description": "Méthode d'authentification et ses champs."
     },
     "ca_pem": {
      "anyOf": [
       {
        "maxLength": 65536,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Autorité de certification privée (PEM) qui signe le certificat du service, 65536 caractères au plus.",
      "title": "Ca Pem"
     },
     "config": {
      "additionalProperties": true,
      "description": "Champs de la section de connexion du descripteur, jusqu'à 100 clés ; l'audit ne garde que les clés.",
      "maxProperties": 100,
      "title": "Config",
      "type": "object"
     },
     "credentials_ref": {
      "anyOf": [
       {
        "maxLength": 512,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence d'identifiants déjà enregistrés, 512 caractères au plus ; l'audit ne garde que sa présence.",
      "title": "Credentials Ref"
     },
     "endpoint": {
      "description": "Adresse du service existant, de 1 à 2048 caractères.",
      "maxLength": 2048,
      "minLength": 1,
      "title": "Endpoint",
      "type": "string"
     },
     "mtls": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/ClientCertificateIn"
       },
       {
        "type": "null"
       }
      ],
      "description": "Certificat client pour le TLS mutuel."
     },
     "name": {
      "description": "Nom du service dans le catalogue : une lettre minuscule puis jusqu'à 62 lettres minuscules, chiffres, tirets ou soulignés.",
      "pattern": "^[a-z][a-z0-9_-]{0,62}$",
      "title": "Name",
      "type": "string"
     },
     "tls_verify": {
      "default": true,
      "description": "Vérifier le certificat TLS du service (vrai par défaut).",
      "title": "Tls Verify",
      "type": "boolean"
     }
    },
    "required": [
     "name",
     "endpoint"
    ],
    "title": "ConnectIn",
    "type": "object"
   },
   "ConnectionIn": {
    "description": "Connexion à enregistrer dans le Manager : les champs du test plus un nom et une description.",
    "properties": {
     "description": {
      "default": "",
      "description": "Description libre, 1000 caractères au plus.",
      "maxLength": 1000,
      "title": "Description",
      "type": "string"
     },
     "name": {
      "description": "Nom de la connexion, de 1 à 100 caractères.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "params": {
      "additionalProperties": true,
      "description": "Paramètres de connexion (hôte, port, base, émetteur...), jusqu'à 20 clés ; l'audit ne garde que les clés.",
      "maxProperties": 20,
      "title": "Params",
      "type": "object"
     },
     "secret": {
      "anyOf": [
       {
        "maxLength": 4096,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mot de passe ou secret client, 4096 caractères au plus. En écriture seule : scellé par le Manager, jamais renvoyé ni audité. Une valeur vide n'est pas envoyée.",
      "title": "Secret"
     },
     "type": {
      "description": "`postgresql`, `redis` ou `oidc`.",
      "pattern": "^(postgresql|redis|oidc)$",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type",
     "name"
    ],
    "title": "ConnectionIn",
    "type": "object"
   },
   "ConnectionTestIn": {
    "description": "Simulation d'une connexion : son type, ses paramètres et le secret, rien n'est enregistré.",
    "properties": {
     "params": {
      "additionalProperties": true,
      "description": "Paramètres de connexion, jusqu'à 20 clés.",
      "maxProperties": 20,
      "title": "Params",
      "type": "object"
     },
     "secret": {
      "anyOf": [
       {
        "maxLength": 4096,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mot de passe ou secret client utilisé pour le test, 4096 caractères au plus ; jamais renvoyé ni audité.",
      "title": "Secret"
     },
     "type": {
      "description": "`postgresql`, `redis` ou `oidc`.",
      "pattern": "^(postgresql|redis|oidc)$",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type"
    ],
    "title": "ConnectionTestIn",
    "type": "object"
   },
   "ConnectorPatch": {
    "description": "Champs d'un connecteur à modifier.",
    "properties": {
     "owner": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 3,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire du connecteur (3 à 200 caractères).",
      "title": "Owner"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "disabled"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`active` ou `disabled` (révoque les baux).",
      "title": "Status"
     }
    },
    "title": "ConnectorPatch",
    "type": "object"
   },
   "ConsentIn": {
    "description": "Consentement d'un participant d'une session temps réel.",
    "properties": {
     "method": {
      "default": "api",
      "description": "Méthode de recueil du consentement : `verbal`, `click`, `written` ou `api` (par défaut).",
      "title": "Method",
      "type": "string"
     },
     "participant_identity": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9._:@-]{1,128}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identité du participant (1 à 128 lettres, chiffres, `.`, `_`, `:`, `@`, `-`). Par défaut : le participant de la session.",
      "title": "Participant Identity"
     },
     "purpose": {
      "description": "Finalité acceptée par le participant (1 à 500 caractères).",
      "maxLength": 500,
      "minLength": 1,
      "title": "Purpose",
      "type": "string"
     },
     "recording_allowed": {
      "default": false,
      "description": "Si l'audio peut être enregistré. Faux par défaut.",
      "title": "Recording Allowed",
      "type": "boolean"
     },
     "retention_days": {
      "description": "Durée de conservation des transcriptions et enregistrements, en jours (1 à 3650).",
      "maximum": 3650.0,
      "minimum": 1.0,
      "title": "Retention Days",
      "type": "integer"
     },
     "transcription_allowed": {
      "default": false,
      "description": "Si la parole peut être transcrite et conservée. Faux par défaut.",
      "title": "Transcription Allowed",
      "type": "boolean"
     }
    },
    "required": [
     "purpose",
     "retention_days"
    ],
    "title": "ConsentIn",
    "type": "object"
   },
   "ControlIn": {
    "description": "Contrôle adopté par le tenant.",
    "properties": {
     "code": {
      "description": "Code du contrôle, 1 à 64 caractères : un code du catalogue comme `EAI-GOV-01`, ou un code personnalisé. Unique dans le tenant.",
      "maxLength": 64,
      "minLength": 1,
      "title": "Code",
      "type": "string"
     },
     "description": {
      "default": "",
      "description": "Mise en œuvre du contrôle (vide par défaut).",
      "title": "Description",
      "type": "string"
     },
     "evidence": {
      "description": "Preuves ajoutées au contrôle.",
      "items": {
       "$ref": "#/components/schemas/eai__domains__risk__EvidenceIn"
      },
      "title": "Evidence",
      "type": "array"
     },
     "frameworks": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Exigences couvertes par référentiel, clés parmi `iso_42001`, `nist_ai_rmf`, `gdpr` et `eu_ai_act`, par exemple `{\"gdpr\": [\"Art. 32\"]}`. Par défaut : la correspondance du catalogue.",
      "title": "Frameworks"
     },
     "owner": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sujet responsable du contrôle. Par défaut : l'appelant.",
      "title": "Owner"
     },
     "status": {
      "default": "planned",
      "description": "`planned` (par défaut), `implemented`, `partially_implemented` ou `not_applicable`.",
      "title": "Status",
      "type": "string"
     },
     "title": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Titre. Par défaut : le titre du catalogue ; obligatoire pour un code personnalisé.",
      "title": "Title"
     }
    },
    "required": [
     "code"
    ],
    "title": "ControlIn",
    "type": "object"
   },
   "Dataset": {
    "description": "Jeu de données d'une suite.",
    "properties": {
     "classification": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification des données du jeu : `public`, `internal`, `confidential`, `restricted` ou `secret`.",
      "title": "Classification"
     },
     "ref": {
      "description": "Référence du jeu de données, 1 caractère au moins, par exemple `golden-faq`.",
      "minLength": 1,
      "title": "Ref",
      "type": "string"
     },
     "version": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version du jeu de données, facultative.",
      "title": "Version"
     }
    },
    "required": [
     "ref"
    ],
    "title": "Dataset",
    "type": "object"
   },
   "DecisionIn": {
    "description": "Décision d'un valideur.",
    "properties": {
     "comment": {
      "default": "",
      "description": "Commentaire conservé avec la décision (vide par défaut).",
      "title": "Comment",
      "type": "string"
     },
     "decision": {
      "description": "`approve` ou `reject`.",
      "enum": [
       "approve",
       "reject"
      ],
      "title": "Decision",
      "type": "string"
     }
    },
    "required": [
     "decision"
    ],
    "title": "DecisionIn",
    "type": "object"
   },
   "DecisionNoteIn": {
    "description": "Note d'une décision.",
    "properties": {
     "reason": {
      "default": "",
      "description": "Raison ou note, 2000 caractères au plus (vide par défaut). Inscrite dans le journal d'audit.",
      "maxLength": 2000,
      "title": "Reason",
      "type": "string"
     }
    },
    "title": "DecisionNoteIn",
    "type": "object"
   },
   "DelegationIn": {
    "description": "Nouvelle délégation racine.",
    "properties": {
     "audience": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 20,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Services qui peuvent accepter la délégation, 20 motifs au plus. `eai-control-plane` par défaut.",
      "title": "Audience"
     },
     "data_classification_max": {
      "anyOf": [
       {
        "pattern": "^(public|internal|confidential|restricted|secret)$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification de données la plus haute que le délégataire peut traiter : `public`, `internal`, `confidential`, `restricted` ou `secret`. Par défaut : l'habilitation de l'appelant.",
      "title": "Data Classification Max"
     },
     "delegate": {
      "description": "Qui reçoit les droits : `agent:<name>`, `app:<id>`, `service:<name>` ou `subagent:<name>`, 3 à 250 caractères.",
      "maxLength": 250,
      "minLength": 3,
      "title": "Delegate",
      "type": "string"
     },
     "expires_at": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Date et heure d'expiration (ISO 8601), à la place de `ttl_seconds`.",
      "title": "Expires At"
     },
     "max_depth": {
      "default": 0,
      "description": "Nombre de niveaux de sous-délégation autorisés en dessous, de 0 à 10 (0 par défaut).",
      "maximum": 10.0,
      "minimum": 0.0,
      "title": "Max Depth",
      "type": "integer"
     },
     "purpose": {
      "default": "",
      "description": "Finalité de la délégation, 2000 caractères au plus, conservée dans le journal.",
      "maxLength": 2000,
      "title": "Purpose",
      "type": "string"
     },
     "redelegation_allowed": {
      "default": false,
      "description": "Si vrai et que `max_depth` est supérieur à 0, le délégataire peut restreindre la délégation pour d'autres (faux par défaut).",
      "title": "Redelegation Allowed",
      "type": "boolean"
     },
     "resources": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 100,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Ressources sur lesquelles le délégataire peut agir, 100 motifs au plus comme `tool:crm.*`. `*` par défaut.",
      "title": "Resources"
     },
     "scopes": {
      "description": "Portées déléguées, 100 au plus. Motifs où `*` remplace toute suite de caractères (lettres, chiffres et `_ . : / @ * -`, 200 caractères au plus chacun) ; chacune doit être couverte par les portées de l'appelant.",
      "items": {
       "type": "string"
      },
      "maxItems": 100,
      "title": "Scopes",
      "type": "array"
     },
     "session_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Session à laquelle la délégation est rattachée, facultative.",
      "title": "Session Id"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "minimum": 60.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie en secondes, 60 au moins, dans la limite de la plateforme. Par défaut : la valeur de la plateforme (8 heures).",
      "title": "Ttl Seconds"
     }
    },
    "required": [
     "delegate"
    ],
    "title": "DelegationIn",
    "type": "object"
   },
   "DeleteObjectIn": {
    "additionalProperties": false,
    "description": "Objet à supprimer d'un profil de stockage. Les champs inconnus sont refusés.",
    "properties": {
     "key": {
      "description": "Clé de l'objet relative au préfixe de clé du profil, de 1 à 1024 caractères, dans un préfixe de présignature ; aucun caractère de contrôle, pas de `/` initial, aucun segment `.` ou `..`.",
      "maxLength": 1024,
      "minLength": 1,
      "title": "Key",
      "type": "string"
     },
     "version_id": {
      "anyOf": [
       {
        "maxLength": 1024,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de l'objet à supprimer sur un bucket versionné, de 1 à 1024 caractères. Omettez-la pour supprimer la version courante.",
      "title": "Version Id"
     }
    },
    "required": [
     "key"
    ],
    "title": "DeleteObjectIn",
    "type": "object"
   },
   "DeploymentIn": {
    "description": "Une demande de déploiement.",
    "properties": {
     "environment": {
      "description": "Environnement visé, parmi ceux de l'application (1 à 50 caractères).",
      "maxLength": 50,
      "minLength": 1,
      "title": "Environment",
      "type": "string"
     },
     "reason": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison de la demande de déploiement, montrée aux approbateurs (2000 caractères au plus).",
      "title": "Reason"
     },
     "version": {
      "description": "Version à déployer (1 à 100 caractères).",
      "maxLength": 100,
      "minLength": 1,
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "version",
     "environment"
    ],
    "title": "DeploymentIn",
    "type": "object"
   },
   "DetectionIn": {
    "description": "Détection à ingérer.",
    "properties": {
     "agent": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent concerné, utilisé pour la corrélation.",
      "title": "Agent"
     },
     "confidence": {
      "default": 0.5,
      "description": "Confiance du détecteur, de 0 à 1 (0,5 par défaut). Le confinement automatique exige au moins 0,9.",
      "title": "Confidence",
      "type": "number"
     },
     "data_classification": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification des données en jeu : `public` à `secret`.",
      "title": "Data Classification"
     },
     "evidence": {
      "additionalProperties": true,
      "description": "Éléments de preuve du constat, 64000 octets au plus, scellés avec l'incident.",
      "title": "Evidence",
      "type": "object"
     },
     "fingerprint": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Clé de dédoublonnage du constat, 200 caractères au plus.",
      "title": "Fingerprint"
     },
     "kind": {
      "description": "Type de détection : `prompt_injection`, `data_exfiltration`, `secret_leak`, `runaway_agent`, `cost_loop`, `mcp_compromised`, `unsigned_plugin`, `privilege_escalation`, `rag_poisoning`, `policy_change_unaudited`, `policy_change_unauthorized`, `audit_tampering`, `runtime_threat`, `security_finding`.",
      "title": "Kind",
      "type": "string"
     },
     "occurred_at": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Moment des faits (ISO 8601). Par défaut : l'heure de réception.",
      "title": "Occurred At"
     },
     "resource": {
      "additionalProperties": true,
      "description": "Ressource concernée, objet libre comme `{\"type\": \"tool\", \"id\": \"...\"}`.",
      "title": "Resource",
      "type": "object"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Exécution d'agent concernée.",
      "title": "Run Id"
     },
     "session_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Session concernée.",
      "title": "Session Id"
     },
     "severity": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`sev1` à `sev4`. Par défaut : la gravité de base du type.",
      "title": "Severity"
     },
     "source": {
      "default": "manual",
      "description": "Source : `guard`, `dlp`, `egress`, `quota`, `plugins`, `runtime`, `authz`, `policy`, `audit`, `knowledge`, `mcp`, `falco`, `tetragon`, `opensearch` ou `manual` (par défaut).",
      "title": "Source",
      "type": "string"
     },
     "targets": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Cibles des réactions du playbook, par clé `session`, `run`, `agent`, `identity`, `credential`, `workload`, `mcp`, `plugin`, `tool`, `connector`, `knowledge_base`, `model` ou `application`.",
      "title": "Targets",
      "type": "object"
     },
     "user": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur concerné, utilisé pour la corrélation.",
      "title": "User"
     }
    },
    "required": [
     "kind"
    ],
    "title": "DetectionIn",
    "type": "object"
   },
   "DetokenizeIn": {
    "description": "Demande de détokenisation.",
    "properties": {
     "purpose": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison pour laquelle les valeurs sont nécessaires, transmise à la politique dans `context.purpose` et inscrite dans le journal d'audit.",
      "title": "Purpose"
     },
     "text": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Texte dont les jetons sont trouvés et remplacés par leurs valeurs.",
      "title": "Text"
     },
     "tokens": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons à résoudre, `tok_<type>_<hex>`.",
      "title": "Tokens"
     }
    },
    "title": "DetokenizeIn",
    "type": "object"
   },
   "DlpIn": {
    "description": "Demande de DLP.",
    "properties": {
     "language": {
      "default": "en",
      "description": "Langue du texte pour les détecteurs, par exemple `en` (par défaut) ou `fr`.",
      "title": "Language",
      "type": "string"
     },
     "mode": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`detect`, `mask`, `tokenize` ou `block`. Par défaut : `detect` pour l'inspection, `mask` pour le masquage.",
      "title": "Mode"
     },
     "text": {
      "description": "Texte à inspecter ou à masquer.",
      "title": "Text",
      "type": "string"
     }
    },
    "required": [
     "text"
    ],
    "title": "DlpIn",
    "type": "object"
   },
   "DocIn": {
    "description": "Un document à ingérer. Fournissez `content` ou `content_base64`.",
    "properties": {
     "chunk_readers": {
      "anyOf": [
       {
        "items": {
         "anyOf": [
          {
           "items": {
            "type": "string"
           },
           "type": "array"
          },
          {
           "type": "null"
          }
         ]
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Lecteurs par fragment issus des permissions de la source, par position de fragment ; `null` pour un fragment garde les lecteurs du document.",
      "title": "Chunk Readers"
     },
     "classification": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`public`, `internal`, `confidential`, `restricted` ou `secret`. Par défaut : la classification de la base ; relevée si des données personnelles sont détectées.",
      "title": "Classification"
     },
     "collection": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Collection du document dans la base (1 à 200 caractères), utilisée par le filtre de requête `collections`. Aucune par défaut.",
      "title": "Collection"
     },
     "content": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Contenu sous forme de texte. Prioritaire sur `content_base64`.",
      "title": "Content"
     },
     "content_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Contenu d'un fichier binaire (PDF, document bureautique...), encodé en base64.",
      "title": "Content Base64"
     },
     "language": {
      "anyOf": [
       {
        "maxLength": 10,
        "minLength": 2,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Langue du document (code ISO 639-1 comme `fr` ou `en`). Par défaut : détectée dans le texte ; chaque fragment reçoit aussi sa propre langue détectée, qui choisit sa configuration plein texte (`simple` si inconnue).",
      "title": "Language"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Paires clé-valeur libres enregistrées avec l'objet et renvoyées telles quelles ; la plateforme ne les interprète jamais.",
      "title": "Metadata",
      "type": "object"
     },
     "mime_type": {
      "default": "text/plain",
      "description": "Type de média du contenu. `text/plain` par défaut.",
      "title": "Mime Type",
      "type": "string"
     },
     "readers": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Lecteurs de ce document. Omis : les lecteurs de la base, alignés sur son ACL.",
      "title": "Readers"
     },
     "source_connector": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Connecteur d'où vient le document (1 à 200 caractères) ; il doit être actif, et ses résultats sont écartés quand il ne l'est plus.",
      "title": "Source Connector"
     },
     "title": {
      "default": "",
      "description": "Titre du document, utilisé aussi comme nom de fichier pour l'analyse et l'extraction. Vide par défaut.",
      "title": "Title",
      "type": "string"
     },
     "uri": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URI source. Un nouveau document de même URI remplace la version précédente.",
      "title": "Uri"
     }
    },
    "title": "DocIn",
    "type": "object"
   },
   "EncryptIn": {
    "description": "Demande de chiffrement.",
    "properties": {
     "aad": {
      "default": "",
      "description": "Données associées liées au chiffré ; la même valeur est exigée pour déchiffrer (vide par défaut).",
      "title": "Aad",
      "type": "string"
     },
     "key": {
      "description": "Clé à utiliser, par identifiant (UUID) ou par nom.",
      "title": "Key",
      "type": "string"
     },
     "plaintext": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Texte UTF-8 à chiffrer.",
      "title": "Plaintext"
     },
     "plaintext_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Octets à chiffrer, encodés en base64 ; utilisés à la place de `plaintext` s'ils sont donnés.",
      "title": "Plaintext Base64"
     }
    },
    "required": [
     "key"
    ],
    "title": "EncryptIn",
    "type": "object"
   },
   "EnrollmentTokenIn": {
    "description": "Options d'un nouveau jeton d'enrôlement.",
    "properties": {
     "expires_in_hours": {
      "default": 24,
      "description": "Durée de vie du jeton en heures, de 1 à 720, 24 par défaut.",
      "maximum": 720.0,
      "minimum": 1.0,
      "title": "Expires In Hours",
      "type": "integer"
     },
     "max_uses": {
      "default": 10,
      "description": "Nombre de machines qui peuvent s'enrôler avec le jeton, de 1 à 10000, 10 par défaut.",
      "maximum": 10000.0,
      "minimum": 1.0,
      "title": "Max Uses",
      "type": "integer"
     }
    },
    "title": "EnrollmentTokenIn",
    "type": "object"
   },
   "EntryIn": {
    "additionalProperties": false,
    "description": "Un service du coffre-fort : nom, URL, identifiant, mot de passe et clé d'API ou clé secrète (les quatre derniers facultatifs). Les champs inconnus sont refusés.",
    "properties": {
     "api_key": {
      "default": "",
      "description": "Clé d'API ou clé secrète, 8192 caractères au plus. Scellée ; renvoyée seulement par `POST .../reveal`.",
      "maxLength": 8192,
      "title": "Api Key",
      "type": "string"
     },
     "login": {
      "default": "",
      "description": "Identifiant du compte, 320 caractères au plus.",
      "maxLength": 320,
      "title": "Login",
      "type": "string"
     },
     "name": {
      "description": "Nom du service, de 1 à 120 caractères, unique dans le coffre-fort.",
      "maxLength": 120,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "password": {
      "default": "",
      "description": "Mot de passe, 4096 caractères au plus. Scellé ; renvoyé seulement par `POST .../reveal`.",
      "maxLength": 4096,
      "title": "Password",
      "type": "string"
     },
     "url": {
      "default": "",
      "description": "Adresse du service, 2048 caractères au plus.",
      "maxLength": 2048,
      "title": "Url",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "EntryIn",
    "type": "object"
   },
   "EntryPatch": {
    "additionalProperties": false,
    "description": "Champs à modifier ; un champ omis est conservé, un mot de passe ou une clé vide l'efface. Les champs inconnus sont refusés.",
    "properties": {
     "api_key": {
      "anyOf": [
       {
        "maxLength": 8192,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle clé d'API ou clé secrète, 8192 caractères au plus ; vide l'efface.",
      "title": "Api Key"
     },
     "login": {
      "anyOf": [
       {
        "maxLength": 320,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvel identifiant, 320 caractères au plus.",
      "title": "Login"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 120,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom, de 1 à 120 caractères, unique dans le coffre-fort (sans tenir compte de la casse).",
      "title": "Name"
     },
     "password": {
      "anyOf": [
       {
        "maxLength": 4096,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau mot de passe, 4096 caractères au plus ; vide l'efface.",
      "title": "Password"
     },
     "url": {
      "anyOf": [
       {
        "maxLength": 2048,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle adresse, 2048 caractères au plus.",
      "title": "Url"
     }
    },
    "title": "EntryPatch",
    "type": "object"
   },
   "EnvironmentIn": {
    "description": "Nouvel environnement d'un projet.",
    "properties": {
     "name": {
      "description": "`dev`, `test`, `staging` ou `prod`.",
      "enum": [
       "dev",
       "test",
       "staging",
       "prod"
      ],
      "title": "Name",
      "type": "string"
     },
     "settings": {
      "additionalProperties": true,
      "description": "Objet de paramètres libre de l'environnement (vide par défaut).",
      "title": "Settings",
      "type": "object"
     }
    },
    "required": [
     "name"
    ],
    "title": "EnvironmentIn",
    "type": "object"
   },
   "EraseIn": {
    "description": "Demande d'effacement pour une personne concernée.",
    "properties": {
     "dry_run": {
      "default": false,
      "description": "Si vrai, compte ce qui serait effacé sans rien supprimer (faux par défaut).",
      "title": "Dry Run",
      "type": "boolean"
     },
     "include_documents": {
      "default": false,
      "description": "Si vrai, supprime aussi les documents des bases de connaissances créés par la personne et leurs vecteurs (faux par défaut).",
      "title": "Include Documents",
      "type": "boolean"
     },
     "reason": {
      "default": "gdpr_art_17",
      "description": "Base légale de l'effacement, inscrite dans le journal d'audit. `gdpr_art_17` par défaut.",
      "title": "Reason",
      "type": "string"
     },
     "subject_id": {
      "description": "Personne à effacer, telle que la plateforme la connaît (sujet utilisateur), 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Subject Id",
      "type": "string"
     }
    },
    "required": [
     "subject_id"
    ],
    "title": "EraseIn",
    "type": "object"
   },
   "EstimateIn": {
    "description": "Appel prévu à valoriser.",
    "properties": {
     "at": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Date et heure auxquelles le prix est pris, ISO 8601 ; maintenant par défaut.",
      "title": "At"
     },
     "cache_hit_ratio": {
      "anyOf": [
       {
        "maximum": 1.0,
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Part des jetons d'entrée attendue depuis le cache, de 0 à 1, valorisée comme entrée en cache.",
      "title": "Cache Hit Ratio"
     },
     "expected_output_tokens": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons de sortie attendus, utilisés quand `max_tokens` n'est pas fourni.",
      "title": "Expected Output Tokens"
     },
     "max_tokens": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre maximal de jetons de sortie ; s'il est fourni, c'est la sortie retenue pour le pire cas.",
      "title": "Max Tokens"
     },
     "model_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle de l'appel, comparé aux motifs de modèle des grilles tarifaires.",
      "title": "Model Id"
     },
     "provider": {
      "description": "Fournisseur de l'appel (obligatoire).",
      "title": "Provider",
      "type": "string"
     },
     "reasoning_effort": {
      "anyOf": [
       {
        "enum": [
         "none",
         "minimal",
         "low",
         "medium",
         "high",
         "max"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Effort de raisonnement (`none`, `minimal`, `low`, `medium`, `high`, `max`) qui donne une réserve par défaut de jetons de raisonnement.",
      "title": "Reasoning Effort"
     },
     "reasoning_tokens": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons de raisonnement explicites, au moins 0 ; remplacent la réserve de `reasoning_effort`.",
      "title": "Reasoning Tokens"
     },
     "region": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Région de l'appel ; les grilles de cette région l'emportent sur celles en `*`.",
      "title": "Region"
     },
     "scope": {
      "additionalProperties": true,
      "description": "Portée utilisée pour trouver les quotas correspondants (`project`, `agent`, `user`, `model`, `provider`, `run`...) ; `model` et `provider` prennent par défaut les valeurs de l'appel.",
      "title": "Scope",
      "type": "object"
     },
     "units": {
      "additionalProperties": true,
      "description": "Unités prévues par clé de prix, par exemple `{\"input_tokens\": 1200}`.",
      "title": "Units",
      "type": "object"
     },
     "usage_type": {
      "default": "llm",
      "description": "Type d'usage, `llm` par défaut.",
      "title": "Usage Type",
      "type": "string"
     }
    },
    "required": [
     "provider"
    ],
    "title": "EstimateIn",
    "type": "object"
   },
   "EventIn": {
    "description": "Un événement à publier.",
    "properties": {
     "headers": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "En-têtes de l'événement, valeurs textuelles. Le contexte de trace y est ajouté.",
      "title": "Headers",
      "type": "object"
     },
     "payload": {
      "description": "Contenu de l'événement, toute valeur JSON. Les filtres des déclencheurs le lisent.",
      "title": "Payload"
     },
     "subject": {
      "description": "Sujet de l'événement (1 à 255 caractères, sujet NATS valide sans joker), par exemple `orders.created`.",
      "maxLength": 255,
      "minLength": 1,
      "title": "Subject",
      "type": "string"
     }
    },
    "required": [
     "subject"
    ],
    "title": "EventIn",
    "type": "object"
   },
   "ExceptionIn": {
    "description": "Dérogation limitée dans le temps à un contrôle ou pour une cible.",
    "properties": {
     "approver": {
      "description": "Sujet qui a approuvé la dérogation ; ce ne doit pas être le demandeur.",
      "minLength": 1,
      "title": "Approver",
      "type": "string"
     },
     "compensating_controls": {
      "default": "",
      "description": "Mesures qui réduisent le risque entre-temps (vide par défaut).",
      "title": "Compensating Controls",
      "type": "string"
     },
     "control_code": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Code du contrôle concerné, à la place de `control_id`.",
      "title": "Control Code"
     },
     "control_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant du contrôle concerné.",
      "title": "Control Id"
     },
     "expires_at": {
      "description": "Fin de la dérogation (ISO 8601), dans le futur et à 365 jours au plus.",
      "title": "Expires At",
      "type": "string"
     },
     "reason": {
      "description": "Raison pour laquelle le contrôle ne peut pas être respecté, 3 caractères au moins.",
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     },
     "target_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cible concernée (agent, modèle...), quand la dérogation ne porte pas sur un contrôle.",
      "title": "Target Ref"
     }
    },
    "required": [
     "reason",
     "approver",
     "expires_at"
    ],
    "title": "ExceptionIn",
    "type": "object"
   },
   "ExerciseIn": {
    "description": "Demande d'exercice de PRA.",
    "properties": {
     "plan_id": {
      "description": "Identifiant du plan de PRA à exercer, de 1 à 100 caractères.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Plan Id",
      "type": "string"
     }
    },
    "required": [
     "plan_id"
    ],
    "title": "ExerciseIn",
    "type": "object"
   },
   "ExportIn": {
    "description": "Options d'un export d'audit.",
    "properties": {
     "format": {
      "default": "ndjson",
      "description": "`ndjson` (par défaut, un événement canonique par ligne) ou `csv` (une ligne par événement, l'événement canonique dans la dernière colonne).",
      "enum": [
       "ndjson",
       "csv"
      ],
      "title": "Format",
      "type": "string"
     },
     "from": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Début de la période (ISO 8601), inclus. Null : depuis le premier événement.",
      "title": "From"
     },
     "retention_days": {
      "default": 365,
      "description": "Jours pendant lesquels les fichiers stockés ne peuvent être ni modifiés ni supprimés, de 1 à 3650 (365 par défaut).",
      "maximum": 3650.0,
      "minimum": 1.0,
      "title": "Retention Days",
      "type": "integer"
     },
     "to": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fin de la période (ISO 8601), incluse. Null : jusqu'au dernier événement.",
      "title": "To"
     }
    },
    "title": "ExportIn",
    "type": "object"
   },
   "ExternalAgentIn": {
    "description": "Une identité d'agent tiers à créer.",
    "properties": {
     "name": {
      "description": "Nom de l'identité : minuscules, chiffres, `.`, `_`, `-` ; la clé est nommée `app:<application>:<nom>`.",
      "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
      "title": "Name",
      "type": "string"
     },
     "scopes": {
      "description": "Scopes supplémentaires de la clé (20 au plus), ajoutés à `tools:invoke` et `models:invoke`.",
      "items": {
       "type": "string"
      },
      "maxItems": 20,
      "title": "Scopes",
      "type": "array"
     }
    },
    "required": [
     "name"
    ],
    "title": "ExternalAgentIn",
    "type": "object"
   },
   "FxRateIn": {
    "description": "Un taux de change.",
    "properties": {
     "as_of": {
      "description": "Date et heure à partir desquelles le taux s'applique, ISO 8601.",
      "format": "date-time",
      "title": "As Of",
      "type": "string"
     },
     "base": {
      "description": "Devise de base, ISO 4217 (trois lettres majuscules).",
      "pattern": "^[A-Z]{3}$",
      "title": "Base",
      "type": "string"
     },
     "quote": {
      "description": "Devise de cotation, ISO 4217 (trois lettres majuscules).",
      "pattern": "^[A-Z]{3}$",
      "title": "Quote",
      "type": "string"
     },
     "rate": {
      "anyOf": [
       {
        "exclusiveMinimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "description": "Unités de `quote` pour une unité de `base`, supérieur à 0.",
      "title": "Rate"
     },
     "source": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Origine du taux ; `manual` par défaut.",
      "title": "Source"
     }
    },
    "required": [
     "base",
     "quote",
     "rate",
     "as_of"
    ],
    "title": "FxRateIn",
    "type": "object"
   },
   "FxRatesIn": {
    "description": "Lot de taux de change.",
    "properties": {
     "rates": {
      "description": "Taux à ajouter ou remplacer, de 1 à 1000.",
      "items": {
       "$ref": "#/components/schemas/FxRateIn"
      },
      "maxItems": 1000,
      "minItems": 1,
      "title": "Rates",
      "type": "array"
     }
    },
    "required": [
     "rates"
    ],
    "title": "FxRatesIn",
    "type": "object"
   },
   "GateIn": {
    "description": "Demande de vérification d'une barrière de qualité.",
    "properties": {
     "run_missing": {
      "default": false,
      "description": "Si vrai, exécute maintenant les suites sans exécution terminée (faux par défaut).",
      "title": "Run Missing",
      "type": "boolean"
     },
     "suite_ids": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Uniquement ces suites bloquantes. Null : toutes les suites bloquantes de la cible.",
      "title": "Suite Ids"
     },
     "target": {
      "$ref": "#/components/schemas/eai__domains__evaluation__Target",
      "description": "Cible dont les suites bloquantes sont vérifiées ; `version` limite aux exécutions de cette version."
     }
    },
    "required": [
     "target"
    ],
    "title": "GateIn",
    "type": "object"
   },
   "GpuSampleIn": {
    "description": "Un échantillon d'utilisation GPU.",
    "properties": {
     "deployment_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Déploiement de modèle qui tournait sur le GPU pendant l'échantillon.",
      "title": "Deployment Id"
     },
     "gpu_model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle du GPU, utilisé pour choisir la politique de répartition.",
      "title": "Gpu Model"
     },
     "gpu_uuid": {
      "description": "UUID du GPU (obligatoire).",
      "minLength": 1,
      "title": "Gpu Uuid",
      "type": "string"
     },
     "interval_seconds": {
      "anyOf": [
       {
        "exclusiveMinimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "description": "Durée couverte par l'échantillon, en secondes, supérieure à 0.",
      "title": "Interval Seconds"
     },
     "memory_used_gb": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Mémoire GPU utilisée en Go, au moins 0, 0 par défaut.",
      "title": "Memory Used Gb"
     },
     "node": {
      "description": "Nœud qui héberge le GPU (obligatoire).",
      "minLength": 1,
      "title": "Node",
      "type": "string"
     },
     "power_w": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Puissance consommée en watts, au moins 0, 0 par défaut ; utilisée pour le coût énergétique.",
      "title": "Power W"
     },
     "sampled_at": {
      "description": "Heure de l'échantillon, ISO 8601.",
      "format": "date-time",
      "title": "Sampled At",
      "type": "string"
     },
     "utilization_pct": {
      "anyOf": [
       {
        "maximum": 100.0,
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Utilisation du GPU en pour cent, de 0 à 100, 0 par défaut.",
      "title": "Utilization Pct"
     }
    },
    "required": [
     "node",
     "gpu_uuid",
     "sampled_at",
     "interval_seconds"
    ],
    "title": "GpuSampleIn",
    "type": "object"
   },
   "GpuSamplesIn": {
    "description": "Lot d'échantillons GPU.",
    "properties": {
     "samples": {
      "description": "Échantillons à enregistrer, de 1 à 10000.",
      "items": {
       "$ref": "#/components/schemas/GpuSampleIn"
      },
      "maxItems": 10000,
      "minItems": 1,
      "title": "Samples",
      "type": "array"
     }
    },
    "required": [
     "samples"
    ],
    "title": "GpuSamplesIn",
    "type": "object"
   },
   "GroupMappingIn": {
    "description": "Nouvelle correspondance entre un groupe de l'annuaire, un rôle dans un projet et un profil de quota facultatif.",
    "properties": {
     "group": {
      "description": "Nom du groupe tel qu'il figure dans la revendication `groups` ou `entra_groups`, 1 à 256 caractères, comparé exactement.",
      "maxLength": 256,
      "minLength": 1,
      "title": "Group",
      "type": "string"
     },
     "project": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant court du projet cible, à la place de `project_id` (63 caractères au plus).",
      "title": "Project"
     },
     "project_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant du projet cible. Donnez-le, ou `project`.",
      "title": "Project Id"
     },
     "quota_profile": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom du profil de quota, à la place de `quota_profile_id`.",
      "title": "Quota Profile"
     },
     "quota_profile_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant du profil de quota donné aux membres. Facultatif.",
      "title": "Quota Profile Id"
     },
     "role": {
      "default": "developer",
      "description": "Rôle donné dans le projet aux membres du groupe : `tenant_admin`, `security_officer`, `finops`, `developer` (par défaut), `auditor`, `agent` ou `viewer`.",
      "enum": [
       "tenant_admin",
       "security_officer",
       "finops",
       "developer",
       "auditor",
       "agent",
       "viewer"
      ],
      "title": "Role",
      "type": "string"
     }
    },
    "required": [
     "group"
    ],
    "title": "GroupMappingIn",
    "type": "object"
   },
   "GroupMappingPatch": {
    "description": "Modifications d'une correspondance de groupe ; les champs omis gardent leur valeur.",
    "properties": {
     "clear_quota_profile": {
      "default": false,
      "description": "Si vrai, détache le profil de quota de la correspondance (faux par défaut).",
      "title": "Clear Quota Profile",
      "type": "boolean"
     },
     "group": {
      "anyOf": [
       {
        "maxLength": 256,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom de groupe, 1 à 256 caractères.",
      "title": "Group"
     },
     "project": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau projet cible, par identifiant court.",
      "title": "Project"
     },
     "project_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau projet cible, par identifiant.",
      "title": "Project Id"
     },
     "quota_profile": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau profil de quota, par nom.",
      "title": "Quota Profile"
     },
     "quota_profile_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau profil de quota, par identifiant.",
      "title": "Quota Profile Id"
     },
     "role": {
      "anyOf": [
       {
        "enum": [
         "tenant_admin",
         "security_officer",
         "finops",
         "developer",
         "auditor",
         "agent",
         "viewer"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau rôle : `tenant_admin`, `security_officer`, `finops`, `developer`, `auditor`, `agent` ou `viewer`.",
      "title": "Role"
     }
    },
    "title": "GroupMappingPatch",
    "type": "object"
   },
   "HTTPValidationError": {
    "description": "Corps d'une réponse 422 quand la requête ne respecte pas le schéma déclaré.",
    "properties": {
     "detail": {
      "description": "Une entrée par champ invalide.",
      "items": {
       "$ref": "#/components/schemas/ValidationError"
      },
      "title": "Detail",
      "type": "array"
     }
    },
    "title": "HTTPValidationError",
    "type": "object"
   },
   "HeartbeatIn": {
    "description": "Un heartbeat envoyé par le runtime d'un run.",
    "properties": {
     "message": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Message d'état (2000 caractères au plus).",
      "title": "Message"
     },
     "progress": {
      "anyOf": [
       {
        "type": "number"
       },
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Progression : un nombre (par exemple une fraction de 0 à 1) ou un objet au choix du runtime.",
      "title": "Progress"
     },
     "step": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Étape courante, texte libre (500 caractères au plus).",
      "title": "Step"
     },
     "worker": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Worker qui exécute le run (200 caractères au plus). Par défaut : le principal appelant.",
      "title": "Worker"
     }
    },
    "title": "HeartbeatIn",
    "type": "object"
   },
   "HoldIn": {
    "description": "Nouveau gel juridique de preuves.",
    "properties": {
     "case_id": {
      "description": "Référence du dossier, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Case Id",
      "type": "string"
     },
     "reason": {
      "description": "Raison du gel, 3 à 1000 caractères.",
      "maxLength": 1000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     },
     "time_from": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Début de la période de date d'événement couverte (ISO 8601). Null : pas de borne basse.",
      "title": "Time From"
     },
     "time_to": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fin de la période couverte (ISO 8601), après `time_from`. Null : pas de borne haute.",
      "title": "Time To"
     }
    },
    "required": [
     "case_id",
     "reason"
    ],
    "title": "HoldIn",
    "type": "object"
   },
   "ImageEditIn": {
    "description": "Une demande de retouche d'image. Fournissez `media_id` ou `content_base64`.",
    "properties": {
     "content_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Image fournie directement, encodée en base64 (analysée avant usage).",
      "title": "Content Base64"
     },
     "media_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Média image enregistré à retoucher.",
      "title": "Media Id"
     },
     "mime_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de média de l'image fournie, par exemple `image/png`.",
      "title": "Mime Type"
     },
     "model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle à utiliser. Par défaut : l'adaptateur configuré.",
      "title": "Model"
     },
     "n": {
      "default": 1,
      "description": "Nombre d'images (1 à 8, 1 par défaut).",
      "maximum": 8.0,
      "minimum": 1.0,
      "title": "N",
      "type": "integer"
     },
     "prompt": {
      "description": "Ce qu'il faut modifier (1 à 8000 caractères).",
      "maxLength": 8000,
      "minLength": 1,
      "title": "Prompt",
      "type": "string"
     },
     "size": {
      "default": "1024x1024",
      "description": "Taille `<largeur>x<hauteur>` en pixels. `1024x1024` par défaut.",
      "title": "Size",
      "type": "string"
     }
    },
    "required": [
     "prompt"
    ],
    "title": "ImageEditIn",
    "type": "object"
   },
   "ImageGenIn": {
    "description": "Une demande de génération d'images.",
    "properties": {
     "model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle à utiliser. Par défaut : l'adaptateur configuré.",
      "title": "Model"
     },
     "n": {
      "default": 1,
      "description": "Nombre d'images (1 à 8, 1 par défaut).",
      "maximum": 8.0,
      "minimum": 1.0,
      "title": "N",
      "type": "integer"
     },
     "prompt": {
      "description": "Description de l'image (1 à 8000 caractères), inspectée par la DLP.",
      "maxLength": 8000,
      "minLength": 1,
      "title": "Prompt",
      "type": "string"
     },
     "size": {
      "default": "1024x1024",
      "description": "Taille `<largeur>x<hauteur>` en pixels. `1024x1024` par défaut.",
      "title": "Size",
      "type": "string"
     }
    },
    "required": [
     "prompt"
    ],
    "title": "ImageGenIn",
    "type": "object"
   },
   "ImportIn": {
    "description": "Export de facturation à importer.",
    "properties": {
     "content": {
      "description": "Contenu de l'export tel que le fournisseur le donne (document JSON ou texte CSV).",
      "title": "Content"
     },
     "filename": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom du fichier importé, conservé pour l'historique.",
      "title": "Filename"
     },
     "mapping": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Correspondance des colonnes d'un export `generic_csv` (quelle colonne porte la date, le montant, la devise, le modèle...).",
      "title": "Mapping"
     },
     "provider": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fournisseur des lignes quand le format ne l'indique pas (par exemple avec `generic_csv`).",
      "title": "Provider"
     },
     "source": {
      "description": "Format de l'export : `openai`, `anthropic`, `aws_cur`, `azure`, `gcp` ou `generic_csv`.",
      "enum": [
       "openai",
       "anthropic",
       "aws_cur",
       "azure",
       "gcp",
       "generic_csv"
      ],
      "title": "Source",
      "type": "string"
     }
    },
    "required": [
     "source",
     "content"
    ],
    "title": "ImportIn",
    "type": "object"
   },
   "IncidentIn": {
    "description": "Nouvel incident.",
    "properties": {
     "assignee": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sujet chargé de l'incident, facultatif.",
      "title": "Assignee"
     },
     "category": {
      "default": "other",
      "description": "Catégorie libre, par exemple `data_leak` ou `prompt_injection`. `other` par défaut.",
      "title": "Category",
      "type": "string"
     },
     "description": {
      "default": "",
      "description": "Ce qui s'est passé (vide par défaut).",
      "title": "Description",
      "type": "string"
     },
     "resources": {
      "description": "Ressources concernées, sous forme d'objets comme `{\"type\": \"agent\", \"id\": \"...\"}`.",
      "items": {
       "additionalProperties": true,
       "type": "object"
      },
      "title": "Resources",
      "type": "array"
     },
     "severity": {
      "default": "sev3",
      "description": "`sev1` (le plus grave) à `sev4` ; les alias `critical`, `high`, `medium` et `low` sont acceptés. `sev3` par défaut.",
      "title": "Severity",
      "type": "string"
     },
     "title": {
      "description": "Titre, 3 à 300 caractères.",
      "maxLength": 300,
      "minLength": 3,
      "title": "Title",
      "type": "string"
     }
    },
    "required": [
     "title"
    ],
    "title": "IncidentIn",
    "type": "object"
   },
   "IncidentPatch": {
    "description": "Modifications d'un incident ; les champs omis gardent leur valeur.",
    "properties": {
     "assignee": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau sujet responsable.",
      "title": "Assignee"
     },
     "note": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Note ajoutée à la chronologie.",
      "title": "Note"
     },
     "severity": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle gravité : `sev1` (le plus grave) à `sev4` ; les alias `critical`, `high`, `medium` et `low` sont acceptés.",
      "title": "Severity"
     },
     "status": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau statut : `open`, `investigating`, `contained`, `resolved` ou `closed`.",
      "title": "Status"
     }
    },
    "title": "IncidentPatch",
    "type": "object"
   },
   "InstallServiceIn": {
    "description": "Un service du catalogue à installer.",
    "properties": {
     "bindings": {
      "additionalProperties": {
       "additionalProperties": true,
       "type": "object"
      },
      "description": "Liaisons du service à d'autres services ou à des connexions, par exemple `{kind: connection, ref: <identifiant de connexion>}` ; jusqu'à 50.",
      "maxProperties": 50,
      "title": "Bindings",
      "type": "object"
     },
     "config": {
      "additionalProperties": true,
      "description": "Valeurs des paramètres du descripteur, jusqu'à 500 clés. Jamais auditées (seulement les clés).",
      "maxProperties": 500,
      "title": "Config",
      "type": "object"
     },
     "method": {
      "anyOf": [
       {
        "pattern": "^[a-z][a-z0-9_-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Méthode d'installation parmi celles du descripteur ; par défaut la méthode par défaut du descripteur.",
      "title": "Method"
     },
     "mode": {
      "anyOf": [
       {
        "pattern": "^[a-z][a-z0-9_-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mode d'installation parmi ceux du descripteur ; par défaut le mode par défaut du descripteur.",
      "title": "Mode"
     },
     "name": {
      "description": "Nom du service dans le catalogue : une lettre minuscule puis jusqu'à 62 lettres minuscules, chiffres, tirets ou soulignés.",
      "pattern": "^[a-z][a-z0-9_-]{0,62}$",
      "title": "Name",
      "type": "string"
     },
     "placements": {
      "description": "Hôtes de chaque composant : nom du composant vers une liste de 500 identifiants d'hôtes au plus ; jusqu'à 50 composants.",
      "maxProperties": 50,
      "patternProperties": {
       "^[a-z][a-z0-9_-]{0,62}$": {
        "items": {
         "maxLength": 64,
         "minLength": 1,
         "type": "string"
        },
        "maxItems": 500,
        "type": "array"
       }
      },
      "title": "Placements",
      "type": "object"
     },
     "version": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version à installer, 64 caractères au plus ; par défaut la version du catalogue.",
      "title": "Version"
     }
    },
    "required": [
     "name"
    ],
    "title": "InstallServiceIn",
    "type": "object"
   },
   "InstanceIn": {
    "description": "Une instance MCP à démarrer.",
    "properties": {
     "agents": {
      "description": "Agents (identifiants de principaux, 20 au plus) autorisés à appeler l'instance avec son propriétaire.",
      "items": {
       "type": "string"
      },
      "maxItems": 20,
      "title": "Agents",
      "type": "array"
     },
     "allowed_tools": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 100,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Outils de l'instance qui peuvent être appelés (100 au plus). Par défaut `[\"*\"]`.",
      "title": "Allowed Tools"
     },
     "credential_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Secret personnel de l'utilisateur, `openbao://...` ou `env://...` (modèle à identifiants `secret_ref`).",
      "title": "Credential Ref"
     },
     "idle_timeout_seconds": {
      "anyOf": [
       {
        "maximum": 86400.0,
        "minimum": 60.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Temps d'inactivité avant que l'instance ne s'endorme, en secondes (60 à 86400), jamais au-delà de la politique du projet. Par défaut : la politique du projet.",
      "title": "Idle Timeout Seconds"
     },
     "name": {
      "anyOf": [
       {
        "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'instance, unique pour l'appelant dans le projet (même motif). Par défaut : le nom du modèle.",
      "title": "Name"
     },
     "params": {
      "additionalProperties": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "integer"
        },
        {
         "items": {
          "type": "string"
         },
         "type": "array"
        }
       ]
      },
      "description": "Paramètres déclarés par le modèle (texte, entier ou liste de textes), vérifiés selon lui et selon la politique du projet.",
      "title": "Params",
      "type": "object"
     },
     "template": {
      "description": "Modèle du catalogue : minuscules, chiffres et `-`, 1 à 63 caractères.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
      "title": "Template",
      "type": "string"
     }
    },
    "required": [
     "template"
    ],
    "title": "InstanceIn",
    "type": "object"
   },
   "InvokeIn": {
    "description": "Un appel d'outil.",
    "properties": {
     "agent_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent (UUID ou nom) qui fait l'appel ; son manifeste doit accorder l'outil.",
      "title": "Agent Id"
     },
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Demande `tool.invoke` approuvée pour ces arguments exacts (comparés par leur empreinte SHA-256).",
      "title": "Approval Id"
     },
     "arguments": {
      "additionalProperties": true,
      "description": "Arguments, validés selon le `input_schema` de l'outil. Envoyés en corps JSON (chaîne de requête pour les points d'accès GET et DELETE).",
      "title": "Arguments",
      "type": "object"
     },
     "data_classification": {
      "anyOf": [
       {
        "enum": [
         "public",
         "internal",
         "confidential",
         "restricted",
         "secret"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification des données traitées : `public`, `internal`, `confidential`, `restricted` ou `secret`. Jamais inférieure à celle de l'outil (relevée à celle-ci).",
      "title": "Data Classification"
     },
     "purpose": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Finalité de l'appel, transmise à la politique et aux approbateurs (500 caractères au plus).",
      "title": "Purpose"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Run auquel appartient l'appel ; envoyé à un point d'accès HTTP en `X-EAI-Run-Id` et utilisé pour le comptage.",
      "title": "Run Id"
     }
    },
    "title": "InvokeIn",
    "type": "object"
   },
   "IssueIn": {
    "description": "Demande de contexte d'exécution signé.",
    "properties": {
     "audience": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 10,
        "minItems": 1,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Services destinataires du contexte (1 à 10), parmi les audiences acceptées par la plateforme. Par défaut : les audiences de la plateforme.",
      "title": "Audience"
     },
     "delegation_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Délégation portée par le contexte. Ignorée quand `run_id` est fourni.",
      "title": "Delegation Id"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Run d'agent visé par le contexte ; sa délégation et son agent sont inclus.",
      "title": "Run Id"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "maximum": 86400.0,
        "minimum": 30.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie en secondes (30 à 86400 selon le schéma, et jamais au-delà du maximum de la plateforme, 3600 par défaut). Par défaut : 900.",
      "title": "Ttl Seconds"
     }
    },
    "title": "IssueIn",
    "type": "object"
   },
   "JobCompleteIn": {
    "description": "Résultat d'un travail en file, signalé par un worker.",
    "properties": {
     "error": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Message d'erreur d'un travail en échec.",
      "title": "Error"
     },
     "output": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sortie de l'opération (transcription, références d'images...).",
      "title": "Output"
     },
     "status": {
      "description": "`running`, `succeeded`, `failed` ou `cancelled`.",
      "title": "Status",
      "type": "string"
     },
     "units": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Unités d'usage mesurées, par exemple `{\"audio_seconds\": 12.5}`.",
      "title": "Units"
     }
    },
    "required": [
     "status"
    ],
    "title": "JobCompleteIn",
    "type": "object"
   },
   "KbIn": {
    "description": "Une base de connaissances à créer.",
    "properties": {
     "acl": {
      "$ref": "#/components/schemas/Acl",
      "description": "Lecteurs et rédacteurs. Par défaut : tout principal lit, les rôles par défaut écrivent."
     },
     "chunk_overlap": {
      "default": 40,
      "description": "Recouvrement entre deux fragments, en mots (0 ou plus, inférieur à `chunk_size`, 40 par défaut).",
      "minimum": 0.0,
      "title": "Chunk Overlap",
      "type": "integer"
     },
     "chunk_size": {
      "default": 200,
      "description": "Taille d'un fragment, en mots (20 à 4000, 200 par défaut).",
      "maximum": 4000.0,
      "minimum": 20.0,
      "title": "Chunk Size",
      "type": "integer"
     },
     "chunking": {
      "default": "words",
      "description": "`words` (par défaut : fragments de `chunk_size` mots avec `chunk_overlap`, chacun situé dans le texte avec sa page, sa section et son type d'élément, une coupure dans un tableau marquée `table_split`) ou `structure` (les fragments suivent titres, paragraphes, listes, tableaux et code ; un tableau reste entier jusqu'à 3 tailles de fragment, un plus long est découpé entre les lignes avec sa ligne d'en-tête répétée).",
      "enum": [
       "words",
       "structure"
      ],
      "title": "Chunking",
      "type": "string"
     },
     "classification": {
      "default": "internal",
      "description": "Classification par défaut des documents : `public`, `internal` (par défaut), `confidential`, `restricted` ou `secret`.",
      "title": "Classification",
      "type": "string"
     },
     "description": {
      "default": "",
      "description": "Description libre. Vide par défaut.",
      "title": "Description",
      "type": "string"
     },
     "dimensions": {
      "anyOf": [
       {
        "maximum": 16384.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Taille des vecteurs, l'une des tailles déclarées par le modèle (1 à 16384 ; par défaut : sa taille native).",
      "title": "Dimensions"
     },
     "embedding_model": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle du registre avec la capacité embeddings (par défaut : le modèle d'embedding RAG de la plateforme).",
      "title": "Embedding Model"
     },
     "name": {
      "description": "Nom, unique dans le tenant (1 à 120 caractères).",
      "maxLength": 120,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "retrieval": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/RetrievalIn"
       },
       {
        "type": "null"
       }
      ],
      "description": "Stratégie de recherche de la base (voir RetrievalIn). Par défaut : chaque champ à sa valeur par défaut."
     }
    },
    "required": [
     "name"
    ],
    "title": "KbIn",
    "type": "object"
   },
   "KeyPatch": {
    "additionalProperties": false,
    "description": "Modifications d'une clé de projet ; chaque champ est facultatif. Les champs inconnus sont refusés.",
    "properties": {
     "budget_period": {
      "anyOf": [
       {
        "enum": [
         "daily",
         "weekly",
         "monthly",
         "total"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Période après laquelle la dépense de la clé repart à zéro : `daily`, `weekly`, `monthly` ou `total` (jamais remise à zéro).",
      "title": "Budget Period"
     },
     "max_budget": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Dépense maximale de la clé par période de budget, décimale, au moins 0 et jamais au-dessus du budget du projet.",
      "title": "Max Budget"
     },
     "max_parallel_requests": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Requêtes que la clé peut exécuter en même temps, au moins 0, jamais au-dessus de la limite du projet.",
      "title": "Max Parallel Requests"
     },
     "models": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 200,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiants des modèles que la clé peut appeler, jusqu'à 200 ; chacun doit être autorisé par le projet si le projet restreint ses modèles.",
      "title": "Models"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 128,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom affiché de la clé, 128 caractères au plus.",
      "title": "Name"
     },
     "rpm_limit": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Requêtes par minute autorisées pour la clé, au moins 0, jamais au-dessus de la limite du projet.",
      "title": "Rpm Limit"
     },
     "tpm_limit": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons par minute autorisés pour la clé, au moins 0, jamais au-dessus de la limite du projet.",
      "title": "Tpm Limit"
     }
    },
    "title": "KeyPatch",
    "type": "object"
   },
   "LeaseIn": {
    "description": "Une demande de bail d'identifiants.",
    "properties": {
     "connector": {
      "description": "Nom du connecteur.",
      "title": "Connector",
      "type": "string"
     },
     "on_behalf_of": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur dont le droit propre est appliqué quand la cible n'accepte qu'un identifiant technique (1 à 200 caractères).",
      "title": "On Behalf Of"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Run pour lequel le bail est émis, enregistré avec lui.",
      "title": "Run Id"
     },
     "scopes": {
      "description": "Scopes du bail, parmi ceux déclarés par le connecteur. Vide (par défaut) : tous.",
      "items": {
       "type": "string"
      },
      "title": "Scopes",
      "type": "array"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "maximum": 3600.0,
        "minimum": 30.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie du bail en secondes (30 à 3600, 600 par défaut).",
      "title": "Ttl Seconds"
     }
    },
    "required": [
     "connector"
    ],
    "title": "LeaseIn",
    "type": "object"
   },
   "LegalHoldIn": {
    "description": "Nouveau gel juridique, ou modification d'un gel existant.",
    "properties": {
     "active": {
      "default": true,
      "description": "true (par défaut) pour geler, false pour lever le gel désigné par `hold_id`.",
      "title": "Active",
      "type": "boolean"
     },
     "hold_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant d'un gel existant à activer ou lever. Omettez-le pour créer un gel.",
      "title": "Hold Id"
     },
     "reason": {
      "default": "",
      "description": "Raison du gel, par exemple un numéro de dossier. Obligatoire pour un nouveau gel.",
      "title": "Reason",
      "type": "string"
     },
     "resource_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de la ressource gelée.",
      "title": "Resource Id"
     },
     "resource_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de la ressource gelée, par exemple `memory`, `media`, `sessions` ou `documents`. Va avec `resource_id`.",
      "title": "Resource Type"
     },
     "subject_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Personne dont toutes les ressources sont gelées.",
      "title": "Subject Id"
     }
    },
    "title": "LegalHoldIn",
    "type": "object"
   },
   "LoginIn": {
    "additionalProperties": false,
    "description": "Identifiants d'un compte local.",
    "properties": {
     "password": {
      "description": "Mot de passe, 1 à 256 caractères. En écriture seule : jamais renvoyé ni journalisé.",
      "maxLength": 256,
      "minLength": 1,
      "title": "Password",
      "type": "string"
     },
     "username": {
      "description": "Identifiant du compte, 1 à 128 caractères, comparé sans tenir compte de la casse.",
      "maxLength": 128,
      "minLength": 1,
      "title": "Username",
      "type": "string"
     }
    },
    "required": [
     "username",
     "password"
    ],
    "title": "LoginIn",
    "type": "object"
   },
   "MandateIn": {
    "description": "Nouveau mandat d'un agent.",
    "properties": {
     "agent": {
      "description": "Nom de l'agent, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Agent",
      "type": "string"
     },
     "audience": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 20,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Audiences, 20 motifs au plus. `eai-control-plane` par défaut.",
      "title": "Audience"
     },
     "data_classification_max": {
      "anyOf": [
       {
        "pattern": "^(public|internal|confidential|restricted|secret)$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Plafond de classification : `public`, `internal`, `confidential`, `restricted` ou `secret`. Par défaut : l'habilitation de l'identité de service.",
      "title": "Data Classification Max"
     },
     "max_depth": {
      "default": 0,
      "description": "Profondeur de sous-délégation autorisée, de 0 à 10 (0 par défaut).",
      "maximum": 10.0,
      "minimum": 0.0,
      "title": "Max Depth",
      "type": "integer"
     },
     "purpose": {
      "description": "Finalité du mandat, 3 à 2000 caractères, montrée aux valideurs.",
      "maxLength": 2000,
      "minLength": 3,
      "title": "Purpose",
      "type": "string"
     },
     "resources": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 100,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Restrictions de ressources, 100 motifs au plus. `*` par défaut.",
      "title": "Resources"
     },
     "schedule_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Planification unique de l'agent couverte par le mandat ; null pour toutes ses planifications.",
      "title": "Schedule Id"
     },
     "scopes": {
      "description": "Portées du mandat, 100 au plus, couvertes par celles de l'identité de service.",
      "items": {
       "type": "string"
      },
      "maxItems": 100,
      "title": "Scopes",
      "type": "array"
     },
     "service_identity_id": {
      "description": "Identité de service sous laquelle les exécutions agissent ; elle doit être active.",
      "title": "Service Identity Id",
      "type": "string"
     },
     "ttl_days": {
      "default": 30,
      "description": "Durée de vie en jours, de 1 au maximum de la plateforme (365 par défaut) ; 30 par défaut.",
      "maximum": 3650.0,
      "minimum": 1.0,
      "title": "Ttl Days",
      "type": "integer"
     }
    },
    "required": [
     "agent",
     "service_identity_id",
     "purpose"
    ],
    "title": "MandateIn",
    "type": "object"
   },
   "McpAuth": {
    "description": "Façon dont la passerelle s'authentifie auprès du serveur MCP amont.",
    "properties": {
     "connector": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Connecteur dont les identifiants sont utilisés, pour `connector`.",
      "title": "Connector"
     },
     "credential_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de secret, `openbao://...` ou `env://...` ; requise pour `bearer`, `api_key` et `query_param`. Résolue au moment de l'appel, jamais renvoyée.",
      "title": "Credential Ref"
     },
     "header": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "En-tête portant la clé pour `api_key`. `X-API-Key` par défaut.",
      "title": "Header"
     },
     "param": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9_.-]{1,64}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Paramètre de requête portant le secret pour `query_param` (1 à 64 lettres, chiffres, `_`, `.`, `-`).",
      "title": "Param"
     },
     "type": {
      "default": "none",
      "description": "`none` (par défaut), `bearer` (en-tête Authorization), `api_key` (en-tête personnalisé), `query_param` (secret dans un paramètre de requête) ou `connector` (identifiants d'un connecteur).",
      "enum": [
       "none",
       "bearer",
       "api_key",
       "query_param",
       "connector"
      ],
      "title": "Type",
      "type": "string"
     }
    },
    "title": "McpAuth",
    "type": "object"
   },
   "McpInstallIn": {
    "description": "Installation d'un serveur MCP à partir d'un preset Navin.",
    "properties": {
     "allowed_tools": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Outils du serveur qui peuvent être appelés. Omis : tous les outils.",
      "title": "Allowed Tools"
     },
     "credential_refs": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Champs secrets du preset sous forme de références `openbao://` ou `env://`, par nom de champ.",
      "title": "Credential Refs",
      "type": "object"
     },
     "name": {
      "anyOf": [
       {
        "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom du serveur (par défaut `<preset>-<slug du projet>`) ; minuscules, chiffres, `.`, `_`, `-`.",
      "title": "Name"
     },
     "transport": {
      "anyOf": [
       {
        "enum": [
         "streamable_http",
         "sse"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`streamable_http` ou `sse`. Par défaut : le transport du preset.",
      "title": "Transport"
     },
     "url": {
      "anyOf": [
       {
        "maxLength": 2000,
        "minLength": 8,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Point d'accès HTTP (8 à 2000 caractères) ; requis pour un preset stdio exécuté par le Navin Manager. Par défaut : l'URL du preset pour un preset HTTP.",
      "title": "Url"
     },
     "values": {
      "additionalProperties": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "integer"
        },
        {
         "type": "boolean"
        }
       ]
      },
      "description": "Champs non secrets du preset (texte, entier ou booléen), par nom de champ.",
      "title": "Values",
      "type": "object"
     }
    },
    "title": "McpInstallIn",
    "type": "object"
   },
   "MemberIn": {
    "description": "Nouvelle appartenance d'un utilisateur au tenant ou à un projet.",
    "properties": {
     "clearance": {
      "default": "internal",
      "description": "Classification de données la plus haute que le membre peut lire : `public`, `internal` (par défaut), `confidential`, `restricted` ou `secret`.",
      "enum": [
       "public",
       "internal",
       "confidential",
       "restricted",
       "secret"
      ],
      "title": "Clearance",
      "type": "string"
     },
     "display_name": {
      "default": "",
      "description": "Nom affiché dans le portail (vide par défaut).",
      "title": "Display Name",
      "type": "string"
     },
     "email": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse e-mail de l'utilisateur, facultative.",
      "title": "Email"
     },
     "project_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Projet auquel l'appartenance est limitée ; null pour tout le tenant.",
      "title": "Project Id"
     },
     "role": {
      "description": "Rôle donné : `platform_admin`, `tenant_admin`, `security_officer`, `finops`, `developer`, `auditor`, `agent`, `viewer`, `soc_analyst`, `ai_governance_officer`, `data_steward`, `application_owner`, `ai_platform_engineer`, `business_owner` or `support_engineer`.",
      "enum": [
       "platform_admin",
       "tenant_admin",
       "security_officer",
       "finops",
       "developer",
       "auditor",
       "agent",
       "viewer",
       "soc_analyst",
       "ai_governance_officer",
       "data_steward",
       "application_owner",
       "ai_platform_engineer",
       "business_owner",
       "support_engineer"
      ],
      "title": "Role",
      "type": "string"
     },
     "user": {
      "description": "Sujet de l'utilisateur : la revendication OIDC `sub`, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "User",
      "type": "string"
     }
    },
    "required": [
     "user",
     "role"
    ],
    "title": "MemberIn",
    "type": "object"
   },
   "MemberPatch": {
    "description": "Modifications d'une appartenance.",
    "properties": {
     "clearance": {
      "anyOf": [
       {
        "enum": [
         "public",
         "internal",
         "confidential",
         "restricted",
         "secret"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle habilitation : `public`, `internal`, `confidential`, `restricted` ou `secret`.",
      "title": "Clearance"
     },
     "role": {
      "anyOf": [
       {
        "enum": [
         "platform_admin",
         "tenant_admin",
         "security_officer",
         "finops",
         "developer",
         "auditor",
         "agent",
         "viewer",
         "soc_analyst",
         "ai_governance_officer",
         "data_steward",
         "application_owner",
         "ai_platform_engineer",
         "business_owner",
         "support_engineer"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau rôle, mêmes valeurs qu'à la création.",
      "title": "Role"
     }
    },
    "title": "MemberPatch",
    "type": "object"
   },
   "MemoryAcl": {
    "description": "Liste d'accès d'un élément de mémoire. Les entrées sont des identités : `user:<id>`, `agent:<id>`, `service:<id>`, `role:<rôle>`, `group:<groupe>`, `*` ou un identifiant de principal seul.",
    "properties": {
     "owners": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaires, autorisés aussi à modifier et supprimer. Par défaut pour un nouvel élément : l'appelant ; non modifiés par une mise à jour.",
      "title": "Owners"
     },
     "readers": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identités autorisées à lire. Par défaut pour un nouvel élément : l'appelant.",
      "title": "Readers"
     },
     "writers": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identités autorisées à modifier et supprimer. Par défaut pour un nouvel élément : l'appelant.",
      "title": "Writers"
     }
    },
    "title": "MemoryAcl",
    "type": "object"
   },
   "MemoryEngineCredentialIn": {
    "description": "Identifiant de connexion du tenant sur une instance de moteur partagée.",
    "properties": {
     "credential_ref": {
      "description": "Référence `openbao://<chemin>` vers un secret du tenant.",
      "maxLength": 210,
      "pattern": "^openbao://.+$",
      "title": "Credential Ref",
      "type": "string"
     }
    },
    "required": [
     "credential_ref"
    ],
    "title": "MemoryEngineCredentialIn",
    "type": "object"
   },
   "MemoryEngineIn": {
    "description": "Une instance de moteur installée et exploitée par le Navin Manager.",
    "properties": {
     "access_control": {
      "default": false,
      "description": "Cognee seulement : l'instance tourne avec le contrôle d'accès côté moteur (un utilisateur par tenant donne une séparation `backend`).",
      "title": "Access Control",
      "type": "boolean"
     },
     "base_url": {
      "description": "Adresse de l'API rendue par le Navin Manager (https en recette et en production).",
      "maxLength": 500,
      "pattern": "^https?://[^/@\\s]+(/[^\\s]*)?$",
      "title": "Base Url",
      "type": "string"
     },
     "credential_ref": {
      "anyOf": [
       {
        "maxLength": 210,
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence vers la clé d'API : `openbao://<chemin>` ou `env://EAI_CRED_<NOM>`.",
      "title": "Credential Ref"
     },
     "id": {
      "description": "Identifiant (minuscules, chiffres et tirets, 2 à 63 caractères).",
      "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
      "title": "Id",
      "type": "string"
     },
     "manager_cluster": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cluster du Navin Manager qui exploite l'instance (information).",
      "title": "Manager Cluster"
     },
     "manager_service": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Service du catalogue du Navin Manager (information).",
      "title": "Manager Service"
     },
     "provider": {
      "description": "`mem0` ou `cognee`.",
      "enum": [
       "mem0",
       "cognee"
      ],
      "title": "Provider",
      "type": "string"
     },
     "tenant_id": {
      "anyOf": [
       {
        "maxLength": 36,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Tenant auquel l'instance est dédiée ; vide : instance partagée.",
      "title": "Tenant Id"
     },
     "timeout_seconds": {
      "default": 10.0,
      "description": "Délai d'un appel en secondes (10 par défaut, au plus 300).",
      "exclusiveMinimum": 0.0,
      "maximum": 300.0,
      "title": "Timeout Seconds",
      "type": "number"
     },
     "tls_verify": {
      "default": true,
      "description": "Vérifier le certificat du moteur (vrai par défaut ; l'autorité du Manager de EAI_MEMORY_ENGINES_CA_FILE est reconnue).",
      "title": "Tls Verify",
      "type": "boolean"
     }
    },
    "required": [
     "id",
     "provider",
     "base_url"
    ],
    "title": "MemoryEngineIn",
    "type": "object"
   },
   "MemoryExportIn": {
    "description": "Export des mémoires d'un agent.",
    "properties": {
     "scopes": {
      "anyOf": [
       {
        "items": {
         "enum": [
          "session",
          "user",
          "agent",
          "shared_agents",
          "project",
          "team",
          "organization",
          "global"
         ],
         "type": "string"
        },
        "maxItems": 8,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Niveaux d'isolation à exporter (par défaut : toutes les portées lisibles).",
      "title": "Scopes"
     }
    },
    "title": "MemoryExportIn",
    "type": "object"
   },
   "MemoryGraphBuildIn": {
    "description": "Jeux de données dont le graphe est construit.",
    "properties": {
     "dataset_ids": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 50,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiants d'index Navin des jeux de données (par défaut : tous les jeux lisibles).",
      "title": "Dataset Ids"
     }
    },
    "title": "MemoryGraphBuildIn",
    "type": "object"
   },
   "MemoryGraphSearchIn": {
    "description": "Une recherche dans le graphe de connaissances.",
    "properties": {
     "dataset_ids": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 50,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiants d'index Navin des jeux de données (par défaut : tous les jeux lisibles).",
      "title": "Dataset Ids"
     },
     "query": {
      "description": "Texte de la requête (1 à 4000 caractères).",
      "maxLength": 4000,
      "minLength": 1,
      "title": "Query",
      "type": "string"
     },
     "search_type": {
      "default": "CHUNKS",
      "description": "Type de recherche Cognee : `CHUNKS` (par défaut), `CHUNKS_LEXICAL`, `SUMMARIES`, `RAG_COMPLETION` ou `GRAPH_COMPLETION`.",
      "enum": [
       "CHUNKS",
       "CHUNKS_LEXICAL",
       "SUMMARIES",
       "RAG_COMPLETION",
       "GRAPH_COMPLETION"
      ],
      "title": "Search Type",
      "type": "string"
     },
     "top_k": {
      "default": 10,
      "description": "Nombre de résultats (1 à 50, 10 par défaut).",
      "maximum": 50.0,
      "minimum": 1.0,
      "title": "Top K",
      "type": "integer"
     }
    },
    "required": [
     "query"
    ],
    "title": "MemoryGraphSearchIn",
    "type": "object"
   },
   "MemoryIn": {
    "description": "Un élément de mémoire à créer ou mettre à jour. Fournissez `content` ou `content_ref`.",
    "properties": {
     "acl": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/MemoryAcl"
       },
       {
        "type": "null"
       }
      ],
      "description": "Lecteurs, rédacteurs et propriétaires. Lors d'une mise à jour, les listes omises sont conservées."
     },
     "classification": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`public`, `internal` (par défaut), `confidential`, `restricted` ou `secret` ; relevée si des données personnelles sont détectées, et jamais au-dessus de l'habilitation de l'appelant.",
      "title": "Classification"
     },
     "content": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Texte à mémoriser ; vectorisé pour la recherche vectorielle.",
      "title": "Content"
     },
     "content_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence vers un contenu stocké ailleurs (non recherchable).",
      "title": "Content Ref"
     },
     "deletable": {
      "default": true,
      "description": "Si faux, seul `tenant_admin` peut supprimer l'élément. Vrai par défaut.",
      "title": "Deletable",
      "type": "boolean"
     },
     "kind": {
      "default": "fact",
      "description": "`episodic`, `semantic`, `procedural`, `preference` ou `fact` (par défaut).",
      "title": "Kind",
      "type": "string"
     },
     "legal_hold": {
      "default": false,
      "description": "Si vrai, l'élément ne peut pas être supprimé. Une fois posé, une mise à jour ne peut pas le lever. Faux par défaut.",
      "title": "Legal Hold",
      "type": "boolean"
     },
     "memory_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Élément existant à mettre à jour (nouvelle version). Omettez-le pour créer un élément.",
      "title": "Memory Id"
     },
     "scope": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/Scope"
       },
       {
        "type": "null"
       }
      ],
      "description": "Portée d'un nouvel élément. Par défaut : `{type: user, id: <appelant>}`."
     },
     "source": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/Provenance"
       },
       {
        "type": "null"
       }
      ],
      "description": "Provenance du contenu."
     },
     "subject_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Personne concernée par l'élément (utilisée par les demandes de confidentialité). Par défaut : l'identifiant de portée pour une portée `user`, sinon l'appelant.",
      "title": "Subject Id"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie en secondes (0 ou plus) ; ensuite l'élément n'est plus renvoyé. Omise : pas d'expiration.",
      "title": "Ttl Seconds"
     }
    },
    "title": "MemoryIn",
    "type": "object"
   },
   "MemoryKnowledgeIn": {
    "description": "Textes à ingérer dans le graphe de connaissances (Cognee).",
    "properties": {
     "build_graph": {
      "default": false,
      "description": "Construire le graphe juste après l'ingestion (cognify de Cognee). Faux par défaut.",
      "title": "Build Graph",
      "type": "boolean"
     },
     "classification": {
      "anyOf": [
       {
        "enum": [
         "public",
         "internal",
         "confidential",
         "restricted"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification déclarée des textes.",
      "title": "Classification"
     },
     "dataset_id": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jeu de données existant de la même portée (identifiant d'index Navin) ; par défaut : le jeu de données de la portée, créé s'il manque.",
      "title": "Dataset Id"
     },
     "legal_hold": {
      "default": false,
      "description": "Placer le jeu de données sous conservation légale. Faux par défaut.",
      "title": "Legal Hold",
      "type": "boolean"
     },
     "retention_days": {
      "anyOf": [
       {
        "maximum": 3650.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Conservation en jours, dans la limite de la politique.",
      "title": "Retention Days"
     },
     "scope": {
      "default": "agent",
      "description": "Niveau d'isolation du jeu de données : `agent` (par défaut), `shared_agents`, `project`, `team`, `organization` ou `global`.",
      "enum": [
       "agent",
       "shared_agents",
       "project",
       "team",
       "organization",
       "global"
      ],
      "title": "Scope",
      "type": "string"
     },
     "source": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/MemoryKnowledgeSource"
       },
       {
        "type": "null"
       }
      ],
      "description": "Source des textes, par exemple un document d'une base de connaissances : elle choisit, ou crée, le jeu de données de cette source dans la portée (ignorée quand `dataset_id` est fourni)."
     },
     "team": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Équipe de la portée `team`.",
      "title": "Team"
     },
     "texts": {
      "description": "Un à 50 textes (chacun jusqu'à 100000 caractères) ; les secrets sont refusés.",
      "items": {
       "type": "string"
      },
      "maxItems": 50,
      "minItems": 1,
      "title": "Texts",
      "type": "array"
     }
    },
    "required": [
     "texts"
    ],
    "title": "MemoryKnowledgeIn",
    "type": "object"
   },
   "MemoryKnowledgeSource": {
    "description": "Source d'un jeu de données de connaissances ; `source_type` et `source_id` sont obligatoires quand une source est fournie.",
    "properties": {
     "source_id": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de la source dans son système, clé de son jeu de données dans la portée.",
      "title": "Source Id"
     },
     "source_type": {
      "anyOf": [
       {
        "maxLength": 64,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de source, par exemple `rag.document`.",
      "title": "Source Type"
     },
     "uri": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse de la source.",
      "title": "Uri"
     },
     "version": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de la source, conservée avec le jeu de données.",
      "title": "Version"
     }
    },
    "title": "MemoryKnowledgeSource",
    "type": "object"
   },
   "MemoryModes": {
    "description": "Modes de mémoire activés pour l'agent.",
    "properties": {
     "agent": {
      "default": true,
      "description": "Mémoire privée à l'agent (portée `agent`). Vrai par défaut.",
      "title": "Agent",
      "type": "boolean"
     },
     "knowledge_graph": {
      "default": false,
      "description": "Graphe de connaissances via Cognee (ingestion, graphe, relations). Faux par défaut.",
      "title": "Knowledge Graph",
      "type": "boolean"
     },
     "long_term": {
      "default": true,
      "description": "Mémoire à long terme d'un utilisateur avec cet agent (portée `user`). Vrai par défaut.",
      "title": "Long Term",
      "type": "boolean"
     },
     "session": {
      "default": true,
      "description": "Mémoire de session (portée `session`, magasin builtin seulement). Vrai par défaut.",
      "title": "Session",
      "type": "boolean"
     },
     "shared": {
      "default": false,
      "description": "Mémoires partagées (portées `shared_agents`, `project`, `team`, `organization`, `global`). Faux par défaut.",
      "title": "Shared",
      "type": "boolean"
     }
    },
    "title": "MemoryModes",
    "type": "object"
   },
   "MemoryPolicy": {
    "description": "Politique de mémorisation et d'accès d'un agent. La mémorisation est pilotée par cette politique : rien n'est écrit dans un moteur sans un appel explicite qu'elle autorise.",
    "properties": {
     "allow_personal_data": {
      "default": true,
      "description": "Si faux, un contenu avec des données personnelles est refusé. Vrai par défaut (la classification est relevée).",
      "title": "Allow Personal Data",
      "type": "boolean"
     },
     "allowed_kinds": {
      "description": "Types qui peuvent être mémorisés (par défaut les cinq).",
      "items": {
       "enum": [
        "episodic",
        "semantic",
        "procedural",
        "preference",
        "fact"
       ],
       "type": "string"
      },
      "title": "Allowed Kinds",
      "type": "array"
     },
     "default_scope": {
      "default": "user",
      "description": "Portée d'une mémoire écrite sans `scope` (par défaut `user`) ; doit être une portée d'écriture.",
      "enum": [
       "session",
       "user",
       "agent",
       "shared_agents",
       "project",
       "team",
       "organization",
       "global"
      ],
      "title": "Default Scope",
      "type": "string"
     },
     "global_access": {
      "default": false,
      "description": "Autorise la portée `global` (tout le tenant) ; posé par `tenant_admin` seulement. Faux par défaut.",
      "title": "Global Access",
      "type": "boolean"
     },
     "infer": {
      "default": false,
      "description": "Laisser Mem0 extraire des faits avec son LLM (`infer=true`) ; faux par défaut : le texte est stocké tel quel.",
      "title": "Infer",
      "type": "boolean"
     },
     "legal_hold": {
      "default": false,
      "description": "Chaque nouvelle mémoire de l'agent est sous conservation légale. Faux par défaut.",
      "title": "Legal Hold",
      "type": "boolean"
     },
     "max_classification": {
      "default": "confidential",
      "description": "Classification la plus haute que l'agent peut mémoriser et lire : `public`, `internal`, `confidential` (par défaut) ou `restricted` ; jamais au-dessus de l'habilitation de l'appelant.",
      "enum": [
       "public",
       "internal",
       "confidential",
       "restricted"
      ],
      "title": "Max Classification",
      "type": "string"
     },
     "modes": {
      "$ref": "#/components/schemas/MemoryModes",
      "description": "Modes de mémoire activés."
     },
     "read_scopes": {
      "description": "Niveaux d'isolation que l'agent peut lire (par défaut `session`, `user`, `agent`).",
      "items": {
       "enum": [
        "session",
        "user",
        "agent",
        "shared_agents",
        "project",
        "team",
        "organization",
        "global"
       ],
       "type": "string"
      },
      "title": "Read Scopes",
      "type": "array"
     },
     "recall_top_k": {
      "default": 8,
      "description": "Nombre de mémoires rappelées par défaut (1 à 50, 8 par défaut).",
      "maximum": 50.0,
      "minimum": 1.0,
      "title": "Recall Top K",
      "type": "integer"
     },
     "required_isolation": {
      "default": "logical",
      "description": "Séparation que le moteur doit garantir : `logical` (par défaut, identifiants et espace de noms Navin), `backend` (contrôle d'accès du moteur) ou `dedicated_instance` (une instance installée pour ce tenant par le Navin Manager).",
      "enum": [
       "logical",
       "backend",
       "dedicated_instance"
      ],
      "title": "Required Isolation",
      "type": "string"
     },
     "retention_days": {
      "anyOf": [
       {
        "maximum": 3650.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limite de conservation en jours (1 à 3650) ; appliquée par défaut et envoyée à Mem0 comme `expiration_date`. Vide : pas de limite.",
      "title": "Retention Days"
     },
     "shared_with": {
      "description": "Agents autorisés à lire les mémoires `shared_agents` de cet agent (relation OpenFGA `memory_reader`).",
      "items": {
       "type": "string"
      },
      "maxItems": 50,
      "title": "Shared With",
      "type": "array"
     },
     "teams": {
      "description": "Équipes (groupes OpenFGA) de la portée `team` ; un lecteur doit en être membre.",
      "items": {
       "type": "string"
      },
      "maxItems": 20,
      "title": "Teams",
      "type": "array"
     },
     "write_scopes": {
      "description": "Niveaux d'isolation dans lesquels l'agent peut écrire (par défaut `session`, `user`, `agent`).",
      "items": {
       "enum": [
        "session",
        "user",
        "agent",
        "shared_agents",
        "project",
        "team",
        "organization",
        "global"
       ],
       "type": "string"
      },
      "title": "Write Scopes",
      "type": "array"
     }
    },
    "title": "MemoryPolicy",
    "type": "object"
   },
   "MemoryPolicyRestoreIn": {
    "description": "Une version de la politique de mémoire à restaurer.",
    "properties": {
     "version": {
      "description": "Numéro de version (1 ou plus).",
      "minimum": 1.0,
      "title": "Version",
      "type": "integer"
     }
    },
    "required": [
     "version"
    ],
    "title": "MemoryPolicyRestoreIn",
    "type": "object"
   },
   "MemoryPurgeIn": {
    "description": "Purge d'une portée.",
    "properties": {
     "confirm": {
      "default": false,
      "description": "Doit valoir vrai.",
      "title": "Confirm",
      "type": "boolean"
     },
     "scope": {
      "description": "Niveau d'isolation à purger.",
      "enum": [
       "session",
       "user",
       "agent",
       "shared_agents",
       "project",
       "team",
       "organization",
       "global"
      ],
      "title": "Scope",
      "type": "string"
     },
     "session_id": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Session de la portée `session`.",
      "title": "Session Id"
     },
     "team": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Équipe de la portée `team`.",
      "title": "Team"
     }
    },
    "required": [
     "scope"
    ],
    "title": "MemoryPurgeIn",
    "type": "object"
   },
   "MemoryRecallIn": {
    "description": "Un rappel ou une recherche dans les mémoires d'un agent.",
    "properties": {
     "query": {
      "description": "Texte de la requête (1 à 4000 caractères).",
      "maxLength": 4000,
      "minLength": 1,
      "title": "Query",
      "type": "string"
     },
     "scopes": {
      "anyOf": [
       {
        "items": {
         "enum": [
          "session",
          "user",
          "agent",
          "shared_agents",
          "project",
          "team",
          "organization",
          "global"
         ],
         "type": "string"
        },
        "maxItems": 8,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Niveaux d'isolation à interroger (par défaut : les portées lisibles de la politique).",
      "title": "Scopes"
     },
     "session_id": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Session de l'appelant, pour inclure sa mémoire de session.",
      "title": "Session Id"
     },
     "top_k": {
      "anyOf": [
       {
        "maximum": 50.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre de résultats (1 à 50, par défaut `recall_top_k` de la politique).",
      "title": "Top K"
     }
    },
    "required": [
     "query"
    ],
    "title": "MemoryRecallIn",
    "type": "object"
   },
   "MemoryRememberIn": {
    "description": "Une mémoire à retenir via le moteur de l'agent.",
    "properties": {
     "classification": {
      "anyOf": [
       {
        "enum": [
         "public",
         "internal",
         "confidential",
         "restricted"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification déclarée ; relevée par la détection, refusée au-dessus de la politique ou de l'habilitation.",
      "title": "Classification"
     },
     "content": {
      "description": "Texte à mémoriser (1 à 20000 caractères) ; les secrets sont refusés.",
      "maxLength": 20000,
      "minLength": 1,
      "title": "Content",
      "type": "string"
     },
     "kind": {
      "default": "fact",
      "description": "`episodic`, `semantic`, `procedural`, `preference` ou `fact` (par défaut).",
      "enum": [
       "episodic",
       "semantic",
       "procedural",
       "preference",
       "fact"
      ],
      "title": "Kind",
      "type": "string"
     },
     "legal_hold": {
      "default": false,
      "description": "Placer la mémoire sous conservation légale. Faux par défaut.",
      "title": "Legal Hold",
      "type": "boolean"
     },
     "retention_days": {
      "anyOf": [
       {
        "maximum": 3650.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Conservation en jours, dans la limite de la politique.",
      "title": "Retention Days"
     },
     "scope": {
      "anyOf": [
       {
        "enum": [
         "session",
         "user",
         "agent",
         "shared_agents",
         "project",
         "team",
         "organization",
         "global"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Niveau d'isolation : `session`, `user`, `agent`, `shared_agents`, `project`, `team`, `organization` ou `global`. Par défaut : `default_scope` de la politique.",
      "title": "Scope"
     },
     "session_id": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Session de l'appelant (requise pour la portée `session` ; vérifiée côté serveur).",
      "title": "Session Id"
     },
     "source": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/MemorySource"
       },
       {
        "type": "null"
       }
      ],
      "description": "Provenance du contenu."
     },
     "team": {
      "anyOf": [
       {
        "maxLength": 63,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Équipe de la politique pour la portée `team` (par défaut : sa première équipe).",
      "title": "Team"
     }
    },
    "required": [
     "content"
    ],
    "title": "MemoryRememberIn",
    "type": "object"
   },
   "MemorySource": {
    "description": "Provenance d'un contenu mémorisé.",
    "properties": {
     "source_id": {
      "description": "Identifiant de l'objet source.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Source Id",
      "type": "string"
     },
     "source_type": {
      "description": "Type de source, par exemple `conversation`, `rag` ou `tool`.",
      "maxLength": 64,
      "minLength": 1,
      "title": "Source Type",
      "type": "string"
     },
     "uri": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URI de la source.",
      "title": "Uri"
     },
     "version": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de la source.",
      "title": "Version"
     }
    },
    "required": [
     "source_type",
     "source_id"
    ],
    "title": "MemorySource",
    "type": "object"
   },
   "MemoryUpdateIn": {
    "description": "Nouveau contenu d'une mémoire.",
    "properties": {
     "content": {
      "description": "Texte (1 à 20000 caractères) ; les secrets sont refusés.",
      "maxLength": 20000,
      "minLength": 1,
      "title": "Content",
      "type": "string"
     }
    },
    "required": [
     "content"
    ],
    "title": "MemoryUpdateIn",
    "type": "object"
   },
   "MessageIn": {
    "description": "Un message à ajouter à une session.",
    "properties": {
     "content": {
      "description": "Texte du message (1 à 200000 caractères).",
      "maxLength": 200000,
      "minLength": 1,
      "title": "Content",
      "type": "string"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Paires clé-valeur libres enregistrées avec l'objet et renvoyées telles quelles ; la plateforme ne les interprète jamais.",
      "title": "Metadata",
      "type": "object"
     },
     "role": {
      "default": "user",
      "description": "Rôle de l'auteur : `user` (par défaut), `assistant`, `system` ou `tool`.",
      "enum": [
       "user",
       "assistant",
       "system",
       "tool"
      ],
      "title": "Role",
      "type": "string"
     }
    },
    "required": [
     "content"
    ],
    "title": "MessageIn",
    "type": "object"
   },
   "Metric": {
    "description": "Métrique d'une suite et son seuil.",
    "properties": {
     "comparator": {
      "default": "gte",
      "description": "`gte` (par défaut) : le score doit être au moins égal au seuil ; `lte` : au plus égal.",
      "title": "Comparator",
      "type": "string"
     },
     "name": {
      "description": "Nom de la métrique. Pour le moteur `custom` : `exact_match`, `contains`, `not_contains`, `json_valid`, `faithfulness`, `latency` ou `refusal` ; les autres moteurs utilisent leurs propres noms.",
      "title": "Name",
      "type": "string"
     },
     "threshold": {
      "description": "Valeur à laquelle le score est comparé.",
      "title": "Threshold",
      "type": "number"
     },
     "weight": {
      "anyOf": [
       {
        "type": "number"
       },
       {
        "type": "null"
       }
      ],
      "description": "Poids relatif de la métrique, facultatif.",
      "title": "Weight"
     }
    },
    "required": [
     "name",
     "threshold"
    ],
    "title": "Metric",
    "type": "object"
   },
   "ModeIn": {
    "description": "Mode d'exploitation à appliquer à un service.",
    "properties": {
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de l'approbation du Manager qui autorise le mode quand le Manager en exige une.",
      "title": "Approval Id"
     },
     "drain_timeout_s": {
      "anyOf": [
       {
        "maximum": 3600.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Secondes pendant lesquelles le drainage attend le travail en cours, de 0 à 3600.",
      "title": "Drain Timeout S"
     },
     "mode": {
      "description": "`drain`, `scale_to_zero`, `security_suspend`, `emergency_stop`, `disable`, `quarantine`, `maintenance` ou `enable`.",
      "pattern": "^(drain|scale_to_zero|security_suspend|emergency_stop|disable|quarantine|maintenance|enable)$",
      "title": "Mode",
      "type": "string"
     },
     "reason": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison du mode appliqué, 500 caractères au plus ; enregistrée par le Manager et dans l'audit.",
      "title": "Reason"
     }
    },
    "required": [
     "mode"
    ],
    "title": "ModeIn",
    "type": "object"
   },
   "MultimodalDlpIn": {
    "description": "Un contrôle DLP. Fournissez `text`, `media_id` ou `content_base64`.",
    "properties": {
     "content_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fichier fourni directement, encodé en base64 ; analysé, puis son texte est extrait.",
      "title": "Content Base64"
     },
     "media_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Média enregistré dont le texte extrait est inspecté.",
      "title": "Media Id"
     },
     "mime_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de média du fichier fourni.",
      "title": "Mime Type"
     },
     "mode": {
      "default": "detect",
      "description": "`detect` (par défaut), `mask`, `tokenize` ou `block`.",
      "title": "Mode",
      "type": "string"
     },
     "text": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Texte à inspecter ; prioritaire sur le média.",
      "title": "Text"
     }
    },
    "title": "MultimodalDlpIn",
    "type": "object"
   },
   "Obligation": {
    "description": "Obligation attachée à une décision d'autorisation.",
    "properties": {
     "params": {
      "additionalProperties": true,
      "description": "Paramètres de l'obligation, par exemple `{\"mode\": \"mask\"}` pour `dlp`.",
      "title": "Params",
      "type": "object"
     },
     "type": {
      "description": "Type d'obligation : `dlp` (masquage des données) et `approval` (validation à quatre yeux préalable) sont appliqués par la plateforme ; les autres types sont renvoyés à l'appelant.",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type"
    ],
    "title": "Obligation",
    "type": "object"
   },
   "OciIn": {
    "description": "Artefact OCI du pack.",
    "properties": {
     "digest": {
      "description": "Empreinte de l'artefact, `sha256:` suivi de 64 caractères hexadécimaux minuscules.",
      "pattern": "^sha256:[a-f0-9]{64}$",
      "title": "Digest",
      "type": "string"
     },
     "ref": {
      "description": "Référence OCI de l'artefact (3 à 500 caractères).",
      "maxLength": 500,
      "minLength": 3,
      "title": "Ref",
      "type": "string"
     }
    },
    "required": [
     "ref",
     "digest"
    ],
    "title": "OciIn",
    "type": "object"
   },
   "OperationIn": {
    "description": "Opération à exécuter sur un service.",
    "properties": {
     "action": {
      "description": "`start`, `stop`, `restart`, `rolling_restart` ou `reinstall`.",
      "pattern": "^(start|stop|restart|rolling_restart|reinstall)$",
      "title": "Action",
      "type": "string"
     },
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de l'approbation du Manager qui autorise l'opération quand le Manager en exige une.",
      "title": "Approval Id"
     },
     "batch_size": {
      "default": 1,
      "description": "Hôtes traités en même temps, de 1 à 100, 1 par défaut.",
      "maximum": 100.0,
      "minimum": 1.0,
      "title": "Batch Size",
      "type": "integer"
     },
     "components": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement ces composants du service ; tous par défaut.",
      "title": "Components"
     },
     "hosts": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement ces hôtes ; par défaut tous les hôtes du service.",
      "title": "Hosts"
     },
     "tolerate_failures": {
      "default": 0,
      "description": "Échecs d'hôtes tolérés avant l'arrêt de l'opération, de 0 à 100, 0 par défaut.",
      "maximum": 100.0,
      "minimum": 0.0,
      "title": "Tolerate Failures",
      "type": "integer"
     }
    },
    "required": [
     "action"
    ],
    "title": "OperationIn",
    "type": "object"
   },
   "OrderIn": {
    "description": "Ordre de confinement manuel.",
    "properties": {
     "confidence": {
      "default": 1.0,
      "description": "Confiance de la décision, de 0 à 1 (1 par défaut).",
      "maximum": 1.0,
      "minimum": 0.0,
      "title": "Confidence",
      "type": "number"
     },
     "incident_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Incident auquel l'ordre se rattache, facultatif (404 s'il est inconnu).",
      "title": "Incident Id"
     },
     "max_blast_radius": {
      "anyOf": [
       {
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre maximal d'objets que l'ordre peut toucher (1 ou plus) ; au-delà, l'ordre est refusé.",
      "title": "Max Blast Radius"
     },
     "params": {
      "additionalProperties": true,
      "description": "Paramètres de la réaction, par exemple un débit pour `RATE_LIMIT`.",
      "title": "Params",
      "type": "object"
     },
     "reaction": {
      "description": "Réaction typée : `BLOCK_REQUEST`, `RATE_LIMIT`, `REQUIRE_APPROVAL`, `CANCEL_RUN`, `CANCEL_SUBAGENTS`, `SUSPEND_AGENT`, `SUSPEND_APPLICATION`, `DISABLE_TOOL`, `QUARANTINE_CONNECTOR`, `REVOKE_CREDENTIAL`, `DISABLE_MODEL_ROUTE`, `ISOLATE_NETWORK`, `TERMINATE_WORKLOAD`, `SUSPEND_IDENTITY`, `SUSPEND_TENANT`, `EMERGENCY_LOCKDOWN`.",
      "title": "Reaction",
      "type": "string"
     },
     "reason": {
      "description": "Raison du confinement, 3 à 2000 caractères.",
      "maxLength": 2000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     },
     "target": {
      "$ref": "#/components/schemas/eai__domains__soc__TargetIn",
      "description": "Cible typée de la réaction."
     }
    },
    "required": [
     "reaction",
     "target",
     "reason"
    ],
    "title": "OrderIn",
    "type": "object"
   },
   "OrganizationIn": {
    "description": "Nouvelle organisation du tenant.",
    "properties": {
     "description": {
      "default": "",
      "description": "Description, 2000 caractères au plus (vide par défaut).",
      "maxLength": 2000,
      "title": "Description",
      "type": "string"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Paires clé-valeur libres enregistrées avec l'objet. Contrôlées par la base : un objet refusé répond 422 `invalid_metadata`.",
      "title": "Metadata",
      "type": "object"
     },
     "name": {
      "description": "Nom affiché, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "slug": {
      "description": "Identifiant court en minuscules, chiffres et tirets, commençant par une lettre ou un chiffre, 63 caractères au plus. Unique, il ne peut plus être modifié.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
      "title": "Slug",
      "type": "string"
     }
    },
    "required": [
     "slug",
     "name"
    ],
    "title": "OrganizationIn",
    "type": "object"
   },
   "OrganizationPatch": {
    "description": "Modifications d'une organisation ; les champs omis gardent leur valeur.",
    "properties": {
     "description": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle description, 2000 caractères au plus.",
      "title": "Description"
     },
     "expected_version": {
      "anyOf": [
       {
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version lue en dernier par l'appelant. Une version périmée répond 409 `version_conflict` (concurrence optimiste).",
      "title": "Expected Version"
     },
     "metadata": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Paires clé-valeur libres enregistrées avec l'objet. Contrôlées par la base : un objet refusé répond 422 `invalid_metadata`.",
      "title": "Metadata"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom affiché, 1 à 200 caractères.",
      "title": "Name"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "archived"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`active` ou `archived`.",
      "title": "Status"
     }
    },
    "title": "OrganizationPatch",
    "type": "object"
   },
   "PackageStatusIn": {
    "description": "Kill switch d'un paquet.",
    "properties": {
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Demande `marketplace.package.resume` approuvée, pour lever le kill switch.",
      "title": "Approval Id"
     },
     "reason": {
      "default": "",
      "description": "Raison du changement de statut (1000 caractères au plus).",
      "maxLength": 1000,
      "title": "Reason",
      "type": "string"
     },
     "status": {
      "description": "`suspended` (immédiat) ou `active` (levée, avec approbation).",
      "enum": [
       "suspended",
       "active"
      ],
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "status"
    ],
    "title": "PackageStatusIn",
    "type": "object"
   },
   "PasswordIn": {
    "additionalProperties": false,
    "description": "Changement du mot de passe du compte local connecté.",
    "properties": {
     "current_password": {
      "description": "Mot de passe actuel, 1 à 256 caractères.",
      "maxLength": 256,
      "minLength": 1,
      "title": "Current Password",
      "type": "string"
     },
     "new_password": {
      "description": "Nouveau mot de passe : au moins la longueur minimale configurée (12 par défaut), 256 caractères au plus, avec des lettres et des caractères autres que des lettres.",
      "maxLength": 256,
      "minLength": 1,
      "title": "New Password",
      "type": "string"
     }
    },
    "required": [
     "current_password",
     "new_password"
    ],
    "title": "PasswordIn",
    "type": "object"
   },
   "PresignIn": {
    "description": "Demande d'une URL présignée pour un objet.",
    "properties": {
     "content_type": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de contenu transmis au stockage lors de la signature de l'URL (le type que l'envoi doit transmettre) ; 200 caractères au plus.",
      "title": "Content Type"
     },
     "expires_seconds": {
      "default": 900,
      "description": "Durée de vie de l'URL en secondes, 900 par défaut, au plus `EAI_STORAGE_PRESIGN_MAX_SECONDS` (3600 par défaut) et jamais plus de 604800.",
      "maximum": 604800.0,
      "minimum": 1.0,
      "title": "Expires Seconds",
      "type": "integer"
     },
     "key": {
      "description": "Clé de l'objet relative au préfixe de clé du profil, de 1 à 1024 caractères, dans un préfixe de présignature du profil.",
      "maxLength": 1024,
      "minLength": 1,
      "title": "Key",
      "type": "string"
     },
     "purpose": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Libellé libre de la raison de l'émission, enregistré avec l'autorisation ; 200 caractères au plus.",
      "title": "Purpose"
     },
     "scope": {
      "description": "Sens de l'URL : `read` donne une URL `GET`, `write` une URL `PUT`.",
      "enum": [
       "read",
       "write"
      ],
      "title": "Scope",
      "type": "string"
     }
    },
    "required": [
     "scope",
     "key"
    ],
    "title": "PresignIn",
    "type": "object"
   },
   "PriceCardIn": {
    "description": "Nouvelle grille tarifaire. Les prix sont décimaux ; les clés de jetons, de caractères et similaires sont valorisées par million d'unités.",
    "properties": {
     "currency": {
      "description": "Devise ISO 4217 des prix (obligatoire).",
      "pattern": "^[A-Z]{3}$",
      "title": "Currency",
      "type": "string"
     },
     "effective_from": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Début de validité, ISO 8601 ; maintenant par défaut. La grille en vigueur à cette date est close.",
      "title": "Effective From"
     },
     "effective_to": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fin de validité, ISO 8601 ; null pour aucune fin.",
      "title": "Effective To"
     },
     "image_prices": {
      "additionalProperties": {
       "anyOf": [
        {
         "type": "number"
        },
        {
         "type": "string"
        }
       ]
      },
      "description": "Prix des images par clé de dimension (taille, qualité), au moins 0.",
      "title": "Image Prices",
      "type": "object"
     },
     "model_pattern": {
      "description": "Identifiant de modèle ou motif glob insensible à la casse (par exemple `gpt-4o*`), de 1 à 200 caractères ; un identifiant exact l'emporte sur un motif.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Model Pattern",
      "type": "string"
     },
     "notes": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Notes libres.",
      "title": "Notes"
     },
     "prices": {
      "additionalProperties": {
       "anyOf": [
        {
         "type": "number"
        },
        {
         "type": "string"
        }
       ]
      },
      "description": "Prix à plat par clé, par exemple `input_tokens`, `output_tokens`, `cached_input_tokens`, `reasoning_tokens`, `characters` (par million), `images`, `audio_seconds`, `video_seconds`, `requests`, `gpu_seconds`. Au moins un champ parmi `prices`, `image_prices`, `video_prices` est obligatoire.",
      "title": "Prices",
      "type": "object"
     },
     "provider": {
      "description": "Fournisseur de la grille, de 1 à 100 caractères.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Provider",
      "type": "string"
     },
     "reasoning_billing": {
      "default": "included_in_output",
      "description": "Facturation des jetons de raisonnement : `included_in_output` (par défaut), `separate`, `not_billed` ou `not_exposed`.",
      "enum": [
       "included_in_output",
       "separate",
       "not_billed",
       "not_exposed"
      ],
      "title": "Reasoning Billing",
      "type": "string"
     },
     "reference": {
      "default": false,
      "description": "Marque la grille comme grille de prix de référence ; l'indicateur est recopié dans l'enregistrement de prix de chaque événement d'usage valorisé avec elle. Faux par défaut.",
      "title": "Reference",
      "type": "boolean"
     },
     "region": {
      "default": "*",
      "description": "Région de la grille, `*` par défaut (toutes les régions).",
      "title": "Region",
      "type": "string"
     },
     "source": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Origine des prix (contrat, page publique...).",
      "title": "Source"
     },
     "tiers": {
      "description": "Paliers de prix qui s'appliquent à partir d'un nombre de jetons d'entrée (tarification des contextes longs).",
      "items": {
       "$ref": "#/components/schemas/TierIn"
      },
      "title": "Tiers",
      "type": "array"
     },
     "usage_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type d'usage valorisé par la grille ; null pour tous.",
      "title": "Usage Type"
     },
     "version": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Libellé de version ; par défaut `v<n>` d'après le nombre de grilles de la même clé.",
      "title": "Version"
     },
     "video_prices": {
      "additionalProperties": {
       "anyOf": [
        {
         "type": "number"
        },
        {
         "type": "string"
        }
       ]
      },
      "description": "Prix des vidéos par clé de dimension (résolution, cadence, mode), au moins 0.",
      "title": "Video Prices",
      "type": "object"
     }
    },
    "required": [
     "provider",
     "model_pattern",
     "currency"
    ],
    "title": "PriceCardIn",
    "type": "object"
   },
   "ProductionIn": {
    "description": "Promotion d'une version en production.",
    "properties": {
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Demande `marketplace.promote` approuvée. Omettez-la au premier appel.",
      "title": "Approval Id"
     }
    },
    "title": "ProductionIn",
    "type": "object"
   },
   "ProfileIn": {
    "additionalProperties": false,
    "description": "Nouveau profil de stockage. Les champs inconnus sont refusés ; les valeurs secrètes sont refusées dans chaque champ.",
    "properties": {
     "auth": {
      "additionalProperties": true,
      "description": "Paramètres d'authentification non secrets : `method` (`sigv4` pour la famille S3, aussi `ibm_iam` pour IBM COS ; `shared_key`, `sas`, `entra_client_secret` ou `workload_identity` pour Azure ; `service_account` pour GCS) et, selon le besoin, `account`, `tenant_id`, `client_id`, `service_instance_id`, `token_url`, `scope`. `account` est obligatoire pour Azure ; `workload_identity` est réservé aux profils de plateforme.",
      "title": "Auth",
      "type": "object"
     },
     "bucket": {
      "description": "Nom du bucket ou du conteneur : 3 à 222 caractères, lettres minuscules, chiffres, points, tirets et soulignés, commençant et finissant par une lettre ou un chiffre, sans `..`.",
      "maxLength": 222,
      "minLength": 3,
      "title": "Bucket",
      "type": "string"
     },
     "credential_ref": {
      "anyOf": [
       {
        "maxLength": 210,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret, 210 caractères au plus : `openbao://<chemin>` pour un profil de tenant, `env://EAI_CRED_<NOM>` pour un profil de plateforme. Inutilisée avec `workload_identity`. La valeur secrète n'est jamais envoyée.",
      "title": "Credential Ref"
     },
     "description": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description libre, 500 caractères au plus.",
      "title": "Description"
     },
     "encryption": {
      "additionalProperties": true,
      "description": "Chiffrement côté serveur, `{mode: provider_default}` par défaut. Modes : `provider_default`, `sse-s3`, `sse-kms` pour la famille S3 ; `provider_default`, `encryption-scope` (avec `encryption_scope`) pour Azure ; `provider_default`, `cmek` (avec `kms_key_id`) pour GCS.",
      "title": "Encryption",
      "type": "object"
     },
     "endpoint": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse HTTPS du service de stockage, 500 caractères au plus. Obligatoire pour `ibm_cos`, `seaweedfs` et `s3_compatible` ; facultative sinon.",
      "title": "Endpoint"
     },
     "key_prefix": {
      "default": "",
      "description": "Préfixe ajouté à chaque clé d'objet écrite par ce profil, 512 caractères au plus ; vide par défaut.",
      "maxLength": 512,
      "title": "Key Prefix",
      "type": "string"
     },
     "name": {
      "description": "Nom unique du profil dans sa portée : 2 à 63 caractères, lettres minuscules, chiffres et tirets, commençant par une lettre ou un chiffre.",
      "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
      "title": "Name",
      "type": "string"
     },
     "options": {
      "additionalProperties": true,
      "description": "Options du fournisseur : `path_style` (adressage par chemin pour S3) et `storage_class`. Toute autre clé est refusée.",
      "title": "Options",
      "type": "object"
     },
     "presign_prefixes": {
      "description": "Préfixes de clés accessibles par l'API (URL présignées, liste, suppression), jusqu'à 20. Vide par défaut : aucune clé n'est accessible.",
      "items": {
       "type": "string"
      },
      "maxItems": 20,
      "title": "Presign Prefixes",
      "type": "array"
     },
     "provider": {
      "description": "Fournisseur de stockage : `aws_s3`, `ibm_cos`, `seaweedfs`, `s3_compatible`, `azure_blob` ou `gcs`.",
      "enum": [
       "aws_s3",
       "ibm_cos",
       "seaweedfs",
       "s3_compatible",
       "azure_blob",
       "gcs"
      ],
      "title": "Provider",
      "type": "string"
     },
     "purposes": {
      "description": "Usages du profil, jusqu'à 10 parmi `audit_worm`, `evidence`, `documents`, `media`, `exports`, `backups`, `general`. `audit_worm` demande verrouillage d'objet, rétention et intégrité ; `evidence` demande verrouillage d'objet et intégrité.",
      "items": {
       "type": "string"
      },
      "maxItems": 10,
      "title": "Purposes",
      "type": "array"
     },
     "region": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Région du bucket, 64 caractères au plus.",
      "title": "Region"
     },
     "required_capabilities": {
      "description": "Capacités que le profil doit avoir en plus de celles de ses usages, jusqu'à 20 parmi `versioning`, `object_lock`, `legal_hold`, `retention`, `multipart`, `presign`, `sse`, `kms`, `storage_classes`, `lifecycle`, `replication`, `integrity`.",
      "items": {
       "type": "string"
      },
      "maxItems": 20,
      "title": "Required Capabilities",
      "type": "array"
     },
     "scope": {
      "default": "tenant",
      "description": "`tenant` (par défaut) pour un profil du tenant de l'appelant, `platform` pour un profil partagé par la plateforme (administrateur de la plateforme seulement).",
      "enum": [
       "tenant",
       "platform"
      ],
      "title": "Scope",
      "type": "string"
     }
    },
    "required": [
     "name",
     "provider",
     "bucket"
    ],
    "title": "ProfileIn",
    "type": "object"
   },
   "ProfilePatch": {
    "additionalProperties": false,
    "description": "Modifications d'un profil de stockage ; chaque champ est facultatif et un champ omis garde sa valeur. Le nom, la portée et le fournisseur ne se modifient pas ici. Les champs inconnus sont refusés.",
    "properties": {
     "auth": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveaux paramètres d'authentification non secrets (mêmes champs qu'à la création). Les modifier efface les capacités.",
      "title": "Auth"
     },
     "bucket": {
      "anyOf": [
       {
        "maxLength": 222,
        "minLength": 3,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom du bucket ou du conteneur, de 3 à 222 caractères. Le modifier efface les capacités.",
      "title": "Bucket"
     },
     "credential_ref": {
      "anyOf": [
       {
        "maxLength": 210,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle référence du secret, 210 caractères au plus (`openbao://` pour un profil de tenant, `env://EAI_CRED_` pour un profil de plateforme). La modifier efface les capacités.",
      "title": "Credential Ref"
     },
     "description": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description libre, 500 caractères au plus.",
      "title": "Description"
     },
     "encryption": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveaux paramètres de chiffrement côté serveur. Les modifier efface les capacités.",
      "title": "Encryption"
     },
     "endpoint": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse HTTPS du service de stockage, 500 caractères au plus. La modifier efface les capacités.",
      "title": "Endpoint"
     },
     "key_prefix": {
      "anyOf": [
       {
        "maxLength": 512,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Préfixe ajouté à chaque clé d'objet, 512 caractères au plus.",
      "title": "Key Prefix"
     },
     "options": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelles options du fournisseur (`path_style`, `storage_class`). Les modifier efface les capacités.",
      "title": "Options"
     },
     "presign_prefixes": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 20,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle liste de préfixes de présignature, jusqu'à 20.",
      "title": "Presign Prefixes"
     },
     "purposes": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 10,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle liste d'usages, jusqu'à 10.",
      "title": "Purposes"
     },
     "region": {
      "anyOf": [
       {
        "maxLength": 64,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Région du bucket, 64 caractères au plus. La modifier efface les capacités.",
      "title": "Region"
     },
     "required_capabilities": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 20,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle liste de capacités exigées, jusqu'à 20.",
      "title": "Required Capabilities"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "disabled"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`disabled` arrête tout usage du profil ; `active` sur un profil désactivé le remet à `unverified` (vérifiez-le avant usage).",
      "title": "Status"
     }
    },
    "title": "ProfilePatch",
    "type": "object"
   },
   "PromptIn": {
    "description": "Une version de prompt à publier.",
    "properties": {
     "description": {
      "default": "",
      "description": "Description du prompt ; une valeur vide garde l'actuelle. Vide par défaut.",
      "title": "Description",
      "type": "string"
     },
     "model_hint": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle pour lequel le modèle est écrit, à titre indicatif.",
      "title": "Model Hint"
     },
     "name": {
      "description": "Nom du prompt : minuscules, chiffres, `.`, `_`, `-`, 1 à 128 caractères, commençant et finissant par une lettre ou un chiffre.",
      "title": "Name",
      "type": "string"
     },
     "template": {
      "description": "Texte du modèle avec des emplacements `{{ variable }}` (au moins 1 caractère).",
      "minLength": 1,
      "title": "Template",
      "type": "string"
     },
     "variables": {
      "anyOf": [
       {
        "items": {
         "$ref": "#/components/schemas/Variable"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Variables déclarées. Omises : chaque emplacement devient une variable texte obligatoire.",
      "title": "Variables"
     }
    },
    "required": [
     "name",
     "template"
    ],
    "title": "PromptIn",
    "type": "object"
   },
   "Provenance": {
    "description": "Origine du contenu d'un élément de mémoire.",
    "properties": {
     "classification": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification de la source.",
      "title": "Classification"
     },
     "hash": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Empreinte du contenu source, par exemple `sha256:...`.",
      "title": "Hash"
     },
     "source_id": {
      "description": "Identifiant de l'objet source.",
      "title": "Source Id",
      "type": "string"
     },
     "source_type": {
      "description": "Type de source, par exemple `rag`, `conversation` ou `tool`.",
      "title": "Source Type",
      "type": "string"
     },
     "uri": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URI de la source.",
      "title": "Uri"
     },
     "version": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de la source.",
      "title": "Version"
     }
    },
    "required": [
     "source_type",
     "source_id"
    ],
    "title": "Provenance",
    "type": "object"
   },
   "PublishIn": {
    "description": "Une version de pack à publier avec ses preuves de chaîne d'approvisionnement.",
    "properties": {
     "archive": {
      "description": "Le pack `.tar.gz`, encodé en base64 (12 Mio de texte au plus).",
      "maxLength": 12582912,
      "minLength": 4,
      "title": "Archive",
      "type": "string"
     },
     "evaluation_report": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Rapport d'évaluation lié à l'empreinte de l'archive.",
      "title": "Evaluation Report"
     },
     "oci": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/OciIn"
       },
       {
        "type": "null"
       }
      ],
      "description": "Artefact OCI du pack ; son empreinte est exigée plus tard par le contrôle de mise en production."
     },
     "provenance": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Déclaration in-toto v1 signée avec un prédicat SLSA v1 : `{statement, signature: {value, signer}}`.",
      "title": "Provenance"
     },
     "sbom": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nomenclature logicielle (SBOM) du pack.",
      "title": "Sbom"
     },
     "signature": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/SignatureIn"
       },
       {
        "type": "null"
       }
      ],
      "description": "Signature de l'empreinte de l'archive par un éditeur de confiance."
     },
     "skill_signatures": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Signatures des skills du pack, par nom de skill.",
      "title": "Skill Signatures",
      "type": "object"
     },
     "test_report": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Rapport de tests lié à l'empreinte de l'archive.",
      "title": "Test Report"
     },
     "vulnerability_scan": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Rapport d'analyse de vulnérabilités lié à l'empreinte de l'archive.",
      "title": "Vulnerability Scan"
     }
    },
    "required": [
     "archive"
    ],
    "title": "PublishIn",
    "type": "object"
   },
   "PublisherIn": {
    "description": "Une clé d'éditeur de confiance.",
    "properties": {
     "name": {
      "description": "Nom de l'éditeur, même motif qu'un nom de paquet.",
      "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
      "title": "Name",
      "type": "string"
     },
     "public_key": {
      "description": "Clé publique, au format PEM ou texte (40 à 4000 caractères).",
      "maxLength": 4000,
      "minLength": 40,
      "title": "Public Key",
      "type": "string"
     }
    },
    "required": [
     "name",
     "public_key"
    ],
    "title": "PublisherIn",
    "type": "object"
   },
   "PurgeIn": {
    "description": "Demande de purge des charges utiles d'audit.",
    "properties": {
     "cutoff": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Pour `retention` : les charges utiles des événements antérieurs à cette date sont purgées (ISO 8601). Par défaut : maintenant moins la durée de conservation des charges utiles.",
      "title": "Cutoff"
     },
     "dry_run": {
      "default": false,
      "description": "Si vrai, indique ce qui serait purgé sans rien supprimer (faux par défaut).",
      "title": "Dry Run",
      "type": "boolean"
     },
     "event_ids": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 1000,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Pour `erasure` : événements dont les charges utiles sont effacées, 1000 au plus. Obligatoire pour un effacement.",
      "title": "Event Ids"
     },
     "kind": {
      "default": "retention",
      "description": "`retention` (par défaut, par ancienneté) ou `erasure` (événements listés).",
      "enum": [
       "retention",
       "erasure"
      ],
      "title": "Kind",
      "type": "string"
     }
    },
    "title": "PurgeIn",
    "type": "object"
   },
   "QueryFilters": {
    "description": "Filtres d'une requête, appliqués dans le moteur avec l'ACL et l'habilitation.",
    "properties": {
     "collections": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 50,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement les documents de ces collections (`collection` donnée à l'ingestion), 50 au plus.",
      "title": "Collections"
     },
     "document_ids": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 1000,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement ces documents (UUID, 1000 au plus) ; un identifiant qui n'est pas un document actif lisible ne correspond à rien.",
      "title": "Document Ids"
     },
     "element_types": {
      "anyOf": [
       {
        "items": {
         "enum": [
          "title",
          "paragraph",
          "table",
          "list",
          "caption",
          "code",
          "formula",
          "footnote",
          "figure",
          "other"
         ],
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement les fragments de ces types d'élément : `title`, `paragraph`, `table`, `list`, `caption`, `code`, `formula`, `footnote`, `figure`, `other`.",
      "title": "Element Types"
     },
     "languages": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 20,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement les fragments dans ces langues (codes ISO 639-1 comme `fr`, `en`), 20 au plus.",
      "title": "Languages"
     },
     "metadata": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement les documents dont les métadonnées contiennent cet objet JSON (inclusion).",
      "title": "Metadata"
     }
    },
    "title": "QueryFilters",
    "type": "object"
   },
   "QueryIn": {
    "description": "Une requête de recherche.",
    "properties": {
     "candidates": {
      "anyOf": [
       {
        "maximum": 20.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Candidats lus par résultat demandé dans chaque liste (1 à 20, 4 par défaut).",
      "title": "Candidates"
     },
     "conflicts": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Si vrai (par défaut), calcule le signal de conflit possible entre passages.",
      "title": "Conflicts"
     },
     "context_window": {
      "anyOf": [
       {
        "maximum": 5.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fragments voisins ajoutés de chaque côté d'un passage (0 à 5, 0 par défaut : aucun).",
      "title": "Context Window"
     },
     "dedupe": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Si vrai (par défaut), écarte les passages quasi identiques à un passage mieux classé.",
      "title": "Dedupe"
     },
     "dedupe_threshold": {
      "anyOf": [
       {
        "maximum": 1.0,
        "minimum": 0.5,
        "type": "number"
       },
       {
        "type": "null"
       }
      ],
      "description": "Recouvrement de Jaccard des triplets de mots à partir duquel deux passages sont quasi identiques (0,5 à 1, 0,9 par défaut).",
      "title": "Dedupe Threshold"
     },
     "diagnostics": {
      "default": false,
      "description": "Si vrai, la réponse ajoute `diagnostics` (étapes avec leur durée, nombres de candidats, stratégie, filtres) ; le cache de recherche n'est pas lu. Faux par défaut ; les diagnostics sont enregistrés dans tous les cas.",
      "title": "Diagnostics",
      "type": "boolean"
     },
     "filters": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/QueryFilters"
       },
       {
        "type": "null"
       }
      ],
      "description": "Filtres par collection, document, métadonnées, type d'élément et langue (voir QueryFilters)."
     },
     "fusion": {
      "anyOf": [
       {
        "enum": [
         "rrf",
         "weighted"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fusion hybride : `rrf` (fusion par rang réciproque, par défaut) ou `weighted` (0,5 similarité + 0,5 recouvrement de mots-clés des candidats denses).",
      "title": "Fusion"
     },
     "min_score": {
      "default": 0.0,
      "description": "Score minimal de similarité vectorielle d'un résultat. 0 par défaut.",
      "title": "Min Score",
      "type": "number"
     },
     "mode": {
      "anyOf": [
       {
        "enum": [
         "vector",
         "dense",
         "lexical",
         "hybrid"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`vector` ou `dense` (similarité cosinus), `lexical` (plein texte PostgreSQL) ou `hybrid` (fusion des deux). Par défaut : le mode de la stratégie de la base, sinon `vector`.",
      "title": "Mode"
     },
     "query": {
      "description": "Texte de la requête (au moins 1 caractère).",
      "minLength": 1,
      "title": "Query",
      "type": "string"
     },
     "rerank": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Si vrai, ordonne les résultats avec un modèle de reranking. Par défaut : la stratégie de la base, sinon faux.",
      "title": "Rerank"
     },
     "rerank_candidates": {
      "anyOf": [
       {
        "maximum": 200.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Passages envoyés au reranking (1 à 200, au moins `top_k` ; `top_k` par défaut).",
      "title": "Rerank Candidates"
     },
     "rerank_model": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle du registre avec la capacité rerank (par défaut : le modèle de reranking de la plateforme). Sans modèle, `warnings` signale `reranker_not_configured`.",
      "title": "Rerank Model"
     },
     "rrf_k": {
      "anyOf": [
       {
        "maximum": 1000.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Constante `k` de la fusion par rang réciproque (1 à 1000, 60 par défaut).",
      "title": "Rrf K"
     },
     "token_budget": {
      "anyOf": [
       {
        "maximum": 32000.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Budget de jetons des passages et de leurs voisins ; les voisins sont ajoutés du plus proche au plus lointain tant qu'il le permet (0 à 32000, 2000 par défaut).",
      "title": "Token Budget"
     },
     "top_k": {
      "default": 5,
      "description": "Nombre de résultats (1 à 100, 5 par défaut).",
      "maximum": 100.0,
      "minimum": 1.0,
      "title": "Top K",
      "type": "integer"
     }
    },
    "required": [
     "query"
    ],
    "title": "QueryIn",
    "type": "object"
   },
   "QuotaProfileIn": {
    "description": "Nouveau profil de quota.",
    "properties": {
     "currency": {
      "anyOf": [
       {
        "pattern": "^[A-Z]{3}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Code ISO 4217 de `limit_cost`, trois lettres majuscules comme `EUR`. Null : la devise du tenant.",
      "title": "Currency"
     },
     "enforcement": {
      "default": "hard",
      "description": "`hard` (par défaut) refuse l'appel une fois la limite atteinte ; `soft` signale seulement le dépassement.",
      "enum": [
       "hard",
       "soft"
      ],
      "title": "Enforcement",
      "type": "string"
     },
     "limit_cost": {
      "anyOf": [
       {
        "maximum": 1000000000000000.0,
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Plafond de dépense de la période, en `currency`, entre 0 et 10^15. Au moins un des champs `limit_cost` et `limit_tokens` est obligatoire.",
      "title": "Limit Cost"
     },
     "limit_tokens": {
      "anyOf": [
       {
        "maximum": 4.611686018427388e+18,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Plafond de jetons de la période (jetons d'entrée et de sortie), entre 0 et 2^62.",
      "title": "Limit Tokens"
     },
     "name": {
      "description": "Nom unique du profil dans le tenant, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "period": {
      "default": "month",
      "description": "Fenêtre du quota : `request`, `run`, `hour`, `day` ou `month` (`month` par défaut).",
      "enum": [
       "request",
       "run",
       "hour",
       "day",
       "month"
      ],
      "title": "Period",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "QuotaProfileIn",
    "type": "object"
   },
   "QuotaProfilePatch": {
    "description": "Modifications d'un profil de quota ; les champs omis gardent leur valeur.",
    "properties": {
     "enforcement": {
      "anyOf": [
       {
        "enum": [
         "hard",
         "soft"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau mode d'application : `hard` ou `soft`.",
      "title": "Enforcement"
     },
     "limit_cost": {
      "anyOf": [
       {
        "maximum": 1000000000000000.0,
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau plafond de dépense, entre 0 et 10^15.",
      "title": "Limit Cost"
     },
     "limit_tokens": {
      "anyOf": [
       {
        "maximum": 4.611686018427388e+18,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau plafond de jetons, entre 0 et 2^62.",
      "title": "Limit Tokens"
     },
     "period": {
      "anyOf": [
       {
        "enum": [
         "request",
         "run",
         "hour",
         "day",
         "month"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle fenêtre : `request`, `run`, `hour`, `day` ou `month`.",
      "title": "Period"
     }
    },
    "title": "QuotaProfilePatch",
    "type": "object"
   },
   "RagAgentBrandingIn": {
    "additionalProperties": false,
    "description": "Champs de l'image de marque à écrire ; un champ absent est conservé, null le ramène à la valeur par défaut du runtime.",
    "properties": {
     "agent_name": {
      "anyOf": [
       {
        "maxLength": 80,
        "minLength": 1,
        "pattern": "^[^<>\\x00-\\x1f\\x7f]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'agent affiché aux utilisateurs, 1 à 80 caractères, sans `<`, `>` ni caractère de contrôle.",
      "title": "Agent Name"
     },
     "app_url": {
      "anyOf": [
       {
        "maxLength": 2048,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URL de l'application web de l'instance une fois connue, https uniquement, sans identifiants (2048 caractères au plus).",
      "title": "App Url"
     },
     "expected_version": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de l'image de marque sur laquelle porte la modification ; une autre version courante répond 409 `rag_agent_branding_version_conflict`.",
      "title": "Expected Version"
     },
     "navbar_color": {
      "anyOf": [
       {
        "pattern": "^#[0-9A-Fa-f]{6}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Couleur de la barre supérieure, `#RRGGBB` ; le texte dessus est noir ou blanc avec un contraste de 4,5:1 ou plus.",
      "title": "Navbar Color"
     },
     "sidebar_color": {
      "anyOf": [
       {
        "pattern": "^#[0-9A-Fa-f]{6}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Couleur de la barre latérale, `#RRGGBB` ; le texte dessus est noir ou blanc avec un contraste de 4,5:1 ou plus.",
      "title": "Sidebar Color"
     }
    },
    "title": "RagAgentBrandingIn",
    "type": "object"
   },
   "RagAgentInstanceIn": {
    "description": "Une instance de l'agent RAG à créer en brouillon.",
    "properties": {
     "business_unit_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Unité métier de l'instance (UUID), contrôlée à la validation.",
      "title": "Business Unit Id"
     },
     "deployment_model": {
      "description": "`shared` (le runtime partagé existant) ou `dedicated` (un runtime installé par le Navin Manager).",
      "enum": [
       "shared",
       "dedicated"
      ],
      "title": "Deployment Model",
      "type": "string"
     },
     "description": {
      "default": "",
      "description": "Description libre (2000 caractères au plus).",
      "maxLength": 2000,
      "title": "Description",
      "type": "string"
     },
     "embedding_model": {
      "description": "Modèle d'embeddings du registre (capacité `embeddings`), utilisé par la base de connaissances de l'instance.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Embedding Model",
      "type": "string"
     },
     "generation_model": {
      "description": "Modèle de génération du registre (capacité `chat`).",
      "maxLength": 200,
      "minLength": 1,
      "title": "Generation Model",
      "type": "string"
     },
     "id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant immuable (UUID) de l'instance ; généré s'il est absent.",
      "title": "Id"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 63 caractères.",
      "pattern": "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$",
      "title": "Name",
      "type": "string"
     },
     "organization_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Organisation de l'instance (UUID), contrôlée à la validation.",
      "title": "Organization Id"
     },
     "project": {
      "description": "Projet de l'instance (identifiant ou slug) ; l'appelant doit en être propriétaire ou être administrateur du tenant.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Project",
      "type": "string"
     },
     "version": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version à déployer ; par défaut : la version que le runtime annonce à l'étape smoke.",
      "title": "Version"
     }
    },
    "required": [
     "name",
     "project",
     "deployment_model",
     "generation_model",
     "embedding_model"
    ],
    "title": "RagAgentInstanceIn",
    "type": "object"
   },
   "RagAgentRestoreIn": {
    "description": "Une demande de restauration.",
    "properties": {
     "backup_id": {
      "description": "Identifiant de la sauvegarde à restaurer.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Backup Id",
      "type": "string"
     }
    },
    "required": [
     "backup_id"
    ],
    "title": "RagAgentRestoreIn",
    "type": "object"
   },
   "RagAgentUpgradeIn": {
    "description": "Une demande de mise à niveau.",
    "properties": {
     "version": {
      "description": "Version à déployer (lettres, chiffres, `.`, `_`, `+`, `-`, 64 caractères au plus).",
      "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$",
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "version"
    ],
    "title": "RagAgentUpgradeIn",
    "type": "object"
   },
   "ReconRunIn": {
    "description": "Période d'un rapprochement.",
    "properties": {
     "from": {
      "anyOf": [
       {
        "format": "date",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Premier jour (date ISO 8601) ; par défaut le premier jour du mois de `to`.",
      "title": "From"
     },
     "to": {
      "anyOf": [
       {
        "format": "date",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Dernier jour (date ISO 8601) ; aujourd'hui par défaut. Ne doit pas précéder `from` (422).",
      "title": "To"
     }
    },
    "title": "ReconRunIn",
    "type": "object"
   },
   "ReconcileIn": {
    "description": "Rapprochement d'une transaction à effet de bord.",
    "properties": {
     "note": {
      "default": "",
      "description": "Note du rapprochement, 2000 caractères au plus.",
      "maxLength": 2000,
      "title": "Note",
      "type": "string"
     },
     "outcome": {
      "description": "`committed` (elle a eu lieu chez le fournisseur) ou `not_executed`.",
      "title": "Outcome",
      "type": "string"
     },
     "provider_reference": {
      "anyOf": [
       {
        "maxLength": 300,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de l'opération chez le fournisseur, 300 caractères au plus.",
      "title": "Provider Reference"
     }
    },
    "required": [
     "outcome"
    ],
    "title": "ReconcileIn",
    "type": "object"
   },
   "RecoveryIn": {
    "description": "Demande de rétablissement.",
    "properties": {
     "kill_switch_id": {
      "description": "Coupe-circuit à lever.",
      "title": "Kill Switch Id",
      "type": "string"
     },
     "reason": {
      "description": "Raison pour laquelle le confinement peut cesser, 3 à 2000 caractères.",
      "maxLength": 2000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "kill_switch_id",
     "reason"
    ],
    "title": "RecoveryIn",
    "type": "object"
   },
   "RedTeamIn": {
    "description": "Demande de campagne de red team.",
    "properties": {
     "engine": {
      "default": "builtin",
      "description": "`builtin` (par défaut) ou `garak`.",
      "title": "Engine",
      "type": "string"
     },
     "max_attack_success_rate": {
      "default": 0.0,
      "description": "Part maximale d'attaques réussies acceptée, de 0 à 1 (0 par défaut : toute attaque réussie fait échouer la campagne).",
      "maximum": 1.0,
      "minimum": 0.0,
      "title": "Max Attack Success Rate",
      "type": "number"
     },
     "probes": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sondes à envoyer. Par défaut : toutes les sondes intégrées, ou `promptinject` et `dan` pour garak.",
      "title": "Probes"
     },
     "target": {
      "$ref": "#/components/schemas/eai__domains__evaluation__Target",
      "description": "Cible attaquée : `model`, `agent`, `prompt`, `rag_pipeline`, `skill` ou `tool`."
     }
    },
    "required": [
     "target"
    ],
    "title": "RedTeamIn",
    "type": "object"
   },
   "ReindexIn": {
    "description": "Cible d'une réindexation.",
    "properties": {
     "dimensions": {
      "anyOf": [
       {
        "maximum": 16384.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Taille des vecteurs, l'une des tailles déclarées par le modèle (1 à 16384 ; par défaut : native).",
      "title": "Dimensions"
     },
     "embedding_model": {
      "description": "Modèle du registre avec la capacité embeddings (1 à 200 caractères).",
      "maxLength": 200,
      "minLength": 1,
      "title": "Embedding Model",
      "type": "string"
     }
    },
    "required": [
     "embedding_model"
    ],
    "title": "ReindexIn",
    "type": "object"
   },
   "RejectIn": {
    "description": "Rejet d'un changement.",
    "properties": {
     "reason": {
      "description": "Raison du rejet (3 à 1000 caractères).",
      "maxLength": 1000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "reason"
    ],
    "title": "RejectIn",
    "type": "object"
   },
   "ReleaseIn": {
    "description": "Approbation de levée d'un gel juridique.",
    "properties": {
     "reason": {
      "description": "Raison pour laquelle le gel peut être levé, 3 à 1000 caractères.",
      "maxLength": 1000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "reason"
    ],
    "title": "ReleaseIn",
    "type": "object"
   },
   "RenderIn": {
    "description": "Valeurs pour rendre un prompt.",
    "properties": {
     "strict": {
      "default": true,
      "description": "Si vrai (par défaut), des valeurs pour des variables non déclarées sont une erreur.",
      "title": "Strict",
      "type": "boolean"
     },
     "variables": {
      "additionalProperties": true,
      "description": "Valeur de chaque variable, par nom.",
      "title": "Variables",
      "type": "object"
     },
     "version": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro de version à rendre. Par défaut : la dernière.",
      "title": "Version"
     }
    },
    "title": "RenderIn",
    "type": "object"
   },
   "ReserveIn": {
    "description": "Montant à réserver sur les quotas correspondants.",
    "properties": {
     "cost": {
      "anyOf": [
       {
        "maximum": 1000000000000000.0,
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       }
      ],
      "default": "0",
      "description": "Coût à réserver dans la devise du tenant, de 0 à 10^15, 0 par défaut ; converti dans la devise de chaque quota.",
      "title": "Cost"
     },
     "scope": {
      "additionalProperties": true,
      "description": "Pour qui est l'appel : clés `project`, `agent`, `user`, `model`, `provider`, `run` (ou les mêmes avec `_id`), comparées aux portées des quotas.",
      "title": "Scope",
      "type": "object"
     },
     "tokens": {
      "default": 0,
      "description": "Jetons à réserver, de 0 à 2^62, 0 par défaut.",
      "maximum": 4.611686018427388e+18,
      "minimum": 0.0,
      "title": "Tokens",
      "type": "integer"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "exclusiveMinimum": 0.0,
        "maximum": 86400.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie de la réservation en secondes, de 1 à 86400 ; par défaut le `reservation_ttl_seconds` du tenant (900). Une réservation expirée est libérée.",
      "title": "Ttl Seconds"
     }
    },
    "title": "ReserveIn",
    "type": "object"
   },
   "RestorePlanIn": {
    "description": "Demande de plan de restauration.",
    "properties": {
     "artifact_id": {
      "description": "Identifiant de la sauvegarde à restaurer, de 1 à 100 caractères.",
      "maxLength": 100,
      "minLength": 1,
      "title": "Artifact Id",
      "type": "string"
     },
     "environment": {
      "description": "Environnement cible : `isolated`, `staging` ou `production`.",
      "pattern": "^(isolated|staging|production)$",
      "title": "Environment",
      "type": "string"
     },
     "target_versions": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Versions de l'environnement cible par composant, comparées par le Manager avec la sauvegarde (même version majeure, sinon incompatible).",
      "title": "Target Versions",
      "type": "object"
     }
    },
    "required": [
     "artifact_id",
     "environment"
    ],
    "title": "RestorePlanIn",
    "type": "object"
   },
   "RestrictIn": {
    "description": "Délégation enfant ; les champs omis héritent des valeurs du parent.",
    "properties": {
     "audience": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 20,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Audiences plus étroites, 20 au plus.",
      "title": "Audience"
     },
     "data_classification_max": {
      "anyOf": [
       {
        "pattern": "^(public|internal|confidential|restricted|secret)$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Plafond de classification inférieur ou égal : `public`, `internal`, `confidential`, `restricted` ou `secret`.",
      "title": "Data Classification Max"
     },
     "delegate": {
      "description": "Sous-délégataire : `agent:<name>`, `app:<id>`, `service:<name>` ou `subagent:<name>`.",
      "maxLength": 250,
      "minLength": 3,
      "title": "Delegate",
      "type": "string"
     },
     "expires_at": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Date et heure d'expiration (ISO 8601), jamais au-delà de l'expiration du parent.",
      "title": "Expires At"
     },
     "max_depth": {
      "anyOf": [
       {
        "maximum": 10.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Profondeur maximale de la chaîne sous cette branche, de 0 à 10 : au moins la profondeur de l'enfant, au plus le `max_depth` du parent (par défaut : la valeur du parent).",
      "title": "Max Depth"
     },
     "purpose": {
      "default": "",
      "description": "Finalité de la délégation enfant, 2000 caractères au plus.",
      "maxLength": 2000,
      "title": "Purpose",
      "type": "string"
     },
     "redelegation_allowed": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Si le sous-délégataire peut restreindre à son tour.",
      "title": "Redelegation Allowed"
     },
     "resources": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 100,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Restrictions de ressources plus étroites, 100 au plus.",
      "title": "Resources"
     },
     "scopes": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 100,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Portées plus étroites, 100 au plus, couvertes par celles du parent.",
      "title": "Scopes"
     },
     "ttl_seconds": {
      "anyOf": [
       {
        "minimum": 60.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie en secondes, 60 au moins, jamais au-delà de l'expiration du parent.",
      "title": "Ttl Seconds"
     }
    },
    "required": [
     "delegate"
    ],
    "title": "RestrictIn",
    "type": "object"
   },
   "ResumeIn": {
    "description": "Façon de reprendre un run.",
    "properties": {
     "checkpoint_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Point de reprise de ce run d'où repartir. Par défaut : le dernier avec `from_last_checkpoint`.",
      "title": "Checkpoint Id"
     },
     "strategy": {
      "anyOf": [
       {
        "enum": [
         "from_last_checkpoint",
         "restart_step",
         "restart_run"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`from_last_checkpoint`, `restart_step` ou `restart_run`. Par défaut : `restart_step` pour un run en attente d'entrée, sinon `from_last_checkpoint`.",
      "title": "Strategy"
     }
    },
    "title": "ResumeIn",
    "type": "object"
   },
   "RetrievalIn": {
    "description": "Stratégie de recherche d'une base ; un champ omis prend sa valeur par défaut et les options d'une requête la remplacent.",
    "properties": {
     "candidates": {
      "anyOf": [
       {
        "maximum": 20.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Candidats par résultat demandé dans chaque liste (1 à 20, 4 par défaut).",
      "title": "Candidates"
     },
     "conflicts": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Calculer le signal de conflit possible. Vrai par défaut.",
      "title": "Conflicts"
     },
     "context_window": {
      "anyOf": [
       {
        "maximum": 5.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fragments voisins de chaque côté d'un passage (0 à 5, 0 par défaut).",
      "title": "Context Window"
     },
     "dedupe": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Écarter les passages quasi identiques. Vrai par défaut.",
      "title": "Dedupe"
     },
     "dedupe_threshold": {
      "anyOf": [
       {
        "maximum": 1.0,
        "minimum": 0.5,
        "type": "number"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seuil de quasi-identité, Jaccard des triplets de mots (0,5 à 1, 0,9 par défaut).",
      "title": "Dedupe Threshold"
     },
     "fusion": {
      "anyOf": [
       {
        "enum": [
         "rrf",
         "weighted"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fusion hybride : `rrf` (par défaut) ou `weighted`.",
      "title": "Fusion"
     },
     "mode": {
      "anyOf": [
       {
        "enum": [
         "vector",
         "dense",
         "lexical",
         "hybrid"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mode par défaut : `vector` (par défaut), `dense`, `lexical` ou `hybrid`.",
      "title": "Mode"
     },
     "rerank": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Reranking de chaque requête par défaut. Faux par défaut.",
      "title": "Rerank"
     },
     "rerank_candidates": {
      "anyOf": [
       {
        "maximum": 200.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Passages envoyés au reranking (1 à 200 ; `top_k` par défaut).",
      "title": "Rerank Candidates"
     },
     "rerank_model": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle du registre avec la capacité rerank (1 à 200 caractères ; par défaut : le modèle de reranking de la plateforme).",
      "title": "Rerank Model"
     },
     "rrf_k": {
      "anyOf": [
       {
        "maximum": 1000.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Constante `k` de la fusion par rang réciproque (1 à 1000, 60 par défaut).",
      "title": "Rrf K"
     },
     "token_budget": {
      "anyOf": [
       {
        "maximum": 32000.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Budget de jetons des passages et de leurs voisins (0 à 32000, 2000 par défaut).",
      "title": "Token Budget"
     }
    },
    "title": "RetrievalIn",
    "type": "object"
   },
   "RetryIn": {
    "description": "Relance d'un travail d'ingestion en échec.",
    "properties": {
     "reason": {
      "default": "",
      "description": "Raison de la relance (2000 caractères au plus), conservée dans l'historique. Vide par défaut.",
      "maxLength": 2000,
      "title": "Reason",
      "type": "string"
     }
    },
    "title": "RetryIn",
    "type": "object"
   },
   "ReviewIn": {
    "description": "Décision sur un document en quarantaine.",
    "properties": {
     "decision": {
      "description": "`release` (de nouveau recherchable) ou `reject`.",
      "enum": [
       "release",
       "reject"
      ],
      "title": "Decision",
      "type": "string"
     },
     "reason": {
      "default": "",
      "description": "Raison (2000 caractères au plus). Vide par défaut.",
      "maxLength": 2000,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "decision"
    ],
    "title": "ReviewIn",
    "type": "object"
   },
   "RevokeIn": {
    "description": "Raison de la révocation.",
    "properties": {
     "reason": {
      "default": "",
      "description": "Raison de la révocation, 2000 caractères au plus (vide par défaut). Inscrite dans le journal d'audit.",
      "maxLength": 2000,
      "title": "Reason",
      "type": "string"
     }
    },
    "title": "RevokeIn",
    "type": "object"
   },
   "RevokeKeyIn": {
    "description": "Révocation d'une clé de signature.",
    "properties": {
     "reason": {
      "description": "Raison de la révocation de la clé (3 à 2000 caractères).",
      "maxLength": 2000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "reason"
    ],
    "title": "RevokeKeyIn",
    "type": "object"
   },
   "RevokePrincipalIn": {
    "description": "Révocation de toutes les délégations d'un principal.",
    "properties": {
     "principal_id": {
      "description": "Principal dont les délégations sont révoquées, par exemple un sujet d'utilisateur, 1 à 300 caractères.",
      "maxLength": 300,
      "minLength": 1,
      "title": "Principal Id",
      "type": "string"
     },
     "reason": {
      "default": "",
      "description": "Raison, 2000 caractères au plus (vide par défaut). Inscrite dans le journal d'audit.",
      "maxLength": 2000,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "principal_id"
    ],
    "title": "RevokePrincipalIn",
    "type": "object"
   },
   "RotateIn": {
    "description": "Options de la rotation d'une clé d'API.",
    "properties": {
     "grace_seconds": {
      "default": 0,
      "description": "Secondes pendant lesquelles l'ancienne clé reste valide, de 0 à 604800 (7 jours). 0 (par défaut) la révoque immédiatement.",
      "maximum": 604800.0,
      "minimum": 0.0,
      "title": "Grace Seconds",
      "type": "integer"
     }
    },
    "title": "RotateIn",
    "type": "object"
   },
   "Rule": {
    "description": "Règle d'une politique intégrée.",
    "properties": {
     "actions": {
      "description": "Actions ciblées, motifs comme `model.*`. `[\"*\"]` par défaut.",
      "items": {
       "type": "string"
      },
      "title": "Actions",
      "type": "array"
     },
     "conditions": {
      "additionalProperties": true,
      "description": "Conditions qui doivent toutes être vraies pour que la règle s'applique : `classification_max`, `classification_min` (sur `context.data_classification`), `clearance_required`, `regions`, `roles`, `environments`, `cost_max`, `cost_min` (sur `context.cost_estimate`), `resource_attributes`, `subject_kinds`, `purposes`. Une condition inconnue n'est jamais vraie.",
      "title": "Conditions",
      "type": "object"
     },
     "effect": {
      "default": "allow",
      "description": "`allow` (par défaut) ou `deny`. Un refus qui s'applique l'emporte toujours.",
      "enum": [
       "allow",
       "deny"
      ],
      "title": "Effect",
      "type": "string"
     },
     "id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de la règle dans la politique, affiché dans les raisons.",
      "title": "Id"
     },
     "obligations": {
      "description": "Obligations ajoutées à la décision quand cette règle d'autorisation s'applique.",
      "items": {
       "$ref": "#/components/schemas/Obligation"
      },
      "title": "Obligations",
      "type": "array"
     },
     "reason": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison affichée dans la décision. Par défaut : `allowed by <policy>/<rule>` ou `denied by <policy>/<rule>`.",
      "title": "Reason"
     },
     "resource_types": {
      "description": "Types de ressources ciblés, motifs. Vide (par défaut) : tous les types.",
      "items": {
       "type": "string"
      },
      "title": "Resource Types",
      "type": "array"
     }
    },
    "title": "Rule",
    "type": "object"
   },
   "RuleFile": {
    "description": "Un fichier de dépôt à importer comme règle.",
    "properties": {
     "content": {
      "description": "Contenu du fichier (65536 caractères au plus).",
      "maxLength": 65536,
      "title": "Content",
      "type": "string"
     },
     "path": {
      "description": "Chemin du fichier dans le dépôt, par exemple `.cursor/rules/style.mdc` (1 à 500 caractères).",
      "maxLength": 500,
      "minLength": 1,
      "title": "Path",
      "type": "string"
     }
    },
    "required": [
     "path",
     "content"
    ],
    "title": "RuleFile",
    "type": "object"
   },
   "RulesImportIn": {
    "description": "Fichiers de règles à importer.",
    "properties": {
     "files": {
      "description": "Fichiers à importer (1 à 200).",
      "items": {
       "$ref": "#/components/schemas/RuleFile"
      },
      "maxItems": 200,
      "minItems": 1,
      "title": "Files",
      "type": "array"
     }
    },
    "required": [
     "files"
    ],
    "title": "RulesImportIn",
    "type": "object"
   },
   "SandboxIn": {
    "description": "Une commande à exécuter dans une sandbox isolée.",
    "properties": {
     "command": {
      "description": "Commande et arguments, un élément par argument.",
      "items": {
       "type": "string"
      },
      "title": "Command",
      "type": "array"
     },
     "env": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Variables d'environnement, valeurs textuelles.",
      "title": "Env",
      "type": "object"
     },
     "image": {
      "description": "Référence de l'image de conteneur (1 à 500 caractères, chemin de registre en minuscules, tag ou empreinte admis).",
      "maxLength": 500,
      "minLength": 1,
      "title": "Image",
      "type": "string"
     },
     "limits": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limites de ressources : `cpu` (`500m` par défaut, 4 cœurs au plus), `memory` (`512Mi` par défaut, `8Gi` au plus), `timeout_seconds` (1 à 3600, 300 par défaut), `gpu` (1 au plus), `max_steps`.",
      "title": "Limits"
     },
     "profile": {
      "default": "gvisor",
      "description": "Profil d'isolation : `gvisor` (par défaut), `kata`, `wasm` ou `none` (sans isolation, `tenant_admin` seulement).",
      "enum": [
       "gvisor",
       "kata",
       "wasm",
       "none"
      ],
      "title": "Profile",
      "type": "string"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Run auquel appartient l'exécution (doit exister) ; sert au comptage d'usage.",
      "title": "Run Id"
     }
    },
    "required": [
     "image"
    ],
    "title": "SandboxIn",
    "type": "object"
   },
   "ScheduleIn": {
    "description": "Une planification cron d'un agent.",
    "properties": {
     "agent_id": {
      "description": "Agent à exécuter (UUID ou nom).",
      "title": "Agent Id",
      "type": "string"
     },
     "cron": {
      "description": "Expression cron (1 à 200 caractères), évaluée dans `timezone`.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Cron",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Si faux, la planification est conservée mais ne lance aucun run. Vrai par défaut.",
      "title": "Enabled",
      "type": "boolean"
     },
     "input": {
      "additionalProperties": true,
      "description": "Entrée donnée à chaque run lancé par la planification.",
      "title": "Input",
      "type": "object"
     },
     "timezone": {
      "default": "UTC",
      "description": "Fuseau horaire IANA, par exemple `Europe/Paris`. `UTC` par défaut.",
      "title": "Timezone",
      "type": "string"
     }
    },
    "required": [
     "agent_id",
     "cron"
    ],
    "title": "ScheduleIn",
    "type": "object"
   },
   "SchedulePatch": {
    "description": "Champs d'une planification à modifier.",
    "properties": {
     "cron": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle expression cron.",
      "title": "Cron"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Active ou met en pause la planification.",
      "title": "Enabled"
     },
     "input": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle entrée des runs.",
      "title": "Input"
     },
     "timezone": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau fuseau horaire IANA.",
      "title": "Timezone"
     }
    },
    "title": "SchedulePatch",
    "type": "object"
   },
   "Scope": {
    "description": "Portée d'un élément de mémoire.",
    "properties": {
     "id": {
      "description": "Identifiant de la session, de l'agent, de l'utilisateur, du projet ou du tenant (1 à 200 caractères).",
      "maxLength": 200,
      "minLength": 1,
      "title": "Id",
      "type": "string"
     },
     "type": {
      "description": "`session`, `agent`, `user`, `project` ou `tenant`.",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type",
     "id"
    ],
    "title": "Scope",
    "type": "object"
   },
   "SearchIn": {
    "description": "Une recherche dans la mémoire.",
    "properties": {
     "kind": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement les éléments de ce type.",
      "title": "Kind"
     },
     "mode": {
      "default": "hybrid",
      "description": "`hybrid` (par défaut), `keyword` (sans appel d'embedding) ou `vector`.",
      "title": "Mode",
      "type": "string"
     },
     "query": {
      "description": "Texte de la requête (au moins 1 caractère).",
      "minLength": 1,
      "title": "Query",
      "type": "string"
     },
     "scope": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/Scope"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seulement les éléments de cette portée."
     },
     "top_k": {
      "default": 10,
      "description": "Nombre de résultats (1 à 100, 10 par défaut).",
      "maximum": 100.0,
      "minimum": 1.0,
      "title": "Top K",
      "type": "integer"
     }
    },
    "required": [
     "query"
    ],
    "title": "SearchIn",
    "type": "object"
   },
   "SecretIn": {
    "description": "Secret à écrire.",
    "properties": {
     "path": {
      "description": "Chemin du secret, 1 à 255 caractères parmi les lettres, les chiffres, `_`, `.`, `/` et `-`, sans commencer par `.`, `/` ou `-`.",
      "pattern": "^[A-Za-z0-9_][A-Za-z0-9_./-]{0,254}$",
      "title": "Path",
      "type": "string"
     },
     "value": {
      "description": "Valeur du secret, 1 à 65536 caractères. En écriture seule : chiffrée au repos, jamais renvoyée ni journalisée.",
      "maxLength": 65536,
      "minLength": 1,
      "title": "Value",
      "type": "string"
     }
    },
    "required": [
     "path",
     "value"
    ],
    "title": "SecretIn",
    "type": "object"
   },
   "ServerIn": {
    "description": "Un serveur MCP à enregistrer.",
    "properties": {
     "allowed_resources": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "URI de ressources qui peuvent être listées et lues (motifs). Par défaut `[\"*\"]`.",
      "title": "Allowed Resources"
     },
     "allowed_tools": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Outils qui peuvent être listés et appelés (motifs). Par défaut `[\"*\"]`.",
      "title": "Allowed Tools"
     },
     "auth": {
      "$ref": "#/components/schemas/McpAuth",
      "description": "Authentification amont. `none` par défaut."
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres, `.`, `_`, `-`, 1 à 128 caractères.",
      "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
      "title": "Name",
      "type": "string"
     },
     "owner": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire du serveur. Par défaut : l'appelant.",
      "title": "Owner"
     },
     "project": {
      "anyOf": [
       {
        "maxLength": 64,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant ou slug du projet auquel appartient le serveur (par défaut : l'en-tête `X-Project` ; aucun : partagé par le tenant).",
      "title": "Project"
     },
     "transport": {
      "default": "streamable_http",
      "description": "`streamable_http` (par défaut) ou `sse`.",
      "enum": [
       "streamable_http",
       "sse"
      ],
      "title": "Transport",
      "type": "string"
     },
     "url": {
      "description": "Point d'accès HTTP(S) du serveur sur un hôte interne (8 à 2000 caractères).",
      "maxLength": 2000,
      "minLength": 8,
      "title": "Url",
      "type": "string"
     }
    },
    "required": [
     "name",
     "url"
    ],
    "title": "ServerIn",
    "type": "object"
   },
   "ServerStatusIn": {
    "description": "Changement de statut d'un serveur MCP.",
    "properties": {
     "reason": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison du changement de statut (2000 caractères au plus).",
      "title": "Reason"
     },
     "status": {
      "description": "`active` (via une promotion) ou `disabled` (immédiat).",
      "enum": [
       "active",
       "disabled"
      ],
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "status"
    ],
    "title": "ServerStatusIn",
    "type": "object"
   },
   "ServiceIdentityIn": {
    "description": "Nouvelle identité de service (charge de travail) du tenant.",
    "properties": {
     "clearance": {
      "default": "internal",
      "description": "Classification de données la plus haute que l'identifiant peut lire : `public`, `internal` (par défaut), `confidential`, `restricted` ou `secret`.",
      "enum": [
       "public",
       "internal",
       "confidential",
       "restricted",
       "secret"
      ],
      "title": "Clearance",
      "type": "string"
     },
     "name": {
      "description": "Nom de l'identité, 1 à 200 caractères ; le sujet devient `service:<name>`.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "roles": {
      "description": "Rôles de l'identité. Uniquement des rôles détenus par l'appelant, sauf si celui-ci est `tenant_admin`.",
      "items": {
       "enum": [
        "platform_admin",
        "tenant_admin",
        "security_officer",
        "finops",
        "developer",
        "auditor",
        "agent",
        "viewer",
        "soc_analyst",
        "ai_governance_officer",
        "data_steward",
        "application_owner",
        "ai_platform_engineer",
        "business_owner",
        "support_engineer"
       ],
       "type": "string"
      },
      "title": "Roles",
      "type": "array"
     },
     "scopes": {
      "description": "Portées de l'identité, motifs détenus par l'appelant. Vide par défaut.",
      "items": {
       "type": "string"
      },
      "title": "Scopes",
      "type": "array"
     },
     "spiffe_id": {
      "description": "Identifiant SPIFFE de la charge de travail, `spiffe://<trust domain>/<path>`, unique sur la plateforme.",
      "pattern": "^spiffe://[A-Za-z0-9._-]+/.+$",
      "title": "Spiffe Id",
      "type": "string"
     }
    },
    "required": [
     "name",
     "spiffe_id"
    ],
    "title": "ServiceIdentityIn",
    "type": "object"
   },
   "SettingsIn": {
    "description": "Paramètres FinOps du tenant ; chaque champ est facultatif.",
    "properties": {
     "currency": {
      "anyOf": [
       {
        "pattern": "^[A-Z]{3}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Devise ISO 4217 dans laquelle les coûts du tenant sont tenus.",
      "title": "Currency"
     },
     "monthly_budget": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Budget mensuel du tenant, au moins 0 : la dépense mesurée du mois lui est comparée et des alertes de budget se déclenchent aux seuils (50, 80 et 100 pour cent par défaut). Il ne refuse pas d'appel par lui-même ; utilisez pour cela des quotas stricts.",
      "title": "Monthly Budget"
     },
     "reservation_ttl_seconds": {
      "anyOf": [
       {
        "exclusiveMinimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie par défaut d'une réservation de quota en secondes, supérieure à 0 (900 par défaut).",
      "title": "Reservation Ttl Seconds"
     },
     "variance_threshold_pct": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Écart entre coût mesuré et coût facturé, en pour cent, au-delà duquel une alerte d'écart est levée (10 par défaut).",
      "title": "Variance Threshold Pct"
     }
    },
    "title": "SettingsIn",
    "type": "object"
   },
   "SignatureIn": {
    "description": "Signature d'une archive de pack.",
    "properties": {
     "signer": {
      "description": "Nom de la clé d'éditeur qui a signé (1 à 200 caractères).",
      "maxLength": 200,
      "minLength": 1,
      "title": "Signer",
      "type": "string"
     },
     "value": {
      "description": "Valeur de signature ou bundle cosign (1 à 4096 caractères).",
      "maxLength": 4096,
      "minLength": 1,
      "title": "Value",
      "type": "string"
     }
    },
    "required": [
     "value",
     "signer"
    ],
    "title": "SignatureIn",
    "type": "object"
   },
   "SimulateIn": {
    "description": "Simulation d'une politique candidate.",
    "properties": {
     "candidate": {
      "$ref": "#/components/schemas/eai__domains__policies__PolicyIn",
      "description": "Politique candidate, moteur `builtin`."
     },
     "inputs": {
      "description": "Demandes Policy Contract `{subject, action, resource, context}` à décider, 200 au plus. Sans `subject`, l'appelant est utilisé.",
      "items": {
       "additionalProperties": true,
       "type": "object"
      },
      "maxItems": 200,
      "title": "Inputs",
      "type": "array"
     },
     "policy_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Politique que la candidate remplace ; null pour l'ajouter à l'ensemble actif.",
      "title": "Policy Id"
     },
     "recorded": {
      "default": 0,
      "description": "Rejouer aussi les N dernières décisions enregistrées, de 0 à 500 (0 par défaut).",
      "maximum": 500.0,
      "minimum": 0.0,
      "title": "Recorded",
      "type": "integer"
     }
    },
    "required": [
     "candidate"
    ],
    "title": "SimulateIn",
    "type": "object"
   },
   "SkillStatusIn": {
    "description": "Changement de statut d'un skill.",
    "properties": {
     "reason": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison du changement de statut (2000 caractères au plus).",
      "title": "Reason"
     },
     "status": {
      "description": "`draft`, `approved` (via une approbation), `restricted`, `deprecated` ou `blocked`.",
      "enum": [
       "draft",
       "approved",
       "restricted",
       "deprecated",
       "blocked"
      ],
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "status"
    ],
    "title": "SkillStatusIn",
    "type": "object"
   },
   "SshBootstrapIn": {
    "description": "Machines à enrôler par SSH.",
    "properties": {
     "host_key_fingerprints": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Empreinte attendue de la clé d'hôte SSH (`SHA256:...`) de chaque hôte, hôte vers empreinte, jusqu'à 200. Chaque hôte en demande une (le Manager répond 422 sinon) ; la tâche d'amorçage de l'hôte y est liée.",
      "maxProperties": 200,
      "title": "Host Key Fingerprints",
      "type": "object"
     },
     "hosts": {
      "description": "Noms ou adresses d'hôtes, de 1 à 200, chacun de 1 à 253 caractères (lettres, chiffres, points, tirets).",
      "items": {
       "maxLength": 253,
       "minLength": 1,
       "pattern": "^[A-Za-z0-9]([A-Za-z0-9.-]{0,252})$",
       "type": "string"
      },
      "maxItems": 200,
      "minItems": 1,
      "title": "Hosts",
      "type": "array"
     },
     "private_key": {
      "description": "Clé privée SSH, de 20 à 20000 caractères. Relayée au Manager, gardée en mémoire pour le seul amorçage ; jamais enregistrée ni auditée par le Control Plane.",
      "maxLength": 20000,
      "minLength": 20,
      "title": "Private Key",
      "type": "string"
     },
     "ssh_port": {
      "default": 22,
      "description": "Port SSH, de 1 à 65535, 22 par défaut.",
      "maximum": 65535.0,
      "minimum": 1.0,
      "title": "Ssh Port",
      "type": "integer"
     },
     "ssh_user": {
      "description": "Compte SSH, de 1 à 64 caractères, commençant par une lettre minuscule ou un souligné.",
      "maxLength": 64,
      "minLength": 1,
      "pattern": "^[a-z_][a-z0-9_.-]*$",
      "title": "Ssh User",
      "type": "string"
     },
     "use_sudo": {
      "default": true,
      "description": "Exécuter l'installation avec sudo (vrai par défaut).",
      "title": "Use Sudo",
      "type": "boolean"
     }
    },
    "required": [
     "hosts",
     "ssh_user",
     "private_key",
     "host_key_fingerprints"
    ],
    "title": "SshBootstrapIn",
    "type": "object"
   },
   "StepIn": {
    "description": "Une étape déclarée par le runtime d'un run.",
    "properties": {
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Demande approuvée qui autorise une étape soumise à validation humaine ; elle doit être approuvée pour ce run.",
      "title": "Approval Id"
     },
     "attempt": {
      "default": 1,
      "description": "Numéro de tentative, à partir de 1 (1 par défaut). Comparé à `retry_policy.max_attempts`.",
      "minimum": 1.0,
      "title": "Attempt",
      "type": "integer"
     },
     "cost": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Coût de l'étape : un montant, ou `{amount, currency}` dans la devise du budget du run.",
      "title": "Cost"
     },
     "error": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Erreur d'une étape en échec (`code`, `message`, `retryable`) ; le code décide si une nouvelle tentative est permise.",
      "title": "Error"
     },
     "final": {
      "default": false,
      "description": "Vrai si c'est la dernière étape du run. Faux par défaut.",
      "title": "Final",
      "type": "boolean"
     },
     "input_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de l'entrée de l'étape (stockée ailleurs, pas le contenu).",
      "title": "Input Ref"
     },
     "kind": {
      "description": "Type d'étape : `plan`, `act`, `verify`, `recover`, `model_call`, `tool_call`, `subagent`, `approval` ou `checkpoint`.",
      "enum": [
       "plan",
       "act",
       "verify",
       "recover",
       "model_call",
       "tool_call",
       "subagent",
       "approval",
       "checkpoint"
      ],
      "title": "Kind",
      "type": "string"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de la cible (outil, modèle, agent), comparé à `autonomy.human_approval_for` (500 caractères au plus).",
      "title": "Name"
     },
     "output_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de la sortie de l'étape.",
      "title": "Output Ref"
     },
     "quorum": {
      "anyOf": [
       {
        "maximum": 10.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre d'approbateurs requis par une étape de type `approval` (1 à 10, 1 par défaut).",
      "title": "Quorum"
     },
     "run_state": {
      "anyOf": [
       {
        "enum": [
         "running",
         "waiting_input",
         "succeeded",
         "failed"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "État demandé par le runtime quand le contrôleur garde le run en cours : `running`, `waiting_input`, `succeeded` ou `failed`.",
      "title": "Run State"
     },
     "state": {
      "default": "succeeded",
      "description": "État de l'étape : `pending`, `running`, `succeeded` (par défaut), `failed`, `skipped` ou `cancelled`.",
      "enum": [
       "pending",
       "running",
       "succeeded",
       "failed",
       "skipped",
       "cancelled"
      ],
      "title": "State",
      "type": "string"
     },
     "tokens": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons consommés (0 ou plus). Par défaut : la somme des jetons d'entrée, de sortie et de raisonnement de `usage`.",
      "title": "Tokens"
     },
     "usage": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Compteurs d'usage ajoutés à la consommation de budget, par exemple `input_tokens`, `output_tokens`.",
      "title": "Usage"
     }
    },
    "required": [
     "kind"
    ],
    "title": "StepIn",
    "type": "object"
   },
   "SttIn": {
    "description": "Une demande de transcription ou de diarisation. Fournissez `media_id` ou `content_base64`.",
    "properties": {
     "content_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Audio fourni directement, encodé en base64 (analysé avant usage).",
      "title": "Content Base64"
     },
     "dlp_mode": {
      "default": "detect",
      "description": "DLP appliquée à la transcription : `detect` (par défaut), `mask`, `tokenize` ou `block`.",
      "title": "Dlp Mode",
      "type": "string"
     },
     "duration_seconds": {
      "anyOf": [
       {
        "type": "number"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de l'audio en secondes, utilisée pour le comptage. Par défaut : lue dans un en-tête WAV, sinon 0.",
      "title": "Duration Seconds"
     },
     "language": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Langue parlée, par exemple `fr`. Par défaut : détectée par l'adaptateur.",
      "title": "Language"
     },
     "max_speakers": {
      "anyOf": [
       {
        "maximum": 32.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre maximal de locuteurs (1 à 32, diarisation seulement).",
      "title": "Max Speakers"
     },
     "media_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Média audio enregistré.",
      "title": "Media Id"
     },
     "mime_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de média de l'audio fourni, par exemple `audio/wav`.",
      "title": "Mime Type"
     },
     "min_speakers": {
      "anyOf": [
       {
        "maximum": 32.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre minimal de locuteurs (1 à 32, diarisation seulement).",
      "title": "Min Speakers"
     },
     "model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle à utiliser. Par défaut : l'adaptateur configuré.",
      "title": "Model"
     },
     "word_timestamps": {
      "default": false,
      "description": "Renvoie un horodatage par mot (`stt` seulement). Faux par défaut.",
      "title": "Word Timestamps",
      "type": "boolean"
     }
    },
    "title": "SttIn",
    "type": "object"
   },
   "SubagentIn": {
    "description": "Un run de sous-agent à lancer.",
    "properties": {
     "agent": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent enfant, nom ou `agent:<nom>`. Par défaut : l'agent du run parent.",
      "title": "Agent"
     },
     "budgets": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limites de budget de l'enfant, plafonnées par ce qu'il reste au parent.",
      "title": "Budgets"
     },
     "start": {
      "default": false,
      "description": "Si vrai, le run enfant démarre en `running`. Faux par défaut (`pending`).",
      "title": "Start",
      "type": "boolean"
     },
     "task": {
      "additionalProperties": true,
      "description": "Tâche du run enfant (`task_id`, `objective`, `input`).",
      "title": "Task",
      "type": "object"
     }
    },
    "title": "SubagentIn",
    "type": "object"
   },
   "SuiteIn": {
    "description": "Nouvelle suite d'évaluation.",
    "properties": {
     "blocking": {
      "default": true,
      "description": "Si vrai (par défaut), la suite participe à la barrière de qualité de sa cible.",
      "title": "Blocking",
      "type": "boolean"
     },
     "cases": {
      "description": "Cas de la suite : objets avec `input`, `system` facultatif, les valeurs attendues des métriques et éventuellement une `output` déjà prête.",
      "items": {
       "additionalProperties": true,
       "type": "object"
      },
      "title": "Cases",
      "type": "array"
     },
     "dataset": {
      "$ref": "#/components/schemas/Dataset",
      "description": "Référence du jeu de données des cas."
     },
     "engine": {
      "default": "custom",
      "description": "`ragas`, `promptfoo`, `garak` ou `custom` (par défaut).",
      "title": "Engine",
      "type": "string"
     },
     "kind": {
      "default": "quality",
      "description": "`quality` (par défaut), `regression`, `red_team`, `safety`, `cost` ou `latency`.",
      "title": "Kind",
      "type": "string"
     },
     "metrics": {
      "description": "Métriques et seuils, une au moins.",
      "items": {
       "$ref": "#/components/schemas/Metric"
      },
      "minItems": 1,
      "title": "Metrics",
      "type": "array"
     },
     "name": {
      "description": "Nom unique de la suite dans le tenant, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "run_on_promotion": {
      "default": true,
      "description": "Si vrai (par défaut), la suite s'exécute à la promotion de sa cible.",
      "title": "Run On Promotion",
      "type": "boolean"
     },
     "target": {
      "$ref": "#/components/schemas/eai__domains__evaluation__Target",
      "description": "Cible évaluée : `model`, `agent`, `prompt`, `rag_pipeline`, `skill` ou `tool`."
     }
    },
    "required": [
     "name",
     "target",
     "dataset",
     "metrics"
    ],
    "title": "SuiteIn",
    "type": "object"
   },
   "SyncIn": {
    "description": "Options d'une exécution de synchronisation.",
    "properties": {
     "full": {
      "default": false,
      "description": "Si vrai, ignore le curseur de la dernière exécution et synchronise tout. Faux par défaut.",
      "title": "Full",
      "type": "boolean"
     },
     "resources": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Ressources à synchroniser, parmi celles lisibles. Par défaut : toutes les ressources lisibles.",
      "title": "Resources"
     }
    },
    "title": "SyncIn",
    "type": "object"
   },
   "TelephonyAgentIn": {
    "description": "Un agent humain du tenant.",
    "properties": {
     "display_name": {
      "description": "Nom de l'agent.",
      "maxLength": 80,
      "minLength": 1,
      "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
      "title": "Display Name",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "extension_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Extension du tenant sur laquelle l'agent répond.",
      "title": "Extension Id"
     },
     "skills": {
      "description": "Compétences de l'agent (50 au plus).",
      "items": {
       "type": "string"
      },
      "maxItems": 50,
      "title": "Skills",
      "type": "array"
     },
     "user_id": {
      "description": "Identifiant de l'utilisateur dans la plateforme, unique dans le tenant.",
      "maxLength": 200,
      "minLength": 1,
      "title": "User Id",
      "type": "string"
     }
    },
    "required": [
     "user_id",
     "display_name"
    ],
    "title": "TelephonyAgentIn",
    "type": "object"
   },
   "TelephonyAgentPatch": {
    "description": "Champs à modifier : un agent humain du tenant. Les champs omis gardent leur valeur.",
    "properties": {
     "display_name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'agent.",
      "title": "Display Name"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled"
     },
     "extension_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Extension du tenant sur laquelle l'agent répond.",
      "title": "Extension Id"
     },
     "skills": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Compétences de l'agent (50 au plus).",
      "title": "Skills"
     },
     "user_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant de l'utilisateur dans la plateforme, unique dans le tenant.",
      "title": "User Id"
     }
    },
    "title": "TelephonyAgentPatch",
    "type": "object"
   },
   "TelephonyBlockIn": {
    "description": "Un blocage manuel du tenant ou d'une extension.",
    "properties": {
     "extension": {
      "anyOf": [
       {
        "pattern": "^[0-9]{2,8}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro d'extension, obligatoire pour la portée `extension` seulement.",
      "title": "Extension"
     },
     "reason": {
      "description": "Motif conservé avec le blocage et dans la piste d'audit.",
      "maxLength": 300,
      "minLength": 1,
      "title": "Reason",
      "type": "string"
     },
     "scope": {
      "description": "`tenant` (tous les appels du tenant) ou `extension`.",
      "enum": [
       "tenant",
       "extension"
      ],
      "title": "Scope",
      "type": "string"
     }
    },
    "required": [
     "scope",
     "reason"
    ],
    "title": "TelephonyBlockIn",
    "type": "object"
   },
   "TelephonyConferenceIn": {
    "description": "Une salle de conférence du tenant.",
    "properties": {
     "max_members": {
      "default": 10,
      "description": "Participants au plus, 2 à 500 (10 par défaut).",
      "maximum": 500.0,
      "minimum": 2.0,
      "title": "Max Members",
      "type": "integer"
     },
     "name": {
      "description": "Nom de la salle.",
      "maxLength": 80,
      "minLength": 1,
      "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
      "title": "Name",
      "type": "string"
     },
     "number": {
      "description": "Numéro de la salle, 2 à 8 chiffres, unique dans le tenant.",
      "pattern": "^[0-9]{2,8}$",
      "title": "Number",
      "type": "string"
     },
     "pin": {
      "anyOf": [
       {
        "pattern": "^[0-9]{4,8}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Code PIN, 4 à 8 chiffres. En écriture seule (`has_pin`).",
      "title": "Pin"
     },
     "recording": {
      "default": false,
      "description": "Indique si la conférence est enregistrée.",
      "title": "Recording",
      "type": "boolean"
     }
    },
    "required": [
     "number",
     "name"
    ],
    "title": "TelephonyConferenceIn",
    "type": "object"
   },
   "TelephonyConferencePatch": {
    "description": "Champs à modifier : une salle de conférence du tenant. Les champs omis gardent leur valeur.",
    "properties": {
     "max_members": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Participants au plus, 2 à 500 (10 par défaut).",
      "title": "Max Members"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de la salle.",
      "title": "Name"
     },
     "number": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro de la salle, 2 à 8 chiffres, unique dans le tenant.",
      "title": "Number"
     },
     "pin": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Code PIN, 4 à 8 chiffres. En écriture seule (`has_pin`).",
      "title": "Pin"
     },
     "recording": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si la conférence est enregistrée.",
      "title": "Recording"
     }
    },
    "title": "TelephonyConferencePatch",
    "type": "object"
   },
   "TelephonyDeleteIn": {
    "description": "Motif d'une suppression, conservé dans la piste d'audit.",
    "properties": {
     "reason": {
      "default": "user request",
      "description": "Motif (`user request` par défaut).",
      "maxLength": 300,
      "minLength": 1,
      "title": "Reason",
      "type": "string"
     }
    },
    "title": "TelephonyDeleteIn",
    "type": "object"
   },
   "TelephonyDrainIn": {
    "description": "Une destination du dispatcher et son état.",
    "properties": {
     "address": {
      "description": "URI SIP de la destination, par exemple `sip:10.0.0.20:5070`.",
      "pattern": "^sip:[A-Za-z0-9.:;=-]{1,200}$",
      "title": "Address",
      "type": "string"
     },
     "group": {
      "anyOf": [
       {
        "maximum": 65535.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jeu du dispatcher (par défaut : le jeu du pool).",
      "title": "Group"
     },
     "state": {
      "description": "`drain` (aucun nouvel appel) ou `active`.",
      "enum": [
       "drain",
       "active"
      ],
      "title": "State",
      "type": "string"
     }
    },
    "required": [
     "address",
     "state"
    ],
    "title": "TelephonyDrainIn",
    "type": "object"
   },
   "TelephonyExtensionIn": {
    "description": "Une extension du tenant.",
    "properties": {
     "display_name": {
      "description": "Nom affiché comme identité de l'appelant.",
      "maxLength": 80,
      "minLength": 1,
      "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
      "title": "Display Name",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "forwarding": {
      "$ref": "#/components/schemas/TelephonyForwarding",
      "description": "Règles de renvoi d'appel."
     },
     "kind": {
      "default": "sip",
      "description": "`sip` (téléphone avec mot de passe) ou `webrtc` (softphone du portail, identifiants éphémères).",
      "enum": [
       "sip",
       "webrtc"
      ],
      "title": "Kind",
      "type": "string"
     },
     "max_concurrent_calls": {
      "default": 2,
      "description": "Appels simultanés de l'extension, 1 à 100 (2 par défaut), appliqués par le dialplan.",
      "maximum": 100.0,
      "minimum": 1.0,
      "title": "Max Concurrent Calls",
      "type": "integer"
     },
     "number": {
      "description": "Numéro d'extension, 2 à 8 chiffres, unique dans le tenant.",
      "pattern": "^[0-9]{2,8}$",
      "title": "Number",
      "type": "string"
     },
     "password": {
      "anyOf": [
       {
        "maxLength": 128,
        "minLength": 12,
        "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mot de passe SIP, 12 à 128 caractères, obligatoire pour `sip`. En écriture seule : conservé dans le coffre, jamais renvoyé (`has_password`).",
      "title": "Password"
     },
     "permissions": {
      "$ref": "#/components/schemas/TelephonyPermissions",
      "description": "Destinations que l'extension peut appeler."
     },
     "recording_policy": {
      "default": "off",
      "description": "Enregistrement : `off` (par défaut), `always`, `inbound` ou `outbound` (MixMonitor dans le dossier du tenant).",
      "enum": [
       "off",
       "always",
       "inbound",
       "outbound"
      ],
      "title": "Recording Policy",
      "type": "string"
     },
     "voicemail_email": {
      "anyOf": [
       {
        "maxLength": 200,
        "pattern": "^[^@\\s,;]+@[^@\\s,;]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse prévenue des nouveaux messages.",
      "title": "Voicemail Email"
     },
     "voicemail_enabled": {
      "default": false,
      "description": "Indique si l'extension a une messagerie vocale (demande un code PIN).",
      "title": "Voicemail Enabled",
      "type": "boolean"
     },
     "voicemail_pin": {
      "anyOf": [
       {
        "pattern": "^[0-9]{4,8}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Code PIN de messagerie, 4 à 8 chiffres. En écriture seule (`has_voicemail_pin`).",
      "title": "Voicemail Pin"
     }
    },
    "required": [
     "number",
     "display_name"
    ],
    "title": "TelephonyExtensionIn",
    "type": "object"
   },
   "TelephonyExtensionPatch": {
    "description": "Champs à modifier : une extension du tenant. Les champs omis gardent leur valeur.",
    "properties": {
     "display_name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom affiché comme identité de l'appelant.",
      "title": "Display Name"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled"
     },
     "forwarding": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/TelephonyForwarding"
       },
       {
        "type": "null"
       }
      ],
      "description": "Règles de renvoi d'appel."
     },
     "kind": {
      "anyOf": [
       {
        "enum": [
         "sip",
         "webrtc"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`sip` (téléphone avec mot de passe) ou `webrtc` (softphone du portail, identifiants éphémères).",
      "title": "Kind"
     },
     "max_concurrent_calls": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Appels simultanés de l'extension, 1 à 100 (2 par défaut), appliqués par le dialplan.",
      "title": "Max Concurrent Calls"
     },
     "number": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro d'extension, 2 à 8 chiffres, unique dans le tenant.",
      "title": "Number"
     },
     "password": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mot de passe SIP, 12 à 128 caractères, obligatoire pour `sip`. En écriture seule : conservé dans le coffre, jamais renvoyé (`has_password`).",
      "title": "Password"
     },
     "permissions": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/TelephonyPermissions"
       },
       {
        "type": "null"
       }
      ],
      "description": "Destinations que l'extension peut appeler."
     },
     "recording_policy": {
      "anyOf": [
       {
        "enum": [
         "off",
         "always",
         "inbound",
         "outbound"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Enregistrement : `off` (par défaut), `always`, `inbound` ou `outbound` (MixMonitor dans le dossier du tenant).",
      "title": "Recording Policy"
     },
     "voicemail_email": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse prévenue des nouveaux messages.",
      "title": "Voicemail Email"
     },
     "voicemail_enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'extension a une messagerie vocale (demande un code PIN).",
      "title": "Voicemail Enabled"
     },
     "voicemail_pin": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Code PIN de messagerie, 4 à 8 chiffres. En écriture seule (`has_voicemail_pin`).",
      "title": "Voicemail Pin"
     }
    },
    "title": "TelephonyExtensionPatch",
    "type": "object"
   },
   "TelephonyForwarding": {
    "description": "Renvoi d'appel d'une extension.",
    "properties": {
     "always": {
      "anyOf": [
       {
        "pattern": "^([0-9]{2,8}|\\+[1-9][0-9]{6,14})$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cible du renvoi inconditionnel : un numéro d'extension ou un numéro E.164.",
      "title": "Always"
     },
     "busy": {
      "anyOf": [
       {
        "pattern": "^([0-9]{2,8}|\\+[1-9][0-9]{6,14})$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cible quand l'extension est occupée.",
      "title": "Busy"
     },
     "no_answer": {
      "anyOf": [
       {
        "pattern": "^([0-9]{2,8}|\\+[1-9][0-9]{6,14})$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cible quand l'extension ne répond pas.",
      "title": "No Answer"
     },
     "no_answer_seconds": {
      "default": 25,
      "description": "Durée de sonnerie avant le cas sans réponse, 5 à 120 secondes (25 par défaut).",
      "maximum": 120.0,
      "minimum": 5.0,
      "title": "No Answer Seconds",
      "type": "integer"
     }
    },
    "title": "TelephonyForwarding",
    "type": "object"
   },
   "TelephonyInventorySyncIn": {
    "description": "L'instance Asterisk du Navin Manager dont le pool PBX est créé ou rafraîchi.",
    "properties": {
     "cluster_id": {
      "description": "Identifiant du cluster du Manager.",
      "maxLength": 64,
      "minLength": 1,
      "pattern": "^[A-Za-z0-9_-]{1,64}$",
      "title": "Cluster Id",
      "type": "string"
     },
     "kamailio_service": {
      "anyOf": [
       {
        "pattern": "^[a-z][a-z0-9_-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instance Kamailio à utiliser (par défaut : celles liées à cet Asterisk).",
      "title": "Kamailio Service"
     },
     "livekit_service": {
      "anyOf": [
       {
        "pattern": "^[a-z][a-z0-9_-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instance LiveKit à utiliser (par défaut : celle à laquelle LiveKit SIP est lié, sinon la seule).",
      "title": "Livekit Service"
     },
     "livekit_sip_service": {
      "anyOf": [
       {
        "pattern": "^[a-z][a-z0-9_-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instance LiveKit SIP à utiliser (par défaut : celle à laquelle Kamailio est lié, sinon la seule).",
      "title": "Livekit Sip Service"
     },
     "mode": {
      "default": "shared",
      "description": "`shared` (par défaut) ou `dedicated` pour un nouveau pool.",
      "enum": [
       "shared",
       "dedicated"
      ],
      "title": "Mode",
      "type": "string"
     },
     "name": {
      "anyOf": [
       {
        "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom d'un nouveau pool (par défaut : le nom de l'instance) ; ignoré lors d'un rafraîchissement.",
      "title": "Name"
     },
     "service": {
      "description": "Nom de l'instance Asterisk dans le Manager (par exemple `asterisk`).",
      "pattern": "^[a-z][a-z0-9_-]{0,62}$",
      "title": "Service",
      "type": "string"
     },
     "tenant_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Tenant d'un nouveau pool dédié.",
      "title": "Tenant Id"
     }
    },
    "required": [
     "cluster_id",
     "service"
    ],
    "title": "TelephonyInventorySyncIn",
    "type": "object"
   },
   "TelephonyIvrFlowIn": {
    "description": "Un flux SVI du tenant.",
    "properties": {
     "description": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description du flux.",
      "title": "Description"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "TelephonyIvrFlowIn",
    "type": "object"
   },
   "TelephonyIvrFlowPatch": {
    "description": "Champs à modifier : un flux SVI du tenant. Les champs omis gardent leur valeur.",
    "properties": {
     "description": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description du flux.",
      "title": "Description"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "title": "Name"
     }
    },
    "title": "TelephonyIvrFlowPatch",
    "type": "object"
   },
   "TelephonyIvrVersionIn": {
    "description": "Une version brouillon d'un flux SVI.",
    "properties": {
     "graph": {
      "additionalProperties": true,
      "description": "Graphe du flux : `start` et `nodes` ; les nœuds menu portent `prompt`, `options` (`digit`, `next`), `timeout`, `invalid`, `timeout_seconds` et `max_retries`.",
      "title": "Graph",
      "type": "object"
     }
    },
    "required": [
     "graph"
    ],
    "title": "TelephonyIvrVersionIn",
    "type": "object"
   },
   "TelephonyKamailioEndpoint": {
    "description": "Un point d'accès JSONRPC Kamailio.",
    "properties": {
     "name": {
      "description": "Nom de l'instance Kamailio.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     },
     "url": {
      "description": "Adresse JSONRPC, par exemple `http://10.0.0.10:5071/jsonrpc`.",
      "pattern": "^https?://[^\\s]+$",
      "title": "Url",
      "type": "string"
     }
    },
    "required": [
     "name",
     "url"
    ],
    "title": "TelephonyKamailioEndpoint",
    "type": "object"
   },
   "TelephonyLegalHoldIn": {
    "description": "Conservation légale d'un enregistrement.",
    "properties": {
     "legal_hold": {
      "description": "true : conserver l'enregistrement ; false : lever la conservation.",
      "title": "Legal Hold",
      "type": "boolean"
     },
     "reason": {
      "description": "Motif conservé dans la piste d'audit (300 caractères au plus).",
      "maxLength": 300,
      "minLength": 1,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "legal_hold",
     "reason"
    ],
    "title": "TelephonyLegalHoldIn",
    "type": "object"
   },
   "TelephonyLiftIn": {
    "description": "Pourquoi un blocage est levé.",
    "properties": {
     "reason": {
      "description": "Motif conservé avec le blocage et dans la piste d'audit.",
      "maxLength": 300,
      "minLength": 1,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "reason"
    ],
    "title": "TelephonyLiftIn",
    "type": "object"
   },
   "TelephonyLiveKitBinding": {
    "description": "Liaison LiveKit d'un pool.",
    "properties": {
     "sip_host": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9.:-]{1,253}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse SIP de LiveKit SIP qu'Asterisk compose, par exemple `10.0.0.30:5090`.",
      "title": "Sip Host"
     },
     "url": {
      "anyOf": [
       {
        "pattern": "^https?://[^\\s]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse HTTP de LiveKit pour l'API Twirp SIP (aucune : les objets LiveKit ne sont pas réconciliés).",
      "title": "Url"
     }
    },
    "title": "TelephonyLiveKitBinding",
    "type": "object"
   },
   "TelephonyMemberIn": {
    "description": "Un membre d'une file.",
    "properties": {
     "agent_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent humain du tenant, joint sur son extension.",
      "title": "Agent Id"
     },
     "extension_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Extension du tenant (ou `agent_id`, pas les deux).",
      "title": "Extension Id"
     },
     "penalty": {
      "default": 0,
      "description": "Pénalité de 0 à 100, la plus petite sonne en premier.",
      "maximum": 100.0,
      "minimum": 0.0,
      "title": "Penalty",
      "type": "integer"
     }
    },
    "title": "TelephonyMemberIn",
    "type": "object"
   },
   "TelephonyMemberPatch": {
    "description": "Nouvelle pénalité d'un membre de file.",
    "properties": {
     "penalty": {
      "description": "Nouvelle pénalité, 0 à 100.",
      "maximum": 100.0,
      "minimum": 0.0,
      "title": "Penalty",
      "type": "integer"
     }
    },
    "required": [
     "penalty"
    ],
    "title": "TelephonyMemberPatch",
    "type": "object"
   },
   "TelephonyNumberIn": {
    "description": "Un numéro public du tenant et sa destination.",
    "properties": {
     "branch": {
      "default": "pbx",
      "description": "`pbx` (Kamailio vers Asterisk, par défaut) ou `ai` (Kamailio directement vers LiveKit SIP, agent vocal seulement).",
      "enum": [
       "pbx",
       "ai"
      ],
      "title": "Branch",
      "type": "string"
     },
     "destination_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant (UUID) de l'objet de destination dans le tenant (tous les types sauf `external`).",
      "title": "Destination Id"
     },
     "destination_kind": {
      "description": "Type de destination : `extension`, `ivr`, `queue`, `voice_agent`, `conference` ou `external`.",
      "enum": [
       "extension",
       "ivr",
       "queue",
       "voice_agent",
       "external",
       "conference"
      ],
      "title": "Destination Kind",
      "type": "string"
     },
     "destination_value": {
      "anyOf": [
       {
        "pattern": "^\\+[1-9][0-9]{6,14}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro E.164 d'une destination `external`.",
      "title": "Destination Value"
     },
     "e164": {
      "description": "Numéro public au format E.164, unique sur la plateforme.",
      "pattern": "^\\+[1-9][0-9]{6,14}$",
      "title": "E164",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "label": {
      "anyOf": [
       {
        "maxLength": 80,
        "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Libellé pour l'affichage.",
      "title": "Label"
     },
     "trunk_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Trunk du tenant d'où vient le numéro.",
      "title": "Trunk Id"
     }
    },
    "required": [
     "e164",
     "destination_kind"
    ],
    "title": "TelephonyNumberIn",
    "type": "object"
   },
   "TelephonyNumberPatch": {
    "description": "Champs à modifier : un numéro public du tenant et sa destination. Les champs omis gardent leur valeur.",
    "properties": {
     "branch": {
      "anyOf": [
       {
        "enum": [
         "pbx",
         "ai"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`pbx` (Kamailio vers Asterisk, par défaut) ou `ai` (Kamailio directement vers LiveKit SIP, agent vocal seulement).",
      "title": "Branch"
     },
     "destination_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant (UUID) de l'objet de destination dans le tenant (tous les types sauf `external`).",
      "title": "Destination Id"
     },
     "destination_kind": {
      "anyOf": [
       {
        "enum": [
         "extension",
         "ivr",
         "queue",
         "voice_agent",
         "external",
         "conference"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de destination : `extension`, `ivr`, `queue`, `voice_agent`, `conference` ou `external`.",
      "title": "Destination Kind"
     },
     "destination_value": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro E.164 d'une destination `external`.",
      "title": "Destination Value"
     },
     "e164": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro public au format E.164, unique sur la plateforme.",
      "title": "E164"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled"
     },
     "label": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Libellé pour l'affichage.",
      "title": "Label"
     },
     "trunk_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Trunk du tenant d'où vient le numéro.",
      "title": "Trunk Id"
     }
    },
    "title": "TelephonyNumberPatch",
    "type": "object"
   },
   "TelephonyNumberingSettings": {
    "description": "Plan de numérotation et options du tenant.",
    "properties": {
     "country_code": {
      "default": "33",
      "description": "Indicatif du pays du tenant, par exemple `33` (par défaut).",
      "pattern": "^[1-9][0-9]{0,3}$",
      "title": "Country Code",
      "type": "string"
     },
     "emergency_numbers": {
      "description": "Numéros d'urgence, toujours permis, routés sur chaque trunk (`112` par défaut).",
      "items": {
       "type": "string"
      },
      "maxItems": 10,
      "title": "Emergency Numbers",
      "type": "array"
     },
     "international_prefix": {
      "default": "00",
      "description": "Préfixe international (`00` par défaut).",
      "pattern": "^[0-9]{1,4}$",
      "title": "International Prefix",
      "type": "string"
     },
     "national_prefix": {
      "default": "0",
      "description": "Préfixe national composé devant les numéros nationaux (`0` par défaut).",
      "pattern": "^[0-9]{0,2}$",
      "title": "National Prefix",
      "type": "string"
     },
     "timezone": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z_]+(/[A-Za-z_+-]+){0,2}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Fuseau horaire des conditions horaires, par exemple `Europe/Paris`.",
      "title": "Timezone"
     },
     "wss_url": {
      "anyOf": [
       {
        "pattern": "^wss://[^\\s]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse WSS de Kamailio pour le softphone (`wss://<domaine SIP>:7443` par défaut).",
      "title": "Wss Url"
     }
    },
    "title": "TelephonyNumberingSettings",
    "type": "object"
   },
   "TelephonyPermissions": {
    "description": "Destinations qu'une extension peut appeler.",
    "properties": {
     "internal": {
      "default": true,
      "description": "Appels vers les autres extensions du tenant (true par défaut).",
      "title": "Internal",
      "type": "boolean"
     },
     "international": {
      "default": false,
      "description": "Appels vers d'autres pays, limités aux préfixes de pays autorisés des quotas (false par défaut).",
      "title": "International",
      "type": "boolean"
     },
     "national": {
      "default": true,
      "description": "Appels vers les numéros nationaux (true par défaut).",
      "title": "National",
      "type": "boolean"
     },
     "premium": {
      "default": false,
      "description": "Appels vers les préfixes surtaxés des quotas (false par défaut).",
      "title": "Premium",
      "type": "boolean"
     }
    },
    "title": "TelephonyPermissions",
    "type": "object"
   },
   "TelephonyPoolIn": {
    "description": "Un pool PBX installé par le Navin Manager.",
    "properties": {
     "ami_events_secret_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret de l'utilisateur AMI des événements en lecture seule : `openbao://<chemin>` ou `env://EAI_CRED_<NOM>`.",
      "title": "Ami Events Secret Ref"
     },
     "ami_events_username": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9_.-]{1,64}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur AMI en lecture seule de la connexion d'événements (classes call, cdr, cel, agent, reporting, user) ; aucun : l'utilisateur de configuration.",
      "title": "Ami Events Username"
     },
     "ami_secret_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret AMI : `openbao://<chemin>` ou `env://EAI_CRED_<NOM>`.",
      "title": "Ami Secret Ref"
     },
     "ami_tls": {
      "default": false,
      "description": "Indique si l'AMI est jointe en TLS.",
      "title": "Ami Tls",
      "type": "boolean"
     },
     "ami_username": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9_.-]{1,64}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur AMI du pool.",
      "title": "Ami Username"
     },
     "ari_secret_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret ARI.",
      "title": "Ari Secret Ref"
     },
     "ari_username": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9_.-]{1,64}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur ARI du pool.",
      "title": "Ari Username"
     },
     "dispatcher_set": {
      "default": 1,
      "description": "Jeu du dispatcher du pool (1 pour le pool partagé, 100 et plus pour les pools dédiés).",
      "maximum": 65535.0,
      "minimum": 1.0,
      "title": "Dispatcher Set",
      "type": "integer"
     },
     "enabled": {
      "default": true,
      "description": "Indique si le pool est réconcilié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "ephemeral_secret_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret partagé avec `auth_ephemeral` de Kamailio.",
      "title": "Ephemeral Secret Ref"
     },
     "kamailio_addresses": {
      "description": "Adresses des instances Kamailio : les seules sources que les extensions acceptent.",
      "items": {
       "type": "string"
      },
      "maxItems": 16,
      "title": "Kamailio Addresses",
      "type": "array"
     },
     "kamailio_endpoints": {
      "description": "Points d'accès JSONRPC Kamailio du pool (8 au plus).",
      "items": {
       "$ref": "#/components/schemas/TelephonyKamailioEndpoint"
      },
      "maxItems": 8,
      "title": "Kamailio Endpoints",
      "type": "array"
     },
     "kamailio_secret_ref": {
      "anyOf": [
       {
        "pattern": "^(openbao|env)://.+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de l'identifiant JSONRPC `utilisateur:mot de passe` (aucune : sans authentification).",
      "title": "Kamailio Secret Ref"
     },
     "livekit_binding": {
      "$ref": "#/components/schemas/TelephonyLiveKitBinding",
      "description": "Liaison LiveKit du pool."
     },
     "livekit_sip_set": {
      "default": 2,
      "description": "Jeu du dispatcher de LiveKit SIP (2 par défaut).",
      "maximum": 65535.0,
      "minimum": 1.0,
      "title": "Livekit Sip Set",
      "type": "integer"
     },
     "manager_cluster": {
      "anyOf": [
       {
        "maxLength": 120,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cluster du pool dans le Navin Manager.",
      "title": "Manager Cluster"
     },
     "manager_service": {
      "default": "asterisk",
      "description": "Service du catalogue du Navin Manager (`asterisk` par défaut).",
      "maxLength": 120,
      "title": "Manager Service",
      "type": "string"
     },
     "mode": {
      "description": "`shared` (plusieurs tenants) ou `dedicated` (un tenant).",
      "enum": [
       "shared",
       "dedicated"
      ],
      "title": "Mode",
      "type": "string"
     },
     "name": {
      "description": "Nom du pool, unique sur la plateforme.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
      "title": "Name",
      "type": "string"
     },
     "nodes": {
      "description": "Nœuds Asterisk du pool (32 au plus) : chacun reçoit chaque publication.",
      "items": {
       "$ref": "#/components/schemas/TelephonyPoolNode"
      },
      "maxItems": 32,
      "title": "Nodes",
      "type": "array"
     },
     "tenant_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Tenant d'un pool dédié (aucun pour un pool partagé).",
      "title": "Tenant Id"
     },
     "tls_verify": {
      "default": true,
      "description": "Indique si les certificats TLS des moteurs sont vérifiés (true par défaut).",
      "title": "Tls Verify",
      "type": "boolean"
     }
    },
    "required": [
     "name",
     "mode"
    ],
    "title": "TelephonyPoolIn",
    "type": "object"
   },
   "TelephonyPoolNode": {
    "description": "Un nœud Asterisk d'un pool.",
    "properties": {
     "ami_port": {
      "default": 5038,
      "description": "Port AMI (5038 par défaut, 5039 avec TLS).",
      "maximum": 65535.0,
      "minimum": 1.0,
      "title": "Ami Port",
      "type": "integer"
     },
     "ari_url": {
      "anyOf": [
       {
        "pattern": "^https?://[^\\s]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URL de base de l'ARI du nœud, par exemple `https://10.0.0.20:8089` (par défaut `http://<hôte>:8088`) ; le chemin `/ari` est ajouté.",
      "title": "Ari Url"
     },
     "host": {
      "description": "Adresse d'administration du nœud (AMI).",
      "pattern": "^[A-Za-z0-9.:-]{1,253}$",
      "title": "Host",
      "type": "string"
     },
     "manager_instance": {
      "anyOf": [
       {
        "maxLength": 120,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'instance de service dans le Navin Manager.",
      "title": "Manager Instance"
     },
     "name": {
      "description": "Nom du nœud.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     },
     "sip_address": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9.:-]{1,253}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresse SIP du nœud, autorisée par les trunks LiveKit SIP.",
      "title": "Sip Address"
     }
    },
    "required": [
     "name",
     "host"
    ],
    "title": "TelephonyPoolNode",
    "type": "object"
   },
   "TelephonyPoolPatch": {
    "description": "Champs à modifier : un pool PBX installé par le Navin Manager. Les champs omis gardent leur valeur.",
    "properties": {
     "ami_events_secret_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret de l'utilisateur AMI des événements en lecture seule : `openbao://<chemin>` ou `env://EAI_CRED_<NOM>`.",
      "title": "Ami Events Secret Ref"
     },
     "ami_events_username": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur AMI en lecture seule de la connexion d'événements (classes call, cdr, cel, agent, reporting, user) ; aucun : l'utilisateur de configuration.",
      "title": "Ami Events Username"
     },
     "ami_secret_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret AMI : `openbao://<chemin>` ou `env://EAI_CRED_<NOM>`.",
      "title": "Ami Secret Ref"
     },
     "ami_tls": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'AMI est jointe en TLS.",
      "title": "Ami Tls"
     },
     "ami_username": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur AMI du pool.",
      "title": "Ami Username"
     },
     "ari_secret_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret ARI.",
      "title": "Ari Secret Ref"
     },
     "ari_username": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur ARI du pool.",
      "title": "Ari Username"
     },
     "dispatcher_set": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jeu du dispatcher du pool (1 pour le pool partagé, 100 et plus pour les pools dédiés).",
      "title": "Dispatcher Set"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si le pool est réconcilié (true par défaut).",
      "title": "Enabled"
     },
     "ephemeral_secret_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence du secret partagé avec `auth_ephemeral` de Kamailio.",
      "title": "Ephemeral Secret Ref"
     },
     "kamailio_addresses": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Adresses des instances Kamailio : les seules sources que les extensions acceptent.",
      "title": "Kamailio Addresses"
     },
     "kamailio_endpoints": {
      "anyOf": [
       {
        "items": {
         "$ref": "#/components/schemas/TelephonyKamailioEndpoint"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Points d'accès JSONRPC Kamailio du pool (8 au plus).",
      "title": "Kamailio Endpoints"
     },
     "kamailio_secret_ref": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Référence de l'identifiant JSONRPC `utilisateur:mot de passe` (aucune : sans authentification).",
      "title": "Kamailio Secret Ref"
     },
     "livekit_binding": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/TelephonyLiveKitBinding"
       },
       {
        "type": "null"
       }
      ],
      "description": "Liaison LiveKit du pool."
     },
     "livekit_sip_set": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jeu du dispatcher de LiveKit SIP (2 par défaut).",
      "title": "Livekit Sip Set"
     },
     "manager_cluster": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cluster du pool dans le Navin Manager.",
      "title": "Manager Cluster"
     },
     "manager_service": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Service du catalogue du Navin Manager (`asterisk` par défaut).",
      "title": "Manager Service"
     },
     "mode": {
      "anyOf": [
       {
        "enum": [
         "shared",
         "dedicated"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`shared` (plusieurs tenants) ou `dedicated` (un tenant).",
      "title": "Mode"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom du pool, unique sur la plateforme.",
      "title": "Name"
     },
     "nodes": {
      "anyOf": [
       {
        "items": {
         "$ref": "#/components/schemas/TelephonyPoolNode"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nœuds Asterisk du pool (32 au plus) : chacun reçoit chaque publication.",
      "title": "Nodes"
     },
     "tenant_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Tenant d'un pool dédié (aucun pour un pool partagé).",
      "title": "Tenant Id"
     },
     "tls_verify": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si les certificats TLS des moteurs sont vérifiés (true par défaut).",
      "title": "Tls Verify"
     }
    },
    "title": "TelephonyPoolPatch",
    "type": "object"
   },
   "TelephonyPublishIn": {
    "description": "Une demande de publication.",
    "properties": {
     "reason": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison, conservée avec la version et dans l'audit.",
      "title": "Reason"
     }
    },
    "title": "TelephonyPublishIn",
    "type": "object"
   },
   "TelephonyQueueIn": {
    "description": "Une file d'appels du tenant.",
    "properties": {
     "display_name": {
      "anyOf": [
       {
        "maxLength": 80,
        "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom pour l'affichage.",
      "title": "Display Name"
     },
     "max_wait_seconds": {
      "default": 300,
      "description": "Attente maximale d'un appelant, 0 à 86400 secondes (300 par défaut).",
      "maximum": 86400.0,
      "minimum": 0.0,
      "title": "Max Wait Seconds",
      "type": "integer"
     },
     "music_on_hold": {
      "default": "default",
      "description": "Classe de musique d'attente (`default` par défaut).",
      "pattern": "^[a-z0-9][a-z0-9_-]{0,40}$",
      "title": "Music On Hold",
      "type": "string"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     },
     "number": {
      "anyOf": [
       {
        "pattern": "^[0-9]{2,8}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro interne qui joint la file (facultatif).",
      "title": "Number"
     },
     "recording": {
      "default": false,
      "description": "Indique si les appels de la file sont enregistrés.",
      "title": "Recording",
      "type": "boolean"
     },
     "sla_seconds": {
      "default": 20,
      "description": "Seuil de niveau de service, 1 à 3600 secondes (20 par défaut).",
      "maximum": 3600.0,
      "minimum": 1.0,
      "title": "Sla Seconds",
      "type": "integer"
     },
     "strategy": {
      "default": "ringall",
      "description": "Stratégie de sonnerie : ringall, leastrecent, fewestcalls, random, rrmemory, linear ou wrandom.",
      "enum": [
       "ringall",
       "leastrecent",
       "fewestcalls",
       "random",
       "rrmemory",
       "linear",
       "wrandom"
      ],
      "title": "Strategy",
      "type": "string"
     },
     "timeout_seconds": {
      "default": 15,
      "description": "Durée de sonnerie par membre, 1 à 600 secondes (15 par défaut).",
      "maximum": 600.0,
      "minimum": 1.0,
      "title": "Timeout Seconds",
      "type": "integer"
     },
     "wrapup_seconds": {
      "default": 0,
      "description": "Pause d'un membre après un appel, 0 à 3600 secondes.",
      "maximum": 3600.0,
      "minimum": 0.0,
      "title": "Wrapup Seconds",
      "type": "integer"
     }
    },
    "required": [
     "name"
    ],
    "title": "TelephonyQueueIn",
    "type": "object"
   },
   "TelephonyQueuePatch": {
    "description": "Champs à modifier : une file d'appels du tenant. Les champs omis gardent leur valeur.",
    "properties": {
     "display_name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom pour l'affichage.",
      "title": "Display Name"
     },
     "max_wait_seconds": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Attente maximale d'un appelant, 0 à 86400 secondes (300 par défaut).",
      "title": "Max Wait Seconds"
     },
     "music_on_hold": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classe de musique d'attente (`default` par défaut).",
      "title": "Music On Hold"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "title": "Name"
     },
     "number": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro interne qui joint la file (facultatif).",
      "title": "Number"
     },
     "recording": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si les appels de la file sont enregistrés.",
      "title": "Recording"
     },
     "sla_seconds": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Seuil de niveau de service, 1 à 3600 secondes (20 par défaut).",
      "title": "Sla Seconds"
     },
     "strategy": {
      "anyOf": [
       {
        "enum": [
         "ringall",
         "leastrecent",
         "fewestcalls",
         "random",
         "rrmemory",
         "linear",
         "wrandom"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Stratégie de sonnerie : ringall, leastrecent, fewestcalls, random, rrmemory, linear ou wrandom.",
      "title": "Strategy"
     },
     "timeout_seconds": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de sonnerie par membre, 1 à 600 secondes (15 par défaut).",
      "title": "Timeout Seconds"
     },
     "wrapup_seconds": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Pause d'un membre après un appel, 0 à 3600 secondes.",
      "title": "Wrapup Seconds"
     }
    },
    "title": "TelephonyQueuePatch",
    "type": "object"
   },
   "TelephonyQuotasIn": {
    "description": "Quotas et limites anti-fraude du tenant.",
    "properties": {
     "allowed_country_prefixes": {
      "description": "Préfixes internationaux permis, par exemple `+32` (aucun : tous les pays pour les extensions qui ont l'autorisation internationale).",
      "items": {
       "type": "string"
      },
      "maxItems": 300,
      "title": "Allowed Country Prefixes",
      "type": "array"
     },
     "blocked_premium_prefixes": {
      "description": "Préfixes surtaxés, par exemple `+3389` : seules les extensions avec l'autorisation surtaxée les joignent.",
      "items": {
       "type": "string"
      },
      "maxItems": 300,
      "title": "Blocked Premium Prefixes",
      "type": "array"
     },
     "cps": {
      "default": 2,
      "description": "Nouveaux appels par seconde, appliqués par Kamailio.",
      "maximum": 10000.0,
      "minimum": 0.0,
      "title": "Cps",
      "type": "integer"
     },
     "max_call_duration_seconds": {
      "default": 7200,
      "description": "Durée maximale d'un appel, 60 à 86400 secondes (7200 par défaut).",
      "maximum": 86400.0,
      "minimum": 60.0,
      "title": "Max Call Duration Seconds",
      "type": "integer"
     },
     "max_concurrent_calls": {
      "default": 10,
      "description": "Appels simultanés du tenant (`tenant_limits` de Kamailio et dialplan).",
      "maximum": 100000.0,
      "minimum": 0.0,
      "title": "Max Concurrent Calls",
      "type": "integer"
     },
     "monthly_minutes": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Minutes par mois (aucune valeur : illimité).",
      "title": "Monthly Minutes"
     },
     "recording_retention_days": {
      "default": 90,
      "description": "Conservation des enregistrements, 1 à 3650 jours (90 par défaut).",
      "maximum": 3650.0,
      "minimum": 1.0,
      "title": "Recording Retention Days",
      "type": "integer"
     }
    },
    "title": "TelephonyQuotasIn",
    "type": "object"
   },
   "TelephonyRollbackIn": {
    "description": "Une demande de retour arrière.",
    "properties": {
     "reason": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison, conservée avec la version et dans l'audit.",
      "title": "Reason"
     },
     "version": {
      "description": "Numéro de la version à publier à nouveau.",
      "minimum": 1.0,
      "title": "Version",
      "type": "integer"
     }
    },
    "required": [
     "version"
    ],
    "title": "TelephonyRollbackIn",
    "type": "object"
   },
   "TelephonyRuleIn": {
    "description": "Une règle de routage du tenant.",
    "properties": {
     "destination_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant (UUID) de l'objet de destination dans le tenant (tous les types sauf `external`).",
      "title": "Destination Id"
     },
     "destination_kind": {
      "anyOf": [
       {
        "enum": [
         "extension",
         "ivr",
         "queue",
         "voice_agent",
         "external",
         "conference"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de destination : `extension`, `ivr`, `queue`, `voice_agent`, `conference` ou `external`.",
      "title": "Destination Kind"
     },
     "destination_value": {
      "anyOf": [
       {
        "pattern": "^\\+[1-9][0-9]{6,14}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro E.164 d'une destination `external`.",
      "title": "Destination Value"
     },
     "direction": {
      "description": "`inbound` (DID appelé) ou `outbound` (numéro E.164 composé).",
      "enum": [
       "inbound",
       "outbound"
      ],
      "title": "Direction",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     },
     "pattern": {
      "description": "Motif Asterisk sur le numéro E.164, par exemple `_+33X.`.",
      "pattern": "^_?[0-9XZN.+\\[\\]-]{1,40}$",
      "title": "Pattern",
      "type": "string"
     },
     "prepend": {
      "default": "",
      "description": "Chiffres ajoutés devant le numéro à la numérotation.",
      "pattern": "^\\+?[0-9]{0,10}$",
      "title": "Prepend",
      "type": "string"
     },
     "priority": {
      "default": 100,
      "description": "Ordre des règles, le plus petit d'abord (100 par défaut).",
      "maximum": 10000.0,
      "minimum": 0.0,
      "title": "Priority",
      "type": "integer"
     },
     "strip_digits": {
      "default": 0,
      "description": "Chiffres retirés au début du numéro avant la numérotation (0 à 10).",
      "maximum": 10.0,
      "minimum": 0.0,
      "title": "Strip Digits",
      "type": "integer"
     },
     "time_conditions": {
      "description": "Plages horaires où la règle s'applique (aucune : toujours).",
      "items": {
       "$ref": "#/components/schemas/TelephonyTimeWindow"
      },
      "maxItems": 20,
      "title": "Time Conditions",
      "type": "array"
     },
     "trunk_ids": {
      "description": "Sortant : trunks du tenant essayés dans cet ordre en cas d'échec (aucun : tous les trunks par `failover_order`).",
      "items": {
       "format": "uuid",
       "type": "string"
      },
      "maxItems": 10,
      "title": "Trunk Ids",
      "type": "array"
     }
    },
    "required": [
     "name",
     "direction",
     "pattern"
    ],
    "title": "TelephonyRuleIn",
    "type": "object"
   },
   "TelephonyRulePatch": {
    "description": "Champs à modifier : une règle de routage du tenant. Les champs omis gardent leur valeur.",
    "properties": {
     "destination_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant (UUID) de l'objet de destination dans le tenant (tous les types sauf `external`).",
      "title": "Destination Id"
     },
     "destination_kind": {
      "anyOf": [
       {
        "enum": [
         "extension",
         "ivr",
         "queue",
         "voice_agent",
         "external",
         "conference"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de destination : `extension`, `ivr`, `queue`, `voice_agent`, `conference` ou `external`.",
      "title": "Destination Kind"
     },
     "destination_value": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro E.164 d'une destination `external`.",
      "title": "Destination Value"
     },
     "direction": {
      "anyOf": [
       {
        "enum": [
         "inbound",
         "outbound"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`inbound` (DID appelé) ou `outbound` (numéro E.164 composé).",
      "title": "Direction"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "title": "Name"
     },
     "pattern": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Motif Asterisk sur le numéro E.164, par exemple `_+33X.`.",
      "title": "Pattern"
     },
     "prepend": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Chiffres ajoutés devant le numéro à la numérotation.",
      "title": "Prepend"
     },
     "priority": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Ordre des règles, le plus petit d'abord (100 par défaut).",
      "title": "Priority"
     },
     "strip_digits": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Chiffres retirés au début du numéro avant la numérotation (0 à 10).",
      "title": "Strip Digits"
     },
     "time_conditions": {
      "anyOf": [
       {
        "items": {
         "$ref": "#/components/schemas/TelephonyTimeWindow"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Plages horaires où la règle s'applique (aucune : toujours).",
      "title": "Time Conditions"
     },
     "trunk_ids": {
      "anyOf": [
       {
        "items": {
         "format": "uuid",
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sortant : trunks du tenant essayés dans cet ordre en cas d'échec (aucun : tous les trunks par `failover_order`).",
      "title": "Trunk Ids"
     }
    },
    "title": "TelephonyRulePatch",
    "type": "object"
   },
   "TelephonyRuntimeSettingsIn": {
    "description": "Réglages d'exécution de la téléphonie du tenant.",
    "properties": {
     "fraud_auto_block": {
      "default": true,
      "description": "Indique si les signaux de fraude bloquent automatiquement (true par défaut).",
      "title": "Fraud Auto Block",
      "type": "boolean"
     },
     "fraud_extension_hourly_calls": {
      "default": 60,
      "description": "Appels sortants d'une extension en une heure au-delà desquels elle est signalée et bloquée (60 par défaut).",
      "maximum": 100000.0,
      "minimum": 1.0,
      "title": "Fraud Extension Hourly Calls",
      "type": "integer"
     },
     "fraud_refused_attempts": {
      "default": 5,
      "description": "Destinations refusées d'une extension qui la bloquent, 1 à 1000 (5 par défaut).",
      "maximum": 1000.0,
      "minimum": 1.0,
      "title": "Fraud Refused Attempts",
      "type": "integer"
     },
     "fraud_tenant_block_factor": {
      "default": 2,
      "description": "Le tenant est bloqué quand ses appels actifs atteignent ce multiple de son quota ; 0 désactive (2 par défaut).",
      "maximum": 100.0,
      "minimum": 0.0,
      "title": "Fraud Tenant Block Factor",
      "type": "integer"
     },
     "fraud_window_minutes": {
      "default": 10,
      "description": "Fenêtre des destinations refusées, 1 à 1440 minutes (10 par défaut).",
      "maximum": 1440.0,
      "minimum": 1.0,
      "title": "Fraud Window Minutes",
      "type": "integer"
     },
     "voicemail_max_seconds": {
      "default": 180,
      "description": "Message de la messagerie de la plateforme le plus long, 10 à 600 secondes (180 par défaut).",
      "maximum": 600.0,
      "minimum": 10.0,
      "title": "Voicemail Max Seconds",
      "type": "integer"
     },
     "voicemail_storage": {
      "default": "asterisk",
      "description": "`asterisk` (app_voicemail garde les messages sur le PBX) ou `platform` (enregistrés et rangés dans SeaweedFS, listés par l'API).",
      "enum": [
       "asterisk",
       "platform"
      ],
      "title": "Voicemail Storage",
      "type": "string"
     }
    },
    "title": "TelephonyRuntimeSettingsIn",
    "type": "object"
   },
   "TelephonySettingsIn": {
    "description": "Paramètres de téléphonie du tenant.",
    "properties": {
     "cdr_retention_days": {
      "default": 365,
      "description": "Conservation des enregistrements d'appels, 1 à 3650 jours (365 par défaut).",
      "maximum": 3650.0,
      "minimum": 1.0,
      "title": "Cdr Retention Days",
      "type": "integer"
     },
     "enabled": {
      "default": true,
      "description": "Indique si le tenant est compilé dans son pool (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "model": {
      "default": "shared",
      "description": "`shared` (pool partagé par plusieurs tenants, par défaut) ou `dedicated`. Changé par `platform_admin`.",
      "enum": [
       "shared",
       "dedicated"
      ],
      "title": "Model",
      "type": "string"
     },
     "pbx_instance_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Pool PBX qui sert le tenant. Changé par `platform_admin`.",
      "title": "Pbx Instance Id"
     },
     "settings": {
      "$ref": "#/components/schemas/TelephonyNumberingSettings",
      "description": "Plan de numérotation et options."
     },
     "sip_domain": {
      "description": "Domaine SIP du tenant, unique sur la plateforme ; Kamailio le relie au tenant.",
      "pattern": "^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?)+$",
      "title": "Sip Domain",
      "type": "string"
     }
    },
    "required": [
     "sip_domain"
    ],
    "title": "TelephonySettingsIn",
    "type": "object"
   },
   "TelephonyTimeWindow": {
    "description": "Une plage horaire en syntaxe Asterisk.",
    "properties": {
     "days": {
      "default": "*",
      "description": "Jours : `*` ou `mon` à `sun`, ou une plage comme `mon-fri` (`*` par défaut).",
      "pattern": "^(\\*|[a-z]{3}(-[a-z]{3})?)$",
      "title": "Days",
      "type": "string"
     },
     "end": {
      "description": "Heure de fin, HH:MM, après le début.",
      "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
      "title": "End",
      "type": "string"
     },
     "mdays": {
      "default": "*",
      "description": "Jours du mois : `*` ou une plage comme `1-15` (`*` par défaut).",
      "pattern": "^(\\*|[0-9]{1,2}(-[0-9]{1,2})?)$",
      "title": "Mdays",
      "type": "string"
     },
     "months": {
      "default": "*",
      "description": "Mois : `*` ou `jan` à `dec`, ou une plage (`*` par défaut).",
      "pattern": "^(\\*|[a-z]{3}(-[a-z]{3})?)$",
      "title": "Months",
      "type": "string"
     },
     "start": {
      "description": "Heure de début, HH:MM.",
      "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
      "title": "Start",
      "type": "string"
     }
    },
    "required": [
     "start",
     "end"
    ],
    "title": "TelephonyTimeWindow",
    "type": "object"
   },
   "TelephonyTransferIn": {
    "description": "Transfert d'un appel actif.",
    "properties": {
     "target": {
      "description": "Numéro d'extension du tenant (2 à 8 chiffres) ou numéro E.164.",
      "pattern": "^([0-9]{2,8}|\\+[1-9][0-9]{6,14})$",
      "title": "Target",
      "type": "string"
     },
     "uniqueid": {
      "anyOf": [
       {
        "maxLength": 64,
        "pattern": "^[A-Za-z0-9._-]{1,64}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Canal à déplacer (par défaut : le premier canal de l'appel).",
      "title": "Uniqueid"
     }
    },
    "required": [
     "target"
    ],
    "title": "TelephonyTransferIn",
    "type": "object"
   },
   "TelephonyTrunkIn": {
    "description": "Un trunk SIP du tenant vers un opérateur.",
    "properties": {
     "allowed_numbers": {
      "description": "Numéros E.164 que le trunk peut présenter (200 au plus).",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Allowed Numbers",
      "type": "array"
     },
     "auth_username": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9_.+-]{1,64}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom d'utilisateur de l'authentification digest auprès de l'opérateur.",
      "title": "Auth Username"
     },
     "codecs": {
      "description": "Codecs par ordre de préférence parmi ulaw, alaw, g722, g729, opus et gsm (ulaw, alaw par défaut).",
      "items": {
       "type": "string"
      },
      "maxItems": 6,
      "title": "Codecs",
      "type": "array"
     },
     "enabled": {
      "default": true,
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "failover_order": {
      "default": 100,
      "description": "Ordre du trunk quand une règle ne désigne aucun trunk (le plus petit d'abord, 100 par défaut).",
      "maximum": 10000.0,
      "minimum": 0.0,
      "title": "Failover Order",
      "type": "integer"
     },
     "host": {
      "description": "Nom d'hôte ou adresse du serveur SIP de l'opérateur.",
      "pattern": "^[A-Za-z0-9.:-]{1,253}$",
      "title": "Host",
      "type": "string"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     },
     "outbound_caller_id": {
      "anyOf": [
       {
        "pattern": "^\\+[1-9][0-9]{6,14}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro E.164 présenté sur les appels passant par ce trunk.",
      "title": "Outbound Caller Id"
     },
     "password": {
      "anyOf": [
       {
        "maxLength": 128,
        "minLength": 8,
        "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mot de passe du trunk, 8 à 128 caractères. En écriture seule : conservé dans le coffre, jamais renvoyé (`has_password`).",
      "title": "Password"
     },
     "port": {
      "default": 5060,
      "description": "Port SIP de l'opérateur (5060 par défaut).",
      "maximum": 65535.0,
      "minimum": 1.0,
      "title": "Port",
      "type": "integer"
     },
     "provider": {
      "anyOf": [
       {
        "maxLength": 80,
        "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'opérateur, pour l'affichage.",
      "title": "Provider"
     },
     "registration": {
      "default": false,
      "description": "Indique si Asterisk s'enregistre auprès de l'opérateur (demande `auth_username`).",
      "title": "Registration",
      "type": "boolean"
     },
     "transport": {
      "default": "udp",
      "description": "Transport SIP : `udp` (par défaut), `tcp` ou `tls`.",
      "enum": [
       "udp",
       "tcp",
       "tls"
      ],
      "title": "Transport",
      "type": "string"
     }
    },
    "required": [
     "name",
     "host"
    ],
    "title": "TelephonyTrunkIn",
    "type": "object"
   },
   "TelephonyTrunkPatch": {
    "description": "Champs à modifier : un trunk SIP du tenant vers un opérateur. Les champs omis gardent leur valeur.",
    "properties": {
     "allowed_numbers": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéros E.164 que le trunk peut présenter (200 au plus).",
      "title": "Allowed Numbers"
     },
     "auth_username": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom d'utilisateur de l'authentification digest auprès de l'opérateur.",
      "title": "Auth Username"
     },
     "codecs": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Codecs par ordre de préférence parmi ulaw, alaw, g722, g729, opus et gsm (ulaw, alaw par défaut).",
      "title": "Codecs"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled"
     },
     "failover_order": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Ordre du trunk quand une règle ne désigne aucun trunk (le plus petit d'abord, 100 par défaut).",
      "title": "Failover Order"
     },
     "host": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom d'hôte ou adresse du serveur SIP de l'opérateur.",
      "title": "Host"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "title": "Name"
     },
     "outbound_caller_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Numéro E.164 présenté sur les appels passant par ce trunk.",
      "title": "Outbound Caller Id"
     },
     "password": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Mot de passe du trunk, 8 à 128 caractères. En écriture seule : conservé dans le coffre, jamais renvoyé (`has_password`).",
      "title": "Password"
     },
     "port": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Port SIP de l'opérateur (5060 par défaut).",
      "title": "Port"
     },
     "provider": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de l'opérateur, pour l'affichage.",
      "title": "Provider"
     },
     "registration": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si Asterisk s'enregistre auprès de l'opérateur (demande `auth_username`).",
      "title": "Registration"
     },
     "transport": {
      "anyOf": [
       {
        "enum": [
         "udp",
         "tcp",
         "tls"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Transport SIP : `udp` (par défaut), `tcp` ou `tls`.",
      "title": "Transport"
     }
    },
    "title": "TelephonyTrunkPatch",
    "type": "object"
   },
   "TelephonyVoiceAgentIn": {
    "description": "Un agent vocal IA du tenant, exécuté par les workers LiveKit Agents `navin-voice`.",
    "properties": {
     "allowed_tools": {
      "description": "Outils que l'agent peut appeler (100 au plus).",
      "items": {
       "type": "string"
      },
      "maxItems": 100,
      "title": "Allowed Tools",
      "type": "array"
     },
     "display_name": {
      "anyOf": [
       {
        "maxLength": 80,
        "pattern": "^[^\\x00-\\x1f\\[\\];]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom pour l'affichage.",
      "title": "Display Name"
     },
     "enabled": {
      "default": true,
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "fallback_queue_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "File du tenant qui prend l'appel quand l'agent échoue.",
      "title": "Fallback Queue Id"
     },
     "greeting": {
      "anyOf": [
       {
        "maxLength": 1000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Première phrase dite à l'appelant.",
      "title": "Greeting"
     },
     "instructions": {
      "default": "",
      "description": "Instructions de l'agent (prompt système), 20000 caractères au plus.",
      "maxLength": 20000,
      "title": "Instructions",
      "type": "string"
     },
     "language": {
      "default": "fr-FR",
      "description": "Langue, par exemple `fr-FR` (par défaut).",
      "pattern": "^[a-z]{2}(-[A-Z]{2})?$",
      "title": "Language",
      "type": "string"
     },
     "llm_model_alias": {
      "description": "Alias du modèle de langage dans la passerelle LLM.",
      "maxLength": 120,
      "minLength": 1,
      "title": "Llm Model Alias",
      "type": "string"
     },
     "max_duration_seconds": {
      "default": 900,
      "description": "Durée maximale d'un appel, 30 à 14400 secondes (900 par défaut).",
      "maximum": 14400.0,
      "minimum": 30.0,
      "title": "Max Duration Seconds",
      "type": "integer"
     },
     "name": {
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
      "title": "Name",
      "type": "string"
     },
     "stt_model": {
      "anyOf": [
       {
        "maxLength": 120,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle de reconnaissance vocale.",
      "title": "Stt Model"
     },
     "transfer_target": {
      "anyOf": [
       {
        "maxLength": 60,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Transfert vers un humain : `extension:<numéro>`, `queue:<nom>` ou `number:<E.164>`.",
      "title": "Transfer Target"
     },
     "tts_model": {
      "anyOf": [
       {
        "maxLength": 120,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle de synthèse vocale.",
      "title": "Tts Model"
     },
     "tts_voice": {
      "anyOf": [
       {
        "maxLength": 120,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Voix du modèle de synthèse vocale.",
      "title": "Tts Voice"
     }
    },
    "required": [
     "name",
     "llm_model_alias"
    ],
    "title": "TelephonyVoiceAgentIn",
    "type": "object"
   },
   "TelephonyVoiceAgentPatch": {
    "description": "Champs à modifier : un agent vocal IA du tenant, exécuté par les workers LiveKit Agents `navin-voice`. Les champs omis gardent leur valeur.",
    "properties": {
     "allowed_tools": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Outils que l'agent peut appeler (100 au plus).",
      "title": "Allowed Tools"
     },
     "display_name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom pour l'affichage.",
      "title": "Display Name"
     },
     "enabled": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si l'objet est compilé et publié (true par défaut).",
      "title": "Enabled"
     },
     "fallback_queue_id": {
      "anyOf": [
       {
        "format": "uuid",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "File du tenant qui prend l'appel quand l'agent échoue.",
      "title": "Fallback Queue Id"
     },
     "greeting": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Première phrase dite à l'appelant.",
      "title": "Greeting"
     },
     "instructions": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instructions de l'agent (prompt système), 20000 caractères au plus.",
      "title": "Instructions"
     },
     "language": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Langue, par exemple `fr-FR` (par défaut).",
      "title": "Language"
     },
     "llm_model_alias": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Alias du modèle de langage dans la passerelle LLM.",
      "title": "Llm Model Alias"
     },
     "max_duration_seconds": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée maximale d'un appel, 30 à 14400 secondes (900 par défaut).",
      "title": "Max Duration Seconds"
     },
     "name": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom, unique dans le tenant : minuscules, chiffres et `-`, 1 à 41 caractères ; il entre dans les noms sur les moteurs.",
      "title": "Name"
     },
     "stt_model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle de reconnaissance vocale.",
      "title": "Stt Model"
     },
     "transfer_target": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Transfert vers un humain : `extension:<numéro>`, `queue:<nom>` ou `number:<E.164>`.",
      "title": "Transfer Target"
     },
     "tts_model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle de synthèse vocale.",
      "title": "Tts Model"
     },
     "tts_voice": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Voix du modèle de synthèse vocale.",
      "title": "Tts Voice"
     }
    },
    "title": "TelephonyVoiceAgentPatch",
    "type": "object"
   },
   "TelephonyWebrtcIn": {
    "description": "Une demande d'identifiants WebRTC.",
    "properties": {
     "extension_id": {
      "description": "Extension WebRTC du tenant.",
      "format": "uuid",
      "title": "Extension Id",
      "type": "string"
     },
     "ttl_seconds": {
      "default": 3600,
      "description": "Durée de vie des identifiants, 60 à 3600 secondes (3600 par défaut).",
      "maximum": 3600.0,
      "minimum": 60.0,
      "title": "Ttl Seconds",
      "type": "integer"
     }
    },
    "required": [
     "extension_id"
    ],
    "title": "TelephonyWebrtcIn",
    "type": "object"
   },
   "TelephonyWorkerEventIn": {
    "description": "Un événement d'appel signalé par un worker vocal.",
    "properties": {
     "data": {
      "additionalProperties": true,
      "description": "Détails libres de l'événement (ni audio ni transcription).",
      "title": "Data",
      "type": "object"
     },
     "occurred_at": {
      "anyOf": [
       {
        "format": "date-time",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Instant de l'événement (ISO 8601, maintenant par défaut).",
      "title": "Occurred At"
     },
     "type": {
      "description": "`started`, `answered`, `transfer_requested`, `ended` ou `error`.",
      "enum": [
       "started",
       "answered",
       "transfer_requested",
       "ended",
       "error"
      ],
      "title": "Type",
      "type": "string"
     },
     "voice_agent_id": {
      "description": "Agent vocal de l'appel.",
      "format": "uuid",
      "title": "Voice Agent Id",
      "type": "string"
     }
    },
    "required": [
     "type",
     "voice_agent_id"
    ],
    "title": "TelephonyWorkerEventIn",
    "type": "object"
   },
   "TelephonyWorkerToolIn": {
    "description": "Arguments d'un outil de recherche appelé par un agent vocal.",
    "properties": {
     "arguments": {
      "additionalProperties": true,
      "description": "Arguments de l'outil, vérifiés par son schéma d'entrée.",
      "title": "Arguments",
      "type": "object"
     },
     "voice_agent_id": {
      "description": "Agent vocal de l'appel.",
      "format": "uuid",
      "title": "Voice Agent Id",
      "type": "string"
     }
    },
    "required": [
     "voice_agent_id"
    ],
    "title": "TelephonyWorkerToolIn",
    "type": "object"
   },
   "TelephonyWorkerTransferIn": {
    "description": "Transfert du participant SIP d'un appel demandé par son agent vocal.",
    "properties": {
     "reason": {
      "default": "",
      "description": "Pourquoi l'agent transfère (500 caractères au plus).",
      "maxLength": 500,
      "title": "Reason",
      "type": "string"
     },
     "target_uri": {
      "description": "URI cible (sip:, sips: ou tel:), la cible de transfert de la configuration d'exécution.",
      "pattern": "^(sip|sips|tel):[^\\s]{1,256}$",
      "title": "Target Uri",
      "type": "string"
     },
     "voice_agent_id": {
      "description": "Agent vocal de l'appel.",
      "format": "uuid",
      "title": "Voice Agent Id",
      "type": "string"
     }
    },
    "required": [
     "voice_agent_id",
     "target_uri"
    ],
    "title": "TelephonyWorkerTransferIn",
    "type": "object"
   },
   "TenantIn": {
    "description": "Nouveau tenant.",
    "properties": {
     "admins": {
      "description": "Sujets OIDC nommés `tenant_admin` du nouveau tenant.",
      "items": {
       "type": "string"
      },
      "title": "Admins",
      "type": "array"
     },
     "data_residency": {
      "default": "eu",
      "description": "Région où les données du tenant doivent rester, par exemple `eu` (par défaut).",
      "title": "Data Residency",
      "type": "string"
     },
     "name": {
      "description": "Nom affiché, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "settings": {
      "additionalProperties": true,
      "description": "Objet de paramètres libre du tenant (vide par défaut).",
      "title": "Settings",
      "type": "object"
     },
     "slug": {
      "description": "Identifiant court du tenant, minuscules, chiffres et tirets, 2 à 63 caractères. Sert aussi dans la revendication `tenant` et dans les noms de groupes de l'annuaire.",
      "pattern": "^[a-z0-9][a-z0-9-]{1,62}$",
      "title": "Slug",
      "type": "string"
     }
    },
    "required": [
     "slug",
     "name"
    ],
    "title": "TenantIn",
    "type": "object"
   },
   "TenantPatch": {
    "description": "Modifications d'un tenant ; les champs omis gardent leur valeur.",
    "properties": {
     "data_residency": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle région de résidence des données.",
      "title": "Data Residency"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom affiché, 1 à 200 caractères.",
      "title": "Name"
     },
     "settings": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvel objet de paramètres, qui remplace l'actuel.",
      "title": "Settings"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "suspended"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`active` ou `suspended`. Réservé aux administrateurs de plateforme.",
      "title": "Status"
     }
    },
    "title": "TenantPatch",
    "type": "object"
   },
   "TierIn": {
    "description": "Palier de prix qui s'applique à partir d'un nombre de jetons d'entrée.",
    "properties": {
     "min_input_tokens": {
      "description": "Nombre de jetons d'entrée à partir duquel le palier s'applique, au moins 0.",
      "minimum": 0.0,
      "title": "Min Input Tokens",
      "type": "integer"
     },
     "prices": {
      "additionalProperties": {
       "anyOf": [
        {
         "type": "number"
        },
        {
         "type": "string"
        }
       ]
      },
      "description": "Prix du palier par clé de prix (mêmes clés que `prices`).",
      "title": "Prices",
      "type": "object"
     }
    },
    "required": [
     "min_input_tokens",
     "prices"
    ],
    "title": "TierIn",
    "type": "object"
   },
   "TokenValue": {
    "description": "Une valeur à tokeniser.",
    "properties": {
     "type": {
      "description": "Type d'entité, par exemple `email`, `iban` ou `person` ; il apparaît dans le jeton.",
      "title": "Type",
      "type": "string"
     },
     "value": {
      "description": "Valeur à tokeniser. Conservée chiffrée, jamais journalisée.",
      "title": "Value",
      "type": "string"
     }
    },
    "required": [
     "type",
     "value"
    ],
    "title": "TokenValue",
    "type": "object"
   },
   "TokenizeIn": {
    "description": "Demande de tokenisation.",
    "properties": {
     "text": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Texte dont les entités détectées sont remplacées par des jetons.",
      "title": "Text"
     },
     "values": {
      "anyOf": [
       {
        "items": {
         "$ref": "#/components/schemas/TokenValue"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Valeurs à tokeniser, chacune avec son type.",
      "title": "Values"
     }
    },
    "title": "TokenizeIn",
    "type": "object"
   },
   "ToolPatch": {
    "description": "Champs d'un outil à modifier.",
    "properties": {
     "description": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description libre de l'outil.",
      "title": "Description"
     },
     "owner": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 3,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire de l'outil (3 à 200 caractères).",
      "title": "Owner"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "draft",
         "approved",
         "restricted",
         "deprecated",
         "blocked"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`draft`, `approved` (via une promotion), `restricted`, `deprecated` ou `blocked`.",
      "title": "Status"
     }
    },
    "title": "ToolPatch",
    "type": "object"
   },
   "TriggerIn": {
    "description": "Un déclencheur sur événement d'un agent.",
    "properties": {
     "agent_id": {
      "description": "Agent à exécuter (UUID ou nom).",
      "title": "Agent Id",
      "type": "string"
     },
     "filter": {
      "anyOf": [
       {
        "maxLength": 1000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Filtre sur la charge utile (1000 caractères au plus) : clauses `chemin=valeur` ou `chemin!=valeur` jointes par `&&`, le chemin pointé dans la charge utile, par exemple `order.status=paid && region!=us`.",
      "title": "Filter"
     },
     "subject": {
      "description": "Sujet NATS à faire correspondre (1 à 255 caractères, jokers `*` et `>` admis).",
      "maxLength": 255,
      "minLength": 1,
      "title": "Subject",
      "type": "string"
     }
    },
    "required": [
     "agent_id",
     "subject"
    ],
    "title": "TriggerIn",
    "type": "object"
   },
   "TtsIn": {
    "description": "Une demande de synthèse vocale.",
    "properties": {
     "dlp_mode": {
      "default": "block",
      "description": "DLP appliquée au texte : `block` (par défaut), `mask`, `tokenize` ou `detect`.",
      "title": "Dlp Mode",
      "type": "string"
     },
     "model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle à utiliser. Par défaut : l'adaptateur configuré.",
      "title": "Model"
     },
     "text": {
      "description": "Texte à prononcer (1 à 20000 caractères).",
      "maxLength": 20000,
      "minLength": 1,
      "title": "Text",
      "type": "string"
     },
     "voice": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de voix de l'adaptateur. Par défaut : la voix par défaut de l'adaptateur.",
      "title": "Voice"
     }
    },
    "required": [
     "text"
    ],
    "title": "TtsIn",
    "type": "object"
   },
   "UpgradeIn": {
    "description": "Une demande de mise à niveau.",
    "properties": {
     "approved_permissions": {
      "description": "Scopes de permission accordés à la nouvelle version, parmi ceux qu'elle demande (200 au plus).",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Approved Permissions",
      "type": "array"
     },
     "config": {
      "additionalProperties": true,
      "description": "Configuration du pack, validée selon son `configSchema`. Vide : l'actuelle.",
      "title": "Config",
      "type": "object"
     },
     "secret_refs": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Référence de secret par nom de connecteur, `openbao://...` ou `env://...`. Vide : les actuelles.",
      "title": "Secret Refs",
      "type": "object"
     },
     "version": {
      "description": "Version visée, supérieure à celle installée (5 à 64 caractères).",
      "maxLength": 64,
      "minLength": 5,
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "version"
    ],
    "title": "UpgradeIn",
    "type": "object"
   },
   "UploadIn": {
    "description": "Un fichier à ingérer de façon asynchrone, encodé en base64 comme pour la route synchrone.",
    "properties": {
     "classification": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`public`, `internal`, `confidential`, `restricted` ou `secret`. Par défaut : la classification de la base.",
      "title": "Classification"
     },
     "collection": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Collection du document dans la base (1 à 200 caractères). Aucune par défaut.",
      "title": "Collection"
     },
     "content_base64": {
      "description": "Octets du fichier, encodés en base64.",
      "minLength": 1,
      "title": "Content Base64",
      "type": "string"
     },
     "filename": {
      "default": "",
      "description": "Nom de fichier d'origine (500 caractères au plus) ; son extension doit correspondre au format déclaré. Par défaut : le titre.",
      "maxLength": 500,
      "title": "Filename",
      "type": "string"
     },
     "language": {
      "anyOf": [
       {
        "maxLength": 10,
        "minLength": 2,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Langue du document (code ISO 639-1). Par défaut : détectée dans le texte extrait.",
      "title": "Language"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Métadonnées libres conservées avec le document.",
      "title": "Metadata",
      "type": "object"
     },
     "mime_type": {
      "description": "Type de média déclaré, contrôlé face à la signature réelle des octets.",
      "maxLength": 255,
      "minLength": 1,
      "title": "Mime Type",
      "type": "string"
     },
     "readers": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Lecteurs de ce document. Omis : les lecteurs de la base.",
      "title": "Readers"
     },
     "title": {
      "default": "",
      "description": "Titre du document (500 caractères au plus). Vide par défaut.",
      "maxLength": 500,
      "title": "Title",
      "type": "string"
     },
     "uri": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "URI source. Un document portant l'URI d'un document existant devient sa version suivante.",
      "title": "Uri"
     }
    },
    "required": [
     "content_base64",
     "mime_type"
    ],
    "title": "UploadIn",
    "type": "object"
   },
   "ValidationError": {
    "description": "Un champ invalide d'une requête.",
    "properties": {
     "ctx": {
      "description": "Contexte de l'erreur, par exemple la borne dépassée.",
      "title": "Context",
      "type": "object"
     },
     "input": {
      "description": "Valeur reçue pour le champ.",
      "title": "Input"
     },
     "loc": {
      "description": "Emplacement du champ : `body`, `query`, `path` ou `header`, puis le chemin du champ.",
      "items": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "integer"
        }
       ]
      },
      "title": "Location",
      "type": "array"
     },
     "msg": {
      "description": "Message lisible.",
      "title": "Message",
      "type": "string"
     },
     "type": {
      "description": "Type d'erreur, par exemple `missing` ou `string_too_long`.",
      "title": "Error Type",
      "type": "string"
     }
    },
    "required": [
     "loc",
     "msg",
     "type"
    ],
    "title": "ValidationError",
    "type": "object"
   },
   "Variable": {
    "description": "Une variable typée d'un modèle de prompt.",
    "properties": {
     "default": {
      "description": "Valeur utilisée quand aucune n'est fournie.",
      "title": "Default"
     },
     "description": {
      "default": "",
      "description": "Rôle de la variable. Vide par défaut.",
      "title": "Description",
      "type": "string"
     },
     "enum": {
      "anyOf": [
       {
        "items": {},
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Valeurs permises.",
      "title": "Enum"
     },
     "max_length": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Longueur maximale d'une valeur texte, en caractères.",
      "title": "Max Length"
     },
     "name": {
      "description": "Nom utilisé dans l'emplacement : une lettre ou `_`, puis lettres, chiffres ou `_`.",
      "pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
      "title": "Name",
      "type": "string"
     },
     "required": {
      "default": true,
      "description": "Si vrai (par défaut), un rendu sans valeur ni valeur par défaut échoue.",
      "title": "Required",
      "type": "boolean"
     },
     "type": {
      "default": "string",
      "description": "`string` (par défaut), `integer`, `number`, `boolean`, `array` ou `object`.",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "Variable",
    "type": "object"
   },
   "VideoGenIn": {
    "description": "Une demande de génération vidéo.",
    "properties": {
     "model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle à utiliser. Par défaut : l'adaptateur configuré.",
      "title": "Model"
     },
     "prompt": {
      "description": "Description de la vidéo (1 à 8000 caractères).",
      "maxLength": 8000,
      "minLength": 1,
      "title": "Prompt",
      "type": "string"
     },
     "seconds": {
      "default": 5,
      "description": "Durée en secondes (plus de 0, 120 au plus, 5 par défaut).",
      "exclusiveMinimum": 0.0,
      "maximum": 120.0,
      "title": "Seconds",
      "type": "number"
     },
     "size": {
      "default": "1280x720",
      "description": "Taille `<largeur>x<hauteur>` en pixels. `1280x720` par défaut.",
      "title": "Size",
      "type": "string"
     }
    },
    "required": [
     "prompt"
    ],
    "title": "VideoGenIn",
    "type": "object"
   },
   "VideoProcessIn": {
    "description": "Une demande de traitement vidéo. Fournissez `media_id` ou `content_base64`.",
    "properties": {
     "content_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Vidéo fournie directement, encodée en base64 (analysée avant usage).",
      "title": "Content Base64"
     },
     "fps": {
      "default": 1,
      "description": "Images extraites par seconde (plus de 0, 60 au plus, 1 par défaut).",
      "exclusiveMinimum": 0.0,
      "maximum": 60.0,
      "title": "Fps",
      "type": "number"
     },
     "media_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Média vidéo enregistré.",
      "title": "Media Id"
     },
     "mime_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de média de la vidéo fournie, par exemple `video/mp4`.",
      "title": "Mime Type"
     },
     "operations": {
      "description": "Ce qu'il faut extraire : `frames`, `audio` ou les deux. Par défaut `[\"frames\", \"audio\"]`.",
      "items": {
       "type": "string"
      },
      "title": "Operations",
      "type": "array"
     },
     "sample_rate": {
      "default": 16000,
      "description": "Fréquence d'échantillonnage de l'audio extrait, en Hz. 16000 par défaut.",
      "title": "Sample Rate",
      "type": "integer"
     }
    },
    "title": "VideoProcessIn",
    "type": "object"
   },
   "VisionIn": {
    "description": "Une demande d'analyse d'image. Fournissez `media_id` ou `content_base64`.",
    "properties": {
     "content_base64": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Image fournie directement, encodée en base64 (analysée avant usage).",
      "title": "Content Base64"
     },
     "detail": {
      "anyOf": [
       {
        "enum": [
         "auto",
         "low",
         "high"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Niveau de détail de l'image : `auto`, `low` ou `high`, si le fournisseur le prend en charge.",
      "title": "Detail"
     },
     "dlp_mode": {
      "default": "detect",
      "description": "DLP appliquée à la réponse : `detect` (par défaut), `mask`, `tokenize` ou `block`.",
      "title": "Dlp Mode",
      "type": "string"
     },
     "media_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Média image enregistré.",
      "title": "Media Id"
     },
     "mime_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type de média de l'image fournie, par exemple `image/jpeg`.",
      "title": "Mime Type"
     },
     "model": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Modèle à utiliser. Par défaut : l'adaptateur configuré.",
      "title": "Model"
     },
     "prompt": {
      "default": "Describe this image.",
      "description": "Question sur l'image. Par défaut `Describe this image.`",
      "title": "Prompt",
      "type": "string"
     }
    },
    "title": "VisionIn",
    "type": "object"
   },
   "eai__domains__agents__routes__CancelIn": {
    "description": "Annulation d'un run.",
    "properties": {
     "reason": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison de l'annulation ; conservée dans les erreurs du run et le journal d'audit.",
      "title": "Reason"
     }
    },
    "title": "CancelIn",
    "type": "object"
   },
   "eai__domains__agents__routes__RunIn": {
    "description": "Un run à créer.",
    "properties": {
     "budgets": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limites de budget (par exemple jetons maximaux, `max_cost` avec montant et devise). Elles ne peuvent que resserrer le budget du manifeste de l'agent ; la devise doit être la même.",
      "title": "Budgets"
     },
     "delegation_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Délégation accordée à cet agent. Par défaut : la délégation implicite de l'appelant.",
      "title": "Delegation Id"
     },
     "retry_policy": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Politique de nouvelle tentative : `max_attempts` (1 ou plus, 3 par défaut) et `backoff_seconds` (0 ou plus, 2 par défaut).",
      "title": "Retry Policy"
     },
     "session_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Session (UUID) à laquelle appartient le run.",
      "title": "Session Id"
     },
     "start": {
      "default": false,
      "description": "Si vrai, le run démarre en `running`. Faux par défaut (`pending` jusqu'à la première étape).",
      "title": "Start",
      "type": "boolean"
     },
     "task": {
      "additionalProperties": true,
      "description": "Tâche du run : `task_id`, `objective`, `input` sont conservés ; `data_classification` sert au contrôle de politique.",
      "title": "Task",
      "type": "object"
     }
    },
    "title": "RunIn",
    "type": "object"
   },
   "eai__domains__agents__routes__SessionIn": {
    "description": "Une session à ouvrir.",
    "properties": {
     "agent_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent de la conversation (UUID ou nom).",
      "title": "Agent Id"
     },
     "channel": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Canal d'où vient la conversation.",
      "title": "Channel"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Paires clé-valeur libres enregistrées avec l'objet et renvoyées telles quelles ; la plateforme ne les interprète jamais.",
      "title": "Metadata",
      "type": "object"
     },
     "title": {
      "default": "",
      "description": "Titre de la session. Vide par défaut.",
      "title": "Title",
      "type": "string"
     },
     "ttl_minutes": {
      "anyOf": [
       {
        "maximum": 525600.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie en minutes (1 à 525600, un an). Ensuite, aucun message ne peut être ajouté. Par défaut : pas d'expiration.",
      "title": "Ttl Minutes"
     },
     "user_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Propriétaire de la session. Par défaut : l'appelant ; un autre utilisateur demande `tenant_admin` ou `agent`.",
      "title": "User Id"
     }
    },
    "title": "SessionIn",
    "type": "object"
   },
   "eai__domains__agents__routes__StatusIn": {
    "description": "Raison d'un changement de statut d'agent.",
    "properties": {
     "reason": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Raison du changement de statut (2000 caractères au plus).",
      "title": "Reason"
     }
    },
    "title": "StatusIn",
    "type": "object"
   },
   "eai__domains__applications__routes__StatusIn": {
    "description": "Changement de statut d'une application.",
    "properties": {
     "reason": {
      "description": "Raison (1 à 2000 caractères) ; conservée sur une application suspendue.",
      "maxLength": 2000,
      "minLength": 1,
      "title": "Reason",
      "type": "string"
     },
     "status": {
      "description": "`suspended` ou `retired`.",
      "enum": [
       "suspended",
       "retired"
      ],
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "status",
     "reason"
    ],
    "title": "StatusIn",
    "type": "object"
   },
   "eai__domains__approvals__routes__CancelIn": {
    "additionalProperties": false,
    "description": "Annulation d'une demande de validation en attente.",
    "properties": {
     "reason": {
      "description": "Raison du retrait, 3 à 2000 caractères. Inscrite dans le journal d'audit.",
      "maxLength": 2000,
      "minLength": 3,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "reason"
    ],
    "title": "CancelIn",
    "type": "object"
   },
   "eai__domains__context__routes__VerifyIn": {
    "description": "Un jeton à vérifier.",
    "properties": {
     "audience": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Audience attendue par le vérificateur (200 caractères au plus). Par défaut : la première audience de la plateforme.",
      "title": "Audience"
     },
     "token": {
      "description": "Le jeton JWS compact (10 à 65536 caractères).",
      "maxLength": 65536,
      "minLength": 10,
      "title": "Token",
      "type": "string"
     }
    },
    "required": [
     "token"
    ],
    "title": "VerifyIn",
    "type": "object"
   },
   "eai__domains__delegation__routes__AuthorizeIn": {
    "description": "Demande d'autorisation d'une action sous une délégation.",
    "properties": {
     "action": {
      "description": "Action, nom pointé en minuscules comme `tool.call` ou `model.invoke`, 3 à 200 caractères.",
      "maxLength": 200,
      "minLength": 3,
      "pattern": "^[a-z_]+(\\.[a-z_]+)+$",
      "title": "Action",
      "type": "string"
     },
     "audience": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Service qui recevra l'appel, 200 caractères au plus.",
      "title": "Audience"
     },
     "data_classification": {
      "anyOf": [
       {
        "pattern": "^(public|internal|confidential|restricted|secret)$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Classification des données traitées : `public`, `internal`, `confidential`, `restricted` ou `secret`.",
      "title": "Data Classification"
     },
     "delegation_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Délégation à vérifier. Donnez-la, ou `run_id`.",
      "title": "Delegation Id"
     },
     "relation": {
      "anyOf": [
       {
        "pattern": "^[a-z_][a-z0-9_]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Relation ReBAC vérifiée sur la ressource, par exemple `viewer`.",
      "title": "Relation"
     },
     "resource": {
      "anyOf": [
       {
        "maxLength": 300,
        "pattern": "^[a-z_]+:[^\\s]+$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Ressource visée, `<type>:<id>` comme `tool:crm.search`, 300 caractères au plus.",
      "title": "Resource"
     },
     "run_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Exécution dont la délégation est vérifiée, à la place de `delegation_id`.",
      "title": "Run Id"
     },
     "scope": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Portée nécessaire à l'action, 200 caractères au plus.",
      "title": "Scope"
     }
    },
    "required": [
     "action"
    ],
    "title": "AuthorizeIn",
    "type": "object"
   },
   "eai__domains__evaluation__RunIn": {
    "description": "Demande d'exécution d'évaluation.",
    "properties": {
     "outputs": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Sorties précalculées, une par cas dans l'ordre ; la cible n'est pas appelée pour ces cas.",
      "title": "Outputs"
     },
     "suite_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Suite à exécuter. Obligatoire sauf si `trigger` vaut `promotion`.",
      "title": "Suite Id"
     },
     "target": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/eai__domains__evaluation__Target"
       },
       {
        "type": "null"
       }
      ],
      "description": "Cible à évaluer à la place de celle de la suite, par exemple une autre version. Obligatoire pour `trigger: promotion` sans suite."
     },
     "trigger": {
      "default": "manual",
      "description": "Origine de l'exécution : `manual` (par défaut), `promotion`, `gate`, `comparison` ou une étiquette libre comme `ci`.",
      "title": "Trigger",
      "type": "string"
     }
    },
    "title": "RunIn",
    "type": "object"
   },
   "eai__domains__evaluation__Target": {
    "description": "Cible d'une évaluation.",
    "properties": {
     "ref": {
      "description": "Référence de la cible, par exemple un identifiant de modèle ou un nom d'agent, 1 caractère au moins.",
      "minLength": 1,
      "title": "Ref",
      "type": "string"
     },
     "type": {
      "description": "Type de la cible : `model`, `agent`, `prompt`, `rag_pipeline`, `skill` ou `tool`.",
      "title": "Type",
      "type": "string"
     },
     "version": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version de la cible, facultative.",
      "title": "Version"
     }
    },
    "required": [
     "type",
     "ref"
    ],
    "title": "Target",
    "type": "object"
   },
   "eai__domains__evidence__routes__DecryptIn": {
    "description": "Demande de déchiffrement d'une charge utile d'audit.",
    "properties": {
     "reason": {
      "description": "Raison de la lecture, 5 à 500 caractères. Inscrite dans le journal d'accès.",
      "maxLength": 500,
      "minLength": 5,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "reason"
    ],
    "title": "DecryptIn",
    "type": "object"
   },
   "eai__domains__file_security__PolicyIn": {
    "additionalProperties": true,
    "description": "Champs modifiables de la politique du tenant, tous facultatifs ; un champ verrouillé ou inconnu est refusé (422).",
    "properties": {
     "allow_archives": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si les archives (zip, gzip, tar, 7z, rar) sont acceptées ; vrai par défaut.",
      "title": "Allow Archives"
     },
     "allowed_types": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Types de fichiers détectés acceptés, parmi les types connus (`text`, `html`, `pdf`, `ooxml`, `ole`, `odf`, `zip`, `gzip`, `tar`, `7z`, `rar`, `png`, `jpeg`, `gif`, `webp`, `tiff`, `wav`, `mp3`, `ogg`, `flac`, `mp4`, `webm`, `avi`, `executable`, `binary`). Par défaut : tous les types connus sauf `executable`.",
      "title": "Allowed Types"
     },
     "block_office_macros": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si les documents Office avec macros sont bloqués ; vrai par défaut. Si faux, la macro est seulement signalée dans les raisons.",
      "title": "Block Office Macros"
     },
     "block_type_mismatch": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si un fichier dont le type détecté contredit son type de média déclaré est bloqué ; vrai par défaut.",
      "title": "Block Type Mismatch"
     },
     "max_file_bytes": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Plus grand fichier accepté, en octets : de 1024 au plafond de la plateforme `EAI_FILE_SCAN_MAX_BYTES` (valeur par défaut). Un fichier plus grand est bloqué pour violation de politique sans atteindre les moteurs.",
      "title": "Max File Bytes"
     },
     "max_signature_age_hours": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Âge maximal des signatures ClamAV en heures, de 1 à 720, 48 par défaut. Des signatures plus anciennes ou sans date sont périmées.",
      "title": "Max Signature Age Hours"
     },
     "quarantine_retention_days": {
      "anyOf": [
       {
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre de jours de conservation d'un échantillon avant sa suppression par la tâche de rétention, de 1 à 365, 30 par défaut.",
      "title": "Quarantine Retention Days"
     },
     "retain_samples": {
      "anyOf": [
       {
        "type": "boolean"
       },
       {
        "type": "null"
       }
      ],
      "description": "Indique si un échantillon chiffré de chaque fichier bloqué est conservé pour une nouvelle analyse ultérieure ; vrai par défaut.",
      "title": "Retain Samples"
     },
     "stale_signatures": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Effet de signatures ClamAV périmées sur une analyse : `warn` (par défaut) ajoute seulement une raison, `block` bloque le fichier avec le verdict `scan_error`.",
      "title": "Stale Signatures"
     }
    },
    "title": "PolicyIn",
    "type": "object"
   },
   "eai__domains__finops__QuotaIn": {
    "description": "Quota du tenant : une limite de coût et/ou de jetons sur une période, pour une portée.",
    "properties": {
     "currency": {
      "anyOf": [
       {
        "pattern": "^[A-Z]{3}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Devise ISO 4217 de `limit_cost` ; par défaut la devise du tenant.",
      "title": "Currency"
     },
     "enabled": {
      "default": true,
      "description": "Indique si le quota est appliqué, vrai par défaut.",
      "title": "Enabled",
      "type": "boolean"
     },
     "enforcement": {
      "default": "hard",
      "description": "`hard` (par défaut) : une réservation qui dépasserait la limite est refusée en 429 ; `soft` : elle est acceptée et signalée.",
      "enum": [
       "hard",
       "soft"
      ],
      "title": "Enforcement",
      "type": "string"
     },
     "limit_cost": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limite de coût par fenêtre, au moins 0, dans `currency`.",
      "title": "Limit Cost"
     },
     "limit_tokens": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Limite de jetons par fenêtre, au moins 0.",
      "title": "Limit Tokens"
     },
     "name": {
      "description": "Nom du quota, de 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "period": {
      "default": "month",
      "description": "Fenêtre de la limite : `request` (chaque appel), `run`, `hour`, `day` ou `month` (par défaut), en UTC.",
      "enum": [
       "request",
       "run",
       "hour",
       "day",
       "month"
      ],
      "title": "Period",
      "type": "string"
     },
     "scope_type": {
      "default": "tenant",
      "description": "Ce que le quota limite : `tenant` (par défaut), `project`, `agent`, `user`, `model` ou `provider`.",
      "enum": [
       "tenant",
       "project",
       "agent",
       "user",
       "model",
       "provider"
      ],
      "title": "Scope Type",
      "type": "string"
     },
     "scope_value": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Identifiant du projet, de l'agent, de l'utilisateur, du modèle ou du fournisseur ; obligatoire sauf si `scope_type` vaut `tenant`, où il doit être null.",
      "title": "Scope Value"
     }
    },
    "required": [
     "name"
    ],
    "title": "QuotaIn",
    "type": "object"
   },
   "eai__domains__licensing__routes__ActivationIn": {
    "description": "Demande d'activation d'une licence.",
    "properties": {
     "token": {
      "description": "Licence signée : sérialisation JWS compacte (trois segments base64url séparés par des points), algorithme EdDSA, de 20 à 16 384 caractères.",
      "maxLength": 16384,
      "minLength": 20,
      "title": "Token",
      "type": "string"
     }
    },
    "required": [
     "token"
    ],
    "title": "ActivationIn",
    "type": "object"
   },
   "eai__domains__llm_management__KeyIn": {
    "additionalProperties": false,
    "description": "Nouvelle clé de projet. Une limite absente hérite de la limite du projet (équipe LiteLLM). Les champs inconnus sont refusés.",
    "properties": {
     "budget_period": {
      "anyOf": [
       {
        "enum": [
         "daily",
         "weekly",
         "monthly",
         "total"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Période après laquelle la dépense de la clé repart à zéro : `daily`, `weekly`, `monthly` ou `total`. Par défaut : la période de budget du projet.",
      "title": "Budget Period"
     },
     "duration_days": {
      "anyOf": [
       {
        "maximum": 3650.0,
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Durée de vie de la clé en jours, de 1 à 3650 ; la clé expire ensuite. Omettez-la pour une clé sans échéance.",
      "title": "Duration Days"
     },
     "key_alias": {
      "anyOf": [
       {
        "maxLength": 120,
        "minLength": 1,
        "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Alias unique de la clé dans LiteLLM, de 1 à 120 caractères, lettres, chiffres, points, tirets et soulignés, commençant par une lettre ou un chiffre. Généré à partir des noms du tenant et du projet s'il est omis.",
      "title": "Key Alias"
     },
     "max_budget": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Dépense maximale de la clé par période de budget, décimale, au moins 0 et jamais au-dessus du budget du projet. LiteLLM reçoit aussi un budget souple au seuil d'alerte du projet.",
      "title": "Max Budget"
     },
     "max_parallel_requests": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Requêtes que la clé peut exécuter en même temps, au moins 0, jamais au-dessus de la limite du projet.",
      "title": "Max Parallel Requests"
     },
     "models": {
      "description": "Identifiants des modèles que la clé peut appeler, jusqu'à 200, chacun autorisé par le projet. Vide : tous les modèles du projet.",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Models",
      "type": "array"
     },
     "name": {
      "default": "",
      "description": "Nom affiché de la clé, 128 caractères au plus ; vide par défaut.",
      "maxLength": 128,
      "title": "Name",
      "type": "string"
     },
     "rpm_limit": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Requêtes par minute autorisées pour la clé, au moins 0, jamais au-dessus de la limite du projet.",
      "title": "Rpm Limit"
     },
     "tpm_limit": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons par minute autorisés pour la clé, au moins 0, jamais au-dessus de la limite du projet.",
      "title": "Tpm Limit"
     }
    },
    "title": "KeyIn",
    "type": "object"
   },
   "eai__domains__llm_management__ProjectIn": {
    "additionalProperties": false,
    "description": "Nouveau projet LLM. Les champs inconnus sont refusés.",
    "properties": {
     "allowed_categories": {
      "description": "Catégories de modèles que le projet peut utiliser, jusqu'à 200 : chaque modèle du registre dont la catégorie, les catégories ou les capacités correspondent est autorisé.",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Allowed Categories",
      "type": "array"
     },
     "allowed_models": {
      "description": "Identifiants des modèles que le projet peut utiliser, jusqu'à 200 de 200 caractères au plus chacun. Si `allowed_categories` est vide aussi, tous les modèles sont autorisés.",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Allowed Models",
      "type": "array"
     },
     "description": {
      "default": "",
      "description": "Description libre, 2000 caractères au plus.",
      "maxLength": 2000,
      "title": "Description",
      "type": "string"
     },
     "name": {
      "description": "Nom unique du projet dans le tenant, de 1 à 128 caractères : lettres, chiffres, espaces, points, tirets et soulignés, commençant par une lettre ou un chiffre.",
      "maxLength": 128,
      "minLength": 1,
      "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._-]{0,127}$",
      "title": "Name",
      "type": "string"
     },
     "owner": {
      "default": "",
      "description": "Responsable du projet, 200 caractères au plus. Par défaut : l'appelant.",
      "maxLength": 200,
      "title": "Owner",
      "type": "string"
     },
     "project_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Projet du tenant (identifiant ou slug) auquel le projet LLM est lié ; ses quotas FinOps s'appliquent à ce projet. Omettez-le pour un projet LLM limité par LiteLLM seulement.",
      "title": "Project Id"
     },
     "quota": {
      "anyOf": [
       {
        "$ref": "#/components/schemas/eai__domains__llm_management__QuotaIn"
       },
       {
        "type": "null"
       }
      ],
      "description": "Quota initial du projet ; les valeurs par défaut du quota s'appliquent s'il est omis."
     },
     "status": {
      "default": "active",
      "description": "`active` (par défaut), `suspended` ou `archived`. Seul un projet actif reçoit de nouvelles clés et des quotas FinOps.",
      "enum": [
       "active",
       "suspended",
       "archived"
      ],
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "ProjectIn",
    "type": "object"
   },
   "eai__domains__llm_management__ProjectPatch": {
    "additionalProperties": false,
    "description": "Modifications d'un projet LLM ; chaque champ est facultatif. Les champs inconnus sont refusés.",
    "properties": {
     "allowed_categories": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 200,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle liste de catégories de modèles autorisées, jusqu'à 200.",
      "title": "Allowed Categories"
     },
     "allowed_models": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "maxItems": 200,
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle liste d'identifiants de modèles autorisés, jusqu'à 200.",
      "title": "Allowed Models"
     },
     "description": {
      "anyOf": [
       {
        "maxLength": 2000,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Description libre, 2000 caractères au plus.",
      "title": "Description"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 128,
        "minLength": 1,
        "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._-]{0,127}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom unique dans le tenant, de 1 à 128 caractères (même règle qu'à la création).",
      "title": "Name"
     },
     "owner": {
      "anyOf": [
       {
        "maxLength": 200,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Responsable du projet, 200 caractères au plus.",
      "title": "Owner"
     },
     "project_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Projet du tenant (identifiant ou slug) à lier ; null supprime le lien et ses quotas FinOps.",
      "title": "Project Id"
     },
     "status": {
      "anyOf": [
       {
        "enum": [
         "active",
         "suspended",
         "archived"
        ],
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "`active`, `suspended` ou `archived`. Un projet non actif perd ses quotas FinOps et ne peut plus recevoir de nouvelles clés.",
      "title": "Status"
     }
    },
    "title": "ProjectPatch",
    "type": "object"
   },
   "eai__domains__llm_management__QuotaIn": {
    "additionalProperties": false,
    "description": "Quota d'un projet LLM. Chaque limite est facultative (null : pas de limite). Les champs inconnus sont refusés.",
    "properties": {
     "alert_threshold_pct": {
      "default": 80,
      "description": "Seuil d'alerte en part du budget, en pour cent (de 1 à 100, 80 par défaut) : transmis dans les métadonnées de l'équipe LiteLLM et utilisé comme budget souple de chaque nouvelle clé (cette part de son `max_budget`).",
      "maximum": 100.0,
      "minimum": 1.0,
      "title": "Alert Threshold Pct",
      "type": "integer"
     },
     "budget_amount": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "number"
       },
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Budget par `budget_period`, décimal, au moins 0, dans `budget_currency`.",
      "title": "Budget Amount"
     },
     "budget_currency": {
      "default": "USD",
      "description": "Devise ISO 4217 du budget, trois lettres majuscules, `USD` par défaut.",
      "pattern": "^[A-Z]{3}$",
      "title": "Budget Currency",
      "type": "string"
     },
     "budget_period": {
      "default": "monthly",
      "description": "Période du budget : `daily`, `weekly`, `monthly` (par défaut) ou `total`. Seules `daily` et `monthly` deviennent aussi des quotas FinOps.",
      "enum": [
       "daily",
       "weekly",
       "monthly",
       "total"
      ],
      "title": "Budget Period",
      "type": "string"
     },
     "hard_stop": {
      "default": true,
      "description": "Si vrai (par défaut), les appels sont refusés une fois le budget dépensé (budget maximal LiteLLM, quotas FinOps stricts). Si faux, le budget ne fait qu'alerter.",
      "title": "Hard Stop",
      "type": "boolean"
     },
     "max_parallel_requests": {
      "anyOf": [
       {
        "maximum": 100000.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Requêtes que le projet peut exécuter en même temps, de 0 à 100000.",
      "title": "Max Parallel Requests"
     },
     "rpm_limit": {
      "anyOf": [
       {
        "maximum": 10000000.0,
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Requêtes par minute de tout le projet, de 0 à 10000000.",
      "title": "Rpm Limit"
     },
     "token_limit": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nombre maximal de jetons par `token_period`, au moins 0. Appliqué par la passerelle du Control Plane au moyen d'un quota FinOps quand le projet est lié et que la période est `daily` ou `monthly`.",
      "title": "Token Limit"
     },
     "token_period": {
      "default": "monthly",
      "description": "Période de la limite de jetons : `daily`, `weekly`, `monthly` (par défaut) ou `total`.",
      "enum": [
       "daily",
       "weekly",
       "monthly",
       "total"
      ],
      "title": "Token Period",
      "type": "string"
     },
     "tpm_limit": {
      "anyOf": [
       {
        "minimum": 0.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Jetons par minute de tout le projet, au moins 0.",
      "title": "Tpm Limit"
     }
    },
    "title": "QuotaIn",
    "type": "object"
   },
   "eai__domains__marketplace__routes__AuthorizeIn": {
    "description": "Un contrôle de permission à l'exécution.",
    "properties": {
     "permission": {
      "description": "Scope de permission, par exemple `crm.contacts:read` (2 à 128 caractères, minuscules).",
      "pattern": "^[a-z][a-z0-9_.:*-]{1,127}$",
      "title": "Permission",
      "type": "string"
     }
    },
    "required": [
     "permission"
    ],
    "title": "AuthorizeIn",
    "type": "object"
   },
   "eai__domains__marketplace__routes__InstallIn": {
    "description": "Une demande d'installation (aussi le corps du preflight).",
    "properties": {
     "approved_permissions": {
      "description": "Scopes de permission accordés au pack, parmi ceux qu'il demande (200 au plus). Les jokers et les scopes d'administration sont refusés.",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Approved Permissions",
      "type": "array"
     },
     "config": {
      "additionalProperties": true,
      "description": "Configuration du pack, validée selon son `configSchema` ; `model_id` doit désigner un modèle approuvé.",
      "title": "Config",
      "type": "object"
     },
     "environment": {
      "description": "Environnement visé : `dev`, `test`, `staging` ou `prod`.",
      "enum": [
       "dev",
       "test",
       "staging",
       "prod"
      ],
      "title": "Environment",
      "type": "string"
     },
     "package": {
      "description": "Nom du paquet.",
      "pattern": "^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$",
      "title": "Package",
      "type": "string"
     },
     "secret_refs": {
      "additionalProperties": {
       "type": "string"
      },
      "description": "Référence de secret par nom de connecteur, `openbao://...` ou `env://...` ; requise pour un connecteur authentifié.",
      "title": "Secret Refs",
      "type": "object"
     },
     "version": {
      "description": "Version à installer (5 à 64 caractères).",
      "maxLength": 64,
      "minLength": 5,
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "package",
     "version",
     "environment"
    ],
    "title": "InstallIn",
    "type": "object"
   },
   "eai__domains__marketplace__routes__StatusIn": {
    "description": "Suspension ou reprise d'une installation.",
    "properties": {
     "approval_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Demande `marketplace.resume` approuvée, pour reprendre.",
      "title": "Approval Id"
     },
     "permissions": {
      "description": "Suspendre seulement ces capacités accordées (200 au plus ; vide : toute l'installation).",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Permissions",
      "type": "array"
     },
     "reason": {
      "default": "",
      "description": "Raison du changement de statut (1000 caractères au plus).",
      "maxLength": 1000,
      "title": "Reason",
      "type": "string"
     },
     "status": {
      "description": "`suspended` (immédiat) ou `installed` (reprise, avec approbation).",
      "enum": [
       "suspended",
       "installed"
      ],
      "title": "Status",
      "type": "string"
     }
    },
    "required": [
     "status"
    ],
    "title": "StatusIn",
    "type": "object"
   },
   "eai__domains__mcp_runtime__routes__PolicyIn": {
    "description": "Politique du MCP Runtime d'un projet.",
    "properties": {
     "allowed_database_roles": {
      "description": "Rôles de base de données qu'un modèle peut utiliser (100 au plus). Vide (par défaut) : aucun.",
      "items": {
       "type": "string"
      },
      "maxItems": 100,
      "title": "Allowed Database Roles",
      "type": "array"
     },
     "allowed_hosts": {
      "description": "Hôtes que les instances peuvent joindre, noms exacts ou motifs `*.domaine` (200 au plus). Vide (par défaut) : aucun hôte, un modèle qui en a besoin ne peut donc pas démarrer.",
      "items": {
       "type": "string"
      },
      "maxItems": 200,
      "title": "Allowed Hosts",
      "type": "array"
     },
     "allowed_templates": {
      "description": "Modèles que les utilisateurs peuvent démarrer (100 au plus). Vide (par défaut) : tous les modèles du catalogue.",
      "items": {
       "type": "string"
      },
      "maxItems": 100,
      "title": "Allowed Templates",
      "type": "array"
     },
     "idle_timeout_seconds": {
      "default": 900,
      "description": "Temps d'inactivité maximal avant qu'une instance ne s'endorme, en secondes (60 à 86400, 900 par défaut).",
      "maximum": 86400.0,
      "minimum": 60.0,
      "title": "Idle Timeout Seconds",
      "type": "integer"
     },
     "max_instances_per_project": {
      "default": 20,
      "description": "Instances en cours par projet (1 à 200, 20 par défaut).",
      "maximum": 200.0,
      "minimum": 1.0,
      "title": "Max Instances Per Project",
      "type": "integer"
     },
     "max_instances_per_user": {
      "default": 3,
      "description": "Instances en cours par utilisateur (1 à 20, 3 par défaut) ; celles endormies ou en échec ne comptent pas.",
      "maximum": 20.0,
      "minimum": 1.0,
      "title": "Max Instances Per User",
      "type": "integer"
     },
     "max_lifetime_seconds": {
      "default": 28800,
      "description": "Durée de vie maximale d'une génération en cours, en secondes (300 à 86400, 28800 par défaut).",
      "maximum": 86400.0,
      "minimum": 300.0,
      "title": "Max Lifetime Seconds",
      "type": "integer"
     }
    },
    "title": "PolicyIn",
    "type": "object"
   },
   "eai__domains__navin_library__routes__ActivationIn": {
    "description": "Un élément de la bibliothèque à activer pour un projet.",
    "properties": {
     "kind": {
      "description": "`skill`, `plugin`, `command` ou `agent`.",
      "enum": [
       "skill",
       "plugin",
       "command",
       "agent"
      ],
      "title": "Kind",
      "type": "string"
     },
     "name": {
      "description": "Nom de l'élément dans la bibliothèque (1 à 128 caractères).",
      "maxLength": 128,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     }
    },
    "required": [
     "kind",
     "name"
    ],
    "title": "ActivationIn",
    "type": "object"
   },
   "eai__domains__navin_library__routes__RuleIn": {
    "description": "Une règle de projet.",
    "properties": {
     "content": {
      "description": "Contenu Markdown de la règle (1 à 65536 caractères, 64 Kio au plus).",
      "maxLength": 65536,
      "minLength": 1,
      "title": "Content",
      "type": "string"
     },
     "description": {
      "default": "",
      "description": "Quand la règle s'applique (2000 caractères au plus).",
      "maxLength": 2000,
      "title": "Description",
      "type": "string"
     },
     "globs": {
      "description": "Motifs de fichiers concernés par la règle, par exemple `src/**/*.ts` (50 au plus).",
      "items": {
       "type": "string"
      },
      "maxItems": 50,
      "title": "Globs",
      "type": "array"
     },
     "mode": {
      "default": "always",
      "description": "`always` (par défaut, inséré dans le prompt de chaque run) ou `conditional` (listé, lu quand sa portée correspond).",
      "enum": [
       "always",
       "conditional"
      ],
      "title": "Mode",
      "type": "string"
     },
     "source": {
      "default": "navin",
      "description": "Origine de la règle : `navin` (par défaut), `cursor`, `cursor_legacy`, `cline`, `windsurf` ou `copilot`.",
      "enum": [
       "navin",
       "cursor",
       "cursor_legacy",
       "cline",
       "windsurf",
       "copilot"
      ],
      "title": "Source",
      "type": "string"
     }
    },
    "required": [
     "content"
    ],
    "title": "RuleIn",
    "type": "object"
   },
   "eai__domains__observability__slo__TargetIn": {
    "additionalProperties": false,
    "description": "Objectifs d'un service, avec les mêmes bornes que les paramètres `EAI_SLO_*`. Les champs inconnus sont refusés (422).",
    "properties": {
     "availability": {
      "description": "Cible de disponibilité : part des requêtes qui ne doivent pas échouer en 5xx, de 0,5 inclus à 1 exclu (par exemple 0,999).",
      "exclusiveMaximum": 1.0,
      "minimum": 0.5,
      "title": "Availability",
      "type": "number"
     },
     "latency_objective": {
      "description": "Part des requêtes qui doivent être plus rapides que `latency_seconds`, de 0,5 inclus à 1 exclu.",
      "exclusiveMaximum": 1.0,
      "minimum": 0.5,
      "title": "Latency Objective",
      "type": "number"
     },
     "latency_seconds": {
      "description": "Cible de latence en secondes : une requête plus lente compte contre l'objectif de latence. Supérieure à 0, au plus 300.",
      "exclusiveMinimum": 0.0,
      "maximum": 300.0,
      "title": "Latency Seconds",
      "type": "number"
     },
     "reason": {
      "default": "",
      "description": "Raison du changement d'objectifs, 500 caractères au plus, vide par défaut. Inscrite dans le journal d'audit.",
      "maxLength": 500,
      "title": "Reason",
      "type": "string"
     }
    },
    "required": [
     "availability",
     "latency_seconds",
     "latency_objective"
    ],
    "title": "TargetIn",
    "type": "object"
   },
   "eai__domains__platform_manager__InstallIn": {
    "description": "Demande d'installation de services du catalogue.",
    "properties": {
     "service_check": {
      "default": true,
      "description": "Lancer les contrôles de service après l'installation (vrai par défaut).",
      "title": "Service Check",
      "type": "boolean"
     },
     "services": {
      "description": "Services à installer, de 1 à 20.",
      "items": {
       "$ref": "#/components/schemas/InstallServiceIn"
      },
      "maxItems": 20,
      "minItems": 1,
      "title": "Services",
      "type": "array"
     },
     "start": {
      "default": true,
      "description": "Démarrer les services une fois installés (vrai par défaut).",
      "title": "Start",
      "type": "boolean"
     }
    },
    "required": [
     "services"
    ],
    "title": "InstallIn",
    "type": "object"
   },
   "eai__domains__policies__PolicyIn": {
    "description": "Contenu d'une politique.",
    "properties": {
     "description": {
      "default": "",
      "description": "Description de la politique (vide par défaut).",
      "title": "Description",
      "type": "string"
     },
     "engine": {
      "default": "builtin",
      "description": "`builtin` (par défaut, `rules`) ou `opa` (`rego`).",
      "enum": [
       "builtin",
       "opa"
      ],
      "title": "Engine",
      "type": "string"
     },
     "name": {
      "description": "Nom unique dans le tenant : minuscules, chiffres, `_`, `.` et `-`, commençant par une lettre ou un chiffre, 100 caractères au plus.",
      "pattern": "^[a-z0-9][a-z0-9_.-]{0,99}$",
      "title": "Name",
      "type": "string"
     },
     "priority": {
      "default": 100,
      "description": "Ordre d'évaluation, la plus basse d'abord (100 par défaut) ; à priorité égale, tri par nom.",
      "title": "Priority",
      "type": "integer"
     },
     "rego": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Module Rego d'une politique `opa`. Obligatoire pour ce moteur.",
      "title": "Rego"
     },
     "rules": {
      "description": "Règles d'une politique `builtin`.",
      "items": {
       "$ref": "#/components/schemas/Rule"
      },
      "title": "Rules",
      "type": "array"
     },
     "status": {
      "default": "active",
      "description": "`active` (par défaut, évaluée), `draft` (non évaluée ; pour OPA, livrée comme bibliothèque) ou `disabled`.",
      "enum": [
       "draft",
       "active",
       "disabled"
      ],
      "title": "Status",
      "type": "string"
     },
     "version": {
      "default": "1.0.0",
      "description": "Libellé de version de la politique, enregistré dans chaque décision. `1.0.0` par défaut.",
      "title": "Version",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "PolicyIn",
    "type": "object"
   },
   "eai__domains__privacy__DecryptIn": {
    "description": "Demande de déchiffrement.",
    "properties": {
     "aad": {
      "default": "",
      "description": "Données associées fournies au chiffrement (vide par défaut).",
      "title": "Aad",
      "type": "string"
     },
     "as_base64": {
      "default": false,
      "description": "Si vrai, renvoie les données dans `plaintext_base64` (faux par défaut).",
      "title": "As Base64",
      "type": "boolean"
     },
     "ciphertext": {
      "description": "Chiffré renvoyé par le chiffrement, `eaienc1:<key id>:<version>:...`.",
      "title": "Ciphertext",
      "type": "string"
     }
    },
    "required": [
     "ciphertext"
    ],
    "title": "DecryptIn",
    "type": "object"
   },
   "eai__domains__privacy__KeyIn": {
    "description": "Nouvelle clé de chiffrement.",
    "properties": {
     "name": {
      "description": "Nom unique : minuscules, chiffres, `.`, `_` ou `-`, commençant par une lettre ou un chiffre, 63 caractères au plus.",
      "title": "Name",
      "type": "string"
     },
     "purpose": {
      "default": "data",
      "description": "Libellé libre de l'usage de la clé, `data` par défaut.",
      "title": "Purpose",
      "type": "string"
     }
    },
    "required": [
     "name"
    ],
    "title": "KeyIn",
    "type": "object"
   },
   "eai__domains__privacy__RuleIn": {
    "description": "Règle DLP du tenant.",
    "properties": {
     "action": {
      "default": "mask",
      "description": "Action sur un constat : `allow`, `detect`, `mask` (par défaut), `tokenize` ou `block` ; elle remplace le mode de la requête.",
      "title": "Action",
      "type": "string"
     },
     "classification": {
      "default": "confidential",
      "description": "Classification des données trouvées : `public`, `internal`, `confidential` (par défaut), `restricted` ou `secret`.",
      "title": "Classification",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Si faux, la règle est ignorée (vrai par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "entity_type": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Type d'entité intégré auquel la règle s'applique, par exemple `credit_card`. Donnez-le, ou `pattern`.",
      "title": "Entity Type"
     },
     "min_score": {
      "default": 0,
      "description": "Score de confiance minimal d'un constat, 0 par défaut.",
      "title": "Min Score",
      "type": "number"
     },
     "name": {
      "description": "Nom unique de la règle, 1 à 120 caractères.",
      "maxLength": 120,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "pattern": {
      "anyOf": [
       {
        "maxLength": 500,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Expression régulière d'une entité personnalisée, 500 caractères au plus.",
      "title": "Pattern"
     }
    },
    "required": [
     "name"
    ],
    "title": "RuleIn",
    "type": "object"
   },
   "eai__domains__realtime__SessionIn": {
    "description": "Une session temps réel à ouvrir.",
    "properties": {
     "agent_id": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9._:@-]{1,128}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Agent de la session quand un utilisateur l'ouvre (1 à 128 caractères). Ignoré pour un appelant agent, qui est l'agent lui-même.",
      "title": "Agent Id"
     },
     "config": {
      "additionalProperties": true,
      "description": "Configuration de la session : `dlp_mode` (`mask` par défaut, `tokenize` ou `block`), `vad` (`enabled` booléen, `threshold` dans (0, 1]), `record` et options du pipeline.",
      "title": "Config",
      "type": "object"
     },
     "on_behalf_of": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9._:@-]{1,128}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Utilisateur pour lequel agit l'agent (appelant agent). Pour un appelant utilisateur, seul son propre identifiant est accepté.",
      "title": "On Behalf Of"
     },
     "pipeline": {
      "default": "echo",
      "description": "`echo` (par défaut) ou `stt`.",
      "title": "Pipeline",
      "type": "string"
     },
     "room": {
      "anyOf": [
       {
        "pattern": "^[A-Za-z0-9._-]{1,128}$",
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nom de la salle LiveKit (1 à 128 lettres, chiffres, `.`, `_`, `-`). Par défaut : dérivé du tenant et de la session.",
      "title": "Room"
     },
     "ttl_seconds": {
      "default": 3600,
      "description": "Durée de vie du jeton LiveKit, en secondes (60 à 86400, 3600 par défaut).",
      "maximum": 86400.0,
      "minimum": 60.0,
      "title": "Ttl Seconds",
      "type": "integer"
     }
    },
    "title": "SessionIn",
    "type": "object"
   },
   "eai__domains__retention__PolicyIn": {
    "description": "Politique de conservation d'un type de ressource.",
    "properties": {
     "action": {
      "default": "erase",
      "description": "`erase` (par défaut) supprime ; `archive` conserve une copie chiffrée avant suppression (éléments de mémoire).",
      "title": "Action",
      "type": "string"
     },
     "enabled": {
      "default": true,
      "description": "Si faux, les passes ignorent la politique (vrai par défaut).",
      "title": "Enabled",
      "type": "boolean"
     },
     "resource_type": {
      "description": "Type de ressource : `memory`, `sessions`, `media`, `usage_raw`, `telemetry`, `documents` ou `realtime`.",
      "title": "Resource Type",
      "type": "string"
     },
     "ttl_days": {
      "description": "Durée de vie en jours, de 0 à 36500. Les éléments plus anciens sont purgés par la passe suivante.",
      "maximum": 36500.0,
      "minimum": 0.0,
      "title": "Ttl Days",
      "type": "integer"
     }
    },
    "required": [
     "resource_type",
     "ttl_days"
    ],
    "title": "PolicyIn",
    "type": "object"
   },
   "eai__domains__retention__RunIn": {
    "description": "Options d'une passe de conservation.",
    "properties": {
     "dry_run": {
      "default": false,
      "description": "Si vrai, indique ce qui serait purgé sans rien supprimer (faux par défaut).",
      "title": "Dry Run",
      "type": "boolean"
     },
     "resource_types": {
      "anyOf": [
       {
        "items": {
         "type": "string"
        },
        "type": "array"
       },
       {
        "type": "null"
       }
      ],
      "description": "Types de ressources à traiter, parmi `memory`, `sessions`, `media`, `usage_raw`, `telemetry`, `documents` ou `realtime`. Null : tous.",
      "title": "Resource Types"
     }
    },
    "title": "RunIn",
    "type": "object"
   },
   "eai__domains__risk__EvidenceIn": {
    "description": "Preuve jointe à un contrôle ou à une évaluation.",
    "properties": {
     "digest": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Empreinte du contenu de la preuve, par exemple `sha256:<hex>`, pour prouver qu'il n'a pas changé.",
      "title": "Digest"
     },
     "kind": {
      "default": "document",
      "description": "Type de preuve, par exemple `document`, `report` ou `screenshot`. `document` par défaut.",
      "title": "Kind",
      "type": "string"
     },
     "title": {
      "description": "Titre de la preuve, 1 caractère au moins.",
      "minLength": 1,
      "title": "Title",
      "type": "string"
     },
     "uri": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Emplacement de la preuve, facultatif.",
      "title": "Uri"
     }
    },
    "required": [
     "title"
    ],
    "title": "EvidenceIn",
    "type": "object"
   },
   "eai__domains__risk__Target": {
    "description": "Système d'IA concerné par une évaluation.",
    "properties": {
     "ref": {
      "description": "Référence de la cible : nom ou identifiant, 1 caractère au moins.",
      "minLength": 1,
      "title": "Ref",
      "type": "string"
     },
     "type": {
      "description": "Type de la cible, par exemple `agent`, `model` ou `application`. Une cible `agent` met à jour le registre.",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type",
     "ref"
    ],
    "title": "Target",
    "type": "object"
   },
   "eai__domains__soc__EvidenceIn": {
    "description": "Preuve à sceller sur un incident.",
    "properties": {
     "content": {
      "additionalProperties": true,
      "description": "Contenu de l'enregistrement, un objet JSON ; seule son empreinte entre dans la chaîne à côté de lui.",
      "title": "Content",
      "type": "object"
     },
     "kind": {
      "default": "note",
      "description": "Type d'enregistrement, 1 à 64 caractères, `note` par défaut.",
      "maxLength": 64,
      "minLength": 1,
      "title": "Kind",
      "type": "string"
     }
    },
    "required": [
     "content"
    ],
    "title": "EvidenceIn",
    "type": "object"
   },
   "eai__domains__soc__TargetIn": {
    "description": "Cible typée d'un ordre de confinement.",
    "properties": {
     "ref": {
      "description": "Référence de la cible (identifiant ou nom), 1 à 300 caractères.",
      "maxLength": 300,
      "minLength": 1,
      "title": "Ref",
      "type": "string"
     },
     "type": {
      "description": "Type de cible accepté par la réaction : `request`, `session`, `run`, `agent`, `subagent`, `application`, `tool`, `plugin`, `mcp`, `connector`, `knowledge_base`, `model`, `credential`, `identity`, `tenant`, `workload` ou `service`.",
      "title": "Type",
      "type": "string"
     }
    },
    "required": [
     "type",
     "ref"
    ],
    "title": "TargetIn",
    "type": "object"
   },
   "eai__domains__storage__routes__VerifyIn": {
    "description": "Options d'une vérification de capacités.",
    "properties": {
     "probe_write": {
      "default": true,
      "description": "Si vrai (par défaut), un objet sonde est écrit avec le chiffrement, la classe de stockage et la conservation légale configurés, relu puis supprimé, pour vérifier les classes de stockage et l'intégrité. Il est ignoré si le bucket a une rétention par défaut. Si faux, ces deux capacités restent non vérifiées.",
      "title": "Probe Write",
      "type": "boolean"
     }
    },
    "title": "VerifyIn",
    "type": "object"
   },
   "eai__domains__tenancy__ProjectIn": {
    "description": "Nouveau projet.",
    "properties": {
     "business_unit_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Unité métier à laquelle le projet est rattaché, facultative ; elle doit appartenir à l'organisation.",
      "title": "Business Unit Id"
     },
     "description": {
      "default": "",
      "description": "Description du projet (vide par défaut).",
      "title": "Description",
      "type": "string"
     },
     "environments": {
      "description": "Environnements créés avec le projet, parmi `dev`, `test`, `staging` et `prod` ; les doublons sont ignorés.",
      "items": {
       "enum": [
        "dev",
        "test",
        "staging",
        "prod"
       ],
       "type": "string"
      },
      "title": "Environments",
      "type": "array"
     },
     "metadata": {
      "additionalProperties": true,
      "description": "Paires clé-valeur libres enregistrées avec l'objet. Contrôlées par la base : un objet refusé répond 422 `invalid_metadata`.",
      "title": "Metadata",
      "type": "object"
     },
     "name": {
      "description": "Nom affiché, 1 à 200 caractères.",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name",
      "type": "string"
     },
     "organization_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Organisation à laquelle le projet est rattaché, facultative.",
      "title": "Organization Id"
     },
     "settings": {
      "additionalProperties": true,
      "description": "Objet de paramètres libre du projet (vide par défaut).",
      "title": "Settings",
      "type": "object"
     },
     "slug": {
      "description": "Identifiant court du projet, minuscules, chiffres et tirets, 63 caractères au plus, unique dans le tenant. Utilisé dans l'en-tête `X-Project`.",
      "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
      "title": "Slug",
      "type": "string"
     }
    },
    "required": [
     "slug",
     "name"
    ],
    "title": "ProjectIn",
    "type": "object"
   },
   "eai__domains__tenancy__ProjectPatch": {
    "description": "Modifications d'un projet ; les champs omis gardent leur valeur.",
    "properties": {
     "business_unit_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle unité métier ; null détache le projet de son unité.",
      "title": "Business Unit Id"
     },
     "description": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle description.",
      "title": "Description"
     },
     "expected_version": {
      "anyOf": [
       {
        "minimum": 1.0,
        "type": "integer"
       },
       {
        "type": "null"
       }
      ],
      "description": "Version lue en dernier par l'appelant. Une version périmée répond 409 `version_conflict` (concurrence optimiste).",
      "title": "Expected Version"
     },
     "metadata": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Paires clé-valeur libres enregistrées avec l'objet. Contrôlées par la base : un objet refusé répond 422 `invalid_metadata`.",
      "title": "Metadata"
     },
     "name": {
      "anyOf": [
       {
        "maxLength": 200,
        "minLength": 1,
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouveau nom affiché, 1 à 200 caractères.",
      "title": "Name"
     },
     "organization_id": {
      "anyOf": [
       {
        "type": "string"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvelle organisation ; null détache le projet.",
      "title": "Organization Id"
     },
     "settings": {
      "anyOf": [
       {
        "additionalProperties": true,
        "type": "object"
       },
       {
        "type": "null"
       }
      ],
      "description": "Nouvel objet de paramètres, qui remplace l'actuel.",
      "title": "Settings"
     }
    },
    "title": "ProjectPatch",
    "type": "object"
   }
  }
 },
 "info": {
  "description": "L'API du Control Plane est le point d'entrée unique de l'Enterprise AI Platform. Chaque écran du portail, SDK (Python, TypeScript, Go, Java), commande de la CLI et action du Navin Manager passe par elle. Elle gère les tenants et les projets, les identités et les accès, les agents et leurs exécutions, les modèles et la passerelle LLM, les bases de connaissances, les outils et les serveurs MCP, la gouvernance (politiques, validations, risques, vie privée, conservation, preuves), les opérations de sécurité et les coûts.\n\n## Présentation\n\nL'API est une API REST JSON sur HTTPS. Les ressources sont regroupées par tag dans le menu de gauche ; les groupes suivent l'ordre dans lequel un nouveau lecteur les découvre en général : identité et accès, tenants, agents et IA, connaissances et données, outils et intégrations, gouvernance et conformité, sécurité, FinOps et exploitation de la plateforme.\n\nRègles communes :\n\n- les requêtes et les réponses utilisent `application/json` encodé en UTF-8, sauf les routes de téléversement (`multipart/form-data`) et les routes de flux (Server-Sent Events, WebSocket) ;\n- les dates et heures sont des chaînes ISO 8601 en UTC, par exemple `2026-03-14T09:30:00Z` ;\n- les identifiants sont des chaînes opaques (des UUID pour la plupart) ; ne les construisez jamais vous-même ;\n- les champs inconnus d'un corps de requête sont refusés avec `422 validation_error` sur les routes dont le modèle interdit les champs supplémentaires ;\n- la langue des messages d'erreur suit l'en-tête `Accept-Language` (`en` ou `fr`, `en` par défaut).\n\n## URL de base, versions et compatibilité\n\nToutes les routes se trouvent sous le préfixe `/v1` de l'adresse publiée par votre administrateur, par exemple `https://api.example.internal/v1`. Lorsque la documentation est activée, le même serveur sert cette référence sur `/redoc`, la console interactive sur `/docs` et le contrat machine sur `/openapi.json`.\n\nLe contrat publié est `docs/api/openapi-v1.json`. Il porte `info.x-contract-version` (actuellement `1.0`). Au sein d'une version majeure, le contrat ne fait que s'enrichir : de nouvelles routes, de nouveaux champs facultatifs, de nouveaux champs de réponse et de nouvelles valeurs d'énumération peuvent apparaître à tout moment ; les clients doivent donc ignorer les champs qu'ils ne connaissent pas. Les changements suivants sont incompatibles et refusés par la vérification du contrat, sauf si le numéro majeur de la version du contrat est relevé :\n\n1. supprimer une route (méthode et chemin) ;\n2. supprimer un champ d'une réponse JSON 2xx ;\n3. ajouter un champ de requête obligatoire (corps ou paramètre de requête) ;\n4. retirer une valeur d'une énumération de requête.\n\n## Authentification\n\nChaque route `/v1` exige un identifiant, sauf les quelques routes publiques (connexion, santé). Le tenant de l'appel vient toujours de l'identifiant, jamais du corps de la requête. L'API accepte, dans cet ordre :\n\n| Identifiant | En-tête | Appelant type |\n|---|---|---|\n| Jeton d'accès OIDC (JWT) émis par Keycloak | `Authorization: Bearer <token>` | utilisateurs du portail, des SDK et de la CLI après connexion |\n| Session du portail `eais_<prefix>_<secret>` ouverte par `POST /v1/auth/login` | `Authorization: Bearer eais_...` | comptes locaux (administrateur d'installation) |\n| Clé d'API `eai_<prefix>_<secret>` | `X-API-Key: eai_...` ou `Authorization: Bearer eai_...` | scripts, chaînes CI, services back end |\n| Identité de charge de travail SPIFFE | `X-SPIFFE-ID` (posé uniquement par le sidecar du maillage) | charges de travail de la plateforme, quand SPIFFE est activé |\n\nUn jeton OIDC doit être signé avec un algorithme autorisé par une clé du JWKS configuré et porter `exp`, `iat`, `iss`, `sub` (et `aud` quand une audience est configurée). Les rôles sont lus dans les rôles de royaume Keycloak, les rôles client de l'audience et une revendication `roles` simple ; les groupes de l'annuaire (`groups`, `entra_groups`) peuvent donner `platform_admin` ou `tenant_admin` et appliquent les correspondances de groupes du tenant. Le tenant vient de la revendication `tenant_id` ou `tenant`.\n\nPour obtenir un jeton avec le flux client credentials de Keycloak, puis appeler l'API :\n\n```bash\ncurl -s -X POST \"https://sso.example.internal/realms/<realm>/protocol/openid-connect/token\" \\\n  -d grant_type=client_credentials -d client_id=<client_id> -d client_secret=<client_secret>\n\ncurl -s \"https://api.example.internal/v1/identity/whoami\" \\\n  -H \"Authorization: Bearer <token>\"\n```\n\n`GET /v1/identity/whoami` renvoie l'identité résolue par la plateforme : sujet, tenant, rôles, portées, habilitation et méthode d'authentification. Les clés d'API se créent avec `POST /v1/api-keys` ; la clé complète n'est montrée qu'une fois et seule son empreinte est conservée.\n\nLes erreurs d'authentification répondent `401` (`unauthenticated`, `invalid_token`, `invalid_api_key`, `api_key_revoked`, `api_key_expired`, `invalid_session`, `session_expired`), avec un en-tête `WWW-Authenticate: Bearer` quand aucun identifiant n'a été envoyé. Un tenant suspendu répond `403 tenant_suspended`. Une identité ou un identifiant suspendu (coupe-circuit du SOC) est refusé à chaque requête.\n\n## Tenants et projets\n\nLa plateforme est multi-tenant : chaque objet appartient à un seul tenant et un identifiant ne voit jamais que son propre tenant. Dans un tenant, les organisations et les unités métier forment la hiérarchie, et les projets sont les espaces de travail qui portent les agents, les bases de connaissances, les quotas et les secrets. Chaque projet a des environnements (`dev`, `test`, `staging`, `prod`).\n\n- Envoyez `X-Project: <project>` pour travailler dans un projet quand l'identifiant n'y est pas déjà rattaché (une clé d'API peut être rattachée à un projet et à un environnement).\n- Envoyez `X-Environment: dev|test|staging|prod` pour choisir l'environnement ; toute autre valeur est refusée avec `400 invalid_request`.\n- Les routes qui exigent un tenant répondent `403 tenant_required` à un identifiant de plateforme qui n'en a pas.\n\n## Rôles et portées\n\nL'accès est contrôlé en trois couches :\n\n1. Les **rôles** (RBAC) détenus dans le tenant : `platform_admin`, `tenant_admin`, `security_officer`, `finops`, `developer`, `auditor`, `agent`, `viewer`, `soc_analyst`, `ai_governance_officer`, `data_steward`, `application_owner`, `ai_platform_engineer`, `business_owner`, `support_engineer`. `platform_admin` passe tous les contrôles de rôle. Un rôle manquant répond `403 forbidden` avec `details.required_roles`.\n2. Les **portées** (scopes) portées par l'identifiant, comparées comme des motifs (`*`, `admin:*`, `models:invoke`). Les jetons d'utilisateur sans revendication `scope`, ou avec `openid`, reçoivent `*`. Une portée manquante répond `403 insufficient_scope` avec `details.required_scope`.\n3. Les **relations** (ReBAC, OpenFGA) sur une ressource, héritées du tenant au projet puis à la ressource, par exemple `can_assume` sur une identité de service. Une relation manquante répond `403 relation_required`, et le refus est audité.\n\nLes politiques (OPA) et les validations peuvent ensuite ajouter des obligations ou demander une validation à quatre yeux avant une action sensible.\n\n## Erreurs\n\nToute erreur, quelle que soit la route, utilise la même enveloppe. `code` est stable et ne change jamais : testez-le, pas le message. `message` est dans la langue négociée à partir d'`Accept-Language` ; `message_i18n` donne les deux langues ; `details` donne un contexte lisible par une machine ; `request_id` est l'identifiant à transmettre au support.\n\n```json\n{\n  \"error\": {\n    \"code\": \"forbidden\",\n    \"message\": \"Vous n'avez pas le rôle requis pour cette opération.\",\n    \"message_i18n\": {\"en\": \"You do not have the required role for this operation.\", \"fr\": \"Vous n'avez pas le rôle requis pour cette opération.\"},\n    \"details\": {\"required_roles\": [\"tenant_admin\"]},\n    \"request_id\": \"req_3f9a1c2b7d4e5f60\"\n  }\n}\n```\n\nCodes courants :\n\n| HTTP | Code | Signification |\n|---|---|---|\n| 400 | `invalid_request`, `invalid_cursor`, `json_too_deep` | requête mal formée, curseur invalide, JSON trop imbriqué |\n| 401 | `unauthenticated`, `invalid_token`, `invalid_api_key` | aucun identifiant valide |\n| 403 | `forbidden`, `insufficient_scope`, `relation_required`, `tenant_required` | authentifié mais non autorisé |\n| 404 | `not_found` | ressource inconnue, ou ressource d'un autre tenant |\n| 409 | `conflict`, `idempotency_conflict`, `operation_in_progress` | doublon, conflit de version ou d'état |\n| 413 | `payload_too_large` | corps au-delà de la taille autorisée |\n| 414 | `query_too_long` | chaîne de requête de plus de 8192 caractères |\n| 422 | `validation_error` | le corps ou les paramètres ne respectent pas le schéma ; `details.errors` liste `loc`, `msg` et `type` |\n| 429 | `rate_limited` | limite de débit atteinte ; attendez `Retry-After` secondes |\n| 431 | `headers_too_large` | trop d'en-têtes, ou en-têtes trop volumineux |\n| 500 | `internal_error` | erreur inattendue ; seul l'identifiant de requête est renvoyé |\n| 502 | `authz_backend_error` | le moteur d'autorisation n'a pas pu répondre (refus par défaut) |\n| 503 | `service_unavailable` | une dépendance est indisponible |\n\nLes violations de contraintes de la base deviennent `409 conflict` et les valeurs invalides `400 invalid_request`, jamais avec du texte SQL.\n\n## Pagination\n\nLes routes de liste utilisent une pagination par clé. Passez `limit` (50 par défaut, 500 au maximum ; une valeur supérieure est ramenée à 500, une valeur inférieure à 1 est refusée avec `400`) et lisez l'enveloppe de page :\n\n```json\n{\"items\": [ ... ], \"next_cursor\": \"eyJpZCI6IjQyIn0\"}\n```\n\nPour lire la page suivante, renvoyez la même requête avec `cursor=<next_cursor>`. `next_cursor` vaut `null` sur la dernière page. Le curseur est opaque : ne le décodez pas et ne le construisez pas ; un curseur abîmé répond `400 invalid_cursor`. Certaines routes documentent leur propre maximum, plus bas.\n\n## Idempotence et opérations longues\n\nLes créations longues (exécutions d'agents, campagnes d'évaluation et de red team, installations et mises à niveau du marketplace, réindexation d'une base de connaissances, synchronisation d'un connecteur, génération vidéo) renvoient un en-tête `Operation-Id` et un en-tête `Operation-Location` (`/v1/operations/{operation_id}`). Suivez l'opération avec `GET /v1/operations/{operation_id}` : elle renvoie la réponse acceptée, la ressource suivie et son état courant, lu avec vos propres identifiants.\n\nEnvoyez un en-tête `Idempotency-Key` (1 à 200 caractères) pour rendre une nouvelle tentative sûre :\n\n1. la première requête s'exécute et sa réponse est conservée pour votre principal et cette clé ;\n2. une nouvelle tentative avec la même clé et le même corps renvoie la réponse conservée avec `Idempotency-Replayed: true`, sans relancer l'opération ;\n3. la même clé avec un autre corps répond `409 idempotency_conflict` ;\n4. une nouvelle tentative pendant que la première requête s'exécute encore répond `409 operation_in_progress`.\n\nUne requête en échec (hors 2xx) libère la clé, qui peut alors être réutilisée.\n\n## Limites de débit et de taille des requêtes\n\nLes compteurs utilisent des fenêtres fixes d'une minute, partagées par toutes les répliques quand Redis est configuré. Budgets par défaut, que l'exploitant peut modifier :\n\n| Compteur | Défaut | Clé |\n|---|---|---|\n| `principal` | 1200 requêtes par minute | tenant et principal, toutes les routes `/v1` |\n| `gateway` | 600 requêtes par minute | tenant et principal, `/v1/gateway/*` |\n| `anonymous` | 1200 requêtes par minute | IP du client, requêtes sans aucun identifiant |\n| `auth_failures` | 20 échecs par minute | IP du client ; au-delà, toute tentative de cette IP est refusée jusqu'à la fin de la fenêtre |\n\nAu-delà du budget, l'API répond `429 rate_limited` avec un en-tête `Retry-After` et `details.bucket`, `details.limit_per_minute`, `details.retry_after_seconds`. Les routes de santé et de métriques ne sont jamais limitées.\n\nLimites de taille : au plus 100 en-têtes et 32 Kio d'en-têtes (`431`), une chaîne de requête d'au plus 8192 caractères (`414`), un corps d'au plus 10 Mio, ou 100 Mio sur les routes de téléversement (`413 payload_too_large` avec `details.max_bytes`), et un JSON imbriqué sur au plus 64 niveaux (`400 json_too_deep`). La limite de corps s'applique quel que soit l'encodage de transfert.\n\n## Identifiants de requête et traçage\n\nChaque réponse porte `X-Request-Id`. Envoyez votre propre `X-Request-Id` (1 à 128 caractères parmi les lettres, les chiffres et `. _ : -`) pour corréler vos journaux ; toute autre valeur est remplacée par un identifiant généré `req_<hex>`. Le même identifiant figure dans l'enveloppe d'erreur et dans le journal d'accès.\n\nL'API suit W3C Trace Context : envoyez un en-tête `traceparent` et la requête rejoint votre trace ; sans lui, l'API démarre une nouvelle trace. La réponse renvoie le `traceparent` de la requête, et les événements d'audit portent le même identifiant de trace, de sorte que `GET /v1/audit/correlations?trace_id=...` retrouve tous les événements d'une même chaîne d'appels. Hors staging et production, un en-tête `Server-Timing` donne le temps de traitement.\n\n## Audit\n\nChaque modification, chaque refus d'autorisation et chaque lecture sensible écrit un événement d'audit dans la même transaction que la modification : une modification annulée ne laisse aucun événement. Les événements d'un tenant forment une chaîne de hachage (chaque empreinte couvre la précédente), si bien qu'un événement supprimé ou modifié est détecté par `GET /v1/audit/verify`. Chaque événement enregistre l'acteur (sujet, tenant, méthode d'authentification), l'action, la ressource, la décision de politique, le résultat, l'identifiant de trace et, sous un contexte d'exécution délégué, la personne pour le compte de laquelle l'appel agit. Lisez les événements avec `GET /v1/audit/events` et exportez-les avec `POST /v1/audit/export`.",
  "title": "Enterprise AI Platform - Control Plane",
  "version": "1.0.2",
  "x-contract-version": "1.0"
 },
 "openapi": "3.1.0",
 "paths": {
  "/healthz": {
   "get": {
    "description": "Sonde de vie : répond toujours 200 avec `status: ok` et la version du Control Plane tant que le processus sert des requêtes. Elle ne vérifie aucune dépendance. Sans authentification ni limitation de débit.",
    "operationId": "healthz_healthz_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Healthz Healthz Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Vérifier que le processus est vivant",
    "tags": [
     "system"
    ]
   }
  },
  "/readyz": {
   "get": {
    "description": "Sonde de disponibilité : vérifie que la base de données répond et que les migrations du schéma ont été appliquées. Répond 200 `ready` quand chaque contrôle réussit, sinon 503 `not_ready`, avec le résultat de chaque contrôle et un message dans la langue de la requête. L'état du cache Redis est indiqué mais ne rend jamais l'instance indisponible. Sans authentification ni limitation de débit.",
    "operationId": "readyz_readyz_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Vérifier que l'instance est prête",
    "tags": [
     "system"
    ]
   }
  },
  "/v1/a2a/inbox": {
   "get": {
    "description": "Renvoie les messages A2A adressés à `recipient`, du plus ancien au plus récent, chacun avec `verified` (la signature HMAC-SHA256 de l'enveloppe correspond à la clé du tenant). La lecture marque les messages renvoyés comme lus. Une seule page (`next_cursor` vaut toujours null).",
    "operationId": "a2a_inbox_v1_a2a_inbox_get",
    "parameters": [
     {
      "description": "Partie dont la boîte est lue : `agent:<nom>` ou `run:<id>` (au moins 3 caractères).",
      "in": "query",
      "name": "recipient",
      "required": true,
      "schema": {
       "minLength": 3,
       "title": "Recipient",
       "type": "string"
      }
     },
     {
      "description": "Si vrai, seulement les messages pas encore lus. Faux par défaut.",
      "in": "query",
      "name": "unread",
      "required": false,
      "schema": {
       "default": false,
       "title": "Unread",
       "type": "boolean"
      }
     },
     {
      "description": "Nombre maximal de messages renvoyés (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response A2A Inbox V1 A2A Inbox Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une boîte de réception A2A",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/a2a/messages": {
   "post": {
    "description": "Envoie un message signé d'agent à agent (enveloppe `a2a.v1`, signature HMAC-SHA256 propre au tenant). Demande `tenant_admin`, `developer` ou `agent`.\nÉtapes :\n1. `to` et `from` sont résolus : `agent:<nom>` ou `run:<id>` (400 `invalid_request` sinon). Sans `from`, l'expéditeur est le principal appelant.\n2. Un expéditeur `run:` doit être un run créé par l'appelant (ou l'appelant est `tenant_admin`, sinon 403 `forbidden`) et non terminé (409 `run_finished`).\n3. La politique `agent.a2a.send` est évaluée et la charge utile est filtrée comme contenu non fiable ; une injection de prompt est refusée avec `prompt_injection_blocked` après enregistrement de l'événement de sécurité.\n4. L'enveloppe est déposée dans la boîte du destinataire et auditée sous `a2a.send`.",
    "operationId": "send_a2a_v1_a2a_messages_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/A2AIn"
       }
      }
     },
     "description": "Destinataire, type de message, charge utile et fil de conversation facultatif.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Send A2A V1 A2A Messages Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Envoyer un message A2A",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/adapters": {
   "get": {
    "description": "Adaptateurs spécialisés de la passerelle (génération d'images, transcription, synthèse vocale, génération de vidéo...) avec leur modalité, leur libellé dans la langue de la requête et dans les deux langues, leur statut (`configured` quand leur adresse est renseignée, sinon `not_configured`) et les contrôles qui leur sont appliqués. Demande un rôle de lecteur.",
    "operationId": "adapters_list_v1_adapters_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les adaptateurs spécialisés",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/agents": {
   "get": {
    "description": "Agents du tenant, dans l'ordre des noms, paginés avec `cursor`. Tout principal du tenant peut l'appeler.",
    "operationId": "list_agents_v1_agents_get",
    "parameters": [
     {
      "description": "Seulement les agents de ce statut : `active`, `suspended` ou `killed`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Agents V1 Agents Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les agents",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Applique un manifeste Agent : crée l'agent, ou ajoute une version à un agent existant et la rend courante. Le manifeste est validé selon le contrat v1 (422 `invalid_manifest`) ; le niveau de risque `unacceptable` est refusé (422 `agent_risk_unacceptable`) ; la même version avec un autre contenu est refusée (409 `version_immutable`). `apply_status` dans la réponse vaut `created`, `version_added` ou `unchanged`. Demande `tenant_admin`, `developer` ou `application_owner` ; l'appelant devient propriétaire ReBAC. Audité sous `agent.apply`.",
    "operationId": "create_agent_v1_agents_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Manifeste Agent (`kind: Agent`, `metadata` avec nom, version et propriétaire, `spec`).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Agent V1 Agents Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un agent ou une version",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/agents/{agent_id}": {
   "delete": {
    "description": "Supprime l'agent et ses versions. Demande le rôle `tenant_admin` et la relation ReBAC `owner` sur l'agent. Refusé avec 409 `agent_has_active_runs` tant qu'un run de l'agent est encore actif (en attente, en cours, en pause ou suspendu) ; 404 `agent_not_found` pour un agent inconnu. Audité sous `agent.delete`.",
    "operationId": "delete_agent_v1_agents__agent_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un agent",
    "tags": [
     "agents"
    ]
   },
   "get": {
    "description": "Renvoie l'agent, le manifeste de sa version courante et `runs`, le nombre de ses runs par état. Demande la relation ReBAC `viewer` sur l'agent. 404 `agent_not_found`.",
    "operationId": "get_agent_v1_agents__agent_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Agent V1 Agents  Agent Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un agent",
    "tags": [
     "agents"
    ]
   },
   "patch": {
    "description": "Modifie les champs de registre de l'agent (propriétaire, niveau de risque, description, libellés) sans créer de version. Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. Une valeur invalide répond 400 `invalid_request`. Audité sous `agent.update` avec les noms des champs modifiés.",
    "operationId": "patch_agent_v1_agents__agent_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AgentPatch"
       }
      }
     },
     "description": "Champs à modifier ; ceux omis sont conservés.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Agent V1 Agents  Agent Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/agents/{agent_id}/kill": {
   "post": {
    "description": "Kill switch : tous les runs actifs de l'agent sont annulés avec l'erreur `killed` et le statut de l'agent devient `killed` définitivement (il ne peut plus reprendre). Demande `tenant_admin` ou `security_officer`. 409 `agent_killed` s'il est déjà arrêté. La réponse liste `affected_runs`. Audité sous `agent.kill`.",
    "operationId": "kill_agent_v1_agents__agent_id__kill_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/eai__domains__agents__routes__StatusIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative, conservée sur l'agent et dans le journal d'audit."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Kill Agent V1 Agents  Agent Id  Kill Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Arrêter définitivement un agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/agents/{agent_id}/resume": {
   "post": {
    "description": "Ouvre une approbation de promotion pour ramener un agent suspendu à `active` et répond 202 avec `approval_id` et `status: pending_approval` ; les approbateurs sont `tenant_admin` ou `security_officer`. Une fois approuvée, les runs suspendus par la suspension de l'agent reprennent. Demande `tenant_admin` ou `security_officer`. Refusé avec 409 `agent_killed`, ou 409 `kill_switch_active` tant qu'un confinement SOC retient l'agent (levé uniquement via `POST /v1/soc/recoveries`).",
    "operationId": "resume_agent_v1_agents__agent_id__resume_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/eai__domains__agents__routes__StatusIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative, montrée aux approbateurs."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Resume Agent V1 Agents  Agent Id  Resume Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander la reprise d'un agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/agents/{agent_id}/runs": {
   "post": {
    "description": "Crée un run et renvoie son Execution Contract (201). Demande `tenant_admin`, `developer` ou `agent`, le scope `agents:run` et la relation ReBAC `can_invoke`.\nÉtapes :\n1. L'agent doit être `active` (sinon 409 `agent_not_active`, audité comme refus).\n2. La politique `agent.run` est évaluée avec le risque, le propriétaire et la finalité de l'agent et la `data_classification` de la tâche (403 `policy_denied`).\n3. Le budget est celui du manifeste, resserré par `budgets` (une requête ne peut jamais l'élargir ; 422 `invalid_budget`).\n4. La délégation et l'identité de workload sont rattachées, le run est créé en `pending` (ou `running` avec `start=true`) et audité sous `run.create`.\nUne `Idempotency-Key` répétée renvoie le premier run ; la même clé pour un autre agent répond 409 `idempotency_conflict`. Un `session_id` inconnu répond 404 `session_not_found`.",
    "operationId": "create_run_v1_agents__agent_id__runs_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__agents__routes__RunIn"
       }
      }
     },
     "description": "Tâche du run, session facultative, budget, politique de nouvelle tentative et délégation.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Run V1 Agents  Agent Id  Runs Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer un run d'un agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/agents/{agent_id}/suspend": {
   "post": {
    "description": "Kill switch réversible : le statut de l'agent devient `suspended`, ses runs actifs passent à `suspended` et les nouveaux runs sont refusés. Demande `tenant_admin` ou `security_officer`. 409 `agent_killed` pour un agent arrêté. La réponse liste `affected_runs`. Audité sous `agent.suspend`.",
    "operationId": "suspend_agent_v1_agents__agent_id__suspend_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/eai__domains__agents__routes__StatusIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative, conservée sur l'agent et dans le journal d'audit."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Suspend Agent V1 Agents  Agent Id  Suspend Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Suspendre un agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/agents/{agent_id}/versions": {
   "get": {
    "description": "Toutes les versions enregistrées de l'agent, de la plus récente à la plus ancienne, avec manifeste, empreinte, auteur et `current` (vrai pour la version en service). Demande la relation ReBAC `viewer`. Une seule page.",
    "operationId": "agent_versions_v1_agents__agent_id__versions_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Agent Versions V1 Agents  Agent Id  Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions d'un agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/api-keys": {
   "get": {
    "description": "Liste les clés d'API du tenant, paginées par `limit` et `cursor`, triées par identifiant. Chaque élément donne le nom, le préfixe public, les rôles, les portées, l'habilitation, le projet, l'environnement, le créateur, l'expiration et l'état de révocation ; jamais le secret ni son empreinte. `tenant_admin` et `security_officer` voient toutes les clés, un `developer` uniquement celles qu'il a créées.",
    "operationId": "list_api_keys_v1_api_keys_get",
    "parameters": [
     {
      "description": "Nombre maximal de clés d'API dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Api Keys V1 Api Keys Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les clés d'API",
    "tags": [
     "identity"
    ]
   },
   "post": {
    "description": "Crée une clé d'API pour les scripts et les services. Exige `tenant_admin` ou `developer`. La réponse (201) porte le secret complet dans `key`, montré une seule fois : rangez-le aussitôt dans un magasin de secrets.\n\nRègles :\n\n1. les rôles accordés doivent être détenus par l'appelant, sauf si celui-ci est `tenant_admin` ; `platform_admin` exige un administrateur de plateforme (403 `forbidden`) ;\n2. chaque portée doit être détenue par l'appelant (403 `forbidden`, raison `scope_not_held`) ;\n3. `expires_in_days` fixe `expires_at` quand ce dernier n'est pas donné ; sans l'un ni l'autre, la clé n'expire jamais.\n\nEnvoyez la clé dans `X-API-Key: <key>` ou `Authorization: Bearer <key>`. Écrit l'événement d'audit `api_key.create` (nom, préfixe, portées, rôles ; jamais le secret).",
    "operationId": "create_api_key_v1_api_keys_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ApiKeyIn"
       }
      }
     },
     "description": "Nom, droits, rattachement et expiration de la nouvelle clé.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Api Key V1 Api Keys Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une clé d'API",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/api-keys/{key_id}": {
   "delete": {
    "description": "Révoque la clé immédiatement : toute requête ultérieure faite avec elle répond 401 `api_key_revoked`. Révoquer une clé déjà révoquée réussit sans changement. Autorisé à `tenant_admin`, `security_officer` et `developer` ; un développeur ne révoque que les clés qu'il a créées, toute autre clé répond 404 `not_found`. Écrit l'événement d'audit `api_key.revoke`. Répond 204 sans corps.",
    "operationId": "revoke_api_key_v1_api_keys__key_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la clé d'API.",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer une clé d'API",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/api-keys/{key_id}/rotate": {
   "post": {
    "description": "Émet une clé de remplacement avec le même nom, les mêmes rôles, portées, habilitation, projet et environnement, et retire l'ancienne. Le nouveau secret figure dans `key` de la réponse 201, montré une seule fois.\n\n1. Sans `grace_seconds` (ou avec 0), l'ancienne clé est révoquée immédiatement.\n2. Avec `grace_seconds`, l'ancienne clé fonctionne encore jusqu'à la fin de ce délai (7 jours au plus), ce qui permet aux clients de basculer sans interruption.\n\nQui peut faire tourner : `tenant_admin` et les administrateurs de plateforme toute clé du tenant ; `security_officer` toute clé sans rôle d'administrateur ; un `developer` uniquement ses propres clés, dans la limite de ses rôles et portées. Une clé qui porte `platform_admin` exige un administrateur de plateforme. Une clé révoquée répond 409 `api_key_revoked` ; une clé inconnue, ou celle d'un autre développeur, répond 404. Écrit l'événement d'audit `api_key.rotate`.",
    "operationId": "rotate_api_key_v1_api_keys__key_id__rotate_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la clé d'API.",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/RotateIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Délai de grâce facultatif de l'ancienne clé. Un corps vide révoque l'ancienne clé immédiatement."
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Rotate Api Key V1 Api Keys  Key Id  Rotate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Faire tourner une clé d'API",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/applications": {
   "get": {
    "description": "Applications du tenant dans l'ordre des noms, avec type, origine, statut, environnements et ressources accordées. Une seule page.",
    "operationId": "list_applications_v1_applications_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Applications V1 Applications Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les applications",
    "tags": [
     "applications"
    ]
   },
   "post": {
    "description": "Crée l'application (201). Un `external_agent` est toujours d'origine `third_party`. Le propriétaire est par défaut l'appelant, le projet l'en-tête `X-Project`. Un nom déjà utilisé répond 409 `conflict`. Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `application.create`.",
    "operationId": "create_application_v1_applications_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ApplicationIn"
       }
      }
     },
     "description": "Nom, type, origine, propriétaire, projet, environnements, niveau de risque et ressources accordées.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Application V1 Applications Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer une application",
    "tags": [
     "applications"
    ]
   }
  },
  "/v1/applications/{app_id}": {
   "get": {
    "description": "Renvoie l'application et ses déploiements, du plus ancien au plus récent. 404 `application_not_found`.",
    "operationId": "get_application_v1_applications__app_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'application.",
      "in": "path",
      "name": "app_id",
      "required": true,
      "schema": {
       "title": "App Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Application V1 Applications  App Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une application",
    "tags": [
     "applications"
    ]
   }
  },
  "/v1/applications/{app_id}/deployments": {
   "post": {
    "description": "Enregistre le déploiement d'une version dans un environnement de l'application et ouvre une approbation `application_deployment` pour `tenant_admin` ou `security_officer` (201, le déploiement porte `approval_id`). L'environnement doit être déclaré par l'application (400 `invalid_request`) ; la même version dans le même environnement répond 409 `conflict` ; une application suspendue ou retirée répond 409 `application_suspended`. Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `application.deployment.create`.",
    "operationId": "create_deployment_v1_applications__app_id__deployments_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'application.",
      "in": "path",
      "name": "app_id",
      "required": true,
      "schema": {
       "title": "App Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DeploymentIn"
       }
      }
     },
     "description": "Version, environnement et raison montrée aux approbateurs.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Deployment V1 Applications  App Id  Deployments Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander un déploiement",
    "tags": [
     "applications"
    ]
   }
  },
  "/v1/applications/{app_id}/deployments/{deployment_id}/activate": {
   "post": {
    "description": "Active le déploiement une fois son approbation accordée (la séparation des tâches est appliquée par le domaine des approbations ; une approbation manquante est auditée comme refus) : le déploiement actif précédent de l'environnement est retiré et l'application passe à `active`. 409 `application_suspended` ; 404 `application_deployment_not_found`. Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `application.deployment.activate` avec l'approbateur.",
    "operationId": "activate_deployment_v1_applications__app_id__deployments__deployment_id__activate_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'application.",
      "in": "path",
      "name": "app_id",
      "required": true,
      "schema": {
       "title": "App Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du déploiement.",
      "in": "path",
      "name": "deployment_id",
      "required": true,
      "schema": {
       "title": "Deployment Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Activate Deployment V1 Applications  App Id  Deployments  Deployment Id  Activate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Activer un déploiement approuvé",
    "tags": [
     "applications"
    ]
   }
  },
  "/v1/applications/{app_id}/external-agents": {
   "post": {
    "description": "Crée une clé d'API avec le rôle `agent`, liée à l'application et à son projet, avec les scopes `tools:invoke`, `models:invoke` plus ceux demandés (201). La clé figure dans `api_key` et n'est montrée qu'une fois. Demande `tenant_admin`. Audité sous `application.external_agent.register`.",
    "operationId": "register_external_agent_v1_applications__app_id__external_agents_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'application.",
      "in": "path",
      "name": "app_id",
      "required": true,
      "schema": {
       "title": "App Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ExternalAgentIn"
       }
      }
     },
     "description": "Nom de l'identité et scopes supplémentaires.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Register External Agent V1 Applications  App Id  External Agents Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer l'identité d'un agent tiers",
    "tags": [
     "applications"
    ]
   }
  },
  "/v1/applications/{app_id}/status": {
   "post": {
    "description": "Passe l'application à `suspended` ou `retired`, ainsi que ses déploiements actifs ; tout appel de passerelle de ses agents externes est refusé dès lors. Demande `tenant_admin` ou `security_officer`. Audité sous `application.suspended` ou `application.retired`.",
    "operationId": "application_status_v1_applications__app_id__status_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de l'application.",
      "in": "path",
      "name": "app_id",
      "required": true,
      "schema": {
       "title": "App Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__applications__routes__StatusIn"
       }
      }
     },
     "description": "Statut visé et raison.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Application Status V1 Applications  App Id  Status Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Suspendre ou retirer une application",
    "tags": [
     "applications"
    ]
   }
  },
  "/v1/approvals": {
   "get": {
    "description": "Liste les demandes de validation du tenant, les plus récentes d'abord, paginées, éventuellement filtrées par statut. Les demandes en attente dont l'expiration est passée sont d'abord marquées `expired`. Chaque élément porte ses `decisions` et `approvals_count`. Ouvert à tout membre du tenant.",
    "operationId": "list_approvals_v1_approvals_get",
    "parameters": [
     {
      "description": "Uniquement les demandes dans ce statut : `pending`, `approved`, `rejected`, `expired` ou `cancelled`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Nombre maximal de demandes dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Approvals V1 Approvals Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les demandes de validation",
    "tags": [
     "approvals"
    ]
   },
   "post": {
    "description": "Crée une demande de validation en attente. Exige `tenant_admin`, `developer`, `agent`, `security_officer` ou `finops`.\n\n- `run_id`, s'il est donné, doit être une exécution du tenant (404 sinon) ; l'exécution reprend ou échoue quand la demande est tranchée.\n- L'appelant, ainsi que la personne pour le compte de laquelle un agent agit, sont ajoutés à `payload.excluded_approvers`, avec les sujets déjà listés.\n- Sans `approver_roles`, les valideurs sont `tenant_admin`, `security_officer`, `developer` et `business_owner`.\n- Sans `expires_in_seconds`, la demande expire après le délai par défaut de la plateforme (24 heures).\n\nAvec une `Idempotency-Key` déjà utilisée, la demande existante est renvoyée sans changement. Écrit l'événement d'audit `approval.request`. Répond 201 avec la demande.",
    "operationId": "create_approval_v1_approvals_post",
    "parameters": [
     {
      "description": "Clé du client pour la demande : la même clé renvoie la demande déjà créée au lieu d'en créer une seconde.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ApprovalIn"
       }
      }
     },
     "description": "Action, ressource, quorum, valideurs et expiration de la demande.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Approval V1 Approvals Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander une validation",
    "tags": [
     "approvals"
    ]
   }
  },
  "/v1/approvals/{approval_id}": {
   "get": {
    "description": "Renvoie une demande de validation avec ses décisions (valideur, décision, commentaire, date) et `approvals_count`. Une demande en attente dont l'expiration est passée est marquée `expired` et son exécution en attente échoue. Ouvert à tout membre du tenant. Répond 404 `approval_not_found`.",
    "operationId": "get_approval_v1_approvals__approval_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de validation.",
      "in": "path",
      "name": "approval_id",
      "required": true,
      "schema": {
       "title": "Approval Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Approval V1 Approvals  Approval Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une demande de validation",
    "tags": [
     "approvals"
    ]
   }
  },
  "/v1/approvals/{approval_id}/cancel": {
   "post": {
    "description": "Retire une demande en attente. Autorisé à son demandeur, à un `tenant_admin` ou à un `security_officer` (403 `forbidden` sinon, le refus étant audité). La demande finit `cancelled` et son sujet réagit comme à un refus : une exécution en attente échoue (`approval_cancelled`), un accès d'urgence ou un mandat en attente est refusé. Une demande qui n'est plus en attente répond 409 `approval_closed`. Écrit l'événement d'audit `approval.cancel` avec la raison. La réponse porte `run_state` quand une exécution est rattachée.",
    "operationId": "cancel_v1_approvals__approval_id__cancel_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de validation.",
      "in": "path",
      "name": "approval_id",
      "required": true,
      "schema": {
       "title": "Approval Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__approvals__routes__CancelIn"
       }
      }
     },
     "description": "Raison de l'annulation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Cancel V1 Approvals  Approval Id  Cancel Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Annuler une demande de validation",
    "tags": [
     "approvals"
    ]
   }
  },
  "/v1/approvals/{approval_id}/decide": {
   "post": {
    "description": "Enregistre la décision de l'appelant sur une demande en attente.\n\n1. La demande doit être en attente (409 `approval_closed`, y compris quand elle vient d'expirer).\n2. L'appelant ne doit être ni le demandeur ni un valideur exclu (403 `separation_of_duties`, audité).\n3. L'appelant doit détenir l'un des `approver_roles` (403 `forbidden`) et ne pas avoir déjà décidé (409 `already_decided`).\n4. `reject` clôt la demande en `rejected` ; `approve` la clôt en `approved` dès que les approbations atteignent le quorum, sinon elle reste en attente.\n\nUne fois la demande tranchée, le sujet réagit : l'exécution rattachée reprend ou échoue, une `promotion` approuvée est appliquée, une demande `break_glass` ou `mandate` est activée ou refusée. Écrit l'événement d'audit `approval.decide`. La réponse porte `run_state` quand une exécution est rattachée.",
    "operationId": "decide_v1_approvals__approval_id__decide_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de validation.",
      "in": "path",
      "name": "approval_id",
      "required": true,
      "schema": {
       "title": "Approval Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DecisionIn"
       }
      }
     },
     "description": "Décision et commentaire facultatif.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Decide V1 Approvals  Approval Id  Decide Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Approuver ou refuser une demande",
    "tags": [
     "approvals"
    ]
   }
  },
  "/v1/audit/correlations": {
   "get": {
    "description": "Avec `trace_id`, `run_id` ou `session_id` (le premier donné l'emporte), renvoie tout ce qui partage cette clé : `audit_events`, `usage_events` des LLM, `runs`, `spans` de télémétrie, `multimodal_jobs` (jusqu'à 1000 de chaque, 200 pour les exécutions et les tâches), les `trace_ids` rencontrés et `counts`.\n\nSans clé, renvoie `groups` : les clés les plus récentes (trace, exécution, session) avec leur nombre d'événements, leurs première et dernière dates et leurs actions, au plus `limit`. Exige `auditor`, `security_officer` ou `tenant_admin`.",
    "operationId": "correlations_v1_audit_correlations_get",
    "parameters": [
     {
      "description": "Identifiant de trace W3C (32 caractères hexadécimaux) à corréler.",
      "in": "query",
      "name": "trace_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Trace Id"
      }
     },
     {
      "description": "Identifiant d'exécution d'agent à corréler.",
      "in": "query",
      "name": "run_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Run Id"
      }
     },
     {
      "description": "Identifiant de session à corréler.",
      "in": "query",
      "name": "session_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Session Id"
      }
     },
     {
      "description": "Nombre maximal de groupes quand aucune clé n'est donnée (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Correlations V1 Audit Correlations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Corréler les événements par trace, exécution ou session",
    "tags": [
     "audit"
    ]
   }
  },
  "/v1/audit/events": {
   "get": {
    "description": "Recherche les événements d'audit du tenant, les plus récents d'abord (séquence la plus haute d'abord), paginés. Tous les filtres se combinent. Exige `auditor`, `security_officer` ou `tenant_admin`. La recherche elle-même est inscrite dans le journal d'accès aux preuves (qui, quelle requête, combien de résultats).",
    "operationId": "list_events_v1_audit_events_get",
    "parameters": [
     {
      "description": "Uniquement les événements de cet identifiant d'acteur, par exemple un sujet d'utilisateur ou `apikey:<id>`.",
      "in": "query",
      "name": "actor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Actor"
      }
     },
     {
      "description": "Uniquement cette action, par exemple `api_key.create`. Un `*` final cherche un préfixe : `approval.*`.",
      "in": "query",
      "name": "action",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Action"
      }
     },
     {
      "description": "Uniquement cette ressource : `<type>` ou `<type>:<id>`, par exemple `project:42`.",
      "in": "query",
      "name": "resource",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Resource"
      }
     },
     {
      "description": "Uniquement ce résultat : `success`, `failure` ou `error`.",
      "in": "query",
      "name": "outcome",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Outcome"
      }
     },
     {
      "description": "Uniquement les événements de cet identifiant de trace W3C.",
      "in": "query",
      "name": "trace_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Trace Id"
      }
     },
     {
      "description": "Événements à partir de cette date et heure (ISO 8601), incluses.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Événements jusqu'à cette date et heure (ISO 8601), incluses.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     },
     {
      "description": "Nombre maximal d'événements dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Events V1 Audit Events Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rechercher les événements d'audit",
    "tags": [
     "audit"
    ]
   },
   "post": {
    "description": "Ajoute un événement applicatif à la chaîne de hachage du tenant. L'acteur vient de l'authentification ; la séquence, les empreintes et `occurred_at` viennent du serveur ; les `details` du client sont conservés sous `evidence.details.attributes`, à côté de `stream: sdk`. L'événement est scellé et exporté vers la boîte d'envoi et le SIEM comme les événements de la plateforme.\n\n1. Exige `tenant_admin`, `developer`, `agent`, `security_officer`, `application_owner` ou `ai_platform_engineer`.\n2. `details` doit rester sous 16384 octets une fois sérialisé (422 `audit_event_too_large`).\n3. La politique décide d'abord sur `audit.record`.\n4. `event_time` ne peut pas dépasser l'heure du serveur de plus que le décalage d'horloge autorisé (422 `audit_event_time_invalid`).\n\nLa réponse (201) donne `event_id`, `sequence`, `hash`, `prev_hash`, `occurred_at`, `event_time`, `action`, `stream`, `actor` et `decision`.",
    "operationId": "record_event_v1_audit_events_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AppEventIn"
       }
      }
     },
     "description": "L'événement applicatif. Les champs hors du schéma sont refusés.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Record Event V1 Audit Events Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un événement d'audit applicatif",
    "tags": [
     "audit"
    ]
   }
  },
  "/v1/audit/events/{event_id}": {
   "get": {
    "description": "Renvoie un événement d'audit avec sa séquence, ses empreintes, son acteur, sa ressource, sa décision et ses preuves. Exige `auditor`, `security_officer` ou `tenant_admin`. La lecture est inscrite dans le journal d'accès aux preuves. Répond 404 `not_found`.",
    "operationId": "get_event_v1_audit_events__event_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'événement d'audit.",
      "in": "path",
      "name": "event_id",
      "required": true,
      "schema": {
       "title": "Event Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Event V1 Audit Events  Event Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un événement d'audit",
    "tags": [
     "audit"
    ]
   }
  },
  "/v1/audit/export": {
   "post": {
    "description": "Écrit les événements de la période (bornes incluses) dans un stockage à écriture unique, en NDJSON ou en CSV, avec un manifeste. Exige `auditor`, `security_officer` ou `tenant_admin`.\n\n1. Les événements sont lus dans l'ordre des séquences et la chaîne est vérifiée.\n2. Le fichier est haché (SHA-256) et l'empreinte signée (OpenBao Transit, ou HMAC hors production).\n3. Le fichier et le manifeste sont stockés avec verrouillage d'objet pendant `retention_days` ; avec un profil de stockage du tenant dont le verrouillage d'objet et la rétention ont été vérifiés, l'export est `qualified` (409 `storage_capability_missing` quand le profil ne les a pas).\n\nLa réponse (201) est le manifeste : `export_id`, `file`, `manifest_uri`, `format`, `count`, première et dernière séquences, `sha256`, `signature`, `chain`, période et `worm`. Écrit l'événement d'audit `audit.export`.",
    "operationId": "export_v1_audit_export_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/ExportIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Période, rétention et format de l'export. Un corps vide exporte tout en NDJSON conservé 365 jours."
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Export V1 Audit Export Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exporter le journal d'audit",
    "tags": [
     "audit"
    ]
   }
  },
  "/v1/audit/verify": {
   "get": {
    "description": "Recalcule la chaîne de hachage du tenant depuis le premier événement et renvoie `tenant_id`, `valid`, `count`, `last_hash`, `broken_at`, `reason` et `verified_at`. Quand la chaîne est rompue, `broken_at` est la séquence du premier événement fautif et `reason` en donne la cause : `sequence gap`, `prev_hash mismatch` ou `hash mismatch`. Exige `auditor`, `security_officer` ou `tenant_admin`. La vérification est inscrite dans le journal d'accès aux preuves.",
    "operationId": "verify_v1_audit_verify_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify V1 Audit Verify Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Vérifier la chaîne d'audit",
    "tags": [
     "audit"
    ]
   }
  },
  "/v1/auth/login": {
   "post": {
    "description": "Ouvre une session du portail pour un compte local. Aucun identifiant n'est nécessaire. L'identifiant de connexion est comparé sans tenir compte de la casse.\n\nRéponse 200 : `token` (montré une seule fois), `token_type` (`Bearer`), `expires_at`, `must_change_password` et `user` (identifiant, tenant, rôles).\n\nErreurs : 401 `invalid_credentials` pour un identifiant inconnu ou un mauvais mot de passe (même réponse et même coût, la réponse ne révèle donc rien sur l'identifiant) ; 429 `account_locked` avec `Retry-After` après trop de mauvais mots de passe ; 401 `account_disabled` ; 403 `tenant_suspended` ; 403 `local_auth_disabled` ; 429 `rate_limited` quand l'IP du client a dépassé son budget d'échecs d'authentification. Chaque tentative écrit l'événement d'audit `auth.login`.",
    "operationId": "login_v1_auth_login_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/LoginIn"
       }
      }
     },
     "description": "Identifiant et mot de passe du compte local.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Login V1 Auth Login Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Se connecter avec un identifiant et un mot de passe",
    "tags": [
     "auth"
    ]
   }
  },
  "/v1/auth/logout": {
   "post": {
    "description": "Révoque la session du portail en cours : son jeton est refusé dès maintenant (401 `session_revoked`). Uniquement pour une session du portail (403 `password_session_required` avec un autre identifiant). Écrit l'événement d'audit `auth.logout`. Répond 204.",
    "operationId": "logout_v1_auth_logout_post",
    "responses": {
     "204": {
      "description": "Successful Response"
     }
    },
    "summary": "Se déconnecter",
    "tags": [
     "auth"
    ]
   }
  },
  "/v1/auth/password": {
   "post": {
    "description": "Change le mot de passe du compte local connecté. Uniquement pour une session du portail (403 `password_session_required`).\n\n1. `current_password` doit être correct (403 `invalid_current_password`).\n2. `new_password` doit respecter les règles (422 `password_too_weak`, `details` liste les raisons) : au moins la longueur configurée (12 par défaut), 256 caractères au plus, des lettres et des caractères autres que des lettres, ni l'identifiant, ni le mot de passe actuel, ni un mot de passe connu ou initial.\n3. Les autres sessions du compte sont révoquées ; la session en cours reste ouverte et sort de la restriction `must_change_password`.\n\nÉcrit l'événement d'audit `auth.password.change`.",
    "operationId": "change_password_v1_auth_password_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PasswordIn"
       }
      }
     },
     "description": "Mot de passe actuel et nouveau mot de passe.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Change Password V1 Auth Password Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Changer le mot de passe",
    "tags": [
     "auth"
    ]
   }
  },
  "/v1/authz/check": {
   "post": {
    "description": "Indique si `subject` détient `relation` sur `object`, en suivant l'héritage. Jusqu'à 20 `contextual_tuples` sont tenus pour vrais pour cette seule vérification et jamais enregistrés (un tuple invalide répond 422 `validation_error`). Ouvert à tout membre du tenant. La réponse donne `allowed`, `engine`, `subject`, `relation` et `object`. Écrit l'événement d'audit `authz.check` avec la décision. Quand le moteur ne peut pas répondre, la décision est un refus, elle est auditée et l'API répond 502 `authz_backend_error`.",
    "operationId": "check_v1_authz_check_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CheckIn"
       }
      }
     },
     "description": "Sujet, relation, objet et tuples contextuels facultatifs.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Check V1 Authz Check Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier une relation",
    "tags": [
     "authz"
    ]
   }
  },
  "/v1/authz/tuples": {
   "delete": {
    "description": "Supprime un tuple (`{subject, relation, object}`) ou jusqu'à 100 (`{\"tuples\": [...]}`). Exige `tenant_admin` ou `security_officer`. Les tuples inexistants sont ignorés ; la réponse donne `deleted` (nombre supprimé) et `engine`. Écrit l'événement d'audit `authz.delete`. 502 `authz_backend_error` quand le moteur ne peut pas répondre.",
    "operationId": "delete_tuples_v1_authz_tuples_delete",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Payload",
        "type": "object"
       }
      }
     },
     "description": "Un tuple, ou un objet `tuples` avec 1 à 100 tuples.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Tuples V1 Authz Tuples Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer des tuples d'autorisation",
    "tags": [
     "authz"
    ]
   },
   "get": {
    "description": "Liste les tuples enregistrés du tenant, triés par objet, relation et sujet, 1000 au plus, éventuellement filtrés par objet et par sujet (égalité exacte). Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_tuples_v1_authz_tuples_get",
    "parameters": [
     {
      "description": "Uniquement les tuples portant sur cet objet, `<type>:<id>` comme `project:42`.",
      "in": "query",
      "name": "object",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Object"
      }
     },
     {
      "description": "Uniquement les tuples de ce sujet, comme `user:alice` ou `group:fin#member`.",
      "in": "query",
      "name": "subject",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Subject"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Tuples V1 Authz Tuples Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les tuples d'autorisation",
    "tags": [
     "authz"
    ]
   },
   "post": {
    "description": "Accorde un tuple (`{subject, relation, object}`) ou jusqu'à 100 (`{\"tuples\": [...]}`) en une transaction. Exige `tenant_admin` ou `security_officer`. Un sujet générique (`user:*`) sur une relation réservée aux sujets nommés, comme `can_assume`, répond 422 `authz_wildcard_not_allowed`. La réponse (201) donne `written` et `engine`. Écrit l'événement d'audit `authz.write`. 502 `authz_backend_error` quand le moteur ne peut pas répondre.",
    "operationId": "write_tuples_v1_authz_tuples_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Payload",
        "type": "object"
       }
      }
     },
     "description": "Un tuple, ou un objet `tuples` avec 1 à 100 tuples.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Write Tuples V1 Authz Tuples Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Écrire des tuples d'autorisation",
    "tags": [
     "authz"
    ]
   }
  },
  "/v1/break-glass": {
   "get": {
    "description": "Liste les accès d'urgence, les plus récents d'abord : tous pour `tenant_admin`, `security_officer` et `auditor`, sinon ceux que l'appelant a demandés ou dont il bénéficie. Les accès actifs dont l'expiration est passée sont marqués `expired` (audité une fois). Un statut inconnu répond 400.",
    "operationId": "list_break_glass_v1_break_glass_get",
    "parameters": [
     {
      "description": "Uniquement les accès `pending`, `active`, `expired`, `rejected` ou `revoked`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Nombre maximal d'accès (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Break Glass V1 Break Glass Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les accès d'urgence",
    "tags": [
     "delegation"
    ]
   },
   "post": {
    "description": "Demande une élévation exceptionnelle et limitée dans le temps pour le bénéficiaire (l'appelant par défaut). Ouvert à tout membre du tenant.\n\n1. Donnez des rôles du tenant (jamais `platform_admin`) et/ou des portées, une justification d'au moins 10 caractères et une durée comprise entre 60 secondes et le maximum de la plateforme (3600 par défaut, 400 `break_glass_duration_exceeded`).\n2. Une demande de validation est créée (action `break_glass`, quorum 2, valideurs `tenant_admin` ou `security_officer`, demandeur et bénéficiaire exclus).\n3. Une fois approuvé, l'accès est actif pendant `duration_seconds` et expire de lui-même ; les délégations créées par le bénéficiaire peuvent utiliser ses portées et ne lui survivent jamais.\n\nÉcrit les événements d'audit `break_glass.request`, puis `break_glass.activate` ou `break_glass.reject`. Répond 201 avec l'accès en attente et son `approval_id`.",
    "operationId": "request_break_glass_v1_break_glass_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/BreakGlassIn"
       }
      }
     },
     "description": "Rôles, portées, raison, durée, bénéficiaire et référence d'incident.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Break Glass V1 Break Glass Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander un accès d'urgence",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/break-glass/{grant_id}": {
   "get": {
    "description": "Renvoie un accès : demandeur, bénéficiaire, rôles, portées, raison, incident, durée, statut, validation et dates. Visible par son demandeur, son bénéficiaire et par `tenant_admin`, `security_officer` et `auditor` ; toute autre personne reçoit 404 `break_glass_not_found`.",
    "operationId": "get_break_glass_v1_break_glass__grant_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'accès d'urgence.",
      "in": "path",
      "name": "grant_id",
      "required": true,
      "schema": {
       "title": "Grant Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Break Glass V1 Break Glass  Grant Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un accès d'urgence",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/break-glass/{grant_id}/context": {
   "post": {
    "description": "Renvoie un ExecutionContext signé portant l'élévation active, dont la durée de vie ne dépasse jamais l'expiration de l'accès. Seul le bénéficiaire peut le demander (403 `forbidden`) ; l'accès doit être actif (409 `break_glass_not_active`) ; chaque audience doit être connue (400 `context_wrong_audience`). Les services en aval le vérifient comme tout contexte d'exécution.",
    "operationId": "break_glass_context_v1_break_glass__grant_id__context_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'accès d'urgence.",
      "in": "path",
      "name": "grant_id",
      "required": true,
      "schema": {
       "title": "Grant Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/BreakGlassContextIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Audiences et durée de vie facultatives du contexte."
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Break Glass Context V1 Break Glass  Grant Id  Context Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir un contexte d'exécution pour un accès d'urgence",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/break-glass/{grant_id}/revoke": {
   "post": {
    "description": "Met fin à un accès en attente ou actif et révoque les délégations créées sous lui. Autorisé au demandeur, à un `tenant_admin` ou à un `security_officer` (403 `forbidden`). Un accès déjà clos répond 409 `break_glass_closed`. Écrit l'événement d'audit `break_glass.revoke`.",
    "operationId": "revoke_break_glass_v1_break_glass__grant_id__revoke_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'accès d'urgence.",
      "in": "path",
      "name": "grant_id",
      "required": true,
      "schema": {
       "title": "Grant Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/RevokeIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Break Glass V1 Break Glass  Grant Id  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer un accès d'urgence",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/business-units": {
   "get": {
    "description": "Liste les unités métier du tenant, paginées, éventuellement filtrées par organisation ou par unité parente. Ouvert à tout membre du tenant.",
    "operationId": "list_business_units_v1_business_units_get",
    "parameters": [
     {
      "description": "Uniquement les unités de cette organisation.",
      "in": "query",
      "name": "organization_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Organization Id"
      }
     },
     {
      "description": "Uniquement les enfants directs de cette unité métier.",
      "in": "query",
      "name": "parent_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Parent Id"
      }
     },
     {
      "description": "Nombre maximal d'unités métier dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Business Units V1 Business Units Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les unités métier",
    "tags": [
     "tenancy"
    ]
   },
   "post": {
    "description": "Crée une unité métier dans une organisation, éventuellement sous une unité parente de la même organisation (422 `invalid_hierarchy` sinon). Exige `tenant_admin`. Un identifiant court déjà utilisé répond 409 `conflict`. Écrit l'événement d'audit `business_unit.create`.",
    "operationId": "create_business_unit_v1_business_units_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/BusinessUnitIn"
       }
      }
     },
     "description": "Organisation, parent, identifiant court, nom et description de l'unité.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Business Unit V1 Business Units Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une unité métier",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/business-units/{business_unit_id}": {
   "patch": {
    "description": "Modifie le nom, la description, le statut (`active` ou `archived`) ou les métadonnées d'une unité métier. Son organisation et son parent ne peuvent pas changer. Exige `tenant_admin`. Envoyez `expected_version` pour détecter une modification concurrente (409 `version_conflict`). Écrit l'événement d'audit `business_unit.update`.",
    "operationId": "patch_business_unit_v1_business_units__business_unit_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'unité métier.",
      "in": "path",
      "name": "business_unit_id",
      "required": true,
      "schema": {
       "title": "Business Unit Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/BusinessUnitPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Business Unit V1 Business Units  Business Unit Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une unité métier",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/catalog/bricks": {
   "get": {
    "description": "Catalogue des briques de la plateforme lu dans `bricks.yaml` du dossier de catalogue, en une seule page (`next_cursor` vaut toujours null). Le fichier est chargé en YAML sûr (sans ancres ni alias, 2 Mo au plus). Sans authentification.",
    "operationId": "list_bricks_v1_catalog_bricks_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Bricks V1 Catalog Bricks Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les briques de la plateforme",
    "tags": [
     "system"
    ]
   }
  },
  "/v1/catalog/bricks/{brick_id}": {
   "get": {
    "description": "Une brique du catalogue, retrouvée par son `id` sans tenir compte de la casse. Répond 404 `not_found` si aucune brique ne porte cet identifiant. Sans authentification.",
    "operationId": "get_brick_v1_catalog_bricks__brick_id__get",
    "parameters": [
     {
      "description": "Identifiant de la brique dans le catalogue, insensible à la casse.",
      "in": "path",
      "name": "brick_id",
      "required": true,
      "schema": {
       "title": "Brick Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Brick V1 Catalog Bricks  Brick Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une brique de la plateforme",
    "tags": [
     "system"
    ]
   }
  },
  "/v1/channels": {
   "get": {
    "description": "Canaux du tenant dans l'ordre des noms ; `project` (identifiant ou slug) ne garde que les canaux de ce projet (404 `project_not_found`). Une seule page.",
    "operationId": "list_channels_v1_channels_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet dont les canaux sont listés.",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Channels V1 Channels Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les canaux",
    "tags": [
     "channels"
    ]
   },
   "post": {
    "description": "Applique un manifeste Channel (`apply_status` : `created` ou `updated`) ; `project` (identifiant ou slug, par défaut l'en-tête `X-Project`) le rattache à un projet, et un canal d'un autre projet n'est jamais repris (409 `channel_project_conflict`). L'agent de `agent_ref` doit exister (404 `agent_not_found`) ; `signed_webhook` demande `auth.secret_ref` (422 `invalid_manifest`). Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `channel.apply`.",
    "operationId": "create_channel_v1_channels_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet auquel appartient le canal. Par défaut : l'en-tête `X-Project`.",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Manifeste Channel (`kind: Channel`, `metadata`, `spec` avec type, agent, auth, limites, DLP et sessions).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Channel V1 Channels Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer ou mettre à jour un canal",
    "tags": [
     "channels"
    ]
   }
  },
  "/v1/channels/{name}": {
   "delete": {
    "description": "Supprime le canal. Demande `tenant_admin`. 404 `channel_not_found`. Audité sous `channel.delete`.",
    "operationId": "delete_channel_v1_channels__name__delete",
    "parameters": [
     {
      "description": "Nom du canal.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un canal",
    "tags": [
     "channels"
    ]
   },
   "get": {
    "description": "Renvoie le canal avec son manifeste, son type, son agent, son statut, son projet et son preset. 404 `channel_not_found`.",
    "operationId": "get_channel_v1_channels__name__get",
    "parameters": [
     {
      "description": "Nom du canal.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Channel V1 Channels  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un canal",
    "tags": [
     "channels"
    ]
   },
   "patch": {
    "description": "Modifie le statut (`active` ou `disabled` ; un canal désactivé refuse les messages entrants) ou le propriétaire. Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `channel.update`.",
    "operationId": "patch_channel_v1_channels__name__patch",
    "parameters": [
     {
      "description": "Nom du canal.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ChannelPatch"
       }
      }
     },
     "description": "Champs à modifier ; ceux omis sont conservés.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Channel V1 Channels  Name  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un canal",
    "tags": [
     "channels"
    ]
   }
  },
  "/v1/channels/{name}/inbound": {
   "post": {
    "description": "Remet un message d'un utilisateur final (202) et renvoie `session_id`, `session_created`, `message_id`, `run_id` (ou `run_skipped` quand l'agent n'est pas actif) et le résultat DLP. Demande `tenant_admin`, `developer` ou `agent`.\nÉtapes :\n1. Le canal doit être actif (409 `channel_disabled`) ; un canal `signed_webhook` vérifie `X-EAI-Signature` (`t=<secondes unix>,v1=<HMAC-SHA256 hexadécimal de \"<t>.\" + corps brut>`, fenêtre de 5 minutes ; 401 `invalid_signature`).\n2. La politique `channel.inbound` est évaluée ; la taille (413 `message_too_large`) et le débit par utilisateur (429 `rate_limited`) sont contrôlés.\n3. La DLP entrante s'exécute (`detect` par défaut ; `block` répond 403 `dlp_blocked`, `mask` enregistre le texte masqué).\n4. La session ouverte de l'utilisateur et de la conversation est réutilisée, ou une nouvelle est ouverte (429 `too_many_sessions`, 409 `session_closed` au-delà de `max_session_minutes`).\n5. Le message est ajouté et un run de l'agent du canal démarre avec le déclencheur `channel`.",
    "operationId": "inbound_v1_channels__name__inbound_post",
    "parameters": [
     {
      "description": "Nom du canal.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "properties": {
         "conversation_id": {
          "anyOf": [
           {
            "maxLength": 300,
            "type": "string"
           },
           {
            "type": "null"
           }
          ],
          "title": "Conversation Id"
         },
         "metadata": {
          "additionalProperties": true,
          "title": "Metadata",
          "type": "object"
         },
         "text": {
          "maxLength": 1000000,
          "minLength": 1,
          "title": "Text",
          "type": "string"
         },
         "user": {
          "anyOf": [
           {
            "maxLength": 300,
            "type": "string"
           },
           {
            "type": "null"
           }
          ],
          "title": "User"
         }
        },
        "required": [
         "text"
        ],
        "title": "InboundIn",
        "type": "object"
       }
      }
     },
     "description": "Message JSON : `text` (1 à 1000000 caractères, obligatoire), `user` (identifiant externe de l'utilisateur, 300 caractères au plus ; par défaut l'appelant), `conversation_id` (300 caractères au plus ; par défaut l'utilisateur) et `metadata` (conservées avec le message). Les octets bruts servent au contrôle de signature.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Inbound V1 Channels  Name  Inbound Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Envoyer un message entrant",
    "tags": [
     "channels"
    ]
   }
  },
  "/v1/channels/{name}/sessions": {
   "get": {
    "description": "Sessions ouvertes sur le canal, de la plus récemment mise à jour à la plus ancienne, 500 au plus.",
    "operationId": "channel_sessions_v1_channels__name__sessions_get",
    "parameters": [
     {
      "description": "Nom du canal.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Channel Sessions V1 Channels  Name  Sessions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les sessions d'un canal",
    "tags": [
     "channels"
    ]
   }
  },
  "/v1/checkpoints/{checkpoint_id}": {
   "get": {
    "description": "Renvoie un point de reprise : run, numéro de séquence, séquence d'étape au moment de l'enregistrement, libellé et état enregistré. 404 `checkpoint_not_found`.",
    "operationId": "get_checkpoint_v1_checkpoints__checkpoint_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du point de reprise.",
      "in": "path",
      "name": "checkpoint_id",
      "required": true,
      "schema": {
       "title": "Checkpoint Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Checkpoint V1 Checkpoints  Checkpoint Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un point de reprise",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/compliance/instances": {
   "get": {
    "description": "Enregistrements d'activation du tenant, du plus récent au plus ancien, avec statut, secteur, pack, modèle de déploiement, versions et ressources possédées (une page, `next_cursor` nul). Filtres : `status`, `project`, `sector`.",
    "operationId": "list_instances_v1_compliance_instances_get",
    "parameters": [
     {
      "description": "Seulement les instances dans ce statut (draft, ready, failed, ...).",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 20,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les instances de ce projet (identifiant).",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 100,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     },
     {
      "description": "Seulement les instances de ce secteur (generic, banking, insurance, industry).",
      "in": "query",
      "name": "sector",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 20,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Sector"
      }
     },
     {
      "description": "Nombre maximal d'instances renvoyées (1 à 200, 50 par défaut).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "maximum": 200,
       "minimum": 1,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Instances V1 Compliance Instances Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les instances de l'agent de conformité et d'audit",
    "tags": [
     "compliance"
    ]
   },
   "post": {
    "description": "Crée l'enregistrement d'activation en `draft` (201) avec son `id` immuable (fourni, par exemple l'identifiant choisi par le runtime de conformité, ou généré), son secteur et son pack. Demande un administrateur du tenant ou un propriétaire du projet (403 `forbidden`) ; projet inconnu : 404 `compliance_project_not_found` ; un nom ou un identifiant déjà pris : 409 `conflict`. Avec `Idempotency-Key`, une nouvelle tentative rejoue la première réponse. Audité sous `compliance.create`.",
    "operationId": "create_instance_v1_compliance_instances_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ComplianceInstanceIn"
       }
      }
     },
     "description": "Nom, projet, organisation et unité métier, secteur et pack, modèle de déploiement, modèles de génération et d'embeddings du registre, version facultative.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Instance V1 Compliance Instances Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une instance brouillon de l'agent de conformité et d'audit",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}": {
   "delete": {
    "description": "Opération longue (202, statut `deleting` puis `deleted`) depuis `draft`, `stopped` ou `failed` (une instance prête est d'abord arrêtée, sinon 409). Ne supprime que ce que l'instance possède, tel qu'enregistré dans `resources` : sa base de connaissances et ses points Qdrant, les objets sous son préfixe de stockage, les mémoires et la politique de mémoire de son agent, ses tuples, sa clé d'API et son secret, son agent ; l'application gouvernée est retirée. Le runtime partagé, une base de données partagée, la collection Qdrant partagée par les bases du tenant et le profil de stockage sont conservés. Chaque étape liste `deleted` et `kept`. Audité sous `compliance.delete` et `compliance.delete.completed`.",
    "operationId": "delete_instance_v1_compliance_instances__instance_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Instance V1 Compliance Instances  Instance Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une instance",
    "tags": [
     "compliance"
    ]
   },
   "get": {
    "description": "L'enregistrement d'activation avec `serving` (vrai pour `ready` et `degraded`), `sector`, `pack`, l'`operation` et le `run` courants, `validation`, `last_error` et les `steps` de l'exécution courante dans l'ordre (état, tentatives, détail, journaux, horaires). C'est la route que le runtime de conformité lit au démarrage et à la demande.",
    "operationId": "get_instance_v1_compliance_instances__instance_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance, le même dans le Control Plane et dans le runtime de conformité.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Instance V1 Compliance Instances  Instance Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une instance de l'agent de conformité et d'audit",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/backup": {
   "post": {
    "description": "Répond 501 `compliance_backup_not_supported` avec `parts` et la raison de chacune : le runtime de conformité n'a pas de route d'administration pour exporter les lignes du schéma compliance d'une instance, le domaine des connaissances n'a pas d'export de base de connaissances. Rien n'est simulé ; le refus est audité.",
    "operationId": "backup_instance_v1_compliance_instances__instance_id__backup_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Backup Instance V1 Compliance Instances  Instance Id  Backup Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Sauvegarder une instance (non pris en charge)",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/branding": {
   "get": {
    "description": "L'image de marque choisie dans le parcours d'activation, portée par le Control Plane : `agent_name`, `sidebar_color`, `navbar_color`, les couleurs de texte calculées pour elles (`sidebar_text_color`, `navbar_text_color`, noir ou blanc, contraste de 4,5:1 ou plus), `logo` (`content_type`, `sha256`, `size_bytes`, `url`) ou null, `version`, `updated_at`, et `app_url` (l'URL de l'application web une fois connue). Un champ null signifie la valeur par défaut du runtime. Le runtime de conformité en garde une réplique pour sa page de connexion publique et la réécrit quand `version` est plus récente. Tout principal du tenant ; l'instance d'un autre tenant répond 404.",
    "operationId": "get_branding_v1_compliance_instances__instance_id__branding_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Branding V1 Compliance Instances  Instance Id  Branding Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire l'image de marque d'une instance",
    "tags": [
     "compliance"
    ]
   },
   "put": {
    "description": "Écrit les champs fournis (un champ absent est conservé, null le ramène à la valeur par défaut du runtime) et renvoie l'image de marque. Les couleurs sont au format `#RRGGBB` (enregistrées en majuscules) ; le texte affiché dessus est noir ou blanc, selon le meilleur contraste (luminance relative WCAG 2.2), et une couleur dont le texte resterait sous 4,5:1 répond 422 `compliance_branding_contrast`. `app_url` doit être une URL https sans identifiants (422 `compliance_app_url_invalid`). Un changement du nom, des couleurs ou du logo incrémente `version` ; un `expected_version` différent de la version courante répond 409 `compliance_branding_version_conflict`. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps rejoue la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Demande un administrateur du tenant ou un propriétaire du projet (403 `forbidden`) ; refusé quand l'instance est `deleting` ou `deleted` (409). Audité sous `compliance.branding.update`.",
    "operationId": "put_branding_v1_compliance_instances__instance_id__branding_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     },
     {
      "description": "Clé facultative (200 caractères au plus), unique par instance : une nouvelle tentative avec la même clé et le même corps rejoue la première réponse.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 200,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ComplianceBrandingIn"
       }
      }
     },
     "description": "Les champs de l'image de marque à écrire.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Branding V1 Compliance Instances  Instance Id  Branding Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier l'image de marque d'une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/branding/logo": {
   "delete": {
    "description": "Retour au logo par défaut du runtime : l'image de marque ne pointe plus vers un objet, `version` est incrémentée et l'objet stocké est supprimé. Sans logo, renvoie l'image de marque inchangée. Mêmes droits que le PUT ; audité sous `compliance.branding.logo.remove`.",
    "operationId": "delete_branding_logo_v1_compliance_instances__instance_id__branding_logo_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Branding Logo V1 Compliance Instances  Instance Id  Branding Logo Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer le logo d'une instance",
    "tags": [
     "compliance"
    ]
   },
   "get": {
    "description": "Les octets du logo, lus par le domaine de stockage et contrôlés avec le sha256 enregistré, avec le Content-Type du format validé (`image/png` ou `image/jpeg`), `X-Content-Type-Options: nosniff`, une Content-Security-Policy commençant par `default-src 'none'` et un `ETag`. 404 `compliance_logo_not_found` quand l'instance n'a pas de logo. Tout principal du tenant.",
    "operationId": "get_branding_logo_v1_compliance_instances__instance_id__branding_logo_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "image/jpeg": {},
       "image/png": {}
      },
      "description": "The logo bytes."
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le logo d'une instance",
    "tags": [
     "compliance"
    ]
   },
   "post": {
    "description": "Multipart `file` (avec les champs texte facultatifs `filename` et `mime_type`). PNG ou JPEG uniquement : le SVG et tout autre format sont refusés. La signature réelle, le Content-Type déclaré et l'extension du fichier doivent concorder ; 32 à 1024 pixels de côté, 512 Kio au plus ; sinon 422 `compliance_logo_invalid` avec `details.reason` (`format_not_allowed`, `signature_invalid`, `signature_mismatch`, `extension_mismatch`, `dimensions_out_of_bounds`, `too_large`, `empty`). Le fichier est ensuite analysé par le domaine de sécurité des fichiers, en échec fermé : un fichier bloqué répond 422 `file_blocked`, un analyseur indisponible 503 `file_scan_unavailable`. Il est stocké par le domaine de stockage dans le profil de stockage des documents du tenant sous `compliance-agent/<tenant>/<instance>/branding/` (409 `compliance_branding_storage_required` sans un tel profil, 503 `compliance_branding_storage_unavailable` quand il est injoignable) ; l'objet remplacé est supprimé. Incrémente `version` et renvoie l'image de marque. Mêmes droits que le PUT ; audité sous `compliance.branding.logo.update` (un refus est audité comme refus).",
    "operationId": "upload_branding_logo_v1_compliance_instances__instance_id__branding_logo_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "multipart/form-data": {
       "schema": {
        "properties": {
         "file": {
          "format": "binary",
          "type": "string"
         },
         "filename": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         }
        },
        "required": [
         "file"
        ],
        "type": "object"
       }
      }
     },
     "description": "Formulaire multipart avec le logo dans la partie `file`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upload Branding Logo V1 Compliance Instances  Instance Id  Branding Logo Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Téléverser le logo d'une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/health": {
   "get": {
    "description": "Contrôles réels : URL de disponibilité du runtime (`GET /readyz`), base de connaissances (ligne, vector store qdrant, `/readyz` de Qdrant) et passerelle de modèles (modèles de génération et d'embeddings dans le registre, utilisables). `status` vaut `up`, `degraded` ou `down` (runtime hors service) ; `instance_status` est le statut du cycle de vie. Chaque contrôle a `status`, `duration_ms` et `reason` quand il n'est pas `up`.",
    "operationId": "instance_health_v1_compliance_instances__instance_id__health_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Instance Health V1 Compliance Instances  Instance Id  Health Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Santé d'une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/provision": {
   "post": {
    "description": "Opération longue (202, `Operation-Id`) : statut `provisioning` puis `starting`, étapes de `validation` à `activation` exécutées par le worker. Depuis un `draft` validé (sinon 409 `compliance_not_validated`) ou après un provisionnement en échec, qui reprend à l'étape en échec. Répété pendant le provisionnement ou une fois prête : `changed: false`. Audité sous `compliance.provision`, chaque étape sous `compliance.step`.",
    "operationId": "provision_instance_v1_compliance_instances__instance_id__provision_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Provision Instance V1 Compliance Instances  Instance Id  Provision Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Provisionner une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/restart": {
   "post": {
    "description": "Opération longue (202) : depuis `ready`, `degraded`, `stopped` ou après un redémarrage en échec, étapes `deployment` (redémarrage par le Navin Manager pour une instance dédiée), `health`, `smoke`, `activation`. Un redémarrage en cours : `changed: false`.",
    "operationId": "restart_instance_v1_compliance_instances__instance_id__restart_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Restart Instance V1 Compliance Instances  Instance Id  Restart Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Redémarrer une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/restore": {
   "post": {
    "description": "Répond 501 `compliance_backup_not_supported` avec les mêmes `parts` que la sauvegarde : aucune sauvegarde d'instance ne peut encore être faite réellement. Audité.",
    "operationId": "restore_instance_v1_compliance_instances__instance_id__restore_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ComplianceRestoreIn"
       }
      }
     },
     "description": "La sauvegarde à restaurer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Restore Instance V1 Compliance Instances  Instance Id  Restore Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Restaurer une instance (non pris en charge)",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/start": {
   "post": {
    "description": "Opération longue (202) : depuis `stopped` (ou après un démarrage ou un arrêt en échec), étapes `deployment` (démarrage par le Navin Manager pour une instance dédiée, statut seulement pour une partagée), `health`, `smoke`, `activation` (application gouvernée active). Déjà en démarrage ou prête : `changed: false`.",
    "operationId": "start_instance_v1_compliance_instances__instance_id__start_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Start Instance V1 Compliance Instances  Instance Id  Start Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Démarrer une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/stop": {
   "post": {
    "description": "Opération longue (202) : depuis `ready`, `degraded` ou après un démarrage, un redémarrage, une mise à niveau ou un arrêt en échec, étapes `deployment` (arrêt par le Navin Manager pour une instance dédiée, statut seulement pour une partagée) et `activation` (application gouvernée suspendue, raison `compliance_instance_stopped`) ; statut `stopped`. Déjà en arrêt ou arrêtée : `changed: false`.",
    "operationId": "stop_instance_v1_compliance_instances__instance_id__stop_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Stop Instance V1 Compliance Instances  Instance Id  Stop Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Arrêter une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/upgrade": {
   "post": {
    "description": "Opération longue (202), statut `updating` : une instance partagée suit le runtime partagé, donc l'étape de déploiement lit la version du runtime dans son document OpenAPI (`GET /api/v1/compliance/openapi.json`) et vérifie que c'est la version demandée (sinon `shared_runtime_version_mismatch`) ; une instance dédiée est réinstallée par le Navin Manager. Puis `health`, `smoke`, `activation` ; `deployed_version` devient la version. La même version déjà déployée ou en cours : `changed: false`.",
    "operationId": "upgrade_instance_v1_compliance_instances__instance_id__upgrade_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ComplianceUpgradeIn"
       }
      }
     },
     "description": "La version à déployer.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upgrade Instance V1 Compliance Instances  Instance Id  Upgrade Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Mettre à niveau une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/instances/{instance_id}/validate": {
   "post": {
    "description": "Contrôles réels avant le provisionnement : le projet existe et l'appelant peut l'administrer, le modèle de génération existe dans le registre avec la capacité `chat` et un statut utilisable, le modèle d'embeddings se résout dans la Model Gateway (capacité `embeddings`), le quota d'instances, Qdrant répond, le runtime partagé répond (modèle partagé) ou le Navin Manager répond (modèle dédié, un service du catalogue non publié est un avertissement). Depuis `draft` ou après une validation en échec. 200 avec `validation` et le statut `draft` ; 422 `compliance_validation_failed` avec les contrôles en échec (statut `failed`, étape `validation`). Audité sous `compliance.validate` et `compliance.validate.completed`.",
    "operationId": "validate_instance_v1_compliance_instances__instance_id__validate_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Validate Instance V1 Compliance Instances  Instance Id  Validate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Valider une instance",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/compliance/packs": {
   "get": {
    "description": "Summary of the domain packs shipped with the product (read only; the frameworks are not applicable by default).",
    "operationId": "list_packs_v1_compliance_packs_get",
    "parameters": [
     {
      "in": "query",
      "name": "sector",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 20,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Sector"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Packs V1 Compliance Packs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "List Packs",
    "tags": [
     "compliance"
    ]
   }
  },
  "/v1/connectors": {
   "get": {
    "description": "Connecteurs du tenant dans l'ordre des noms, chacun avec les types d'identifiants configurés (jamais leurs valeurs). Une seule page.",
    "operationId": "list_connectors_v1_connectors_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Connectors V1 Connectors Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les connecteurs",
    "tags": [
     "connectors"
    ]
   },
   "post": {
    "description": "Applique un manifeste Connector : crée ou met à jour le connecteur (`apply_status` : `created` ou `updated`). Les URL doivent être permises par la politique de sortie réseau ; OAuth2 demande `auth.token_url` ; les webhooks non signés sont refusés (422 `invalid_manifest`). Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `connector.apply`.",
    "operationId": "create_connector_v1_connectors_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Manifeste Connector (`kind: Connector`, `metadata`, `spec` avec URL de base, auth, ressources, scopes, synchronisation et webhooks).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Connector V1 Connectors Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un connecteur",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/connectors/{name}": {
   "delete": {
    "description": "Supprime le connecteur et ses identifiants dans le magasin de secrets. Demande `tenant_admin`. 404 `connector_not_found`. Audité sous `connector.delete`.",
    "operationId": "delete_connector_v1_connectors__name__delete",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un connecteur",
    "tags": [
     "connectors"
    ]
   },
   "get": {
    "description": "Renvoie le connecteur avec son manifeste, son statut, son propriétaire et ses identifiants configurés (type, noms de champs, date de mise à jour). 404 `connector_not_found`.",
    "operationId": "get_connector_v1_connectors__name__get",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Connector V1 Connectors  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un connecteur",
    "tags": [
     "connectors"
    ]
   },
   "patch": {
    "description": "Modifie le statut ou le propriétaire. Désactiver un connecteur révoque immédiatement ses baux d'identifiants. Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `connector.update`.",
    "operationId": "patch_connector_v1_connectors__name__patch",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectorPatch"
       }
      }
     },
     "description": "Champs à modifier ; ceux omis sont conservés.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Connector V1 Connectors  Name  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un connecteur",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/connectors/{name}/credentials": {
   "post": {
    "description": "Enregistre des identifiants dans le magasin de secrets et ne renvoie que le type et les noms de champs (201). Demande `tenant_admin` ou `security_officer`. Corps : `kind` plus ses champs :\n- `oauth2_client` : `client_id`, `client_secret` ;\n- `oauth2_code` : `code`, `redirect_uri`, `code_verifier` facultatif ; le code est échangé aussitôt auprès de l'URL de jeton (demande un `oauth2_client` enregistré, sinon 409 `credential_unavailable`) et conservé comme `oauth2_token` ;\n- `api_key` : `api_key`, `header` facultatif ;\n- `service_account` : `private_key` ou `token`, `client_email` et `client_id` facultatifs ;\n- `basic` : `username`, `password` ;\n- `webhook_secret` : `secret`.\nUn type inconnu ou des champs manquants répondent 400 `invalid_request`. Audité sous `connector.credentials.write`.",
    "operationId": "put_credentials_v1_connectors__name__credentials_post",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Type d'identifiant et ses champs. En écriture seule : les valeurs ne sont jamais renvoyées.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Credentials V1 Connectors  Name  Credentials Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer les identifiants d'un connecteur",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/connectors/{name}/sync": {
   "post": {
    "description": "Met en file une synchronisation (202) et publie `connectors.<nom>.sync` pour le worker. Le manifeste doit déclarer un mode de synchronisation (409 `sync_disabled`), le connecteur doit être actif (409 `connector_disabled`), les ressources doivent être lisibles (400 `invalid_request`) et aucune autre exécution ne doit être en file ou en cours (409 `sync_in_progress`). Une synchronisation incrémentale part du curseur de la dernière exécution réussie sauf avec `full`. La politique `connector.sync` est évaluée. Demande `tenant_admin`, `developer` ou `agent`. Audité sous `connector.sync`.",
    "operationId": "sync_connector_v1_connectors__name__sync_post",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/SyncIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Ressources et indicateur de synchronisation complète facultatifs."
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Sync Connector V1 Connectors  Name  Sync Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer une synchronisation",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/connectors/{name}/sync-runs": {
   "get": {
    "description": "Exécutions de synchronisation du connecteur, de la plus récente à la plus ancienne : mode, statut, ressources, curseur et dates.",
    "operationId": "sync_runs_v1_connectors__name__sync_runs_get",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     },
     {
      "description": "Nombre maximal d'exécutions renvoyées (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Sync Runs V1 Connectors  Name  Sync Runs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les synchronisations d'un connecteur",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/connectors/{name}/test": {
   "post": {
    "description": "Construit les en-têtes d'authentification à partir des identifiants enregistrés et, quand le manifeste a une URL de base, lui envoie un GET. Renvoie `ok`, `credentials`, `http_status`, `latency_ms` et un code `error` en cas d'échec (`egress_denied`, `upstream_unavailable`, erreurs d'identifiants) ; un test en échec est un 200 avec `ok: false`. La politique `connector.test` est évaluée. Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `connector.test`.",
    "operationId": "test_connector_v1_connectors__name__test_post",
    "parameters": [
     {
      "description": "Nom du connecteur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Test Connector V1 Connectors  Name  Test Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Tester un connecteur",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/context/issue": {
   "post": {
    "description": "Construit et signe un ExecutionContext pour l'appelant et renvoie `token`, `context_id`, `kid`, `alg`, `issued_at` et `expires_at` (201).\n- Avec `run_id` : l'appelant doit avoir créé le run, en être le principal racine ou l'utilisateur pour qui il agit, ou détenir `agent`, `tenant_admin` ou `security_officer` (403 `forbidden`) ; la délégation du run est revérifiée et le manifeste de l'agent est inclus.\n- Avec `delegation_id` : la délégation doit être valide et l'appelant l'une de ses parties, ou `agent` ou `tenant_admin`.\nErreurs : 400 `context_wrong_audience` pour une audience que la plateforme n'accepte pas, 400 `context_ttl_exceeded`, 403 `delegation_expired`, 503 `context_authority_unavailable`. Audité sous `context.issue`.",
    "operationId": "issue_v1_context_issue_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/IssueIn"
       }
      }
     },
     "description": "Audience, durée de vie et run ou délégation visés par le contexte.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Issue V1 Context Issue Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Émettre un contexte d'exécution signé",
    "tags": [
     "context"
    ]
   }
  },
  "/v1/context/jwks": {
   "get": {
    "description": "Clés publiques de vérification sous forme de JWKS (actives, et retirées encore dans leur fenêtre de vérification), chacune avec `kid`, `alg` (`ES256` ou `EdDSA`) et `use: sig`. Tout principal authentifié peut la lire. 503 `context_authority_unavailable` quand le registre de clés est illisible.",
    "operationId": "jwks_v1_context_jwks_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Jwks V1 Context Jwks Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les clés publiques de vérification",
    "tags": [
     "context"
    ]
   }
  },
  "/v1/context/keys": {
   "get": {
    "description": "Registre des clés de signature des contextes : `kid`, algorithme, statut, dates d'utilisation, de retrait et de révocation. Demande `security_officer` ou `auditor`. 503 `context_authority_unavailable` quand le registre est illisible.",
    "operationId": "list_keys_v1_context_keys_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Keys V1 Context Keys Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les clés de signature",
    "tags": [
     "context"
    ]
   }
  },
  "/v1/context/keys/{kid}/revoke": {
   "post": {
    "description": "Révoque immédiatement une clé de signature des contextes : les contextes qu'elle a signés ne sont plus valides. Demande `platform_admin`. 404 `signing_key_not_found`. Audité sous `context.key.revoke` au niveau de la plateforme.",
    "operationId": "revoke_key_v1_context_keys__kid__revoke_post",
    "parameters": [
     {
      "description": "Identifiant de clé, l'empreinte RFC 7638 de la clé publique.",
      "in": "path",
      "name": "kid",
      "required": true,
      "schema": {
       "title": "Kid",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RevokeKeyIn"
       }
      }
     },
     "description": "Raison de la révocation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Key V1 Context Keys  Kid  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer une clé de signature",
    "tags": [
     "context"
    ]
   }
  },
  "/v1/context/plan": {
   "post": {
    "description": "Simulation du Context Manager : calcule le budget de jetons d'une fenêtre de modèle (réserves de sortie, de raisonnement et de sécurité) et les segments conservés, compressés ou écartés selon leur priorité. Avec `model`, la fenêtre et la réserve de sortie viennent du registre. Une fenêtre qui n'est pas un entier positif répond 422 `parameter_invalid` ; des segments qui ne peuvent pas tenir répondent 422 `context_overflow`. Demande un rôle de lecteur.",
    "operationId": "context_plan_v1_context_plan_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`model` (ou `model_id`), ou `window` (jetons) ; `output` (jetons de sortie réservés, par défaut le maximum du modèle plafonné à 4096), `reasoning` (jetons de raisonnement réservés), `safety` (marge) et `segments` (chacun avec `type`, et facultativement `segment_id`, `priority`, `classification` et son contenu).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Planifier la fenêtre de contexte",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/context/verify": {
   "post": {
    "description": "Contrôle un jeton et renvoie `valid`, `context_id`, `kid`, `alg`, les revendications et le document de contexte. Contrôles, dans l'ordre : structure et forme canonique (`context_malformed`), algorithme et signature (`context_invalid_signature`), clé connue et non révoquée (`context_unknown_key`), émetteur (`context_wrong_issuer`), audience (`context_wrong_audience`), durée de vie (`context_expired`), tenant de l'appelant (403 `context_tenant_mismatch`), enregistrement d'émission (`context_revoked`), empreinte du document et chaîne de délégation (une délégation révoquée donne `context_revoked`). Les refus répondent 401 sauf mention contraire et sont audités ; un succès rattache l'identité vérifiée aux événements d'audit de la requête.",
    "operationId": "verify_v1_context_verify_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__context__routes__VerifyIn"
       }
      }
     },
     "description": "Jeton à vérifier et audience attendue.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify V1 Context Verify Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier un contexte d'exécution signé",
    "tags": [
     "context"
    ]
   }
  },
  "/v1/context/{context_id}/revoke": {
   "post": {
    "description": "Révoque un contexte émis avant son expiration ; sa vérification répond ensuite `context_revoked`. Permis pour le principal qui l'a émis, ou `tenant_admin` ou `security_officer` (403 `forbidden`). Une seconde révocation garde la première date. 404 `context_not_found`. Audité sous `context.revoke`.",
    "operationId": "revoke_context_v1_context__context_id__revoke_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du contexte, la revendication `jti` de son jeton.",
      "in": "path",
      "name": "context_id",
      "required": true,
      "schema": {
       "title": "Context Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Context V1 Context  Context Id  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer un contexte d'exécution",
    "tags": [
     "context"
    ]
   }
  },
  "/v1/contracts": {
   "get": {
    "description": "Liste les contrats publiés avec leur `name`, `title`, `kind`, `type`, `file`, l'`id` du schéma et sa `description`. Ouvert à tout appelant authentifié. `next_cursor` vaut toujours null.",
    "operationId": "list_contracts_v1_contracts_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Contracts V1 Contracts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les contrats",
    "tags": [
     "contracts"
    ]
   }
  },
  "/v1/contracts/{name}": {
   "get": {
    "description": "Renvoie le schéma JSON du contrat tel qu'il est publié. Ouvert à tout appelant authentifié. Un nom inconnu répond 404 `unknown_contract`.",
    "operationId": "get_contract_v1_contracts__name__get",
    "parameters": [
     {
      "description": "Nom du contrat, par exemple `agent-manifest`.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Contract V1 Contracts  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un contrat",
    "tags": [
     "contracts"
    ]
   }
  },
  "/v1/contracts/{name}/validate": {
   "post": {
    "description": "Valide le corps avec le contrat, formats compris, sans rien enregistrer. La réponse donne `contract`, `valid` et `errors`, chacune avec le `path` JSON de la valeur fautive et un `message`. Ouvert à tout appelant authentifié. Un contrat inconnu répond 404 `unknown_contract`.",
    "operationId": "validate_contract_v1_contracts__name__validate_post",
    "parameters": [
     {
      "description": "Nom du contrat, par exemple `agent-manifest`.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "title": "Document"
       }
      }
     },
     "description": "Tout document JSON à valider.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Validate Contract V1 Contracts  Name  Validate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Valider un document avec un contrat",
    "tags": [
     "contracts"
    ]
   }
  },
  "/v1/credentials/broker/lease": {
   "post": {
    "description": "Émet un bail de courte durée sur un connecteur à la place de son secret (201) : `lease_id`, `token` (montré une seule fois), `scopes`, `expires_at`, `ttl_seconds`. Les scopes demandés doivent être déclarés par le connecteur (403 `scope_not_allowed` ; par défaut : tous) ; le connecteur doit être actif (409 `connector_disabled`). Avec `on_behalf_of`, l'utilisateur doit détenir la relation `can_invoke` sur le connecteur (403 `relation_required`). Demande `tenant_admin`, `developer` ou `agent`. Audité sous `credential.lease`.",
    "operationId": "lease_v1_credentials_broker_lease_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/LeaseIn"
       }
      }
     },
     "description": "Connecteur, scopes, durée de vie, run et utilisateur représenté.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Lease V1 Credentials Broker Lease Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir un bail d'identifiants",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/delegations": {
   "get": {
    "description": "Liste les délégations, les plus récentes d'abord, paginées : toutes pour `tenant_admin`, `security_officer` et `auditor`, sinon celles dont l'appelant est la racine, le créateur ou la personne représentée. Un statut inconnu répond 400.",
    "operationId": "list_delegations_v1_delegations_get",
    "parameters": [
     {
      "description": "`active` (non expirée), `expired` ou `revoked`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Uniquement les délégations dont la racine est ce principal.",
      "in": "query",
      "name": "root_principal_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Root Principal Id"
      }
     },
     {
      "description": "Nombre maximal de délégations dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Delegations V1 Delegations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les délégations",
    "tags": [
     "delegation"
    ]
   },
   "post": {
    "description": "Crée une délégation racine de l'appelant vers un délégataire. Ouvert aux membres du tenant ; un agent ne peut pas créer seul une délégation racine (403 `delegation_root_forbidden`).\n\nContrôles : format du délégataire `agent:`, `app:`, `service:` ou `subagent:` et agent existant (400, 404) ; portées couvertes par celles de l'appelant, y compris celles d'un accès d'urgence actif (403 `delegation_scope_exceeds`) ; classification au plus égale à l'habilitation de l'appelant (403 `delegation_classification_exceeds`) ; expiration dans le futur et dans la limite de la plateforme (24 heures par défaut, 400 `delegation_ttl_exceeded`). `redelegation_allowed` ne s'applique que si `max_depth` est supérieur à 0. Écrit l'événement d'audit `delegation.create`. Répond 201 avec la délégation.",
    "operationId": "create_delegation_v1_delegations_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DelegationIn"
       }
      }
     },
     "description": "Délégataire, droits, limites et expiration de la délégation.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Delegation V1 Delegations Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une délégation",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/delegations/authorize": {
   "post": {
    "description": "Calcule la permission effective d'une action : utilisateur ET agent ET délégation ET restriction de ressource ET relation (OpenFGA ou les tuples intégrés) ET politique (OPA ou intégrée). Donnez `delegation_id`, ou `run_id` pour utiliser la délégation d'une exécution (400 `invalid_request` sans l'un ni l'autre ; 403 `delegation_required` pour une exécution sans délégation).\n\nLa chaîne est d'abord vérifiée (403 `delegation_revoked`, `delegation_expired` ou `delegation_invalid` ; 404 `delegation_not_found`). La réponse est l'ActionContext de la décision : `allow` (vrai seulement quand chaque couche autorise) et le verdict de chaque couche. La décision est enregistrée dans ses propres transactions, si bien qu'un refus reste dans le journal. Ouvert aux membres du tenant.",
    "operationId": "authorize_v1_delegations_authorize_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__delegation__routes__AuthorizeIn"
       }
      }
     },
     "description": "Action, délégation ou exécution, et la portée, la ressource, la relation, l'audience et la classification à vérifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Authorize V1 Delegations Authorize Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Autoriser une action sous une délégation",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/delegations/revoke-principal": {
   "post": {
    "description": "Révoque toutes les délégations actives dont un principal est la racine ou qui agissent pour lui, avec leurs descendants et leurs contextes d'exécution, par exemple au départ d'un utilisateur ou après la compromission d'un compte. Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `delegation.revoke_principal`.",
    "operationId": "revoke_principal_v1_delegations_revoke_principal_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RevokePrincipalIn"
       }
      }
     },
     "description": "Principal et raison.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Principal V1 Delegations Revoke Principal Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer toutes les délégations d'un principal",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/delegations/{delegation_id}": {
   "get": {
    "description": "Renvoie la délégation avec la vérification de sa chaîne depuis la racine (chaque maillon actif, non expiré, du même tenant, couvert par son parent). Visible par sa racine, son créateur et la personne représentée, ainsi que par `tenant_admin`, `security_officer`, `auditor` et `agent` ; sinon 404 `delegation_not_found`.",
    "operationId": "get_delegation_v1_delegations__delegation_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la délégation.",
      "in": "path",
      "name": "delegation_id",
      "required": true,
      "schema": {
       "title": "Delegation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Delegation V1 Delegations  Delegation Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une délégation",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/delegations/{delegation_id}/restrict": {
   "post": {
    "description": "Crée une délégation enfant, toujours couverte par son parent : un sous-agent n'obtient jamais de droits plus larges. Autorisé à la racine ou au créateur du parent, à un `agent`, à un `tenant_admin` ou à un `security_officer` (403 `forbidden`).\n\nLe parent doit être valide et autoriser la redélégation (403 `delegation_not_redelegable`) avec une profondeur restante (403 `delegation_depth_exceeded`). Les champs omis héritent des valeurs du parent ; ceux qui sont donnés doivent y être couverts (403 `delegation_scope_exceeds`, `delegation_classification_exceeds`, `delegation_expiry_exceeds`). Écrit l'événement d'audit `delegation.restrict`. Répond 201 avec la délégation enfant.",
    "operationId": "restrict_delegation_v1_delegations__delegation_id__restrict_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la délégation parente.",
      "in": "path",
      "name": "delegation_id",
      "required": true,
      "schema": {
       "title": "Delegation Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RestrictIn"
       }
      }
     },
     "description": "Sous-délégataire et droits plus étroits.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Restrict Delegation V1 Delegations  Delegation Id  Restrict Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Restreindre une délégation pour un sous-délégataire",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/delegations/{delegation_id}/revoke": {
   "post": {
    "description": "Révoque la délégation et tous ses descendants ; leurs contextes d'exécution sont révoqués avec eux, avec effet à l'action suivante. Autorisé à sa racine, à son créateur ou à la personne représentée, ainsi qu'à `tenant_admin` et `security_officer` (403 `forbidden`). Écrit l'événement d'audit `delegation.revoke`.",
    "operationId": "revoke_delegation_v1_delegations__delegation_id__revoke_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la délégation.",
      "in": "path",
      "name": "delegation_id",
      "required": true,
      "schema": {
       "title": "Delegation Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/RevokeIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Delegation V1 Delegations  Delegation Id  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer une délégation",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/developer/kit": {
   "get": {
    "description": "Kit du développeur d'une application, d'un agent ou d'un projet : valeurs, états et actions, jamais un secret. Indiquez exactement un paramètre parmi `application_id`, `agent_id` et `project` (400 `invalid_request` sinon). La réponse contient le `subject` (avec les modèles, outils, serveurs MCP, bases de connaissances et suites d'évaluation qu'il utilise), les neuf `rows`, les entrées `env` d'un fichier d'environnement (la clé d'API n'y est qu'un emplacement marqué secret), un `summary` des lignes prêtes et les `next_steps` (créer une clé, lancer les tests, lancer le red team). Une application ou un agent inconnu, ou un projet sans application, répond 404 `not_found`. Demande un principal de tenant (403 `tenant_required`) avec `developer`, `application_owner` ou `tenant_admin` (403 `forbidden`).",
    "operationId": "developer_kit_v1_developer_kit_get",
    "parameters": [
     {
      "description": "Identifiant de l'application pour laquelle le kit est construit, 200 caractères au plus. Exclusif de `agent_id` et `project`.",
      "in": "query",
      "name": "application_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 200,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Application Id"
      }
     },
     {
      "description": "Identifiant de l'agent pour lequel le kit est construit, 200 caractères au plus ; ses ressources viennent du manifeste de sa version courante. Exclusif de `application_id` et `project`.",
      "in": "query",
      "name": "agent_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 200,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent Id"
      }
     },
     {
      "description": "Nom du projet, 200 caractères au plus : le kit fusionne les ressources de toutes les applications du projet. Exclusif de `application_id` et `agent_id`.",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 200,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Developer Kit V1 Developer Kit Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir le kit du développeur",
    "tags": [
     "developer"
    ]
   }
  },
  "/v1/dlp/inspect": {
   "post": {
    "description": "Applique la DLP au texte avec les règles du tenant, en mode `detect` sauf si `mode` en décide autrement. La réponse donne `text`, `findings` (type, position, score, classification, court aperçu, action, règle), `action` (action la plus forte appliquée), `classification`, `mode`, `engine` (`builtin` ou `presidio`), `blocked` et `count`. Un mode inconnu répond 400. Ouvert à tout membre du tenant. Écrit l'événement d'audit `dlp.inspect` (nombres et types uniquement).",
    "operationId": "dlp_inspect_v1_dlp_inspect_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DlpIn"
       }
      }
     },
     "description": "Texte, mode et langue facultatifs.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Dlp Inspect V1 Dlp Inspect Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Inspecter un texte à la recherche de données sensibles",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/dlp/redact": {
   "post": {
    "description": "Comme l'inspection, en mode `mask` par défaut : chaque constat est remplacé par `[TYPE]` (par exemple `[EMAIL]`), ou tokenisé ou bloqué quand une règle du tenant ou `mode` le demande ; un texte bloqué revient vide avec `blocked: true`. Ouvert à tout membre du tenant. Écrit l'événement d'audit `dlp.redact`.",
    "operationId": "dlp_redact_v1_dlp_redact_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DlpIn"
       }
      }
     },
     "description": "Texte, mode et langue facultatifs.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Dlp Redact V1 Dlp Redact Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Masquer les données sensibles d'un texte",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/dlp/rules": {
   "get": {
    "description": "Liste les règles DLP du tenant triées par nom. Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_rules_v1_dlp_rules_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Rules V1 Dlp Rules Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les règles DLP",
    "tags": [
     "privacy"
    ]
   },
   "post": {
    "description": "Crée la règle, ou remplace la règle de même nom. Une règle exige un `entity_type` ou un `pattern` (expression régulière valide) ; les erreurs répondent 400 `invalid_request` (action inconnue, motif ou classification invalide). Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `dlp.rule.upsert`. Répond 201 avec la règle.",
    "operationId": "create_rule_v1_dlp_rules_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__privacy__RuleIn"
       }
      }
     },
     "description": "Nom, type d'entité ou motif, action, classification et score de la règle.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Rule V1 Dlp Rules Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer ou modifier une règle DLP",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/encryption/decrypt": {
   "post": {
    "description": "Déchiffre un chiffré `eaienc1` du tenant avec la version de clé qu'il désigne, en vérifiant la même `aad` qu'au chiffrement. Renvoie `plaintext`, ou `plaintext_base64` avec `as_base64` ou quand les données ne sont pas du texte UTF-8, et `key_version`. Les anciennes versions déchiffrent toujours après une rotation. Ouvert à tout membre du tenant. Écrit l'événement d'audit `encryption.decrypt`.",
    "operationId": "decrypt_v1_encryption_decrypt_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__privacy__DecryptIn"
       }
      }
     },
     "description": "Chiffré, données associées et format de sortie.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Decrypt V1 Encryption Decrypt Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déchiffrer un chiffré",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/encryption/encrypt": {
   "post": {
    "description": "Chiffre `plaintext` (texte UTF-8) ou `plaintext_base64` (octets) avec la version courante de la clé, lié à `aad`. Renvoie `ciphertext` (`eaienc1:...`), `key_id` et `key_version`. L'une des deux entrées est obligatoire, et le base64 doit être valide (400 `invalid_request`) ; une clé inconnue répond 404 `key_not_found`. Ouvert à tout membre du tenant. Écrit l'événement d'audit `encryption.encrypt` (taille uniquement).",
    "operationId": "encrypt_v1_encryption_encrypt_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EncryptIn"
       }
      }
     },
     "description": "Clé, données à chiffrer et données associées.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Encrypt V1 Encryption Encrypt Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Chiffrer des données avec une clé du tenant",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/encryption/keys": {
   "get": {
    "description": "Liste les clés de chiffrement du tenant triées par nom : identifiant, nom, moteur (`openbao-transit` ou `local`), usage, version courante et dates. Jamais le matériel de clé. Ouvert à tout membre du tenant.",
    "operationId": "list_keys_v1_encryption_keys_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Keys V1 Encryption Keys Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les clés de chiffrement",
    "tags": [
     "privacy"
    ]
   },
   "post": {
    "description": "Crée une clé nommée avec sa première version ; la clé de données est générée et enveloppée par OpenBao Transit s'il est configuré, localement sinon. Le nom doit être en minuscules, chiffres, `.`, `_` ou `-`, 63 caractères au plus (400 `invalid_request`) ; un nom déjà utilisé répond 409 `conflict`. Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `encryption.key.create`.",
    "operationId": "create_key_v1_encryption_keys_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__privacy__KeyIn"
       }
      }
     },
     "description": "Nom et usage de la clé.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Key V1 Encryption Keys Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une clé de chiffrement",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/encryption/keys/{key_id}/rotate": {
   "post": {
    "description": "Ajoute une nouvelle version à la clé : les nouveaux chiffrements l'utilisent, les chiffrés plus anciens se déchiffrent toujours avec leur propre version. Exige `tenant_admin` ou `security_officer`. Répond 404 `key_not_found`. Écrit l'événement d'audit `encryption.key.rotate`.",
    "operationId": "rotate_key_v1_encryption_keys__key_id__rotate_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom de la clé.",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Rotate Key V1 Encryption Keys  Key Id  Rotate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Faire tourner une clé de chiffrement",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/evaluations/comparisons": {
   "get": {
    "description": "Liste les comparaisons du tenant, les plus récentes d'abord, 200 au plus, éventuellement pour une seule suite. Chacune donne la référence, le candidat, les identifiants des deux exécutions, les écarts par métrique, la tolérance et le verdict. Exige `tenant_admin`, `developer`, `security_officer`, `auditor`, `viewer` ou `finops`.",
    "operationId": "list_comparisons_v1_evaluations_comparisons_get",
    "parameters": [
     {
      "description": "Uniquement les comparaisons de cette suite (UUID).",
      "in": "query",
      "name": "suite_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Suite Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Comparisons V1 Evaluations Comparisons Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les comparaisons A/B",
    "tags": [
     "evaluation",
     "evaluation"
    ]
   },
   "post": {
    "description": "Exécute une suite deux fois, sur la référence `baseline` et sur le `candidate` (chacun `{type, ref, version}`, type `model` par défaut, `agent` ou `prompt`), et enregistre les deux exécutions, les écarts par métrique et le verdict. Les écarts sont orientés par le comparateur de chaque métrique (`gte` : plus haut est mieux, `lte` : plus bas est mieux).\n\nVerdicts : `regression` (une métrique moins bonne de plus de `tolerance`), `candidate_better` (aucune régression et au moins une métrique meilleure de plus de `tolerance`), `equivalent` (tous les écarts dans la `tolerance`), `inconclusive` (une exécution a fini en erreur).\n\nExige `tenant_admin`, `developer` ou `security_officer`. Erreurs 422 `comparison_invalid` (`details.field`) : `ref` manquant, type inconnu, référence et candidat identiques, `tolerance` hors de 0 à 1, `outputs` qui n'est pas un objet. Écrit l'événement d'audit `evaluation.comparison`.",
    "operationId": "create_comparison_v1_evaluations_comparisons_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Objet JSON : `suite_id`, `baseline`, `candidate`, et en option `tolerance` (0 à 1, 0 par défaut) et `outputs` (`{\"baseline\": [...], \"candidate\": [...]}`, sorties précalculées par cas).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Comparison V1 Evaluations Comparisons Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Comparer un candidat à une référence",
    "tags": [
     "evaluation",
     "evaluation"
    ]
   }
  },
  "/v1/evaluations/comparisons/{comparison_id}": {
   "get": {
    "description": "Renvoie une comparaison. Exige `tenant_admin`, `developer`, `security_officer`, `auditor`, `viewer` ou `finops`. Répond 404 `comparison_not_found`.",
    "operationId": "get_comparison_v1_evaluations_comparisons__comparison_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la comparaison.",
      "in": "path",
      "name": "comparison_id",
      "required": true,
      "schema": {
       "title": "Comparison Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Comparison V1 Evaluations Comparisons  Comparison Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une comparaison A/B",
    "tags": [
     "evaluation",
     "evaluation"
    ]
   }
  },
  "/v1/evaluations/gates/check": {
   "post": {
    "description": "Vérifie que la dernière exécution terminée de chaque suite bloquante de la cible (pour `target.version` si elle est donnée, et limitée à `suite_ids` s'il est donné) a réussi. Avec `run_missing: true`, une suite sans exécution est exécutée maintenant ; sinon elle compte comme `missing`.\n\nRéussite : 200 avec `passed: true`, la cible et un contrôle par suite. Échec : 409 `gate_failed` avec les contrôles en échec dans `details.failures`, pour qu'une chaîne de promotion s'arrête. Un type de cible inconnu répond 400. Ouvert à tout membre du tenant. Écrit l'événement d'audit `evaluation.gate.check`, en refus quand la barrière échoue.",
    "operationId": "gate_check_v1_evaluations_gates_check_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/GateIn"
       }
      }
     },
     "description": "Cible, suites facultatives et exécution ou non des suites manquantes.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Gate Check V1 Evaluations Gates Check Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier la barrière de qualité d'une cible",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/evaluations/runs": {
   "get": {
    "description": "Liste les exécutions d'évaluation du tenant, identifiant le plus récent d'abord, paginées, éventuellement filtrées par suite et statut. Chaque élément suit le contrat d'évaluation (cible, moteur, jeu de données, métriques, résultats, statut, coût) avec `run_id`, `suite_id`, `trigger`, `case_results` et `error`. Ouvert à tout membre du tenant.",
    "operationId": "list_runs_v1_evaluations_runs_get",
    "parameters": [
     {
      "description": "Uniquement les exécutions de cette suite.",
      "in": "query",
      "name": "suite_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Suite Id"
      }
     },
     {
      "description": "Uniquement ce statut : `running`, `passed`, `failed` ou `error`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Nombre maximal d'exécutions dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Runs V1 Evaluations Runs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les exécutions d'évaluation",
    "tags": [
     "evaluation"
    ]
   },
   "post": {
    "description": "Exécute une suite maintenant et renvoie l'exécution terminée (201). Exige `tenant_admin`, `developer`, `security_officer` ou `ai_governance_officer` ou `agent`.\n\n1. Avec `suite_id` : chaque cas est envoyé à la cible (celle de la suite, ou `target` pour évaluer une autre version), sauf si `outputs` donne la sortie du cas ou si le cas en a une.\n2. Chaque métrique est notée et comparée à son seuil ; l'exécution finit `passed`, `failed` ou `error` (cible ou moteur indisponible, métrique sans échantillon), avec son coût tiré des événements de consommation.\n3. Avec `trigger: promotion` et sans `suite_id`, toutes les suites de `target` ayant `run_on_promotion` s'exécutent ; la réponse donne `runs` et `passed` (toutes les suites bloquantes ont réussi).\n\nLa réponse porte `Operation-Id` et accepte `Idempotency-Key`. Un `suite_id` manquant répond 400 ; une suite inconnue 404. Écrit l'événement d'audit `evaluation.run`.",
    "operationId": "create_run_v1_evaluations_runs_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__evaluation__RunIn"
       }
      }
     },
     "description": "Suite, cible facultative, sorties précalculées et déclencheur.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Run V1 Evaluations Runs Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exécuter une suite d'évaluation",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/evaluations/runs/{run_id}": {
   "get": {
    "description": "Renvoie une exécution d'évaluation avec ses résultats par métrique et par cas. Ouvert à tout membre du tenant. Répond 404 `not_found`.",
    "operationId": "get_run_v1_evaluations_runs__run_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'exécution d'évaluation.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Run V1 Evaluations Runs  Run Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une exécution d'évaluation",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/evaluations/suites": {
   "get": {
    "description": "Liste les suites du tenant triées par nom, paginées, sous forme de documents du contrat d'évaluation ; `cases` donne le nombre de cas. Ouvert à tout membre du tenant.",
    "operationId": "list_suites_v1_evaluations_suites_get",
    "parameters": [
     {
      "description": "Nombre maximal de suites dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Suites V1 Evaluations Suites Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les suites d'évaluation",
    "tags": [
     "evaluation"
    ]
   },
   "post": {
    "description": "Crée une suite. Exige `tenant_admin`, `developer`, `security_officer` ou `ai_governance_officer`. Erreurs 400 `invalid_request` : type de cible hors de `model`, `agent`, `prompt`, `rag_pipeline`, `skill` ou `tool`, moteur ou type inconnu, comparateur autre que `gte` ou `lte`, ou, pour le moteur `custom`, une métrique autre que `exact_match`, `contains`, `not_contains`, `json_valid`, `faithfulness`, `latency` ou `refusal`. Un nom déjà utilisé répond 409 `conflict`. Le jeu de données reçoit sa taille `size` et une empreinte SHA-256 `digest` des cas. Écrit l'événement d'audit `evaluation.suite.create`.",
    "operationId": "create_suite_v1_evaluations_suites_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SuiteIn"
       }
      }
     },
     "description": "Nom, cible, moteur, type, jeu de données, cas, métriques et options de barrière.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Suite V1 Evaluations Suites Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une suite d'évaluation",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/evaluations/suites/{suite_id}": {
   "get": {
    "description": "Renvoie une suite avec la liste complète de ses cas et `run_on_promotion`. Ouvert à tout membre du tenant. Répond 404 `not_found`.",
    "operationId": "get_suite_v1_evaluations_suites__suite_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la suite.",
      "in": "path",
      "name": "suite_id",
      "required": true,
      "schema": {
       "title": "Suite Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Suite V1 Evaluations Suites  Suite Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une suite d'évaluation",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/events/publish": {
   "post": {
    "description": "Publie un événement (202) : sur NATS JetStream quand il est configuré, sinon dans l'outbox de la plateforme. Le contexte de trace W3C courant est ajouté aux en-têtes. Chaque déclencheur dont le sujet et le filtre correspondent lance un run de son agent (idempotent par événement et déclencheur) ; la réponse les liste dans `triggered`, avec `skipped` et un code d'erreur quand un run n'a pas pu démarrer. Demande `tenant_admin`, `developer` ou `agent`. 422 `invalid_subject`. Audité sous `event.publish`.",
    "operationId": "publish_event_v1_events_publish_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EventIn"
       }
      }
     },
     "description": "Sujet, charge utile et en-têtes de l'événement.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Publish Event V1 Events Publish Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier un événement",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/evidence/access-log": {
   "get": {
    "description": "Renvoie les derniers accès au journal d'audit, les plus récents d'abord : acteur, rôles, type (`read`, `search`, `verify`, `export`, `payload_decrypt`, `seal_read`), requête, nombre de résultats, raison et événement d'audit écrit. Exige `auditor` ou `security_officer`. La lecture du journal est elle-même enregistrée.",
    "operationId": "access_log_v1_evidence_access_log_get",
    "parameters": [
     {
      "description": "Nombre maximal d'entrées (200 par défaut, 1000 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 200,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Access Log V1 Evidence Access Log Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le journal d'accès à l'audit",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/alerts": {
   "get": {
    "description": "Liste les alertes d'intégrité du tenant ouvertes par le moniteur : `chain_broken`, `sequence_gap`, `seal_invalid`, `outbox_missing` ou `clock_skew`, rafraîchies tant que le problème persiste. Exige `auditor`, `security_officer` ou `tenant_admin`.",
    "operationId": "list_alerts_v1_evidence_alerts_get",
    "parameters": [
     {
      "description": "Uniquement les alertes `open` ou `acknowledged`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "open",
          "acknowledged"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Alerts V1 Evidence Alerts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les alertes d'intégrité",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/bundle": {
   "get": {
    "description": "Renvoie `bundle` (événements, sceaux et clés publiques du tenant) et `manifest` (format, nombres, SHA-256 du paquet canonique, `alg: Ed25519`, identifiant de clé, clé publique et signature par la clé de scellement). Un auditeur externe le vérifie hors ligne avec `python -m eai.domains.evidence.verify`. Exige `auditor` ou `security_officer`. Répond 503 `audit_seal_key_unavailable` sans clé de signature. L'export est inscrit dans le journal d'accès.",
    "operationId": "export_bundle_v1_evidence_bundle_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Export Bundle V1 Evidence Bundle Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Exporter un paquet de preuves hors ligne",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/events/{event_id}/payload": {
   "post": {
    "description": "Renvoie dans `payload` les champs sensibles déchiffrés d'un événement, avec `access_event_id`, l'événement d'audit qui enregistre cet accès. Uniquement `auditor` ou `security_officer` détenus explicitement : `platform_admin` ne suffit pas (403 `forbidden`). Une `reason` est obligatoire. Erreurs : 404 `not_found` (pas de charge utile), 410 `audit_payload_purged` (expirée ou effacée), 409 `audit_payload_tampered` (empreinte du chiffré différente), 503 `audit_payload_key_unavailable`.",
    "operationId": "decrypt_payload_v1_evidence_events__event_id__payload_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'événement d'audit.",
      "in": "path",
      "name": "event_id",
      "required": true,
      "schema": {
       "title": "Event Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__evidence__routes__DecryptIn"
       }
      }
     },
     "description": "Raison du déchiffrement, inscrite dans le journal d'accès.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Decrypt Payload V1 Evidence Events  Event Id  Payload Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déchiffrer la charge utile d'un événement d'audit",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/gateway": {
   "get": {
    "description": "Renvoie l'état de livraison des événements d'audit du tenant vers le SIEM : `targets` configurées, nombres `pending` et `delivered`, `max_attempts` d'un événement en attente et `oldest_pending`. Exige `auditor`, `security_officer` ou `tenant_admin`.",
    "operationId": "gateway_status_v1_evidence_gateway_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Gateway Status V1 Evidence Gateway Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire l'état de l'Audit Gateway",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/holds": {
   "get": {
    "description": "Liste les gels juridiques du tenant avec leur dossier, leur période, leur raison, leur statut et leurs approbations de levée. Exige `auditor`, `security_officer` ou `tenant_admin`.",
    "operationId": "list_holds_v1_evidence_holds_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Holds V1 Evidence Holds Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les gels juridiques de preuves",
    "tags": [
     "evidence"
    ]
   },
   "post": {
    "description": "Pose un gel juridique sur les charges utiles du tenant pour un dossier et une période facultative de date d'événement : tant qu'il est actif, la purge des charges utiles et les effacements ignorent les événements qu'il couvre. `time_to` doit suivre `time_from` (400 `invalid_request`). Exige `auditor`, `security_officer` ou `tenant_admin`. Écrit l'événement d'audit `audit.legal_hold.create`. Répond 201 avec le gel.",
    "operationId": "create_hold_v1_evidence_holds_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/HoldIn"
       }
      }
     },
     "description": "Dossier, raison et période facultative du gel.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Hold V1 Evidence Holds Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Poser un gel juridique de preuves",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/holds/{hold_id}/release": {
   "post": {
    "description": "Enregistre une approbation de levée avec sa raison. Le gel est levé à la seconde approbation par un approbateur distinct ; la même personne deux fois répond 409 `audit_hold_same_approver`, un gel qui n'est plus actif 409 `audit_hold_not_active`, un gel inconnu 404. Exige `auditor`, `security_officer` ou `tenant_admin`. Écrit `audit.legal_hold.release_approve`, puis `audit.legal_hold.release`.",
    "operationId": "release_hold_v1_evidence_holds__hold_id__release_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du gel juridique.",
      "in": "path",
      "name": "hold_id",
      "required": true,
      "schema": {
       "title": "Hold Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReleaseIn"
       }
      }
     },
     "description": "Raison de l'approbation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Release Hold V1 Evidence Holds  Hold Id  Release Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Approuver la levée d'un gel juridique",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/retention/evidence": {
   "get": {
    "description": "Liste les preuves de purge du tenant : type, date limite, nombres purgés et gelés, séquences purgées et racine de Merkle des empreintes des chiffrés purgés, jamais les contenus. Exige `auditor`, `security_officer` ou `tenant_admin`.",
    "operationId": "retention_evidence_v1_evidence_retention_evidence_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Retention Evidence V1 Evidence Retention Evidence Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les preuves de purge",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/retention/run": {
   "post": {
    "description": "Supprime des charges utiles d'audit chiffrées ; les événements, la chaîne et les sceaux restent et continuent de se vérifier.\n\n1. `retention` (par défaut) : charges utiles des événements antérieurs à `cutoff` (par défaut : maintenant moins la durée de conservation des charges utiles de la plateforme).\n2. `erasure` : charges utiles des `event_ids` listés (obligatoires, 400 sinon), pour une demande d'une personne concernée.\n\nLes charges utiles sous gel juridique actif sont ignorées. Une preuve de purge conserve les nombres, les séquences et la racine de Merkle des empreintes des chiffrés purgés. Avec `dry_run`, rien n'est supprimé. Exige `security_officer` ou `tenant_admin`. Écrit l'événement d'audit `audit.payload.retention` ou `audit.payload.erasure`.",
    "operationId": "run_retention_v1_evidence_retention_run_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PurgeIn"
       }
      }
     },
     "description": "Type de purge, date limite, événements et indicateur de simulation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Run Retention V1 Evidence Retention Run Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Purger les charges utiles d'audit expirées",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/runs/{run_id}/components": {
   "post": {
    "description": "Enregistre, en ajout seul, les versions des composants utilisés par une exécution d'agent, ou par l'une de ses étapes avec `step_seq` (appel d'outil, appel de modèle). Chaque déclaration est auditée avec le champ `versions` de l'événement d'audit. Exige `tenant_admin`, `agent_operator` ou `developer`. Répond 201 avec les éléments enregistrés, ou 404 `not_found` pour une exécution inconnue.",
    "operationId": "record_components_v1_evidence_runs__run_id__components_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'exécution d'agent.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ComponentsIn"
       }
      }
     },
     "description": "Étape facultative et 1 à 100 composants.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Record Components V1 Evidence Runs  Run Id  Components Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer les composants d'une exécution",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/runs/{run_id}/configuration": {
   "get": {
    "description": "Reconstitue la configuration d'exécution d'une exécution d'agent : version de l'agent et empreinte du manifeste, composants déclarés par type (modèle, prompt, outil, compétence, plugin) avec leurs versions et empreintes, et décisions de politique enregistrées pour l'exécution. Exige `auditor`, `security_officer` ou `tenant_admin`, `agent_operator` ou `developer`. La lecture est inscrite dans le journal d'accès. Répond 404 `not_found` pour une exécution inconnue.",
    "operationId": "run_configuration_v1_evidence_runs__run_id__configuration_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'exécution d'agent.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Run Configuration V1 Evidence Runs  Run Id  Configuration Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la configuration d'exécution d'une exécution",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/seals": {
   "get": {
    "description": "Liste les sceaux de la chaîne du tenant : numéro, plage de séquences, racine de Merkle, premières et dernières empreintes, empreinte du sceau précédent, identifiant de clé, signature et date. Exige `auditor`, `security_officer` ou `tenant_admin`. La lecture est inscrite dans le journal d'accès.",
    "operationId": "list_seals_v1_evidence_seals_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Seals V1 Evidence Seals Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les sceaux d'audit",
    "tags": [
     "evidence"
    ]
   },
   "post": {
    "description": "Écrit un sceau sur les événements écrits depuis le sceau précédent (jusqu'au maximum par sceau de la plateforme) et le renvoie (201). Exige `security_officer` ou `auditor`. Répond 409 `audit_nothing_to_seal` quand aucun événement n'attend, 503 `audit_seal_key_unavailable` sans clé de signature.",
    "operationId": "create_seal_v1_evidence_seals_post",
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Seal V1 Evidence Seals Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Sceller la chaîne d'audit maintenant",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/seals/{seal_id}/verify": {
   "get": {
    "description": "Recalcule le sceau à partir des événements qu'il couvre et des sceaux qui le précèdent et vérifie sa signature. Renvoie `seal`, `valid` et `failures`. Exige `auditor`, `security_officer` ou `tenant_admin`. Répond 404 `not_found` pour un sceau inconnu. La vérification est inscrite dans le journal d'accès.",
    "operationId": "verify_seal_v1_evidence_seals__seal_id__verify_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du sceau.",
      "in": "path",
      "name": "seal_id",
      "required": true,
      "schema": {
       "title": "Seal Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify Seal V1 Evidence Seals  Seal Id  Verify Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier un sceau d'audit",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/evidence/verify": {
   "get": {
    "description": "Vérifie toute la chaîne du tenant et tous ses sceaux et renvoie `tenant_id`, `valid`, `chain` (résultat de la chaîne de hachage), `seals` (nombre et échecs), `clock` (échecs d'horodatage) et `events` (nombre vérifié). Exige `auditor`, `security_officer` ou `tenant_admin`. La vérification est inscrite dans le journal d'accès.",
    "operationId": "verify_all_v1_evidence_verify_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify All V1 Evidence Verify Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Vérifier la chaîne et tous les sceaux",
    "tags": [
     "evidence"
    ]
   }
  },
  "/v1/file-security/policy": {
   "get": {
    "description": "Politique effective du tenant : les règles verrouillées, chaque règle modifiable (valeurs enregistrées par-dessus les valeurs par défaut, la taille jamais au-dessus du plafond de la plateforme `EAI_FILE_SCAN_MAX_BYTES`), les types de fichiers connus, et la date et l'auteur de la dernière modification. Demande un principal de tenant avec `tenant_admin`, `security_officer` ou `auditor` (403 `forbidden`).",
    "operationId": "get_policy_v1_file_security_policy_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Policy V1 File Security Policy Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire la politique de sécurité des fichiers",
    "tags": [
     "file-security"
    ]
   },
   "put": {
    "description": "Modifie les règles modifiables fournies dans le corps ; les autres règles gardent leur valeur. Les tailles ne peuvent être que plus strictes que le plafond de la plateforme, et les règles verrouillées ne changent jamais (422 `file_security_rule_locked`). Un champ inconnu, un type inconnu ou une valeur hors bornes répond 422 `invalid_request` avec le champ et la raison. Demande un principal de tenant avec `security_officer` ou `tenant_admin` (403 `forbidden`). La modification est auditée (`file_security.policy.update`) avec les valeurs avant et après ; la réponse est la nouvelle politique effective.",
    "operationId": "put_policy_v1_file_security_policy_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__file_security__PolicyIn"
       }
      }
     },
     "description": "Règles modifiables à changer ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Policy V1 File Security Policy Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier la politique de sécurité des fichiers",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/file-security/scans": {
   "get": {
    "description": "Enregistrements d'analyse du tenant, du plus récent au plus ancien ; un principal sans rôle de lecteur (`tenant_admin`, `security_officer`, `auditor`) ne voit que ses propres analyses. Demande un principal de tenant. Un statut, un verdict ou une source inconnus répondent 400 `invalid_request` ; un curseur mal formé 400 `invalid_cursor`.",
    "operationId": "list_scans_v1_file_security_scans_get",
    "parameters": [
     {
      "description": "Seulement les enregistrements de ce statut : `quarantined`, `released` ou `blocked`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les enregistrements de ce verdict : `clean`, `malware`, `suspicious`, `unscannable`, `scan_error` ou `policy_violation`.",
      "in": "query",
      "name": "verdict",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Verdict"
      }
     },
     {
      "description": "Seulement les fichiers de ce point d'entrée : `knowledge`, `media`, `multimodal`, `transcription`, `connector`, `channel` ou `api`.",
      "in": "query",
      "name": "source",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Source"
      }
     },
     {
      "description": "Nombre maximal d'enregistrements dans une page : 50 par défaut, plafonné à 500.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire les enregistrements les plus récents.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Scans V1 File Security Scans Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les enregistrements d'analyse",
    "tags": [
     "file-security"
    ]
   },
   "post": {
    "description": "Analyse un fichier sans l'ingérer et répond 201 avec l'enregistrement, `released` ou `blocked` (un fichier bloqué n'est pas une erreur ici). Tout principal de tenant peut l'appeler (403 `tenant_required` sinon).\n\nÉtapes :\n\n1. L'enregistrement est écrit `quarantined`.\n2. Les contrôles intégrés appliquent la politique du tenant (taille, type, archives, incohérence de type, macros) ; un fichier au-delà de la taille limite n'est pas envoyé aux moteurs.\n3. ClamAV et YARA-X analysent les octets ; le verdict suit l'ordre de priorité malware, suspect, impossible à inspecter, violation de politique, erreur d'analyse.\n4. L'enregistrement devient `released` ou `blocked`, un fichier bloqué garde un échantillon chiffré si la politique le prévoit, et l'analyse est auditée (`file_security.scan`), avec un événement de sécurité pour un fichier bloqué.\n\nErreurs : 400 `invalid_request` pour un JSON invalide, un `content_base64` ou un `file` manquant, ou un base64 invalide ; 503 `file_scan_unavailable` quand l'analyse elle-même échoue (l'enregistrement reste en quarantaine).",
    "operationId": "create_scan_v1_file_security_scans_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "properties": {
         "content_base64": {
          "type": "string"
         },
         "filename": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         }
        },
        "required": [
         "content_base64"
        ],
        "type": "object"
       }
      },
      "multipart/form-data": {
       "schema": {
        "properties": {
         "file": {
          "format": "binary",
          "type": "string"
         },
         "filename": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         }
        },
        "required": [
         "file"
        ],
        "type": "object"
       }
      }
     },
     "description": "Soit du JSON `{content_base64, filename?, mime_type?}`, soit du `multipart/form-data` avec une partie `file` et les champs facultatifs `filename` et `mime_type` (ils remplacent les valeurs de la partie).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Scan V1 File Security Scans Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Analyser un fichier sans l'ingérer",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/file-security/scans/{scan_id}": {
   "get": {
    "description": "Un enregistrement d'analyse : statut, verdict, raisons, source et sa référence, nom de fichier, type déclaré et détecté, taille, SHA-256, réponse de chaque moteur, échantillon conservé ou non, analyse qu'il reprend, créateur et dates (création, analyse, expiration de l'échantillon). Demande un principal de tenant ; l'enregistrement d'un autre n'est visible que par `tenant_admin`, `security_officer` et `auditor` (404 `not_found` sinon).",
    "operationId": "get_scan_v1_file_security_scans__scan_id__get",
    "parameters": [
     {
      "description": "Identifiant de l'enregistrement d'analyse.",
      "in": "path",
      "name": "scan_id",
      "required": true,
      "schema": {
       "title": "Scan Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Scan V1 File Security Scans  Scan Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un enregistrement d'analyse",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/file-security/scans/{scan_id}/rescan": {
   "post": {
    "description": "Analyse à nouveau l'échantillon conservé d'un fichier bloqué, par exemple après une mise à jour des signatures, et répond 201 avec un nouvel enregistrement dont `rescan_of` désigne l'analyse d'origine. L'envoi d'origine n'est jamais libéré : un résultat sain ne fait qu'informer. Demande un principal de tenant avec `security_officer` ou `tenant_admin` (403 `forbidden`). Sans échantillon conservé : 409 `file_sample_unavailable` ; analyse inconnue ou invisible : 404 `not_found`.",
    "operationId": "rescan_v1_file_security_scans__scan_id__rescan_post",
    "parameters": [
     {
      "description": "Identifiant de l'enregistrement d'analyse bloqué dont l'échantillon est analysé à nouveau.",
      "in": "path",
      "name": "scan_id",
      "required": true,
      "schema": {
       "title": "Scan Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Rescan V1 File Security Scans  Scan Id  Rescan Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Analyser à nouveau l'échantillon d'un fichier bloqué",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/file-security/scans/{scan_id}/sample": {
   "delete": {
    "description": "Supprime tout de suite l'échantillon chiffré d'un fichier bloqué, sans attendre la fin de sa rétention, et répond `sample_retained: false`. La purge est auditée (`file_security.sample.purge`) avec le SHA-256 du fichier. Demande un principal de tenant avec `security_officer` ou `tenant_admin` (403 `forbidden`). Une analyse inconnue ou non visible par l'appelant répond 404 `not_found`.",
    "operationId": "purge_sample_v1_file_security_scans__scan_id__sample_delete",
    "parameters": [
     {
      "description": "Identifiant de l'enregistrement d'analyse.",
      "in": "path",
      "name": "scan_id",
      "required": true,
      "schema": {
       "title": "Scan Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Purge Sample V1 File Security Scans  Scan Id  Sample Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Purger l'échantillon d'un fichier bloqué",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/file-security/stats": {
   "get": {
    "description": "Nombre d'analyses sur les `window_hours` dernières heures, par statut, par verdict et par source, avec l'heure du dernier fichier bloqué. Les lecteurs (`tenant_admin`, `security_officer`, `auditor`) comptent toutes les analyses du tenant, les autres seulement les leurs. Demande un principal de tenant. Une fenêtre hors de 1 à 8784 heures répond 400 `invalid_request`.",
    "operationId": "scan_stats_v1_file_security_stats_get",
    "parameters": [
     {
      "description": "Longueur de la fenêtre en heures, de 1 à 8784 (366 jours), 24 par défaut.",
      "in": "query",
      "name": "window_hours",
      "required": false,
      "schema": {
       "default": 24,
       "title": "Window Hours",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Scan Stats V1 File Security Stats Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les statistiques d'analyse",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/file-security/status": {
   "get": {
    "description": "Mode (`enforce` ou `dev`), disponibilité, chaîne de traitement et chaque passerelle ClamAV et worker YARA-X, sondés en direct : version, version des signatures, publication et âge avec un indicateur `stale` au regard de la politique, version, empreinte, nombre et heure de chargement des règles, latence. L'instance est prête quand chaque moteur a au moins un point d'accès sain (en mode enforce, ou quand il est configuré). Demande `tenant_admin`, `security_officer` ou `auditor` (403 `forbidden`).",
    "operationId": "file_security_status_v1_file_security_status_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response File Security Status V1 File Security Status Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire l'état des moteurs d'analyse",
    "tags": [
     "file-security"
    ]
   }
  },
  "/v1/finops/alerts": {
   "get": {
    "description": "Alertes de budget, de quota et d'écart de facturation du tenant, de la plus récente à la plus ancienne, avec leur message dans la langue de la requête. Une seule page. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "get_alerts_v1_finops_alerts_get",
    "parameters": [
     {
      "description": "Seulement les alertes de ce type : `budget`, `quota` ou `variance`.",
      "in": "query",
      "name": "kind",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "budget",
          "quota",
          "variance"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Kind"
      }
     },
     {
      "description": "Nombre maximal d'alertes : 50 par défaut, plafonné à 500.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Alerts V1 Finops Alerts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les alertes FinOps",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/allocation-policies": {
   "get": {
    "description": "Politiques qui transforment les échantillons GPU en coût (coût horaire du GPU, énergie, PUE, stockage et frais généraux) par motif de déploiement et modèle de GPU. Demande un rôle de lecteur.",
    "operationId": "list_allocation_policies_v1_finops_allocation_policies_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Allocation Policies V1 Finops Allocation Policies Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les politiques de répartition GPU",
    "tags": [
     "finops"
    ]
   },
   "post": {
    "description": "Crée une politique qui valorise les échantillons GPU des déploiements correspondants et répond 201. Le motif de déploiement le plus précis l'emporte. Coût d'un échantillon de `s` secondes à `P` watts : GPU `s / 3600 * gpu_hourly_cost`, énergie `P * s / 3600 / 1000 * energy_kwh_price * pue`, stockage `storage_gb * storage_gb_month_price * (s / 3600) / 730`, total multiplié par `1 + overhead_pct / 100`. Audité. Demande `platform_admin`.",
    "operationId": "create_allocation_policy_v1_finops_allocation_policies_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AllocationPolicyIn"
       }
      }
     },
     "description": "La nouvelle politique de répartition.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Allocation Policy V1 Finops Allocation Policies Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une politique de répartition GPU",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/estimate": {
   "post": {
    "description": "Valorise un appel prévu avec la grille tarifaire en vigueur (motif de modèle et région les plus précis d'abord), le convertit dans la devise du tenant et le compare aux quotas correspondants, sans rien réserver. Les jetons de raisonnement sont ajoutés selon le mode de facturation de la grille. Sans grille : 404 `price_card_not_found` ; sans taux de change : 422 `fx_rate_missing`. Demande un rôle d'usage ou de lecteur et un principal de tenant.",
    "operationId": "post_estimate_v1_finops_estimate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EstimateIn"
       }
      }
     },
     "description": "L'appel prévu à valoriser.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Post Estimate V1 Finops Estimate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Estimer le coût d'un appel",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/fx-rates": {
   "get": {
    "description": "Taux de change (globaux et propres au tenant) par devise de base, devise de cotation et date la plus récente d'abord, avec leur source. Demande un rôle de lecteur.",
    "operationId": "get_fx_rates_v1_finops_fx_rates_get",
    "parameters": [
     {
      "description": "Seulement les taux de cette devise de base (ISO 4217).",
      "in": "query",
      "name": "base",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Base"
      }
     },
     {
      "description": "Seulement les taux de cette devise de cotation (ISO 4217).",
      "in": "query",
      "name": "quote",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Quote"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Fx Rates V1 Finops Fx Rates Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les taux de change",
    "tags": [
     "finops"
    ]
   },
   "put": {
    "description": "Ajoute ou remplace de 1 à 1000 taux de change (mêmes devises et même date : remplacé) pour le tenant, ou des taux globaux pour un administrateur de la plateforme sans tenant, et répond le nombre mis à jour avec la liste complète. Les conversions passent par `USD` comme pivot quand aucun taux direct n'existe. Audité. Demande `finops` ou `tenant_admin` (`platform_admin` pour les taux globaux).",
    "operationId": "put_fx_rates_v1_finops_fx_rates_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/FxRatesIn"
       }
      }
     },
     "description": "Les taux à enregistrer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Fx Rates V1 Finops Fx Rates Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir des taux de change",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/gpu/allocation": {
   "get": {
    "description": "Coût des déploiements GPU locaux sur la fenêtre, calculé à partir des échantillons GPU et des politiques de répartition, et partagé entre les tenants au prorata des secondes GPU de leurs événements d'usage sur chaque déploiement. Un tenant voit sa propre part ; un administrateur de la plateforme sans tenant voit tous les tenants. Demande un rôle de lecteur.",
    "operationId": "get_gpu_allocation_v1_finops_gpu_allocation_get",
    "parameters": [
     {
      "description": "Début de la fenêtre, ISO 8601. Par défaut : un jour avant `to`.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Fin de la fenêtre, ISO 8601. Par défaut : maintenant.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Gpu Allocation V1 Finops Gpu Allocation Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la répartition du coût GPU",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/gpu/samples": {
   "post": {
    "description": "Enregistre des échantillons d'utilisation GPU (en général issus de DCGM), de 1 à 10000 par appel, utilisés pour la répartition du coût GPU. Un événement d'audit par lot. Demande `platform_admin`.",
    "operationId": "post_gpu_samples_v1_finops_gpu_samples_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/GpuSamplesIn"
       }
      }
     },
     "description": "Lot d'échantillons GPU.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Post Gpu Samples V1 Finops Gpu Samples Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ingérer des échantillons GPU",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/media-costs/estimate": {
   "post": {
    "description": "Valorise jusqu'à 100 éléments de médias avec la révision en vigueur à leur date (ressource exacte d'abord, puis la révision `*` du fournisseur). Un élément sans prix revient avec `amount: null`, `status: unavailable` et `reason: price_unknown` ; `total` n'est donné que si tous les éléments sont valorisés dans une seule devise (`complete: true`). Des éléments invalides répondent 422 `media_price_invalid` avec le champ. Demande un rôle de lecteur ou `agent`, et un principal de tenant.",
    "operationId": "media_cost_estimate_v1_finops_media_costs_estimate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`items` : de 1 à 100 objets avec `unit` (`audio_second`, `image`, `video_second`, `gpu_second`, `storage_gb_month`), `provider`, `quantity` (nombre positif), et facultativement `resource` (`*` par défaut) et `at` (ISO 8601, maintenant par défaut).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Media Cost Estimate V1 Finops Media Costs Estimate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Estimer des coûts de médias",
    "tags": [
     "finops",
     "finops"
    ]
   }
  },
  "/v1/finops/media-prices": {
   "get": {
    "description": "Révisions datées des prix des médias du tenant par unité, fournisseur, ressource et date, avec la liste des unités prises en charge. Une unité inconnue répond 422 `media_price_invalid`. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "media_price_list_v1_finops_media_prices_get",
    "parameters": [
     {
      "description": "Seulement les prix de cette unité : `audio_second`, `image`, `video_second`, `gpu_second` ou `storage_gb_month`.",
      "in": "query",
      "name": "unit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Unit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Media Price List V1 Finops Media Prices Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les prix des médias",
    "tags": [
     "finops",
     "finops"
    ]
   },
   "post": {
    "description": "Ajoute une révision de prix datée pour une unité de média et répond 201. Une révision ne réécrit jamais une révision plus ancienne. Les mêmes unité, fournisseur, ressource et date de début répondent 409 `media_price_exists` ; un champ invalide 422 `media_price_invalid`. Audité. Demande `finops` ou `tenant_admin`, et un principal de tenant.",
    "operationId": "media_price_create_v1_finops_media_prices_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`unit` (`audio_second`, `image`, `video_second`, `gpu_second`, `storage_gb_month`), `provider`, `unit_price` (nombre, au moins 0), `currency` (ISO 4217), `source` (origine du prix, obligatoire), et facultativement `resource` (`*` par défaut, tout le fournisseur) et `effective_from` (ISO 8601, maintenant par défaut).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Media Price Create V1 Finops Media Prices Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter une révision de prix de média",
    "tags": [
     "finops",
     "finops"
    ]
   }
  },
  "/v1/finops/price-cards": {
   "get": {
    "description": "Grilles tarifaires visibles par l'appelant (globales et propres au tenant), par fournisseur, motif de modèle et date de début. Seulement les grilles actives sauf si `include_retired` ou `at` est fourni. Une seule page. Demande un rôle de lecteur.",
    "operationId": "list_price_cards_v1_finops_price_cards_get",
    "parameters": [
     {
      "description": "Seulement les grilles de ce fournisseur.",
      "in": "query",
      "name": "provider",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Provider"
      }
     },
     {
      "description": "Seulement les grilles dont le motif de modèle correspond à cet identifiant de modèle (motif glob insensible à la casse).",
      "in": "query",
      "name": "model",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Model"
      }
     },
     {
      "description": "Seulement les grilles en vigueur à cette date et heure (ISO 8601), y compris les grilles retirées.",
      "in": "query",
      "name": "at",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "At"
      }
     },
     {
      "description": "Inclure les grilles retirées (faux par défaut).",
      "in": "query",
      "name": "include_retired",
      "required": false,
      "schema": {
       "default": false,
       "title": "Include Retired",
       "type": "boolean"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Price Cards V1 Finops Price Cards Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les grilles tarifaires",
    "tags": [
     "finops"
    ]
   },
   "post": {
    "description": "Crée une grille tarifaire pour le tenant (ou une grille globale pour un administrateur de la plateforme sans tenant) et répond 201. La grille en vigueur pour le même fournisseur, le même motif de modèle, la même région et le même type d'usage est close à la nouvelle date de début ; une grille commençant plus tard dans la nouvelle validité répond 409 `conflict`. Des clés de prix inconnues, des prix négatifs ou une grille sans aucun prix répondent 422 `price_card_invalid`. Audité. Demande `finops` ou `tenant_admin` (`platform_admin` pour une grille globale).",
    "operationId": "create_price_card_v1_finops_price_cards_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PriceCardIn"
       }
      }
     },
     "description": "La nouvelle grille tarifaire.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Price Card V1 Finops Price Cards Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une grille tarifaire",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/price-cards/{card_id}": {
   "get": {
    "description": "Une grille tarifaire avec ses prix, ses paliers, sa validité et son statut. Identifiant inconnu : 404 `not_found`. Demande un rôle de lecteur.",
    "operationId": "get_price_card_v1_finops_price_cards__card_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la grille tarifaire.",
      "in": "path",
      "name": "card_id",
      "required": true,
      "schema": {
       "title": "Card Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Price Card V1 Finops Price Cards  Card Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une grille tarifaire",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/price-cards/{card_id}/retire": {
   "post": {
    "description": "Marque la grille `retired` et termine sa validité à `at` (maintenant par défaut) si elle restait ouverte après cette date. Les usages passés gardent le prix avec lequel ils ont été valorisés. Identifiant inconnu : 404 `not_found`. Audité. Demande `finops` ou `tenant_admin`.",
    "operationId": "retire_price_card_v1_finops_price_cards__card_id__retire_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la grille tarifaire.",
      "in": "path",
      "name": "card_id",
      "required": true,
      "schema": {
       "title": "Card Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`{at}` facultatif : fin de validité, ISO 8601 (maintenant par défaut)."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Retire Price Card V1 Finops Price Cards  Card Id  Retire Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer une grille tarifaire",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/reconciliation/imports": {
   "get": {
    "description": "Exports de facturation importés par le tenant, avec source, fournisseur, nom de fichier, nombre de lignes, montant total, devise et période. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "get_imports_v1_finops_reconciliation_imports_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Imports V1 Finops Reconciliation Imports Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les imports de facturation",
    "tags": [
     "finops"
    ]
   },
   "post": {
    "description": "Analyse un export de facturation d'un fournisseur, convertit chaque ligne dans la devise du tenant et l'enregistre, puis répond 201 avec l'import (nombre de lignes, total, période). Un export illisible répond 422 `import_invalid` avec la raison. Lancez ensuite le rapprochement. Audité. Demande `finops` ou `tenant_admin`, et un principal de tenant.",
    "operationId": "post_import_v1_finops_reconciliation_imports_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ImportIn"
       }
      }
     },
     "description": "L'export de facturation et son format.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Post Import V1 Finops Reconciliation Imports Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Importer un export de facturation",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/reconciliation/report": {
   "get": {
    "description": "Coût mesuré comparé au coût facturé sur la période, avec l'écart, les lignes de facturation non rapprochées et les 100 dernières alertes d'écart. Un `from` postérieur à `to` répond 422 `validation_error`. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "get_recon_report_v1_finops_reconciliation_report_get",
    "parameters": [
     {
      "description": "Premier jour de la période (date ISO 8601). Par défaut : le premier jour du mois de `to`.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Dernier jour de la période (date ISO 8601). Par défaut : aujourd'hui.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Recon Report V1 Finops Reconciliation Report Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le rapport de rapprochement",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/reconciliation/run": {
   "post": {
    "description": "Répartit les lignes facturées importées de la période sur les événements d'usage : une ligne portant un `provider_request_id` va à cet événement, les autres sont réparties au prorata du coût mesuré des événements du même fournisseur, du même modèle et du même jour (à parts égales si rien n'a été mesuré) ; les lignes sans événement candidat sont signalées comme non rapprochées. Des alertes d'écart sont levées au-delà du seuil du tenant. Audité. Demande `finops` ou `tenant_admin`, et un principal de tenant.",
    "operationId": "post_recon_run_v1_finops_reconciliation_run_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReconRunIn"
       }
      }
     },
     "description": "Période à rapprocher ; le corps peut être omis."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Post Recon Run V1 Finops Reconciliation Run Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer le rapprochement de facturation",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/settings": {
   "get": {
    "description": "Devise, budget mensuel, seuil d'écart et durée de vie des réservations du tenant (valeurs par défaut : `USD`, pas de budget, 10 pour cent, 900 secondes). Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "get_settings__v1_finops_settings_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Settings  V1 Finops Settings Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les paramètres FinOps",
    "tags": [
     "finops"
    ]
   },
   "put": {
    "description": "Modifie les paramètres fournis du tenant ; les champs omis ou null gardent leur valeur. Audité (`finops.settings.update`). Demande `finops` ou `tenant_admin`, et un principal de tenant.",
    "operationId": "put_settings_v1_finops_settings_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SettingsIn"
       }
      }
     },
     "description": "Paramètres à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Settings V1 Finops Settings Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier les paramètres FinOps",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/summary": {
   "get": {
    "description": "Événements d'usage du tenant regroupés selon une dimension, par devise : nombre d'événements, jetons, secondes GPU, montants estimés, mesurés et facturés (le facturé vaut null si rien n'a été rapproché). Un regroupement inconnu répond 400 `finops_group_by_invalid`. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "get_summary_v1_finops_summary_get",
    "parameters": [
     {
      "description": "Regroupement : `model` (par défaut), `tenant`, `project`, `agent`, `provider`, `usage_type` ou `day`.",
      "in": "query",
      "name": "group_by",
      "required": false,
      "schema": {
       "default": "model",
       "title": "Group By",
       "type": "string"
      }
     },
     {
      "description": "Début de la période, ISO 8601 (inclus).",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Fin de la période, ISO 8601 (exclue).",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Summary V1 Finops Summary Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Synthétiser la dépense",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/usage": {
   "get": {
    "description": "Événements d'usage du tenant avec leurs unités et leurs coûts estimés, mesurés et facturés, filtrés et paginés. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "get_usage_v1_finops_usage_get",
    "parameters": [
     {
      "description": "Seulement les événements de ce projet.",
      "in": "query",
      "name": "project_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project Id"
      }
     },
     {
      "description": "Seulement les événements de cet agent.",
      "in": "query",
      "name": "agent_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent Id"
      }
     },
     {
      "description": "Seulement les événements de cette session.",
      "in": "query",
      "name": "session_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Session Id"
      }
     },
     {
      "description": "Seulement les événements de cette exécution d'agent.",
      "in": "query",
      "name": "run_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Run Id"
      }
     },
     {
      "description": "Seulement les événements de cette tâche.",
      "in": "query",
      "name": "task_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Task Id"
      }
     },
     {
      "description": "Seulement les événements de ce modèle.",
      "in": "query",
      "name": "model_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Model Id"
      }
     },
     {
      "description": "Seulement les événements de ce fournisseur.",
      "in": "query",
      "name": "provider",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Provider"
      }
     },
     {
      "description": "Seulement les événements de ce type d'usage de Usage Event v1, par exemple `llm`, `embeddings`, `image_generation`, `stt`, `tool` ou `gpu_local`.",
      "in": "query",
      "name": "usage_type",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Usage Type"
      }
     },
     {
      "description": "Seulement les événements de ce statut.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les événements de ce déploiement de modèle local.",
      "in": "query",
      "name": "deployment_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Deployment Id"
      }
     },
     {
      "description": "Début de la période, ISO 8601.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Fin de la période, ISO 8601.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     },
     {
      "description": "Nombre maximal d'événements dans une page : 50 par défaut, plafonné à 500.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Usage V1 Finops Usage Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les événements d'usage",
    "tags": [
     "finops"
    ]
   },
   "post": {
    "description": "Valide un Usage Event v1, le valorise (coût mesuré dans la devise du tenant) et l'enregistre ; répond 201, ou 200 avec `duplicate: true` si le même `event_id` était déjà enregistré. Un événement invalide répond 422 `usage_event_invalid` avec les erreurs ; un événement d'un autre tenant 403 `usage_tenant_mismatch`. Un événement d'audit par appel, jamais le contenu. Demande `finops`, `tenant_admin`, `agent` ou `developer`, et un principal de tenant.",
    "operationId": "ingest_usage_v1_finops_usage_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Event",
        "type": "object"
       }
      }
     },
     "description": "Un Usage Event v1 (`event_id`, `tenant_id`, `occurred_at`, `usage_type`, `units`, `pricing`, identifiants de portée...), tel que défini par le contrat.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Ingest Usage V1 Finops Usage Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ingérer un événement d'usage",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/finops/usage/batch": {
   "post": {
    "description": "Ingère jusqu'à 1000 événements d'usage, chacun dans son propre point de sauvegarde : la réponse liste chaque événement comme `created`, `duplicate` ou `error` (avec code, message et détails) ainsi que les totaux. Plus de 1000 événements répondent 413 `batch_too_large` ; un corps sans liste `events` 422 `validation_error`. Un événement d'audit par lot. Demande `finops`, `tenant_admin`, `agent` ou `developer`, et un principal de tenant.",
    "operationId": "ingest_usage_batch_v1_finops_usage_batch_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`{events: [...]}` : une liste d'objets Usage Event v1.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Ingest Usage Batch V1 Finops Usage Batch Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ingérer des événements d'usage par lot",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/gateway/audio/speech": {
   "post": {
    "description": "Synthèse vocale par l'adaptateur TTS, au format OpenAI `audio/speech` ; l'audio est renvoyé tel quel (`audio/*`). Le kill switch du tenant, la classification des données et l'habilitation de l'appelant, l'isolation des secrets et l'audit s'appliquent, et le coût est réservé sur les quotas selon le nombre de caractères. Demande un principal de tenant avec le scope `models:invoke`. Sans adaptateur configuré : 501 `adapter_not_available` ; adaptateur injoignable : 502 `upstream_error`.",
    "operationId": "gateway_tts_v1_gateway_audio_speech_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête de synthèse OpenAI (`model`, `input`, `voice`, `response_format`...) plus l'objet facultatif `eai` (`data_classification`, `agent_id`, `run_id`).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Synthétiser de la parole à partir d'un texte",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/audio/transcriptions": {
   "post": {
    "description": "Transcription par l'adaptateur STT, au format OpenAI `audio/transcriptions`. Demande un principal de tenant avec le scope `models:invoke`.\n\nÉtapes :\n\n1. Le corps est lu en multipart (partie `file`) ou en JSON (`content_base64`) ; `model` est obligatoire et `timestamp_granularities` demande `response_format: verbose_json` (422 `parameter_invalid` sinon).\n2. L'audio passe par la sécurité des fichiers (quarantaine, ClamAV, YARA-X, validation) : un fichier bloqué répond 422 `file_blocked`, une analyse indisponible 503 `file_scan_unavailable`.\n3. Le kill switch du tenant, la classification, la DLP sur le fichier et la réservation de coût (selon la durée de l'audio) s'appliquent, puis l'adaptateur est appelé.\n4. La transcription (champs `text` du JSON, ou réponses texte, SRT et VTT) passe par la DLP du tenant avant d'être renvoyée ; l'appel est audité avec le SHA-256 du fichier.",
    "operationId": "gateway_stt_v1_gateway_audio_transcriptions_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "properties": {
         "content_base64": {
          "type": "string"
         },
         "eai": {
          "type": "object"
         },
         "filename": {
          "type": "string"
         },
         "language": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         },
         "model": {
          "type": "string"
         },
         "prompt": {
          "type": "string"
         },
         "response_format": {
          "enum": [
           "json",
           "text",
           "srt",
           "verbose_json",
           "vtt"
          ],
          "type": "string"
         },
         "temperature": {
          "maximum": 1.0,
          "minimum": 0.0,
          "type": "number"
         },
         "timestamp_granularities": {
          "description": "needs response_format verbose_json",
          "items": {
           "enum": [
            "word",
            "segment"
           ],
           "type": "string"
          },
          "type": "array"
         }
        },
        "required": [
         "content_base64",
         "model"
        ],
        "type": "object"
       }
      },
      "multipart/form-data": {
       "schema": {
        "properties": {
         "eai": {
          "description": "eai extension as a JSON object",
          "type": "string"
         },
         "file": {
          "format": "binary",
          "type": "string"
         },
         "language": {
          "type": "string"
         },
         "model": {
          "type": "string"
         },
         "prompt": {
          "type": "string"
         },
         "response_format": {
          "enum": [
           "json",
           "text",
           "srt",
           "verbose_json",
           "vtt"
          ],
          "type": "string"
         },
         "temperature": {
          "maximum": 1.0,
          "minimum": 0.0,
          "type": "number"
         },
         "timestamp_granularities": {
          "description": "needs response_format verbose_json",
          "items": {
           "enum": [
            "word",
            "segment"
           ],
           "type": "string"
          },
          "type": "array"
         }
        },
        "required": [
         "file",
         "model"
        ],
        "type": "object"
       }
      }
     },
     "description": "Multipart `file` plus `model`, `language`, `prompt`, `response_format` (`json`, `text`, `srt`, `verbose_json`, `vtt`), `temperature` (de 0 à 1), `timestamp_granularities` (`word`, `segment`) et `eai` (un objet JSON sous forme de texte) ; ou JSON avec `content_base64`, `mime_type`, `filename` et les mêmes champs.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Transcrire un fichier audio",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/chat/completions": {
   "post": {
    "description": "Complétion de conversation compatible OpenAI à travers chaque contrôle de la passerelle ; avec `stream: true` la réponse est en Server-Sent Events, et les garde-fous de coût sont vérifiés pendant le flux. Demande un principal de tenant (403 `platform_only`) dont l'identifiant porte le scope `models:invoke`.\n\nÉtapes :\n\n1. Isolation des secrets : des identifiants ou adresses de fournisseur dans le corps sont refusés (400 `credentials_not_allowed`).\n2. Kill switches, autorisation de l'application, statut et listes d'autorisation du modèle, région, classification des données au regard du plafond du modèle et de l'habilitation de l'appelant, mode d'isolation (403 avec un code stable comme `model_blocked`, `classification_exceeded`, `isolation_external_route`).\n3. La DLP masque ou bloque les données sensibles (422 `dlp_blocked`), détection d'injection de prompt (403 `prompt_injection_blocked` en mode blocage), compression du contexte quand la fenêtre est dépassée (422 `context_overflow` si c'est impossible).\n4. Estimation du coût au regard de la limite par requête et du budget mensuel (429 `budget_exceeded`), puis décision de politique `model.invoke` (403 `policy_denied`).\n5. Appel par LiteLLM avec les replis autorisés (502 `upstream_error` quand tous échouent), DLP de la réponse, événement d'usage et audit avec empreintes seulement.\n\nLes avertissements (modèle déprécié, données masquées, contexte compressé) sont renvoyés avec la réponse ; les en-têtes `X-EAI-Decision-Id`, `X-EAI-Model`, `X-EAI-Cost-Estimated`, `X-EAI-Cost-Measured` et `X-EAI-Injection-Risk` décrivent l'appel.",
    "operationId": "gateway_chat_v1_gateway_chat_completions_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête de conversation OpenAI (`model` est l'identifiant du registre, `messages`, `stream`, `max_tokens`, `tools`...) plus l'objet facultatif `eai` : `data_classification` (`public`, `internal` par défaut, `confidential`, `restricted`, `secret`), `agent_id`, `session_id`, `run_id`, `purpose`, `project_id`, `effort`, `max_cost`, `fallback_policy` et des listes d'autorisation qui ne peuvent que restreindre celles du tenant (`provider_allow`, `model_allow`, `region_allow`).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Appeler un modèle de conversation",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/embeddings": {
   "post": {
    "description": "Embeddings compatibles OpenAI à travers les mêmes contrôles que la conversation (kill switches, listes d'autorisation, classification, DLP sur chaque entrée, garde-fous de coût, décision de politique, audit). Le modèle doit déclarer la capacité embeddings et, si `dimensions` est fourni, la prendre en charge (422 `capability_not_supported`, 422 `embedding_dimensions_invalid`). Demande un principal de tenant avec le scope `models:invoke`.",
    "operationId": "gateway_embeddings_v1_gateway_embeddings_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête d'embeddings OpenAI (`model`, `input` sous forme de texte ou de liste, `dimensions` facultatif) plus l'objet facultatif `eai`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Calculer des embeddings",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/estimate": {
   "post": {
    "description": "Exécute chaque contrôle préalable de la passerelle sans appeler le modèle et renvoie le modèle résolu, l'identifiant de décision, les jetons d'entrée, les jetons de sortie maximaux, l'estimateur, le coût estimé, la limite de coût, les replis, les avertissements et le plan de contexte. Une requête refusée par un contrôle répond la même erreur que l'appel réel. Demande un principal de tenant avec le scope `models:invoke`.",
    "operationId": "gateway_estimate_v1_gateway_estimate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Le corps de l'appel à estimer, avec `kind` facultatif (`chat`, `embeddings` ou `rerank`) ; sans `kind`, il est déduit de `input` (embeddings) ou `query` (rerank), sinon conversation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Estimer un appel à la passerelle",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/images/generations": {
   "post": {
    "description": "Génération d'images par l'adaptateur d'images au format OpenAI. Le prompt passe par la DLP du tenant (422 `dlp_blocked`), le kill switch, la classification, l'isolation des secrets et l'audit s'appliquent, et le coût est réservé selon le nombre d'images ; les champs texte de la réponse (`revised_prompt`) sont aussi analysés. Demande un principal de tenant avec le scope `models:invoke`. Sans adaptateur configuré : 501 `adapter_not_available`.",
    "operationId": "gateway_images_v1_gateway_images_generations_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête d'image OpenAI (`model`, `prompt`, `n`, `size`...) plus l'objet facultatif `eai`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Générer des images",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/rerank": {
   "post": {
    "description": "Réordonne des `documents` par rapport à une `query` à travers les mêmes contrôles que la conversation (DLP sur la requête, garde-fous de coût, décision de politique, audit). Le modèle doit déclarer la capacité rerank (422 `capability_not_supported`). Demande un principal de tenant avec le scope `models:invoke`.",
    "operationId": "gateway_rerank_v1_gateway_rerank_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête de réordonnancement : `model`, `query`, `documents` (textes ou objets), `top_n` facultatif, plus l'objet facultatif `eai`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réordonner des documents",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/video/generations": {
   "post": {
    "description": "Lance une tâche de génération de vidéo sur l'adaptateur vidéo, avec le même kill switch, la même classification, la même DLP du prompt, la même réservation de coût et le même audit que les autres adaptateurs ; lisez la tâche avec `GET /v1/gateway/video/generations/{job_id}`. Demande un principal de tenant avec le scope `models:invoke`. Sans adaptateur configuré : 501 `adapter_not_available`.",
    "operationId": "gateway_video_v1_gateway_video_generations_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête de génération de vidéo de l'adaptateur (`model`, `prompt` et ses options) plus l'objet facultatif `eai`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer une génération de vidéo",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/gateway/video/generations/{job_id}": {
   "get": {
    "description": "État et résultat d'une tâche de génération de vidéo, lus auprès de l'adaptateur vidéo avec le même kill switch et le même audit que les autres appels d'adaptateur. Demande un principal de tenant (403 `platform_only` sinon) dont l'identifiant porte le scope `models:invoke`. Sans adaptateur vidéo configuré : 501 `adapter_not_available`.",
    "operationId": "gateway_video_get_v1_gateway_video_generations__job_id__get",
    "parameters": [
     {
      "description": "Identifiant de la tâche renvoyé par `POST /v1/gateway/video/generations`.",
      "in": "path",
      "name": "job_id",
      "required": true,
      "schema": {
       "title": "Job Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une tâche de génération de vidéo",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/governance/registry": {
   "get": {
    "description": "Inventaire des agents, modèles, fournisseurs, outils, skills, plugins et connecteurs (5000 éléments par type au plus, par nom), une liste par type (`agents`, `models`, ...), avec un `summary` par type : total, nombre par statut, nombre par niveau de risque (`unassessed` à défaut) et nombre d'éléments sans responsable. `missing_sources` liste les tables non installées sur cette instance ; leur liste est vide. Demande un principal de tenant (403 `tenant_required`). Un type inconnu répond 400 `invalid_request` avec les types autorisés.",
    "operationId": "registry_v1_governance_registry_get",
    "parameters": [
     {
      "description": "Types à renvoyer, séparés par des virgules, parmi `agent`, `model`, `provider`, `tool`, `skill`, `plugin`, `connector`. Omettez-le pour tous les types.",
      "in": "query",
      "name": "kind",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Kind"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Registry V1 Governance Registry Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le registre de gouvernance",
    "tags": [
     "governance"
    ]
   }
  },
  "/v1/heartbeats": {
   "get": {
    "description": "Vivacité des runs censés envoyer des heartbeats (`running` et `waiting_input`). Par défaut, seuls les runs sans heartbeat dans le délai sont renvoyés, avec `active`, `stale_count` et `stale_after_seconds`. Avec `format=prometheus`, la réponse est un texte d'exposition Prometheus (`eai_runs_total`, `eai_runs_active`, `eai_runs_stale`, `eai_heartbeat_stale_threshold_seconds`).",
    "operationId": "list_heartbeats_v1_heartbeats_get",
    "parameters": [
     {
      "description": "Délai en secondes après lequel un run sans heartbeat est considéré inactif (1 à 86400). Par défaut : le réglage de la plateforme, 300 secondes sauf configuration.",
      "in": "query",
      "name": "stale_after",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 86400,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Stale After"
      }
     },
     {
      "description": "Si vrai, tous les runs actifs sont renvoyés, inactifs ou non. Faux par défaut.",
      "in": "query",
      "name": "all",
      "required": false,
      "schema": {
       "default": false,
       "title": "All",
       "type": "boolean"
      }
     },
     {
      "description": "Format de la réponse : `json` (par défaut) ou `prometheus` (format d'exposition text/plain 0.0.4).",
      "in": "query",
      "name": "format",
      "required": false,
      "schema": {
       "default": "json",
       "enum": [
        "json",
        "prometheus"
       ],
       "title": "Format",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les heartbeats des runs",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/identity/group-mappings": {
   "get": {
    "description": "Liste toutes les correspondances de groupes de l'annuaire du tenant avec leur groupe, projet, rôle et profil de quota. Exige `tenant_admin`, `security_officer` ou `auditor` (403 `forbidden` sinon). Non paginé : `next_cursor` vaut toujours null.",
    "operationId": "list_group_mappings_v1_identity_group_mappings_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Group Mappings V1 Identity Group Mappings Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les correspondances de groupes",
    "tags": [
     "identity",
     "identity"
    ]
   },
   "post": {
    "description": "Associe un groupe de l'annuaire à un rôle dans un projet et, en option, à un profil de quota. Exige `tenant_admin`.\n\n1. Donnez le nom du groupe tel qu'il apparaît dans la revendication `groups` ou `entra_groups` du jeton Keycloak.\n2. Donnez le projet par `project_id` ou par son identifiant court `project` (l'un des deux est obligatoire, 422 `validation_error` sinon ; projet inconnu : 404).\n3. Donnez éventuellement un profil de quota par `quota_profile_id` ou par son nom `quota_profile`.\n\nÀ la prochaine connexion OIDC de chaque membre, la plateforme ajoute l'appartenance et le quota, et les retire quand le groupe quitte le jeton. Le même groupe, projet et rôle deux fois répond 409 `conflict`. Écrit l'événement d'audit `identity.group_mapping.create`.",
    "operationId": "create_group_mapping_v1_identity_group_mappings_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/GroupMappingIn"
       }
      }
     },
     "description": "Groupe, projet, rôle et profil de quota facultatif de la correspondance.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Group Mapping V1 Identity Group Mappings Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une correspondance de groupe",
    "tags": [
     "identity",
     "identity"
    ]
   }
  },
  "/v1/identity/group-mappings/{mapping_id}": {
   "delete": {
    "description": "Supprime la correspondance et retire ce qu'elle avait accordé (appartenances aux projets et quotas enregistrés comme accordés par elle) ; les appartenances accordées à la main restent. Exige `tenant_admin`. Écrit l'événement d'audit `identity.group_mapping.delete`. Répond 204, ou 404 `not_found` pour une correspondance inconnue.",
    "operationId": "delete_group_mapping_v1_identity_group_mappings__mapping_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la correspondance de groupe.",
      "in": "path",
      "name": "mapping_id",
      "required": true,
      "schema": {
       "title": "Mapping Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une correspondance de groupe",
    "tags": [
     "identity",
     "identity"
    ]
   },
   "get": {
    "description": "Renvoie une correspondance de groupe. Exige `tenant_admin`, `security_officer` ou `auditor`. Répond 404 `not_found` pour une correspondance inconnue.",
    "operationId": "get_group_mapping_v1_identity_group_mappings__mapping_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la correspondance de groupe.",
      "in": "path",
      "name": "mapping_id",
      "required": true,
      "schema": {
       "title": "Mapping Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Group Mapping V1 Identity Group Mappings  Mapping Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une correspondance de groupe",
    "tags": [
     "identity",
     "identity"
    ]
   },
   "patch": {
    "description": "Modifie le groupe, le projet, le rôle ou le profil de quota d'une correspondance ; seuls les champs envoyés changent. `clear_quota_profile: true` détache le profil. Les nouvelles valeurs s'appliquent à la prochaine connexion de chaque membre. Exige `tenant_admin`. Une modification qui dupliquerait une autre correspondance répond 409 `conflict` ; un projet ou un profil inconnu répond 404. Écrit l'événement d'audit `identity.group_mapping.update`.",
    "operationId": "patch_group_mapping_v1_identity_group_mappings__mapping_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la correspondance de groupe.",
      "in": "path",
      "name": "mapping_id",
      "required": true,
      "schema": {
       "title": "Mapping Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/GroupMappingPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Group Mapping V1 Identity Group Mappings  Mapping Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une correspondance de groupe",
    "tags": [
     "identity",
     "identity"
    ]
   }
  },
  "/v1/identity/quota-profiles": {
   "get": {
    "description": "Liste les profils de quota du tenant (nom, période, mode d'application, plafonds de coût et de jetons, devise). Exige `tenant_admin`, `security_officer` ou `auditor`.",
    "operationId": "list_quota_profiles_v1_identity_quota_profiles_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Quota Profiles V1 Identity Quota Profiles Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les profils de quota",
    "tags": [
     "identity",
     "identity"
    ]
   },
   "post": {
    "description": "Crée un profil de quota réutilisable par les correspondances de groupes. Exige `tenant_admin`. Au moins un des champs `limit_cost` et `limit_tokens` est obligatoire (422 `validation_error`). Un nom déjà utilisé répond 409 `conflict`. Écrit l'événement d'audit `identity.quota_profile.create`.",
    "operationId": "create_quota_profile_v1_identity_quota_profiles_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/QuotaProfileIn"
       }
      }
     },
     "description": "Nom, période, mode d'application et plafonds du profil.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Quota Profile V1 Identity Quota Profiles Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un profil de quota",
    "tags": [
     "identity",
     "identity"
    ]
   }
  },
  "/v1/identity/quota-profiles/{profile_id}": {
   "delete": {
    "description": "Supprime un profil de quota. Exige `tenant_admin`. Un profil encore utilisé par une correspondance de groupe ne peut pas être supprimé (409 `conflict`) : détachez-le d'abord des correspondances. Écrit l'événement d'audit `identity.quota_profile.delete`. Répond 204, ou 404 `not_found`.",
    "operationId": "delete_quota_profile_v1_identity_quota_profiles__profile_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de quota.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un profil de quota",
    "tags": [
     "identity",
     "identity"
    ]
   },
   "patch": {
    "description": "Modifie la période, le mode d'application ou les plafonds d'un profil, ainsi que les quotas d'utilisateurs déjà créés à partir de lui par les correspondances de groupes, dans la même transaction. Exige `tenant_admin`. Écrit l'événement d'audit `identity.quota_profile.update` avec le nombre de quotas modifiés. Répond 404 `not_found` pour un profil inconnu.",
    "operationId": "patch_quota_profile_v1_identity_quota_profiles__profile_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de quota.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/QuotaProfilePatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Quota Profile V1 Identity Quota Profiles  Profile Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un profil de quota",
    "tags": [
     "identity",
     "identity"
    ]
   }
  },
  "/v1/identity/whoami": {
   "get": {
    "description": "Renvoie l'identité résolue par la plateforme pour cette requête, quel que soit l'identifiant (jeton OIDC, session du portail, clé d'API, SPIFFE) : `id`, `kind` (`user`, `service`, `agent`, `system`), `tenant_id`, `tenant_slug`, `roles`, `scopes`, `clearance`, `auth_method` et `display`. En lecture seule, ouvert à tout appelant authentifié, y compris quand un compte local doit encore changer son mot de passe. Utilisez-la pour vérifier un nouveau jeton ou une nouvelle clé.",
    "operationId": "whoami_v1_identity_whoami_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Whoami V1 Identity Whoami Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Afficher l'identité appelante",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/incidents": {
   "get": {
    "description": "Liste les incidents du tenant, identifiant le plus récent d'abord, paginés, éventuellement filtrés par statut et gravité. Les éléments ne portent ni la chronologie ni les actions (lisez un incident pour les obtenir). Ouvert à tout membre du tenant.",
    "operationId": "list_incidents_v1_incidents_get",
    "parameters": [
     {
      "description": "Uniquement ce statut : `open`, `investigating`, `contained`, `resolved` ou `closed`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Uniquement cette gravité : `sev1`, `sev2`, `sev3` ou `sev4`.",
      "in": "query",
      "name": "severity",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Severity"
      }
     },
     {
      "description": "Nombre maximal d'incidents dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Incidents V1 Incidents Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les incidents",
    "tags": [
     "incidents"
    ]
   },
   "post": {
    "description": "Ouvre un incident au statut `open`, ajoute l'entrée `created` à la chronologie et le transmet au SIEM. Exige `tenant_admin`, `security_officer`, `developer` ou `agent`. Une gravité inconnue répond 400 `invalid_request`. Écrit l'événement d'audit `incident.create`. La réponse (201) porte l'incident avec sa chronologie.",
    "operationId": "create_incident_v1_incidents_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/IncidentIn"
       }
      }
     },
     "description": "Titre, gravité, description, catégorie, ressources et responsable.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Incident V1 Incidents Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ouvrir un incident",
    "tags": [
     "incidents"
    ]
   }
  },
  "/v1/incidents/alerts": {
   "post": {
    "description": "Récepteur webhook de Prometheus Alertmanager (charge utile en version 4). Chaque alerte est identifiée par son `fingerprint` :\n\n1. une alerte `firing` ouvre un incident de catégorie `alert`, ou met à jour l'incident ouvert ayant la même empreinte ;\n2. une alerte `resolved` le résout ;\n3. le label `severity` donne la gravité (`critical` ou `page` : `sev1`, `high` ou `error` : `sev2`, `warning` : `sev3`, `info` ou `none` : `sev4`, `sev3` par défaut).\n\nAuthentification : une clé d'API de service à laquelle la portée `incidents:alerts` (ou `incidents:*`) a été accordée explicitement, ou le rôle `alert_receiver` ; la portée globale `*` d'une session interactive ne compte pas (403 `forbidden`). Les alertes arrivent dans le tenant de la clé ; un label `tenant=<uuid>` n'est suivi que pour une clé d'administrateur de plateforme. Un statut autre que `firing` ou `resolved` répond 400. Chaque changement est audité (`incident.alert.open`, `incident.alert.update`, `incident.alert.resolve`) et transmis au SIEM. La réponse donne `received` et un élément par alerte.",
    "operationId": "receive_alerts_v1_incidents_alerts_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AlertmanagerIn"
       }
      }
     },
     "description": "Charge utile du webhook Alertmanager, 500 alertes au plus.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Receive Alerts V1 Incidents Alerts Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Recevoir des alertes Alertmanager",
    "tags": [
     "incidents"
    ]
   }
  },
  "/v1/incidents/{incident_id}": {
   "get": {
    "description": "Renvoie l'incident avec sa `timeline` (type, message, données, acteur, date) et ses `actions` de confinement (action, cible, statut, résultat). Ouvert à tout membre du tenant. Répond 404 `not_found`.",
    "operationId": "get_incident_v1_incidents__incident_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Incident V1 Incidents  Incident Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un incident",
    "tags": [
     "incidents"
    ]
   },
   "patch": {
    "description": "Modifie le statut, la gravité ou le responsable et ajoute éventuellement une note. Chaque changement est ajouté à la chronologie et transmis au SIEM ; passer à `resolved` ou `closed` renseigne `resolved_at`. Exige `tenant_admin` ou `security_officer`. Un statut ou une gravité inconnus répondent 400 `invalid_request`. Écrit l'événement d'audit `incident.update`.",
    "operationId": "patch_incident_v1_incidents__incident_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/IncidentPatch"
       }
      }
     },
     "description": "Champs à modifier et note facultative.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Incident V1 Incidents  Incident Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un incident",
    "tags": [
     "incidents"
    ]
   }
  },
  "/v1/incidents/{incident_id}/actions": {
   "post": {
    "description": "Lance une action de confinement par le domaine propriétaire de la cible et l'enregistre sur l'incident. Exige `tenant_admin` ou `security_officer`.\n\nActions : `suspend_agent`, `resume_agent`, `kill_agent` (cible `id` ou `name` de l'agent) ; `suspend_model`, `disable_model` (modèle cible, `scope: platform` pour un modèle partagé) ; `disable_provider` ; `rotate_key`, `revoke_key` (cible `kind` : `api_key` ou `encryption_key`, et `grace_seconds` pour la rotation d'une clé d'API).\n\nLe statut de l'action vaut `executed`, `failed` (l'erreur est conservée dans le résultat) ou `pending` avec la raison `requires_platform_admin` quand un intervenant du tenant cible un modèle ou un fournisseur de niveau plateforme. Une action exécutée fait passer un incident `open` ou `investigating` à `contained`. Une clé d'API tournée renvoie son nouveau secret une seule fois dans `secret` (`shown_once: true`). Une action inconnue répond 400 `unknown_action` ; une cible sans `id` ni `name` 400 `invalid_request`. Écrit l'événement d'audit `incident.action.<action>` et le transmet au SIEM.",
    "operationId": "incident_action_v1_incidents__incident_id__actions_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ActionIn"
       }
      }
     },
     "description": "Action, cible et raison.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Incident Action V1 Incidents  Incident Id  Actions Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer une action de confinement",
    "tags": [
     "incidents"
    ]
   }
  },
  "/v1/knowledge-bases": {
   "get": {
    "description": "Bases de connaissances du tenant que l'appelant peut lire selon leur ACL, dans l'ordre des noms, paginées avec `cursor`.",
    "operationId": "list_kbs_v1_knowledge_bases_get",
    "parameters": [
     {
      "description": "Nombre maximal de bases dans une page (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Kbs V1 Knowledge Bases Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les bases de connaissances",
    "tags": [
     "knowledge"
    ]
   },
   "post": {
    "description": "Crée une base avec son modèle d'embedding (par défaut : le modèle d'embedding RAG de la plateforme), qui doit être approuvé, avoir la capacité embeddings et accepter la classification. `chunk_overlap` doit être inférieur à `chunk_size` (400 `invalid_request`) ; une classification inconnue répond 400. Un nom déjà utilisé dans le tenant répond 409 `conflict`. Demande `tenant_admin` ou `developer` ; l'appelant devient propriétaire ReBAC. Audité sous `knowledge_base.create`.",
    "operationId": "create_kb_v1_knowledge_bases_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/KbIn"
       }
      }
     },
     "description": "Nom, découpage, classification, ACL et modèle d'embedding de la base.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Kb V1 Knowledge Bases Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une base de connaissances",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}": {
   "delete": {
    "description": "Supprime la base, ses documents et leurs vecteurs dans le magasin vectoriel. Demande la relation ReBAC `owner`, et l'appelant doit être `tenant_admin` ou son créateur (403 `forbidden`). 404 `not_found` ; 503 `vector_store_unavailable`. Audité sous `knowledge_base.delete`.",
    "operationId": "delete_kb_v1_knowledge_bases__kb_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une base de connaissances",
    "tags": [
     "knowledge"
    ]
   },
   "get": {
    "description": "Renvoie la base avec son nombre de documents, son `pipeline` d'ingestion et de recherche, le travail de réindexation (`reindex`) en cours le cas échéant et les `datasets` visibles par l'appelant. Demande la relation ReBAC `viewer` et le droit de lecture par l'ACL ; sinon 404 `not_found`.",
    "operationId": "get_kb_v1_knowledge_bases__kb_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Kb V1 Knowledge Bases  Kb Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une base de connaissances",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/acl": {
   "put": {
    "description": "Remplace les lecteurs et rédacteurs de la base. Avec `apply_to_documents`, les documents qui héritent de l'ACL de la base (sans lecteurs propres) et leurs fragments reçoivent les nouveaux lecteurs ; `documents_updated` en donne le nombre. Demande la relation ReBAC `owner`, et l'appelant doit être `tenant_admin`, `security_officer` ou le créateur. Audité sous `knowledge_base.acl.update` ; le cache de recherche est invalidé.",
    "operationId": "put_acl_v1_knowledge_bases__kb_id__acl_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AclIn"
       }
      }
     },
     "description": "La nouvelle ACL.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Acl V1 Knowledge Bases  Kb Id  Acl Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Remplacer l'ACL d'une base",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents": {
   "get": {
    "description": "Documents lisibles par l'appelant (une de ses identités dans les `readers` du document, classification dans les limites de son habilitation), paginés avec `cursor`. Demande la relation ReBAC `viewer` et le droit de lecture sur la base.",
    "operationId": "list_documents_v1_knowledge_bases__kb_id__documents_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Nombre maximal de documents dans une page (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Documents V1 Knowledge Bases  Kb Id  Documents Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les documents d'une base",
    "tags": [
     "knowledge"
    ]
   },
   "post": {
    "description": "Ingère un document (201). Demande la relation ReBAC `editor` et le droit d'écriture par l'ACL (403 `not_permitted_acl`).\nÉtapes :\n1. Le contenu (`content` ou `content_base64`, l'un est requis, sinon 400) est analysé par ClamAV et YARA-X (422 `file_blocked`, 503 `file_scan_unavailable`), puis le texte est extrait par Docling (422 `unsupported_media`).\n2. La détection de données personnelles peut relever la classification ; un `source_connector` doit être actif.\n3. Un document de même `uri` remplace la version précédente (version + 1).\n4. Le texte est découpé, vectorisé et enregistré ; le contrôle d'empoisonnement peut mettre le document en quarantaine (`status: quarantined`).\n409 `knowledge_base_reindexing` pendant une réindexation ; 409 `embedding_dimension_mismatch` ; 503 `vector_store_unavailable`. Audité sous `knowledge.document.ingest`.",
    "operationId": "add_document_v1_knowledge_bases__kb_id__documents_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DocIn"
       }
      }
     },
     "description": "Contenu, titre, URI, classification, lecteurs et métadonnées du document.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Add Document V1 Knowledge Bases  Kb Id  Documents Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter un document",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents/uploads": {
   "post": {
    "description": "Contrôle la taille (413 `payload_too_large`, limite `EAI_RAG_UPLOAD_MAX_BYTES`), le `mime_type` déclaré face à la signature réelle des octets (PDF, DOCX, XLSX et PPTX comme paquets ZIP aux bons types de contenu, PNG, JPEG, TIFF ; les formats texte doivent être de l'UTF-8 valide sans NUL) et l'extension de `filename` : 422 `unsupported_format`, `format_mismatch`, `extension_mismatch` ou `archive_refused` (bombe d'archive, archive imbriquée, trop volumineuse une fois décompressée). Les octets sont rangés sous le préfixe de quarantaine du profil de stockage des documents (409 `ingestion_storage_required` sans profil, 503 `ingestion_storage_unavailable`), la version du document est enregistrée avec `document_status` `uploaded` et un travail durable est créé. Répond 202 avec `job_id`, `kb_id`, `document_id`, `document_key`, `version`, `state` (`uploaded`), `detected_format`, `size_bytes` et `sha256`, et un en-tête `Location`. La tâche du worker `knowledge_ingestion` analyse ensuite le fichier par le domaine de sécurité des fichiers (fermé en cas d'échec), l'extrait, le découpe, calcule ses vecteurs et l'indexe. La même `Idempotency-Key` rejoue la même réponse (en-tête `Idempotency-Replayed`) ; avec un autre corps, elle est refusée en 409 `idempotency_conflict`. Demande la relation ReBAC `editor`.",
    "operationId": "upload_document_v1_knowledge_bases__kb_id__documents_uploads_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Clé facultative (200 caractères au plus), unique par tenant : une nouvelle tentative avec la même clé et le même corps rejoue la première réponse.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 200,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UploadIn"
       }
      }
     },
     "description": "Octets du fichier en base64, type de média déclaré, nom de fichier, titre, URI, classification, lecteurs, métadonnées, collection et langue.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Envoyer un document pour une ingestion asynchrone",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents/{doc_id}": {
   "delete": {
    "description": "Supprime le document et ses vecteurs. Demande la relation ReBAC `editor` et le droit d'écriture par l'ACL : `tenant_admin`, une identité listée dans `writers`, ou, si `writers` est vide, `developer` ou `agent` (sinon 403 `not_permitted_acl`). 404 `not_found`. Audité sous `knowledge.document.delete`.",
    "operationId": "delete_document_v1_knowledge_bases__kb_id__documents__doc_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du document.",
      "in": "path",
      "name": "doc_id",
      "required": true,
      "schema": {
       "title": "Doc Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un document",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents/{doc_id}/chunks": {
   "get": {
    "description": "Fragments de la version courante d'un document dans l'ordre de leur position, paginés avec `cursor` : `chunk_id`, `ordinal`, `content` (déchiffré pour les fragments scellés), classification, `sealed`, `active`, `generation`, `fts_config` (configuration de recherche plein texte) et `metadata` (`source_page`, `section_title`, `element_type`, `language`, `char_start`, `char_end`, `bbox`, `document_version`, `content_hash`, `embedding_model`, `token_count`, `structure` avec les marques de découpe des tableaux). Seuls les fragments dont l'appelant passe les lecteurs et la classification sont listés. Un document en quarantaine ou rejeté n'est montré qu'à `tenant_admin`, `security_officer` ou `data_steward` (403 `forbidden`). Demande la relation ReBAC `viewer` ; 404 `not_found` pour un document que l'appelant ne peut pas lire.",
    "operationId": "list_chunks_v1_knowledge_bases__kb_id__documents__doc_id__chunks_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du document courant.",
      "in": "path",
      "name": "doc_id",
      "required": true,
      "schema": {
       "title": "Doc Id",
       "type": "string"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 100,
       "maximum": 500,
       "minimum": 1,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Si faux, `content` vaut null (métadonnées seules). Vrai par défaut.",
      "in": "query",
      "name": "include_content",
      "required": false,
      "schema": {
       "default": true,
       "title": "Include Content",
       "type": "boolean"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Chunks V1 Knowledge Bases  Kb Id  Documents  Doc Id  Chunks Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les fragments d'un document",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents/{doc_id}/review": {
   "post": {
    "description": "Libère (fragments de nouveau recherchables, `status: active`) ou rejette (`status: rejected`) un document en quarantaine. Demande `tenant_admin`, `security_officer` ou `data_steward` ; la politique `knowledge.document.review` est évaluée. 409 `document_not_quarantined` ; 404 `not_found`. La décision est enregistrée et auditée sous `knowledge.document.release` ou `knowledge.document.reject`.",
    "operationId": "review_document_v1_knowledge_bases__kb_id__documents__doc_id__review_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du document.",
      "in": "path",
      "name": "doc_id",
      "required": true,
      "schema": {
       "title": "Doc Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReviewIn"
       }
      }
     },
     "description": "Décision et raison.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Review Document V1 Knowledge Bases  Kb Id  Documents  Doc Id  Review Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Examiner un document en quarantaine",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents/{doc_id}/status": {
   "get": {
    "description": "État d'une version de document : `document_id`, `document_key`, `version`, `version_state` (`current`, `superseded` ou `deleted`), `document_status` (`uploaded`, `active`, `quarantined`, `rejected`, `failed`...), `searchable` (vrai seulement pour la version courante active), `chunks`, `extractor`, et `ingestion`, le dernier travail d'ingestion (`job_id`, `state`, `attempts`, `last_error`, `updated_at`) ou null pour une ingestion synchrone. Demande la relation ReBAC `viewer` ; 404 `not_found` pour un document que l'appelant ne peut pas lire.",
    "operationId": "document_status_v1_knowledge_bases__kb_id__documents__doc_id__status_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de la version du document, le `document_id` de la réponse d'envoi.",
      "in": "path",
      "name": "doc_id",
      "required": true,
      "schema": {
       "title": "Doc Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Document Status V1 Knowledge Bases  Kb Id  Documents  Doc Id  Status Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire l'état d'un document",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/documents/{doc_id}/versions": {
   "get": {
    "description": "Historique des versions d'un document, de la plus récente à la plus ancienne : `document_key` (stable d'une version à l'autre) et `items` avec `document_id`, `version`, `state` (`current`, `superseded` ou `deleted`), `searchable` (seule la version courante active est recherchée), titre, URI, type de média, classification, `content_hash`, taille, nombre de fragments, extracteur, langue, collection, `document_status`, création, `superseded_by` et `superseded_at`, `deleted_at`, et les `artifacts` Docling (nature, profil de stockage, clé d'objet, taille, SHA-256). `doc_id` peut être le document courant ou n'importe quelle version passée. Une version n'est listée que si l'appelant fait partie de ses lecteurs et si sa classification respecte son habilitation. Demande la relation ReBAC `viewer` ; 404 `not_found` sinon.",
    "operationId": "list_versions_v1_knowledge_bases__kb_id__documents__doc_id__versions_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du document ou de l'une de ses versions passées.",
      "in": "path",
      "name": "doc_id",
      "required": true,
      "schema": {
       "title": "Doc Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Versions V1 Knowledge Bases  Kb Id  Documents  Doc Id  Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions d'un document",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/ingestion-jobs/{job_id}": {
   "get": {
    "description": "Un travail d'ingestion asynchrone : `job_id`, `kb_id`, `document_id`, `document_key`, `version`, `state` (`uploaded`, `quarantined`, `scanning`, `rejected`, `extracting`, `normalizing`, `chunking`, `embedding`, `indexing`, `enriching_memory`, `ready`, `partially_ready`, `failed`, `reindexing`, `deleting` ou `deleted`), `attempts`, `lease_owner`, `lease_expires_at`, `next_attempt_at`, `last_error`, titre, nom de fichier, type de média, `detected_format`, `size_bytes`, `sha256`, `scan_id`, `extractor` (celui qui a tourné), `storage` (profil et emplacement `quarantine` ou `permanent`), création, mise à jour et fin, et `history` : chaque transition avec `from_state`, `to_state`, `at` et `detail` (verdict d'analyse, extracteur, nombre de fragments, nouvelle tentative et son délai, échec de l'OCR, mémoire `skipped`). Demande la relation ReBAC `viewer` ; 404 `not_found` pour un travail d'une autre base ou un document que l'appelant ne peut pas lire.",
    "operationId": "get_ingestion_job_v1_knowledge_bases__kb_id__ingestion_jobs__job_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du travail d'ingestion, le `job_id` de la réponse d'envoi.",
      "in": "path",
      "name": "job_id",
      "required": true,
      "schema": {
       "title": "Job Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Ingestion Job V1 Knowledge Bases  Kb Id  Ingestion Jobs  Job Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un travail d'ingestion",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/ingestion-jobs/{job_id}/retry": {
   "post": {
    "description": "Remet en file un travail `failed` : en `extracting` quand son analyse a déjà libéré le fichier (jamais analysé deux fois), sinon en `quarantined` ; les tentatives repartent de zéro. Tout autre état est refusé en 409 `ingestion_job_not_failed` (un fichier `rejected` n'est jamais relancé). Répond le travail avec son historique. Audité `knowledge.ingestion.retry`. Demande la relation ReBAC `editor`.",
    "operationId": "retry_ingestion_job_v1_knowledge_bases__kb_id__ingestion_jobs__job_id__retry_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du travail d'ingestion en échec.",
      "in": "path",
      "name": "job_id",
      "required": true,
      "schema": {
       "title": "Job Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/RetryIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative de la relance."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Retry Ingestion Job V1 Knowledge Bases  Kb Id  Ingestion Jobs  Job Id  Retry Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Relancer un travail d'ingestion en échec",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/quarantine": {
   "get": {
    "description": "Documents isolés par le contrôle d'empoisonnement (file de revue du data steward), du plus ancien au plus récent, 200 au plus, chacun avec les raisons du contrôle dans `quarantine`. Leurs fragments ne sont pas recherchables. Demande `tenant_admin`, `security_officer` ou `data_steward` et la relation ReBAC `viewer`.",
    "operationId": "list_quarantine_v1_knowledge_bases__kb_id__quarantine_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Quarantine V1 Knowledge Bases  Kb Id  Quarantine Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les documents en quarantaine",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/query": {
   "post": {
    "description": "Recherche dans la base et renvoie `results` (fragment, score, risque d'injection, classification, provenance), `citations` et un `lineage_run_id`. Demande la relation ReBAC `can_invoke`.\nÉtapes :\n1. La politique `knowledge.query` est évaluée et une réservation de quota est prise.\n2. La requête est vectorisée avec le modèle de la base ; seuls les fragments dont les lecteurs correspondent aux identités de l'appelant et dont la classification respecte son habilitation sont recherchés.\n3. Les fragments trouvés sont contrôlés contre l'injection de prompt (écartés en mode blocage) et vérifiés par rapport aux sources actives.\n4. `hybrid` recalcule le score des candidats avec le recouvrement de mots-clés ; `rerank` les ordonne avec un modèle de reranking (un refus est signalé dans `warnings`, jamais caché).\nUn appelant hors ACL obtient un résultat vide, audité comme refus. Audité sous `knowledge.query` ; l'usage est mesuré.\n\n**Modes de recherche** (`mode`, par défaut : la stratégie de la base, sinon `vector`) : `vector` ou `dense` (similarité cosinus du vecteur de la requête), `lexical` (recherche plein texte PostgreSQL, chaque fragment analysé avec la configuration de sa langue, `simple` à défaut, termes combinés par OU, classés par `ts_rank_cd`), `hybrid` (fusion par rang réciproque des listes dense et lexicale, `somme(1 / (rrf_k + rang))`, ou l'ancien recalcul pondéré avec `fusion: weighted`).\n**Après la recherche** : les passages quasi identiques sont écartés (`dedupe`), les passages finaux sont relus avec l'ACL, l'habilitation et l'état du document (un passage qui ne passe plus est écarté), les fragments voisins sont ajoutés dans la limite de `token_budget` si `context_window` est supérieur à 0, et `signals.possible_conflicts` liste les passages de documents différents sur le même sujet avec des nombres différents ou une négation d'un seul côté : un signal heuristique pour les comparer, jamais un verdict.\n**Réponse** : chaque résultat porte `rank`, `score` avec sa nature dans `score_kind` (`cosine_similarity`, `lexical_rank`, `rrf`, `weighted_overlap` ; jamais une probabilité), `scores` (similarité et rang denses, score et rang lexicaux, rrf, pertinence du reranking), `metadata` (page, section, type d'élément, langue, positions, boîtes englobantes, version du document, empreinte, modèle d'embedding, nombre de jetons, structure) et `context` (voisins). Sans aucun passage, `no_result` donne `code: no_passage_found` et la raison `reason` (`no_candidates`, `filters_matched_no_document`, `candidates_removed_by_checks`, `acl`). Les diagnostics (durée par étape, nombres de candidats) sont toujours enregistrés sous `diagnostics_id` et renvoyés avec `diagnostics: true`.",
    "operationId": "query_kb_v1_knowledge_bases__kb_id__query_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Si vrai, la réponse ajoute `filters` : les identités, l'habilitation et la génération de vecteurs utilisées, et les filtres appliqués. Faux par défaut.",
      "in": "query",
      "name": "explain",
      "required": false,
      "schema": {
       "default": false,
       "title": "Explain",
       "type": "boolean"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/QueryIn"
       }
      }
     },
     "description": "Texte de la requête et options de recherche.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Query Kb V1 Knowledge Bases  Kb Id  Query Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Interroger une base de connaissances",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/query-diagnostics": {
   "get": {
    "description": "Diagnostics enregistrés des requêtes sur la base, du plus récent au plus ancien, `diagnostics_id` d'une réponse pour en choisir un : mode, stratégie effective, `stages` (nom de l'étape, durée en millisecondes, candidats en entrée et en sortie), `counts` (documents filtrés, candidats denses et lexicaux, fusionnés, contrôlés, dédoublonnés, écartés par la revalidation, finaux, voisins de contexte, cache utilisé) et `results` (rang, fragment, document, scores nommés). Le texte de la requête n'est pas conservé, seulement son SHA-256. Un appelant voit ses propres requêtes ; `tenant_admin`, `security_officer`, `data_steward` et `auditor` voient toutes les requêtes. Demande la relation ReBAC `viewer` ; 404 `not_found` pour un appelant hors ACL ou un `diagnostics_id` qu'il ne peut pas voir.",
    "operationId": "list_diagnostics_v1_knowledge_bases__kb_id__query_diagnostics_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de diagnostics enregistrés, le `diagnostics_id` d'une réponse de requête. Facultatif.",
      "in": "query",
      "name": "diagnostics_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 64,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Diagnostics Id"
      }
     },
     {
      "description": "Nombre maximal de diagnostics renvoyés (1 à 100, 20 par défaut).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 20,
       "maximum": 100,
       "minimum": 1,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Diagnostics V1 Knowledge Bases  Kb Id  Query Diagnostics Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les diagnostics des requêtes",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/reindex": {
   "post": {
    "description": "Met en file le passage de la base à un autre modèle d'embedding (travail `embeddings_reindex`) et répond 202 avec le travail. Le modèle doit être approuvé, avoir la capacité embeddings et accepter la classification la plus haute de la base et de ses documents (422 sinon). Les nouveaux vecteurs forment une nouvelle génération ; les deux espaces ne sont jamais mélangés. Demande `tenant_admin` ou `developer`, la relation ReBAC `owner` et le droit d'écriture. 409 `knowledge_base_reindexing` si un travail est déjà en cours. Audité sous `knowledge_base.reindex.request`.",
    "operationId": "reindex_kb_v1_knowledge_bases__kb_id__reindex_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReindexIn"
       }
      }
     },
     "description": "Modèle d'embedding et dimension visés.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reindex Kb V1 Knowledge Bases  Kb Id  Reindex Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réindexer avec un autre modèle d'embedding",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/reindex-jobs": {
   "get": {
    "description": "Travaux de réindexation de la base : statut, modèle, dimensions et génération source et cible, progression en documents et fragments, tentatives, erreur et dates. Demande la relation ReBAC `viewer` et le droit de lecture.",
    "operationId": "list_reindex_jobs_v1_knowledge_bases__kb_id__reindex_jobs_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Reindex Jobs V1 Knowledge Bases  Kb Id  Reindex Jobs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les travaux de réindexation",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/knowledge-bases/{kb_id}/retrieval": {
   "put": {
    "description": "Remplace la stratégie de recherche de la base : mode par défaut, fusion et son `rrf_k`, candidats par résultat, reranking et son modèle, déduplication, fenêtre de contexte et budget de jetons, signal de conflit. Un champ omis prend sa valeur par défaut ; les options d'une requête remplacent la stratégie pour cette requête. Demande la relation ReBAC `owner` et le droit d'écriture par l'ACL (403 `not_permitted_acl`) ; 404 `not_found` pour un appelant hors ACL. Audité sous `knowledge_base.retrieval.update` ; le cache de recherche est invalidé. Renvoie la base avec sa `retrieval`.",
    "operationId": "put_retrieval_v1_knowledge_bases__kb_id__retrieval_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la base de connaissances.",
      "in": "path",
      "name": "kb_id",
      "required": true,
      "schema": {
       "title": "Kb Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RetrievalIn"
       }
      }
     },
     "description": "La stratégie de recherche.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Retrieval V1 Knowledge Bases  Kb Id  Retrieval Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir la stratégie de recherche",
    "tags": [
     "knowledge"
    ]
   }
  },
  "/v1/license": {
   "get": {
    "description": "État non sensible de la licence de l'installation : état, édition, produit, durée, dates de validité et de grâce, jours restants, renouvellement planifié ou non, actions restreintes à cet instant, fonctions qu'une licence ne restreint jamais, et bandeau affiché par le portail (niveau `none`, `info`, `warning` ou `critical`, avec un message en anglais et en français). Aucun identifiant client, aucune référence de support ni aucun jeton n'est renvoyé. Tout principal authentifié peut l'appeler, sous réserve de la décision de politique `license.read` (403 `policy_denied`).",
    "operationId": "license_status_v1_license_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response License Status V1 License Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire l'état de la licence",
    "tags": [
     "licensing"
    ]
   }
  },
  "/v1/license/activations": {
   "post": {
    "description": "Vérifie localement une licence signée puis l'enregistre. Demande le rôle `platform_admin` et la décision de politique `license.activate`.\n\nÉtapes :\n\n1. Le jeton est contrôlé : JWS compact, en-tête `{alg: EdDSA, kid, typ}`, signature d'une clé d'éditeur de confiance, contenu JSON canonique conforme au contrat License Entitlement v1, durée de 12 ou 36 mois avec `valid_until` égal à `valid_from` plus la durée, et aucun champ de prix, de siège, de quota, de limite ou de compteur.\n2. L'environnement de l'installation doit figurer dans la licence (422 `license_environment_not_included`).\n3. La première licence lie le groupe d'installations ; une licence d'un autre groupe répond 409 `license_group_mismatch`.\n4. La licence, son contrat de support, un événement d'historique et un événement d'audit `license.activate` sont écrits dans une seule transaction.\n\nRenvoyer le même jeton répond `activation: already_active` ; un autre jeton portant le même identifiant de licence répond 409 `license_conflict`. Les erreurs de vérification répondent 422 avec un code stable : `license_invalid_token`, `license_unknown_key`, `license_signature_invalid`, `license_invalid_payload`, `license_invalid_term`, `license_forbidden_field`. Chaque activation refusée est conservée dans l'historique et la chaîne d'audit.",
    "operationId": "activate_license_v1_license_activations_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__licensing__routes__ActivationIn"
       }
      }
     },
     "description": "La licence signée reçue de l'éditeur.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Activate License V1 License Activations Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Activer une licence",
    "tags": [
     "licensing"
    ]
   }
  },
  "/v1/license/history": {
   "get": {
    "description": "Historique des licences de l'installation, de l'événement le plus récent au plus ancien : activations, activations refusées, actions refusées après la grâce et avis d'expiration. La réponse donne aussi l'identité de l'installation (identifiant, groupe d'installations, date et auteur de la liaison), chaque licence enregistrée avec son identifiant client, sa référence de support et ses environnements, ainsi que les contrats de support. Demande le rôle `platform_admin` (403 `forbidden`) et la décision de politique `license.history.read`. Un curseur mal formé répond 400 `invalid_cursor`.",
    "operationId": "license_history_v1_license_history_get",
    "parameters": [
     {
      "description": "Nombre maximal d'événements d'historique dans une page : 50 par défaut, plafonné à 500 ; une valeur inférieure à 1 répond 400 `invalid_request`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire les événements les plus récents.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response License History V1 License History Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister l'historique des licences",
    "tags": [
     "licensing"
    ]
   }
  },
  "/v1/lineage/datasets/{dataset_id}": {
   "get": {
    "description": "Renvoie le nœud du jeu de données, les traitements qui le produisent (`produced_by`), ceux qui le consomment (`consumed_by`) et ses graphes amont et aval jusqu'à `depth` sauts. Ouvert à tout membre du tenant. Répond 404 `not_found` pour un jeu de données inconnu.",
    "operationId": "dataset_v1_lineage_datasets__dataset_id__get",
    "parameters": [
     {
      "description": "Clé du jeu de données, `dataset:<namespace>:<name>` ou `<namespace>:<name>` (le préfixe est ajouté), encodée pour l'URL.",
      "in": "path",
      "name": "dataset_id",
      "required": true,
      "schema": {
       "title": "Dataset Id",
       "type": "string"
      }
     },
     {
      "description": "Nombre de sauts suivis en amont et en aval (2 par défaut).",
      "in": "query",
      "name": "depth",
      "required": false,
      "schema": {
       "default": 2,
       "title": "Depth",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Dataset V1 Lineage Datasets  Dataset Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le lignage d'un jeu de données",
    "tags": [
     "lineage"
    ]
   }
  },
  "/v1/lineage/events": {
   "post": {
    "description": "Accepte un RunEvent OpenLineage ou `{\"events\": [RunEvent, ...]}`, en une transaction. Chaque événement exige `run.runId`, `job.namespace` et `job.name` ; `eventType` vaut `START`, `RUNNING`, `COMPLETE`, `ABORT`, `FAIL` ou `OTHER` (par défaut ; toute autre valeur répond 400) ; `eventTime` vaut par défaut l'heure de réception.\n\nPour chaque événement, la plateforme enregistre l'événement brut, crée ou met à jour les nœuds de traitement, d'exécution et de jeux de données et les relie (`instance_of`, `input_to`, `produces`). Un événement invalide répond 422 `validation_error` et rien n'est enregistré. La réponse (201) donne `accepted` et, pour chaque événement, `run_id`, `job` et `event_type`. Écrit l'événement d'audit `lineage.ingest`.",
    "operationId": "post_events_v1_lineage_events_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Un RunEvent OpenLineage, ou un objet `events` qui en contient une liste.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Post Events V1 Lineage Events Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Envoyer des événements OpenLineage",
    "tags": [
     "lineage"
    ]
   }
  },
  "/v1/lineage/graph": {
   "get": {
    "description": "Renvoie `nodes` et `edges`. Avec `node`, le graphe est parcouru depuis ce nœud dans la direction `direction` jusqu'à `depth` sauts (404 `not_found` pour un nœud inconnu). Sans `node`, renvoie jusqu'à `limit` nœuds de traitements et de jeux de données triés par clé, et les arêtes qui les relient. Une `direction` autre que `both`, `upstream` ou `downstream` répond 400 `invalid_request`. Ouvert à tout membre du tenant.",
    "operationId": "graph_v1_lineage_graph_get",
    "parameters": [
     {
      "description": "Clé du nœud de départ, comme `dataset:warehouse:sales` ou `job:etl:daily`, encodée pour l'URL. Omettez-la pour tout le graphe.",
      "in": "query",
      "name": "node",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Node"
      }
     },
     {
      "description": "Nombre de sauts suivis depuis `node`, de 1 à 10 (3 par défaut).",
      "in": "query",
      "name": "depth",
      "required": false,
      "schema": {
       "default": 3,
       "title": "Depth",
       "type": "integer"
      }
     },
     {
      "description": "`upstream`, `downstream` ou `both` (par défaut).",
      "in": "query",
      "name": "direction",
      "required": false,
      "schema": {
       "default": "both",
       "title": "Direction",
       "type": "string"
      }
     },
     {
      "description": "Nombre maximal de nœuds sans `node` (500 par défaut, 5000 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 500,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Graph V1 Lineage Graph Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le graphe de lignage",
    "tags": [
     "lineage"
    ]
   }
  },
  "/v1/litellm/config": {
   "get": {
    "description": "Configuration du proxy LiteLLM générée à partir des modèles visibles et des fournisseurs activés (un fournisseur du tenant l'emporte sur un fournisseur partagé du même type). Elle ne contient jamais de valeur secrète : les identifiants sont des références `os.environ/<NOM>` résolues par le déploiement LiteLLM. Demande `tenant_admin`, `platform_admin`, `ai_platform_engineer`, `security_officer` ou `auditor`.",
    "operationId": "litellm_get_config_v1_litellm_config_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Générer la configuration LiteLLM",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/litellm/sync": {
   "post": {
    "description": "Fait en sorte que le proxy LiteLLM contienne exactement la liste de modèles générée : les modèles créés par le Control Plane et devenus inutiles sont retirés, ceux qui manquent sont ajoutés ; les modèles ajoutés à la main dans LiteLLM ne sont pas touchés. Sans `EAI_LITELLM_URL` rien n'est envoyé (`mode: in_process`). Une erreur de transport répond 502 `upstream_error`. Audité (`litellm.sync`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "litellm_sync_v1_litellm_sync_post",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Envoyer la configuration à LiteLLM",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/llm/categories": {
   "get": {
    "description": "Catégories de modèles visibles par l'appelant (prédéfinies, partagées et propres au tenant) avec leur libellé et leur description en anglais et en français, leur icône, leur ordre et le nombre de modèles qui les utilisent. Une seule page. Demande un rôle de lecteur.",
    "operationId": "categories_list_v1_llm_categories_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les catégories de modèles",
    "tags": [
     "llm"
    ]
   },
   "post": {
    "description": "Crée une catégorie pour le tenant, ou une catégorie partagée avec `scope: platform` (`platform_admin` seulement, 403 `platform_only`), et répond 201. Un slug déjà utilisé répond 409 `category_exists` ; des champs invalides 422 `parameter_invalid`. Audité. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "categories_create_v1_llm_categories_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`slug` (lettres minuscules, chiffres, tirets et soulignés, de 1 à 40 caractères, obligatoire), `label` (texte ou `{en, fr}`, 80 caractères au plus, obligatoire), `description` (500 caractères au plus), `icon`, `sort_order` (de 0 à 10000, 100 par défaut), `scope` (`platform` pour une catégorie partagée).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une catégorie de modèles",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/llm/categories/{slug}": {
   "delete": {
    "description": "Supprime une catégorie créée par le tenant ou la plateforme et répond 204. Une catégorie prédéfinie ne peut pas être supprimée (409 `category_builtin`), ni une catégorie encore utilisée par un modèle (409 `category_in_use`) ; une catégorie partagée demande `platform_admin` (403 `platform_only`). Audité. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "categories_delete_v1_llm_categories__slug__delete",
    "parameters": [
     {
      "description": "Slug de la catégorie : lettres minuscules, chiffres, tirets et soulignés, de 1 à 40 caractères.",
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
       "title": "Slug",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une catégorie de modèles",
    "tags": [
     "llm"
    ]
   },
   "get": {
    "description": "Une catégorie de modèles avec son nombre de modèles. Slug inconnu : 404 `category_not_found`. Demande un rôle de lecteur.",
    "operationId": "categories_get_v1_llm_categories__slug__get",
    "parameters": [
     {
      "description": "Slug de la catégorie.",
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
       "title": "Slug",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une catégorie de modèles",
    "tags": [
     "llm"
    ]
   },
   "patch": {
    "description": "Modifie le libellé, la description, l'icône ou l'ordre d'une catégorie ; le slug ne change jamais. Une catégorie prédéfinie répond 409 `category_builtin` ; une catégorie partagée demande `platform_admin`. Des valeurs invalides répondent 422 `parameter_invalid`. Audité. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "categories_patch_v1_llm_categories__slug__patch",
    "parameters": [
     {
      "description": "Slug de la catégorie.",
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
       "title": "Slug",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Champs à modifier : `label` et `description` (un texte, ou `{en, fr}` ; libellé de 80 caractères au plus, description de 500), `icon` (nom en minuscules de 1 à 48 caractères, ou null), `sort_order` (de 0 à 10000).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une catégorie de modèles",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/llm/isolation": {
   "get": {
    "description": "Mode d'isolation effectif du tenant ou d'un de ses projets : le réglage du projet s'il existe, sinon celui du tenant, sinon `connected`, avec les hôtes internes déclarés. Demande un principal de tenant (400 `tenant_required`) avec `tenant_admin`, `platform_admin`, `security_officer`, `finops`, `developer`, `auditor` ou `viewer`.",
    "operationId": "isolation_get_v1_llm_isolation_get",
    "parameters": [
     {
      "description": "Projet dont le mode est lu. Omettez-le pour le mode du tenant.",
      "in": "query",
      "name": "project_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le mode d'isolation",
    "tags": [
     "llm",
     "llm"
    ]
   },
   "put": {
    "description": "Définit le mode d'isolation du tenant ou d'un projet. En `local_only` ou `air_gapped`, seuls les fournisseurs locaux (vLLM, Ollama, LM Studio) et les hôtes internes déclarés sont joignables : une route externe est refusée au contrôle préalable (403 `isolation_external_route`) et l'appel sortant est vérifié à nouveau (403 `isolation_egress_denied`). Audité (`llm.isolation.set`). Demande un principal de tenant avec `tenant_admin`, `platform_admin` ou `security_officer`.",
    "operationId": "isolation_put_v1_llm_isolation_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`mode` (`connected`, `local_only` ou `air_gapped`), `project_id` facultatif (vide pour tout le tenant) et `internal_hosts` (noms d'hôtes, éventuellement avec un port, en lettres minuscules, chiffres, points et tirets).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir le mode d'isolation",
    "tags": [
     "llm",
     "llm"
    ]
   }
  },
  "/v1/llm/management/status": {
   "get": {
    "description": "Configuration d'administration LiteLLM sans aucune valeur secrète : configurée ou non, adresse d'administration, présence de la clé maîtresse et origine de sa lecture (paramètre, référence d'environnement ou référence OpenBao), paramètres encore manquants, et sonde de vie (`reachable`) quand elle est configurée. `error_code` vaut `litellm_not_configured`, `litellm_unreachable` ou null. Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "management_status_v1_llm_management_status_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Vérifier la connexion d'administration LiteLLM",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects": {
   "get": {
    "description": "Projets LLM du tenant par nom, chacun avec ses modèles effectifs (identifiants explicites plus les modèles du registre de ses catégories), son quota, son équipe LiteLLM et son état de synchronisation, ses quotas FinOps et son nombre de clés non révoquées. Une seule page (`next_cursor` vaut toujours null). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "projects_list_v1_llm_projects_get",
    "parameters": [
     {
      "description": "Seulement les projets de ce statut : `active`, `suspended` ou `archived`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "active",
          "suspended",
          "archived"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les projets LLM",
    "tags": [
     "llm-management"
    ]
   },
   "post": {
    "description": "Crée un projet LLM dans le tenant de l'appelant et répond 201 avec le projet. Demande `tenant_admin` ou `platform_admin`.\n\nÉtapes :\n\n1. Le projet du tenant indiqué dans `project_id` est résolu (404 `tenant_project_not_found`) et le nom doit être libre dans le tenant (409 `llm_project_exists`).\n2. Le projet et son quota sont enregistrés, les quotas FinOps sont créés s'il est lié et actif, et la création est auditée (`llm_project.create`).\n3. Le projet est envoyé dans LiteLLM sous forme d'équipe ; un échec ne fait pas échouer la requête mais apparaît dans `sync_status` (`pending` ou `failed`) et peut être relancé avec `POST .../sync`.",
    "operationId": "projects_create_v1_llm_projects_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__llm_management__ProjectIn"
       }
      }
     },
     "description": "Le nouveau projet LLM. Les champs inconnus sont refusés.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un projet LLM",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}": {
   "delete": {
    "description": "Supprime le projet, son équipe LiteLLM et ses quotas FinOps, et répond 204. Refusé avec 409 `llm_project_has_keys` tant que le projet a des clés non révoquées. Une erreur de LiteLLM pendant la suppression de l'équipe arrête la suppression. Audité (`llm_project.delete`). Demande `tenant_admin` ou `platform_admin` ; identifiant inconnu : 404 `llm_project_not_found`.",
    "operationId": "projects_delete_v1_llm_projects__project_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un projet LLM",
    "tags": [
     "llm-management"
    ]
   },
   "get": {
    "description": "Un projet LLM avec ses modèles effectifs, son quota, son équipe LiteLLM, son état et son erreur de synchronisation, ses quotas FinOps et son nombre de clés. Demande `tenant_admin` ou `platform_admin` ; identifiant inconnu : 404 `llm_project_not_found`.",
    "operationId": "projects_get_v1_llm_projects__project_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un projet LLM",
    "tags": [
     "llm-management"
    ]
   },
   "patch": {
    "description": "Modifie les champs fournis du projet, reconstruit ses quotas FinOps (supprimés quand le projet n'est plus actif ou plus lié) et le renvoie dans LiteLLM ; un échec de LiteLLM ne fait pas échouer la requête mais apparaît dans `sync_status` et `sync_error`. Un autre projet portant le nouveau nom répond 409 `llm_project_exists` ; un projet de tenant inconnu 404 `tenant_project_not_found`. Audité (`llm_project.update`). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "projects_update_v1_llm_projects__project_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__llm_management__ProjectPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur. Le quota se modifie avec `PUT .../quota`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un projet LLM",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}/keys": {
   "get": {
    "description": "Clés d'un projet, de la plus récente à la plus ancienne, avec leur alias, leur préfixe d'affichage, leurs modèles, leurs limites, leur statut, leur dépense et leur échéance ; jamais la clé en clair. Avec `refresh`, la dépense de 50 clés non révoquées au plus est relue dans LiteLLM et enregistrée ; `usage_source` vaut alors `litellm`, ou `stored` avec `litellm_error` si LiteLLM n'a pas répondu. Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "keys_list_v1_llm_projects__project_id__keys_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Relit la dépense courante dans LiteLLM avant de répondre (vrai par défaut) ; faux renvoie seulement les valeurs enregistrées.",
      "in": "query",
      "name": "refresh",
      "required": false,
      "schema": {
       "default": true,
       "title": "Refresh",
       "type": "boolean"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les clés d'un projet",
    "tags": [
     "llm-management"
    ]
   },
   "post": {
    "description": "Génère une clé LiteLLM pour le projet et répond 201 avec l'enregistrement de la clé plus `key` (la clé en clair) et `key_shown_once: true`, avec `Cache-Control: no-store`. La clé en clair ne figure que dans cette réponse : elle n'est jamais enregistrée ni auditée. Demande `tenant_admin` ou `platform_admin`.\n\nÉtapes :\n\n1. Le projet doit être `active` (409 `llm_project_inactive`).\n2. Les limites sont contrôlées par rapport au projet (422 `llm_key_limit_exceeds_project`, 422 `llm_key_model_not_allowed`) ; sans modèles, la clé reçoit tous les modèles d'un projet restreint. L'alias doit être libre (409 `invalid_field`) ; sans alias, un alias est généré.\n3. L'équipe LiteLLM est d'abord créée si le projet n'en a pas, puis LiteLLM génère la clé.\n4. L'empreinte, l'alias, le préfixe de 7 caractères, les limites et l'échéance sont enregistrés et audités (`llm_key.create`). Si l'enregistrement échoue, la clé est supprimée dans LiteLLM pour qu'aucune clé inconnue ne reste utilisable.",
    "operationId": "keys_create_v1_llm_projects__project_id__keys_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__llm_management__KeyIn"
       }
      }
     },
     "description": "Nom, alias, modèles et limites de la nouvelle clé. Les champs inconnus sont refusés.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Générer une clé de projet",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}/keys/{key_id}": {
   "delete": {
    "description": "Révoque une clé : elle est supprimée dans LiteLLM et conservée ici au statut `revoked` avec sa date de révocation, pour l'historique. Une clé révoquée ne peut plus être modifiée (409 `llm_key_revoked`). Audité (`llm_key.revoke`). Demande `tenant_admin` ou `platform_admin` ; clé inconnue : 404 `llm_key_not_found`.",
    "operationId": "keys_revoke_v1_llm_projects__project_id__keys__key_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de la clé dans la plateforme (pas le jeton LiteLLM).",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer une clé de projet",
    "tags": [
     "llm-management"
    ]
   },
   "patch": {
    "description": "Modifie le nom, les modèles et les limites d'une clé, d'abord dans LiteLLM puis ici. Les limites fusionnées sont contrôlées par rapport au projet : une limite plus large répond 422 `llm_key_limit_exceeds_project`, un modèle que le projet n'autorise pas 422 `llm_key_model_not_allowed`. Une clé révoquée répond 409 `llm_key_revoked`. Audité (`llm_key.update`). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "keys_update_v1_llm_projects__project_id__keys__key_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de la clé dans la plateforme.",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/KeyPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur, une limite envoyée à null est supprimée.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une clé de projet",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}/keys/{key_id}/block": {
   "post": {
    "description": "Bloque la clé dans LiteLLM : les appels qui l'utilisent sont refusés jusqu'à son déblocage. Le statut devient `blocked`. Une clé révoquée répond 409 `llm_key_revoked`. Audité (`llm_key.block`). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "keys_block_v1_llm_projects__project_id__keys__key_id__block_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de la clé dans la plateforme.",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Bloquer une clé de projet",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}/keys/{key_id}/unblock": {
   "post": {
    "description": "Débloque la clé dans LiteLLM ; le statut redevient `active`. Une clé révoquée répond 409 `llm_key_revoked`. Audité (`llm_key.unblock`). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "keys_unblock_v1_llm_projects__project_id__keys__key_id__unblock_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de la clé dans la plateforme.",
      "in": "path",
      "name": "key_id",
      "required": true,
      "schema": {
       "title": "Key Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Débloquer une clé de projet",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}/quota": {
   "put": {
    "description": "Remplace tout le quota du projet (les champs omis prennent leur valeur par défaut), reconstruit ses quotas FinOps et l'envoie dans LiteLLM. Avec `hard_stop` à faux, LiteLLM ne reçoit aucun budget maximal (alerte seulement) et les quotas FinOps sont souples. Un quota invalide répond 422 `invalid_quota`. Audité (`llm_project.quota`). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "projects_quota_v1_llm_projects__project_id__quota_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__llm_management__QuotaIn"
       }
      }
     },
     "description": "Le nouveau quota complet du projet.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir le quota d'un projet",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/llm/projects/{project_id}/sync": {
   "post": {
    "description": "Renvoie le projet dans LiteLLM (équipe créée ou mise à jour) et répond le projet avec son nouveau `sync_status`. Contrairement aux autres modifications, un échec est renvoyé : 503 `litellm_not_configured`, 502 `litellm_unreachable` ou 502 `litellm_rejected`. Audité (`llm_project.sync`). Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "projects_sync_v1_llm_projects__project_id__sync_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet LLM.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Renvoyer un projet dans LiteLLM",
    "tags": [
     "llm-management"
    ]
   }
  },
  "/v1/mandates": {
   "get": {
    "description": "Liste les mandats du tenant, les plus récents d'abord, éventuellement pour un agent, avec leur statut effectif (`pending`, `approved`, `rejected`, `revoked` ou `expired`). Ouvert à tout membre du tenant.",
    "operationId": "list_mandates_v1_mandates_get",
    "parameters": [
     {
      "description": "Uniquement les mandats de cet agent (nom).",
      "in": "query",
      "name": "agent",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent"
      }
     },
     {
      "description": "Nombre maximal de mandats (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Mandates V1 Mandates Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les mandats",
    "tags": [
     "delegation"
    ]
   },
   "post": {
    "description": "Demande un mandat pour qu'un agent (éventuellement l'une de ses planifications) s'exécute de façon autonome sous une identité de service. Exige `tenant_admin`, `developer` ou `security_officer`.\n\nContrôles : identité de service active (404 `service_identity_not_found`, 409 `service_identity_disabled`) ; portées couvertes par celles de l'identité (403 `delegation_scope_exceeds`) ; classification dans la limite de son habilitation (403 `delegation_classification_exceeds`) ; planification de cet agent (404 `schedule_not_found`) ; durée dans la limite de la plateforme (365 jours par défaut, 400 `mandate_ttl_exceeded`).\n\nUne demande de validation est créée (action `mandate`, valideurs `tenant_admin` ou `security_officer`, demandeur exclu). Écrit l'événement d'audit `mandate.request`, puis `mandate.approve` ou `mandate.reject`. Répond 201 avec le mandat en attente.",
    "operationId": "create_mandate_v1_mandates_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MandateIn"
       }
      }
     },
     "description": "Agent, identité de service, planification, droits, finalité et durée du mandat.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Mandate V1 Mandates Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander un mandat pour un agent",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/mandates/{mandate_id}": {
   "get": {
    "description": "Renvoie un mandat : agent, planification, identité de service, portées, audience, ressources, plafond de classification, profondeur, finalité, statut, validation et expiration. Ouvert à tout membre du tenant. Répond 404 `mandate_not_found`.",
    "operationId": "get_mandate_v1_mandates__mandate_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du mandat.",
      "in": "path",
      "name": "mandate_id",
      "required": true,
      "schema": {
       "title": "Mandate Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Mandate V1 Mandates  Mandate Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un mandat",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/mandates/{mandate_id}/revoke": {
   "post": {
    "description": "Révoque le mandat et les délégations qui en découlent : la prochaine exécution planifiée de l'agent est refusée (`mandate_required`). Autorisé au demandeur, à un `tenant_admin` ou à un `security_officer` (403 `forbidden`). Un mandat révoqué ou refusé répond 409 `mandate_closed`. Écrit l'événement d'audit `mandate.revoke`.",
    "operationId": "revoke_mandate_v1_mandates__mandate_id__revoke_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du mandat.",
      "in": "path",
      "name": "mandate_id",
      "required": true,
      "schema": {
       "title": "Mandate Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/RevokeIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Mandate V1 Mandates  Mandate Id  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer un mandat",
    "tags": [
     "delegation"
    ]
   }
  },
  "/v1/manifests/apply": {
   "post": {
    "description": "Crée ou met à jour l'objet décrit par un manifeste, en une transaction. Exige `tenant_admin` ou `developer`.\n\n1. Le manifeste est validé avec le contrat de son `kind` (422 `unknown_kind`, ou 422 `invalid_manifest` avec les erreurs).\n2. `metadata.tenant`, s'il est donné, doit être l'identifiant court du tenant de l'appelant (403 `tenant_mismatch`).\n3. La politique décide sur `manifest.apply` (403 `policy_denied` en cas de refus).\n4. Le document est enregistré et le registre du type l'applique.\n\nLa réponse donne `kind`, `name`, `version` (`1.0.0` par défaut), `status` (`created`, `updated` ou le statut posé par le registre) et les champs renvoyés par le registre, comme `id`. Écrit l'événement d'audit `manifest.apply`.",
    "operationId": "apply_manifest_v1_manifests_apply_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Document de manifeste avec `kind`, `metadata` et la spécification de l'objet.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Apply Manifest V1 Manifests Apply Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Appliquer un manifeste",
    "tags": [
     "contracts"
    ]
   }
  },
  "/v1/manifests/{kind}/{name}": {
   "get": {
    "description": "Renvoie le manifeste courant d'un objet du tenant : construit par le registre du type quand il sait exporter, sinon le dernier document appliqué. Ouvert à tout membre du tenant. Un type inconnu répond 422 `unknown_kind` (avec les types pris en charge) ; un objet inconnu 404 `not_found`.",
    "operationId": "export_manifest_v1_manifests__kind___name__get",
    "parameters": [
     {
      "description": "Type de manifeste, sans tenir compte de la casse : `Agent`, `Model`, `Tool`, `Skill`, `Plugin`, `Connector` ou `Channel`.",
      "in": "path",
      "name": "kind",
      "required": true,
      "schema": {
       "title": "Kind",
       "type": "string"
      }
     },
     {
      "description": "Nom de l'objet, le `metadata.name` de son manifeste.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Export Manifest V1 Manifests  Kind   Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exporter un manifeste",
    "tags": [
     "contracts"
    ]
   }
  },
  "/v1/marketplace/changes": {
   "get": {
    "description": "Demandes de changement du tenant, de la plus récente à la plus ancienne, 200 au plus, filtrables par type, référence et étape. Tout principal du tenant peut l'appeler.",
    "operationId": "list_changes_v1_marketplace_changes_get",
    "parameters": [
     {
      "description": "Seulement ce type : `prompt`, `skill`, `connector`, `rule`, `model`, `policy`, `tool`, `agent` ou `pack`.",
      "in": "query",
      "name": "kind",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Kind"
      }
     },
     {
      "description": "Seulement les changements de ce nom d'objet.",
      "in": "query",
      "name": "ref",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Ref"
      }
     },
     {
      "description": "Seulement les changements à cette étape : `draft`, `test`, `security_review`, `business_approval`, `staging`, `production`, `monitoring` ou `rejected`.",
      "in": "query",
      "name": "stage",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Stage"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Changes V1 Marketplace Changes Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les demandes de changement",
    "tags": [
     "marketplace"
    ]
   },
   "post": {
    "description": "Ouvre un changement d'un prompt, skill, connecteur, règle, modèle, politique, outil, agent ou pack à l'étape `draft`. Demande `tenant_admin` ou `developer` ; la politique `change.create` est évaluée. Audité sous `change.create`.",
    "operationId": "create_change_v1_marketplace_changes_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ChangeIn"
       }
      }
     },
     "description": "Type, nom d'objet, version et titre du changement.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Change V1 Marketplace Changes Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ouvrir une demande de changement",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/changes/{change_id}": {
   "get": {
    "description": "Renvoie le changement avec son étape, ses preuves, son approbation en attente et son `history` (une entrée par transition avec acteur et date). 404 `change_not_found`.",
    "operationId": "get_change_v1_marketplace_changes__change_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de changement.",
      "in": "path",
      "name": "change_id",
      "required": true,
      "schema": {
       "title": "Change Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Change V1 Marketplace Changes  Change Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une demande de changement",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/changes/{change_id}/advance": {
   "post": {
    "description": "Fait passer le changement à l'étape suivante. Demande `tenant_admin`, `developer` ou `security_officer`. Contrôles par étape visée :\n1. `test` : seul l'auteur (ou `tenant_admin`) quitte `draft` (403 `change_not_author`).\n2. `security_review` : `evidence.tests` doit réussir (`passed` > 0, aucun `failed`, aucune `errors`), sinon 422 `change_evidence_missing`.\n3. `business_approval` : approbation `change.security_review` par un `security_officer`.\n4. `staging` : approbation `change.business_approval` par un `tenant_admin`.\n5. `production` : `staging_smoke` réussi, puis approbation `change.production` avec un quorum de deux.\n6. `monitoring` : `production_smoke` réussi.\nSans `approval_id`, une étape soumise à approbation répond 202 avec l'approbation à obtenir ; rappelez avec l'identifiant approuvé pour obtenir 200. L'auteur n'approuve jamais. Un changement clos répond 409 `change_closed`.",
    "operationId": "advance_change_v1_marketplace_changes__change_id__advance_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de changement.",
      "in": "path",
      "name": "change_id",
      "required": true,
      "schema": {
       "title": "Change Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AdvanceIn"
       }
      }
     },
     "description": "Preuves à ajouter et demande approuvée, quand l'étape en exige une.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Advance Change V1 Marketplace Changes  Change Id  Advance Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Faire avancer un changement",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/changes/{change_id}/reject": {
   "post": {
    "description": "Clôt le changement à l'étape `rejected` depuis n'importe quelle étape ouverte, la raison étant gardée dans `history`. Demande `tenant_admin` ou `security_officer`. 409 `change_closed` s'il est déjà clos. Audité sous `change.reject`.",
    "operationId": "reject_change_v1_marketplace_changes__change_id__reject_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de changement.",
      "in": "path",
      "name": "change_id",
      "required": true,
      "schema": {
       "title": "Change Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RejectIn"
       }
      }
     },
     "description": "Raison du rejet.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reject Change V1 Marketplace Changes  Change Id  Reject Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rejeter une demande de changement",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations": {
   "get": {
    "description": "Installations du tenant, de la plus ancienne à la plus récente, chacune avec paquet, version et ressources créées. Une seule page.",
    "operationId": "list_installations_v1_marketplace_installations_get",
    "parameters": [
     {
      "description": "Seulement les installations de cet environnement : `dev`, `test`, `staging` ou `prod`.",
      "in": "query",
      "name": "environment",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "dev",
          "test",
          "staging",
          "prod"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Environment"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Installations V1 Marketplace Installations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les installations",
    "tags": [
     "marketplace"
    ]
   },
   "post": {
    "description": "Ouvre une demande d'installation et répond 202 avec l'installation, l'`approval_id`, le `quorum` et le rapport de preflight. Demande `tenant_admin`.\nÉtapes :\n1. Le preflight s'exécute (voir `POST /v1/marketplace/preflight`) ; un échec répond 403 `permission_unsafe`, 422 `config_invalid`, 422 `permission_not_requested`, 409 `package_suspended` ou 422 `preflight_failed`.\n2. Une approbation `marketplace.install` est ouverte pour `tenant_admin` ou `security_officer` : quorum 1 par défaut, au moins 2 quand une permission sensible est accordée ou que l'environnement est `prod`.\n3. Une fois approuvée, appelez `POST .../installations/{id}/apply`.\n409 `installation_exists` quand le paquet est déjà installé ou suspendu dans cet environnement.",
    "operationId": "request_install_v1_marketplace_installations_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__marketplace__routes__InstallIn"
       }
      }
     },
     "description": "Paquet, version, environnement, permissions accordées, configuration et références de secrets.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Install V1 Marketplace Installations Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander l'installation d'un pack",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}": {
   "get": {
    "description": "Renvoie l'installation : statut, paquet et version, permissions accordées et suspendues, configuration, derniers résultats de preflight et de smoke test, objets et ressources créés. 404 `installation_not_found`.",
    "operationId": "get_installation_v1_marketplace_installations__installation_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Installation V1 Marketplace Installations  Installation Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une installation",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/apply": {
   "post": {
    "description": "Exécute l'installation ou la mise à niveau en attente une fois son approbation accordée : la politique et le preflight sont revérifiés, les objets du pack sont créés dans un point de sauvegarde, les objets de l'ancienne version qui n'en font plus partie sont retirés et un smoke test s'exécute. Un smoke test en échec annule tout et répond 422 `smoke_failed`. Demande `tenant_admin`. 409 `installation_state` quand rien n'est en attente. Audité sous `marketplace.install` ou `marketplace.upgrade`.",
    "operationId": "apply_v1_marketplace_installations__installation_id__apply_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Apply V1 Marketplace Installations  Installation Id  Apply Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Appliquer une installation approuvée",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/authorize": {
   "post": {
    "description": "Contrôle de permission à l'exécution utilisé par les serveurs MCP et les connecteurs du pack : permis seulement si l'installation est `installed`, que la permission a été accordée, n'est pas suspendue et n'est pas un export externe refusé par les réglages de protection des données. Sinon 403 avec `permission_not_granted`, `permission_suspended`, `installation_suspended` ou `external_export_denied`, enregistré comme événement `authorize_denied`. Demande `tenant_admin`, `developer`, `agent` ou `service`.",
    "operationId": "authorize_v1_marketplace_installations__installation_id__authorize_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__marketplace__routes__AuthorizeIn"
       }
      }
     },
     "description": "Scope de permission à vérifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Authorize V1 Marketplace Installations  Installation Id  Authorize Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier une permission à l'exécution",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/events": {
   "get": {
    "description": "Historique du cycle de vie de l'installation, du plus ancien au plus récent : demandes, installations, mises à niveau, retours arrière, smoke tests en échec, suspensions, reprises, autorisations refusées, avec versions, résultat et acteur. Une seule page.",
    "operationId": "installation_events_v1_marketplace_installations__installation_id__events_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Installation Events V1 Marketplace Installations  Installation Id  Events Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les événements d'une installation",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/rollback": {
   "post": {
    "description": "Réinstalle la version précédente avec ses permissions précédentes, dans un point de sauvegarde avec smoke test (422 `smoke_failed` garde la version courante). Demande `tenant_admin`. 409 `installation_state` sauf si `installed` ; 409 `rollback_unavailable` quand il n'y a pas de version précédente. Audité sous `marketplace.rollback`.",
    "operationId": "rollback_v1_marketplace_installations__installation_id__rollback_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Rollback V1 Marketplace Installations  Installation Id  Rollback Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Revenir à la version précédente",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/status": {
   "post": {
    "description": "Demande `tenant_admin` ou `security_officer`.\n- `status: suspended` : immédiat. Sans `permissions`, toute l'installation est suspendue (outils bloqués, agents suspendus, serveurs MCP désactivés, ressources suspendues) ; avec `permissions`, seulement ces capacités accordées (422 `permission_not_granted` pour les autres). Répond 200.\n- `status: installed` : reprise. Sans `approval_id`, ouvre une approbation `marketplace.resume` avec un quorum de deux et répond 202 ; avec l'identifiant approuvé, lève la suspension et répond 200. Refusé avec 409 `package_suspended` tant que le kill switch du paquet est actif.\n409 `installation_state` pour un état qui ne permet pas le changement.",
    "operationId": "installation_status_v1_marketplace_installations__installation_id__status_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__marketplace__routes__StatusIn"
       }
      }
     },
     "description": "Statut visé, raison, capacités à suspendre et approbation pour une reprise.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Installation Status V1 Marketplace Installations  Installation Id  Status Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Suspendre ou reprendre une installation",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/supervision": {
   "get": {
    "description": "Activité, qualité, coûts et incidents d'un pack installé, agrégés à partir des objets qu'il a créés : invocations d'outils et runs par état, autorisations refusées, résultats d'évaluation, dernier smoke test, taux de réussite des runs, coûts des runs par devise, et incidents citant ses objets (les 50 plus récents).",
    "operationId": "installation_supervision_v1_marketplace_installations__installation_id__supervision_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Installation Supervision V1 Marketplace Installations  Installation Id  Supervision Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Superviser un pack installé",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/uninstall": {
   "post": {
    "description": "Retire tous les objets et ressources créés par l'installation et la passe à `uninstalled` (permissions, suspensions et version précédente effacées). Permis depuis `installed`, `failed`, `pending_approval` ou `suspended` (sinon 409 `installation_state`). Demande `tenant_admin` ; la politique `marketplace.uninstall` est évaluée. La réponse liste `removed`. Audité sous `marketplace.uninstall`.",
    "operationId": "uninstall_v1_marketplace_installations__installation_id__uninstall_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Uninstall V1 Marketplace Installations  Installation Id  Uninstall Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Désinstaller un pack",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/installations/{installation_id}/upgrade": {
   "post": {
    "description": "Ouvre une demande de mise à niveau vers une version supérieure (202 avec approbation, quorum et preflight), puis appliquez-la avec `POST .../apply` une fois approuvée. L'installation doit être `installed` et la version strictement supérieure (sinon 409 `installation_state`). La configuration et les références de secrets reprennent par défaut les actuelles. Demande `tenant_admin`. Les échecs de preflight répondent comme pour une demande d'installation.",
    "operationId": "request_upgrade_v1_marketplace_installations__installation_id__upgrade_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'installation.",
      "in": "path",
      "name": "installation_id",
      "required": true,
      "schema": {
       "title": "Installation Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpgradeIn"
       }
      }
     },
     "description": "Version visée, permissions accordées, configuration et références de secrets.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Upgrade V1 Marketplace Installations  Installation Id  Upgrade Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander la mise à niveau d'une installation",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/packages": {
   "get": {
    "description": "Paquets du catalogue dans l'ordre des noms, avec statut, dernière version, éditeur et catégorie. Une seule page.",
    "operationId": "list_packages_v1_marketplace_packages_get",
    "parameters": [
     {
      "description": "Seulement les paquets de ce domaine métier.",
      "in": "query",
      "name": "domain",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Domain"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Packages V1 Marketplace Packages Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les paquets",
    "tags": [
     "marketplace"
    ]
   },
   "post": {
    "description": "Publie une archive de pack comme nouvelle version (201). Demande `tenant_admin` ou `developer` ; la politique `marketplace.publish` est évaluée.\nÉtapes :\n1. L'archive est décodée (422 `marketplace_archive_invalid`) et analysée par ClamAV et YARA-X avant d'être dépaquetée (422 `file_blocked`, 503 `file_scan_unavailable`).\n2. Tous les contrôles s'exécutent : schéma, signature, SBOM, analyse de vulnérabilités, tests, évaluations, licence, permissions, documentation, secrets, provenance, confidentialité, sécurité, approbations, configuration, résilience. Tout échec répond 422 `publication_gate_failed` avec le résultat de chaque contrôle.\n3. Le paquet est créé à la première publication. 409 `package_suspended` quand son kill switch est actif, 409 `marketplace_version_exists` pour une version déjà publiée.\nAudité sous `marketplace.publish`.",
    "operationId": "publish_v1_marketplace_packages_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PublishIn"
       }
      }
     },
     "description": "L'archive du pack et ses preuves de chaîne d'approvisionnement.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Publish V1 Marketplace Packages Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier une version de paquet",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/packages/{name}": {
   "get": {
    "description": "Renvoie le paquet et toutes ses versions, de la plus ancienne à la plus récente, avec résultats des contrôles et résumé des vulnérabilités. 404 `marketplace_package_not_found`.",
    "operationId": "get_package_v1_marketplace_packages__name__get",
    "parameters": [
     {
      "description": "Nom du paquet.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Package V1 Marketplace Packages  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un paquet et ses versions",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/packages/{name}/status": {
   "post": {
    "description": "Demande `tenant_admin` ou `security_officer`.\n- `status: suspended` : immédiat. Toutes les installations actives du paquet sont suspendues ; installations, mises à niveau, publications et promotions sont refusées. Répond 200 avec `suspended_installations`.\n- `status: active` : lève le kill switch. Sans `approval_id`, ouvre une approbation `marketplace.package.resume` avec un quorum de deux (202) ; avec l'identifiant approuvé, le paquet et les installations qu'il avait suspendues reprennent (200 avec `resumed_installations`).\n409 `installation_state` pour lever un paquet qui n'est pas suspendu.",
    "operationId": "package_status_v1_marketplace_packages__name__status_post",
    "parameters": [
     {
      "description": "Nom du paquet.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PackageStatusIn"
       }
      }
     },
     "description": "Statut visé, raison et approbation pour une levée.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Package Status V1 Marketplace Packages  Name  Status Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Actionner le kill switch d'un paquet",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/packages/{name}/versions/{version}": {
   "get": {
    "description": "Renvoie une version : manifeste, empreinte, empreintes des fichiers, signature et son statut, licence, référence OCI, résultats des contrôles, statut de provenance et approbation de production. L'archive et le SBOM ne sont pas inclus. 404 `marketplace_version_not_found`.",
    "operationId": "get_version_v1_marketplace_packages__name__versions__version__get",
    "parameters": [
     {
      "description": "Nom du paquet.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     },
     {
      "description": "Version sémantique, par exemple `1.2.0`.",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Version V1 Marketplace Packages  Name  Versions  Version  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une version de paquet",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/packages/{name}/versions/{version}/production": {
   "post": {
    "description": "Contrôle de mise en production, requis pour installer une version en `prod`. Demande `tenant_admin` ou `security_officer`.\nÉtapes :\n1. Sans `approval_id` : tous les contrôles enregistrés doivent avoir réussi, la signature doit toujours être valide et une empreinte d'artefact OCI doit être jointe (sinon 422 `publication_gate_failed`) ; ensuite une approbation `marketplace.promote` est ouverte avec un quorum d'au moins deux et la réponse est 202 avec les vérifications.\n2. Avec l'`approval_id` approuvé : la version passe à `production` (200).\nUne version déjà en production répond 200 aussitôt ; 409 `package_suspended` tant que le kill switch est actif.",
    "operationId": "promote_v1_marketplace_packages__name__versions__version__production_post",
    "parameters": [
     {
      "description": "Nom du paquet.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     },
     {
      "description": "Version sémantique, par exemple `1.2.0`.",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ProductionIn"
       }
      }
     },
     "description": "Demande approuvée, au second appel.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Promote V1 Marketplace Packages  Name  Versions  Version  Production Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Promouvoir une version en production",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/packages/{name}/versions/{version}/sbom": {
   "get": {
    "description": "Renvoie la nomenclature logicielle (SBOM) enregistrée à la publication, son empreinte et le rapport d'analyse de vulnérabilités.",
    "operationId": "get_sbom_v1_marketplace_packages__name__versions__version__sbom_get",
    "parameters": [
     {
      "description": "Nom du paquet.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     },
     {
      "description": "Version sémantique, par exemple `1.2.0`.",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Sbom V1 Marketplace Packages  Name  Versions  Version  Sbom Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le SBOM d'une version",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/preflight": {
   "post": {
    "description": "Simulation des contrôles d'installation, rien n'est installé : paquet actif, environnement, configuration selon le `configSchema` du pack, version disponible (et en production pour `prod`), compatibilité de plateforme, dépendances installées, empreinte de l'archive, signature, permissions demandées et sûres, références de secrets des connecteurs, modèle approuvé, absence de conflit de noms. Renvoie `passed`, chaque vérification, les permissions accordées, non accordées et sensibles, les approbations requises et la protection des données appliquée. Demande `tenant_admin` ou `security_officer`. Audité sous `marketplace.preflight`.",
    "operationId": "preflight_v1_marketplace_preflight_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__marketplace__routes__InstallIn"
       }
      }
     },
     "description": "Même corps qu'une demande d'installation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Preflight V1 Marketplace Preflight Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Simuler les contrôles d'installation",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/publishers": {
   "get": {
    "description": "Clés de signature de confiance, par nom : algorithme, statut (`active` ou `revoked`) et `scope` (`platform` pour une clé commune à tous les tenants, `tenant` pour une clé de ce tenant). Les clés publiques ne sont pas renvoyées.",
    "operationId": "list_publishers_v1_marketplace_publishers_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Publishers V1 Marketplace Publishers Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les éditeurs",
    "tags": [
     "marketplace"
    ]
   },
   "post": {
    "description": "Enregistre une clé publique de signature pour le tenant ; les signatures des packs sont vérifiées avec les clés actives. Demande `tenant_admin` ou `security_officer`. Une clé illisible répond 422 `publisher_invalid_key`. Audité sous `marketplace.publisher.add`.",
    "operationId": "add_publisher_v1_marketplace_publishers_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PublisherIn"
       }
      }
     },
     "description": "Nom de l'éditeur et clé publique.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Add Publisher V1 Marketplace Publishers Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter une clé d'éditeur de confiance",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/marketplace/publishers/{name}/revoke": {
   "post": {
    "description": "Révoque toutes les clés du tenant enregistrées sous ce nom ; les signatures faites avec elles ne sont plus valides. Demande `tenant_admin` ou `security_officer`. 404 `publisher_not_found`. Les clés de plateforme ne peuvent pas être révoquées ici. Audité sous `marketplace.publisher.revoke`.",
    "operationId": "revoke_publisher_v1_marketplace_publishers__name__revoke_post",
    "parameters": [
     {
      "description": "Nom de l'éditeur.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Publisher V1 Marketplace Publishers  Name  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer un éditeur",
    "tags": [
     "marketplace"
    ]
   }
  },
  "/v1/mcp/catalog": {
   "get": {
    "description": "Catalogue : outils, schémas d'entrée et de sortie, versions, scopes et statut d'approbation (`approved`, `pending_approval`, `rejected`), pour les seuls serveurs visibles par la requête, comme `GET /v1/mcp/servers`.",
    "operationId": "mcp_catalog_v1_mcp_catalog_get",
    "parameters": [
     {
      "description": "Seulement le catalogue de ce serveur (UUID ou nom).",
      "in": "query",
      "name": "server",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Server"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Mcp Catalog V1 Mcp Catalog Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le catalogue des outils MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/gateway/{server_id}": {
   "post": {
    "description": "Proxy JSON-RPC 2.0. Le corps est une requête ou un lot (50 au plus sont traitées). Demande `tenant_admin`, `developer` ou `agent` et la relation ReBAC `can_invoke`. Les refus sont renvoyés comme erreurs JSON-RPC avec le statut HTTP 200, et audités.\nContrôles, dans l'ordre : JSON-RPC 2.0 valide ; serveur `active` ; isolation par projet (`mcp_server_project_mismatch`) ; appelants permis (`mcp_principal_not_allowed`) ; une instance hébergée en sommeil est réveillée ; une application tierce doit être active et avoir reçu le serveur ; méthode permise ; pour `tools/call` : outil dans `allowed_tools`, scope `tools:invoke`, DLP sur les arguments ; politique `mcp.call` ; réservation de quota ; pour `resources/read` : URI dans `allowed_resources`.\nL'appel est ensuite transmis avec les identifiants amont ; `tools/list` et `resources/list` sont filtrés par les listes d'autorisation, les résultats de `tools/call` sont masqués par la DLP et contrôlés contre l'injection de prompt, et l'appel est compté comme usage d'outil.",
    "operationId": "mcp_gateway_v1_mcp_gateway__server_id__post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom du serveur MCP.",
      "in": "path",
      "name": "server_id",
      "required": true,
      "schema": {
       "title": "Server Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "title": "Message"
       }
      }
     },
     "description": "Une requête JSON-RPC 2.0 (`jsonrpc`, `id`, `method`, `params`) ou un tableau de requêtes.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Mcp Gateway V1 Mcp Gateway  Server Id  Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Appeler un serveur MCP via la passerelle",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/hosted/sync": {
   "post": {
    "description": "Publie les serveurs MCP hébergés par le Navin Manager (service mcp-host) dans leur projet : jeton conservé dans le Credential Broker, serveur du projet créé ou mis à jour, serveurs qui ne sont plus hébergés désactivés. `project` (identifiant ou slug, par défaut le projet de la requête) limite la synchronisation à un projet ; un projet différent de celui de la requête répond 403 `mcp_project_forbidden`. Demande `tenant_admin` et un tenant (403 `tenant_required`).",
    "operationId": "sync_hosted_v1_mcp_hosted_sync_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du seul projet à synchroniser. Par défaut : le projet de la requête.",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Sync Hosted V1 Mcp Hosted Sync Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier les serveurs MCP hébergés par le Navin Manager",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/runtime/jwks": {
   "get": {
    "description": "Clés publiques de vérification des jetons que la MCP Gateway remet aux proxys ToolHive (aucun secret : des clés publiques seulement, comme tout JWKS OIDC). Lues sans authentification par le MCPOIDCConfig `eai-mcp-runtime` de chaque espace de noms de projet. 503 `context_authority_unavailable` quand le registre de clés est illisible.",
    "operationId": "runtime_jwks_v1_mcp_runtime_jwks_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Runtime Jwks V1 Mcp Runtime Jwks Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les clés de vérification du runtime MCP",
    "tags": [
     "mcp-runtime"
    ]
   }
  },
  "/v1/mcp/servers": {
   "get": {
    "description": "Serveurs visibles par la requête : `project` (identifiant ou slug) ne garde que les serveurs de ce projet ; une requête faite dans un projet (en-tête `X-Project` ou clé d'API liée à un projet) voit les serveurs de son projet et ceux partagés par le tenant, jamais ceux d'un autre projet (403 `mcp_project_forbidden`). Les secrets amont ne sont jamais renvoyés, seulement leurs références.",
    "operationId": "list_servers_v1_mcp_servers_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet dont les serveurs sont listés.",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Servers V1 Mcp Servers Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les serveurs MCP",
    "tags": [
     "mcp"
    ]
   },
   "post": {
    "description": "Enregistre un serveur amont (201). L'URL doit être http(s) sur un hôte interne permis par la politique de sortie MCP (403 `mcp_online_forbidden` sinon) ; `bearer`, `api_key` et `query_param` demandent un `credential_ref` `openbao://` ou `env://`, `query_param` demande `param`, `connector` demande `connector` (400 `invalid_request`). Un nom déjà utilisé répond 409 `conflict`. Le projet (par défaut `X-Project`) doit être celui de la requête (403 `mcp_project_forbidden`). Demande `tenant_admin`, `developer` ou `application_owner` ; l'appelant devient propriétaire ReBAC. Audité sous `mcp.server.create`.",
    "operationId": "create_server_v1_mcp_servers_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ServerIn"
       }
      }
     },
     "description": "Nom, URL, transport, authentification amont, listes d'autorisation, propriétaire et projet.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Server V1 Mcp Servers Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un serveur MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/servers/{server_id}": {
   "delete": {
    "description": "Retire le serveur du registre. Demande `tenant_admin`, la relation ReBAC `owner` et l'accès au projet du serveur. 404 `mcp_server_not_found`. Audité sous `mcp.server.delete`.",
    "operationId": "delete_server_v1_mcp_servers__server_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom du serveur MCP.",
      "in": "path",
      "name": "server_id",
      "required": true,
      "schema": {
       "title": "Server Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un serveur MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/servers/{server_id}/access": {
   "put": {
    "description": "Contrôle d'accès de la MCP Gateway : quels utilisateurs et agents peuvent appeler ce serveur ; une liste vide laisse tout membre de son projet l'appeler. Les identifiants de principaux sont faits de lettres, chiffres et `:._@/-` (128 caractères au plus, 400 `invalid_request`). L'accès d'une instance hébergée est géré par le runtime MCP (409 `mcp_instance_access_managed`). Demande `tenant_admin`, `developer` ou `application_owner` et l'accès au projet du serveur. Audité sous `mcp.server.access`.",
    "operationId": "put_access_v1_mcp_servers__server_id__access_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom du serveur MCP.",
      "in": "path",
      "name": "server_id",
      "required": true,
      "schema": {
       "title": "Server Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AccessIn"
       }
      }
     },
     "description": "Les principaux permis.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Access V1 Mcp Servers  Server Id  Access Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir qui peut appeler un serveur MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/servers/{server_id}/discover": {
   "post": {
    "description": "Découverte par le protocole MCP (`initialize`, `tools/list`) : chaque schéma d'outil nouveau ou modifié devient une version du catalogue en attente d'approbation (`mcp_tool_version`, approbateurs `tenant_admin` ou `security_officer`). Renvoie les versions créées avec leurs identifiants d'approbation. Demande `tenant_admin`, `developer` ou `application_owner`, la relation ReBAC `editor` et l'accès au projet du serveur.",
    "operationId": "discover_server_v1_mcp_servers__server_id__discover_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom du serveur MCP.",
      "in": "path",
      "name": "server_id",
      "required": true,
      "schema": {
       "title": "Server Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Discover Server V1 Mcp Servers  Server Id  Discover Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Découvrir les outils d'un serveur MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/servers/{server_id}/status": {
   "post": {
    "description": "`active` passe par une promotion : une approbation est ouverte et la réponse est 202 avec `approval_id`. `disabled` s'applique aussitôt (tout appel de passerelle est alors refusé) et est audité sous `mcp.server.status`. Demande `tenant_admin` ou `security_officer`, la relation ReBAC `editor` et l'accès au projet du serveur.",
    "operationId": "server_status_v1_mcp_servers__server_id__status_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom du serveur MCP.",
      "in": "path",
      "name": "server_id",
      "required": true,
      "schema": {
       "title": "Server Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ServerStatusIn"
       }
      }
     },
     "description": "Statut visé et raison.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Server Status V1 Mcp Servers  Server Id  Status Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Activer ou désactiver un serveur MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/mcp/servers/{server_id}/tools": {
   "get": {
    "description": "Appelle `tools/list` sur le serveur via la passerelle et renvoie les outils permis par `allowed_tools`. Demande la relation ReBAC `can_invoke` et l'accès au projet du serveur. Une erreur amont répond 502 `upstream_error`.",
    "operationId": "server_tools_v1_mcp_servers__server_id__tools_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou nom du serveur MCP.",
      "in": "path",
      "name": "server_id",
      "required": true,
      "schema": {
       "title": "Server Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Server Tools V1 Mcp Servers  Server Id  Tools Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les outils d'un serveur MCP",
    "tags": [
     "mcp"
    ]
   }
  },
  "/v1/me": {
   "get": {
    "description": "Qui est l'appelant : identifiant du principal, type, tenant, habilitation, projet, environnement, méthode d'authentification et nom affiché, avec ses rôles et ses scopes. Si le principal appartient à un tenant, le tenant (identifiant, slug, nom, statut, résidence des données) est aussi renvoyé. Demande une authentification (401 sinon).",
    "operationId": "me_v1_me_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Me V1 Me Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire le principal courant",
    "tags": [
     "system"
    ]
   }
  },
  "/v1/media": {
   "get": {
    "description": "Médias du tenant (documents Multimodal Contract), paginés par clé avec `cursor`, filtrés par `media_type` et `classification` (400 `invalid_request` pour une valeur inconnue).",
    "operationId": "list_media_v1_media_get",
    "parameters": [
     {
      "description": "Seulement ce type : `image`, `audio`, `video` ou `document`.",
      "in": "query",
      "name": "media_type",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Media Type"
      }
     },
     {
      "description": "Seulement cette classification : `public`, `internal`, `confidential`, `restricted` ou `secret`.",
      "in": "query",
      "name": "classification",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Classification"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Media V1 Media Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les médias",
    "tags": [
     "multimodal"
    ]
   },
   "post": {
    "description": "Enregistre un média (201) à partir de JSON `{content_base64, mime_type, filename?, operation?, dlp_mode?, retention_days?, classification?}` ou de multipart/form-data avec une partie `file` et les mêmes champs. Tout principal du tenant peut l'appeler.\nÉtapes :\n1. Taille contrôlée par rapport au maximum de la plateforme (50 Mio sauf configuration ; 413 `media_too_large`).\n2. Analyse du fichier par ClamAV et YARA-X (422 `file_blocked`, 503 `file_scan_unavailable`).\n3. Extraction du texte et DLP avec `dlp_mode` (`detect` par défaut ; `block` répond 422 `dlp_blocked` quand des données personnelles sont trouvées) ; la classification est relevée en conséquence.\n4. Le contenu est chiffré avec une clé du tenant et enregistré. Renvoie le document Multimodal Contract.",
    "operationId": "upload_media_v1_media_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "properties": {
         "classification": {
          "enum": [
           "public",
           "internal",
           "confidential",
           "restricted",
           "secret"
          ],
          "type": "string"
         },
         "content_base64": {
          "type": "string"
         },
         "dlp_mode": {
          "enum": [
           "detect",
           "mask",
           "tokenize",
           "block"
          ],
          "type": "string"
         },
         "filename": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         },
         "operation": {
          "enum": [
           "vision_understanding",
           "image_generation",
           "image_edit",
           "video_processing",
           "video_generation",
           "stt",
           "diarization",
           "tts",
           "voice_live",
           "ocr",
           "document_extraction"
          ],
          "type": "string"
         },
         "retention_days": {
          "minimum": 0,
          "type": "integer"
         }
        },
        "required": [
         "content_base64",
         "mime_type"
        ],
        "type": "object"
       }
      },
      "multipart/form-data": {
       "schema": {
        "properties": {
         "classification": {
          "enum": [
           "public",
           "internal",
           "confidential",
           "restricted",
           "secret"
          ],
          "type": "string"
         },
         "dlp_mode": {
          "enum": [
           "detect",
           "mask",
           "tokenize",
           "block"
          ],
          "type": "string"
         },
         "file": {
          "format": "binary",
          "type": "string"
         },
         "filename": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         },
         "operation": {
          "enum": [
           "vision_understanding",
           "image_generation",
           "image_edit",
           "video_processing",
           "video_generation",
           "stt",
           "diarization",
           "tts",
           "voice_live",
           "ocr",
           "document_extraction"
          ],
          "type": "string"
         },
         "retention_days": {
          "minimum": 0,
          "type": "integer"
         }
        },
        "required": [
         "file"
        ],
        "type": "object"
       }
      }
     },
     "description": "Le fichier (JSON avec contenu en base64, ou multipart avec une partie `file`) et ses options : `operation` à laquelle le média est destiné, `dlp_mode` (`detect`, `mask`, `tokenize`, `block`), `retention_days` (0 ou plus), `classification`.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upload Media V1 Media Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Téléverser un média",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/media/{media_id}": {
   "delete": {
    "description": "Supprime le média et son contenu stocké. Permis pour son créateur, `tenant_admin` ou `security_officer` (403 `forbidden`). Un média sous conservation légale répond 409 `legal_hold`. Audité sous `media.delete`.",
    "operationId": "delete_media_v1_media__media_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du média.",
      "in": "path",
      "name": "media_id",
      "required": true,
      "schema": {
       "title": "Media Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Media V1 Media  Media Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un média",
    "tags": [
     "multimodal"
    ]
   },
   "get": {
    "description": "Renvoie le document Multimodal Contract du média. Avec `include_content=true`, la réponse est `{media, content_base64}` avec le contenu déchiffré ; cela demande une habilitation au moins égale à la classification du média (403 `policy_denied`) et est audité sous `media.read`.",
    "operationId": "get_media_v1_media__media_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du média.",
      "in": "path",
      "name": "media_id",
      "required": true,
      "schema": {
       "title": "Media Id",
       "type": "string"
      }
     },
     {
      "description": "Si vrai, renvoie aussi le contenu déchiffré, encodé en base64. Faux par défaut.",
      "in": "query",
      "name": "include_content",
      "required": false,
      "schema": {
       "default": false,
       "title": "Include Content",
       "type": "boolean"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Media V1 Media  Media Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un média",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/members": {
   "get": {
    "description": "Liste les appartenances du tenant, paginées : sujet de l'utilisateur, e-mail, nom affiché, rôle, habilitation, projet et date de création. Ouvert à tout membre du tenant.",
    "operationId": "list_members_v1_members_get",
    "parameters": [
     {
      "description": "Nombre maximal de membres dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Members V1 Members Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les membres",
    "tags": [
     "tenancy"
    ]
   },
   "post": {
    "description": "Donne à un utilisateur (sujet OIDC) un rôle et une habilitation dans le tenant, ou dans un projet avec `project_id`. La fiche utilisateur est créée si elle est inconnue ; une appartenance existante pour le même utilisateur et le même projet est mise à jour. Exige `tenant_admin` ; accorder `platform_admin` exige un administrateur de plateforme (403 `forbidden`). Écrit l'événement d'audit `member.assign`.",
    "operationId": "add_member_v1_members_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemberIn"
       }
      }
     },
     "description": "Utilisateur, rôle, habilitation et projet facultatif de l'appartenance.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Add Member V1 Members Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter un membre",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/members/{member_id}": {
   "delete": {
    "description": "Supprime l'appartenance : l'utilisateur perd le rôle qu'elle donnait dès la requête suivante. Exige `tenant_admin`. Écrit l'événement d'audit `member.remove`. Répond 204, ou 404 `not_found`.",
    "operationId": "delete_member_v1_members__member_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'appartenance.",
      "in": "path",
      "name": "member_id",
      "required": true,
      "schema": {
       "title": "Member Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer un membre",
    "tags": [
     "tenancy"
    ]
   },
   "patch": {
    "description": "Modifie le rôle ou l'habilitation d'une appartenance. Exige `tenant_admin` ; accorder `platform_admin` exige un administrateur de plateforme (403 `forbidden`). Écrit l'événement d'audit `member.update`. Répond 404 `not_found` pour une appartenance inconnue.",
    "operationId": "patch_member_v1_members__member_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'appartenance.",
      "in": "path",
      "name": "member_id",
      "required": true,
      "schema": {
       "title": "Member Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemberPatch"
       }
      }
     },
     "description": "Nouveau rôle et/ou nouvelle habilitation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Member V1 Members  Member Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un membre",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/memory": {
   "get": {
    "description": "Éléments lisibles par l'appelant (présent dans `readers`, classification dans les limites de son habilitation, non expirés, non supprimés), sous forme de documents Memory Contract, paginés avec `cursor`.",
    "operationId": "list_memory_v1_memory_get",
    "parameters": [
     {
      "description": "Seulement ce type de portée : `session`, `agent`, `user`, `project` ou `tenant`.",
      "in": "query",
      "name": "scope_type",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Scope Type"
      }
     },
     {
      "description": "Seulement cet identifiant de portée.",
      "in": "query",
      "name": "scope_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Scope Id"
      }
     },
     {
      "description": "Seulement ce type : `episodic`, `semantic`, `procedural`, `preference` ou `fact`.",
      "in": "query",
      "name": "kind",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Kind"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Memory V1 Memory Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les éléments de mémoire",
    "tags": [
     "memory"
    ]
   },
   "post": {
    "description": "Crée un élément, ou une nouvelle version de `memory_id` (201, document Memory Contract).\nÉtapes :\n1. `kind` et `scope.type` sont vérifiés (400 `invalid_request`) ; `content` ou `content_ref` est requis.\n2. La détection de données personnelles peut relever la classification ; une classification au-dessus de l'habilitation de l'appelant répond 403 `policy_denied` ; la politique `memory.write` est évaluée.\n3. Pour une mise à jour, l'appelant doit être rédacteur ou propriétaire (403 `not_permitted_acl`) avant l'envoi du contenu au modèle d'embedding.\n4. Le contenu est vectorisé et enregistré. Un nouvel élément a par défaut l'appelant comme lecteur, rédacteur et propriétaire, et la portée `user` de l'appelant.\nAudité sous `memory.create` ou `memory.update`.",
    "operationId": "write_memory_v1_memory_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryIn"
       }
      }
     },
     "description": "L'élément de mémoire à écrire.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Write Memory V1 Memory Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Écrire un élément de mémoire",
    "tags": [
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/knowledge": {
   "post": {
    "description": "Ajoute des textes au jeu de données Cognee d'une portée (Cognee `POST /api/v1/add`, puis `POST /api/v1/cognify` si `build_graph`), seulement si la politique active `knowledge_graph` et si le moteur est `cognee` ou `hybrid` (sinon 403 ou 422 `memory_capability_unsupported`). Mêmes contrôles DLP, de classification, de conservation et d'isolation qu'une mémoire ; relation `editor` sur l'agent. 201 avec `dataset`, `items` et `graph`. Accepte un en-tête `Idempotency-Key`. Audité sous `memory.knowledge.create` sans le contenu.",
    "operationId": "ingest_knowledge_v1_memory_agents__agent_id__knowledge_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryKnowledgeIn"
       }
      }
     },
     "description": "Textes à ingérer et leur portée.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Ingest Knowledge V1 Memory Agents  Agent Id  Knowledge Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ingérer des connaissances",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/knowledge/datasets": {
   "get": {
    "description": "Jeux de données que l'appelant peut lire via cet agent, paginés avec `cursor`.",
    "operationId": "list_datasets_v1_memory_agents__agent_id__knowledge_datasets_get",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Seulement ce niveau d'isolation : `agent`, `shared_agents`, `project`, `team`, `organization` ou `global`.",
      "in": "query",
      "name": "scope",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "agent",
          "shared_agents",
          "project",
          "team",
          "organization",
          "global"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Scope"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Datasets V1 Memory Agents  Agent Id  Knowledge Datasets Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les jeux de données de connaissances",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/knowledge/datasets/{dataset_id}": {
   "delete": {
    "description": "Supprime le jeu de données dans Cognee (`DELETE /api/v1/datasets/{dataset_id}`) et ses entrées d'index. 409 `legal_hold` si le jeu de données ou l'un de ses éléments est sous conservation légale. Audité sous `memory.knowledge.delete`.",
    "operationId": "delete_knowledge_dataset_v1_memory_agents__agent_id__knowledge_datasets__dataset_id__delete",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'entrée d'index Navin du jeu de données.",
      "in": "path",
      "name": "dataset_id",
      "required": true,
      "schema": {
       "title": "Dataset Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Knowledge Dataset V1 Memory Agents  Agent Id  Knowledge Datasets  Dataset Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un jeu de données de connaissances",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/knowledge/datasets/{dataset_id}/relations": {
   "get": {
    "description": "Nœuds et arêtes du graphe de connaissances d'un jeu de données lisible (Cognee `GET /api/v1/datasets/{dataset_id}/graph`), contrôlés contre l'injection de prompt et renvoyés comme données non fiables. 404 pour un jeu de données que l'appelant ne peut pas lire. Audité sous `memory.provider.read`.",
    "operationId": "knowledge_relations_v1_memory_agents__agent_id__knowledge_datasets__dataset_id__relations_get",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'entrée d'index Navin du jeu de données.",
      "in": "path",
      "name": "dataset_id",
      "required": true,
      "schema": {
       "title": "Dataset Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Knowledge Relations V1 Memory Agents  Agent Id  Knowledge Datasets  Dataset Id  Relations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les relations d'un jeu de données",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/knowledge/graph": {
   "post": {
    "description": "Lance Cognee `POST /api/v1/cognify` sur les jeux de données lisibles de l'instance de l'agent (tous par défaut). Relation `editor` sur l'agent. Audité sous `memory.knowledge.build`.",
    "operationId": "build_knowledge_graph_v1_memory_agents__agent_id__knowledge_graph_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryGraphBuildIn"
       }
      }
     },
     "description": "Jeux de données à traiter.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Build Knowledge Graph V1 Memory Agents  Agent Id  Knowledge Graph Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Construire le graphe de connaissances",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/knowledge/search": {
   "post": {
    "description": "Cognee `POST /api/v1/search`, un jeu de données lisible par appel afin que chaque passage soit attribué à son jeu de données ; les résultats sont contrôlés et renvoyés comme données non fiables avec `framed_content`. La décision de politique `memory.search` est évaluée. Audité sous `memory.provider.read`.",
    "operationId": "search_knowledge_v1_memory_agents__agent_id__knowledge_search_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryGraphSearchIn"
       }
      }
     },
     "description": "Requête, jeux de données, nombre de résultats et type de recherche.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Search Knowledge V1 Memory Agents  Agent Id  Knowledge Search Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rechercher dans le graphe de connaissances",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/memories": {
   "get": {
    "description": "Entrées d'index des mémoires que l'appelant peut lire via cet agent (portées lisibles de la politique, classification dans l'habilitation, non expirées), paginées avec `cursor`. Avec `include_content=true` (au plus 100 par page), le contenu est lu dans le moteur et renvoyé comme donnée non fiable. Audité sous `memory.provider.read`.",
    "operationId": "list_memories_v1_memory_agents__agent_id__memories_get",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Seulement ce niveau d'isolation : `session`, `user`, `agent`, `shared_agents`, `project`, `team`, `organization` ou `global`.",
      "in": "query",
      "name": "scope",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "session",
          "user",
          "agent",
          "shared_agents",
          "project",
          "team",
          "organization",
          "global"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Scope"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Lire aussi le contenu dans le moteur (faux par défaut).",
      "in": "query",
      "name": "include_content",
      "required": false,
      "schema": {
       "default": false,
       "title": "Include Content",
       "type": "boolean"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Memories V1 Memory Agents  Agent Id  Memories Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les mémoires d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   },
   "post": {
    "description": "Écrit une mémoire via le moteur de la politique de l'agent (portée session : toujours le magasin builtin ; `hybrid` : Mem0). 201 avec `items` (entrées d'index, sans contenu), `engine` et `engine_instance`.\nLe contexte (tenant, organisation, projet, utilisateur, agent, session) est dérivé côté serveur du principal authentifié, du registre des agents et de la session vérifiée, jamais du corps. Contrôles, chaque refus étant audité : un agent n'utilise que sa propre mémoire (403 `memory_agent_mismatch`), la portée et le type doivent être permis par la politique (403), les portées partagées exigent `editor` sur l'agent, le moteur doit annoncer `remember` (422 `memory_capability_unsupported`) et garantir l'isolation exigée (409), les secrets, mots de passe, jetons et clés sont refusés (422 `memory_secret_refused`), les données personnelles si la politique les interdit (422), une classification au-dessus de la politique ou de l'habilitation (403), une conservation au-delà de la politique (422). Accepte un en-tête `Idempotency-Key`. Audité sous `memory.provider.create` sans le contenu.",
    "operationId": "remember_v1_memory_agents__agent_id__memories_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryRememberIn"
       }
      }
     },
     "description": "La mémoire à retenir.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Remember V1 Memory Agents  Agent Id  Memories Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Mémoriser via le moteur de l'agent",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/memories/export": {
   "post": {
    "description": "Exporte au plus 1000 mémoires lisibles avec leur contenu (données non fiables). Refusé aux agents (403). Audité sous `memory.export` sans le contenu.",
    "operationId": "export_memories_v1_memory_agents__agent_id__memories_export_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryExportIn"
       }
      }
     },
     "description": "Portées à exporter (par défaut : toutes les portées lisibles).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Export Memories V1 Memory Agents  Agent Id  Memories Export Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exporter les mémoires d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/memories/purge": {
   "post": {
    "description": "Supprime toutes les mémoires et tous les jeux de données d'une portée de l'appelant (Mem0 `DELETE /memories` avec les identifiants de la portée, suppression du jeu de données Cognee, suppression logique builtin). `confirm` doit être vrai (400). 409 `legal_hold` si un élément de la portée est sous conservation légale. Audité sous `memory.provider.purge`.",
    "operationId": "purge_memories_v1_memory_agents__agent_id__memories_purge_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryPurgeIn"
       }
      }
     },
     "description": "La portée à purger et la confirmation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Purge Memories V1 Memory Agents  Agent Id  Memories Purge Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Purger une portée d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/memories/search": {
   "post": {
    "description": "Même recherche que `recall` pour une personne qui consulte les mémoires (sans `context_segments`) ; les portées partagées exigent `editor` sur l'agent. Les résultats sont des données non fiables. Audité sous `memory.provider.read`.",
    "operationId": "search_memories_v1_memory_agents__agent_id__memories_search_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryRecallIn"
       }
      }
     },
     "description": "Requête, portées, nombre de résultats et session.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Search Memories V1 Memory Agents  Agent Id  Memories Search Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rechercher dans les mémoires d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/memories/{item_id}": {
   "delete": {
    "description": "Supprime la mémoire dans son moteur (Mem0 `DELETE /memories/{memory_id}`, suppression de l'élément de données Cognee, suppression logique builtin) et marque l'entrée d'index supprimée. 409 `legal_hold` ; seul l'agent propriétaire d'une mémoire partagée (403). Audité sous `memory.provider.delete`.",
    "operationId": "forget_memory_item_v1_memory_agents__agent_id__memories__item_id__delete",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'entrée d'index Navin.",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Forget Memory Item V1 Memory Agents  Agent Id  Memories  Item Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Oublier une mémoire d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   },
   "get": {
    "description": "Entrée d'index et contenu (lu dans le moteur, donnée non fiable) d'une mémoire. Un élément d'une portée que l'appelant ne peut pas lire répond 404, jamais 403, pour ne pas révéler son existence. Audité sous `memory.provider.read`.",
    "operationId": "get_memory_item_v1_memory_agents__agent_id__memories__item_id__get",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'entrée d'index Navin.",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Memory Item V1 Memory Agents  Agent Id  Memories  Item Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une mémoire d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   },
   "patch": {
    "description": "Remplace le contenu d'une mémoire dans son moteur (Mem0 `PUT /memories/{memory_id}`, nouvelle version builtin) après les mêmes contrôles DLP qu'une création ; la version augmente. Seul l'agent propriétaire d'une mémoire partagée peut la modifier (403) ; un élément sous conservation légale répond 409 `legal_hold`. Audité sous `memory.provider.update`.",
    "operationId": "update_memory_item_v1_memory_agents__agent_id__memories__item_id__patch",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'entrée d'index Navin.",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryUpdateIn"
       }
      }
     },
     "description": "Le nouveau contenu.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Update Memory Item V1 Memory Agents  Agent Id  Memories  Item Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une mémoire d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/policy": {
   "get": {
    "description": "Choix du moteur (`builtin`, `mem0`, `cognee`, `hybrid`), instances, modes, portées, limites de mémorisation, conservation, partage et isolation exigée de l'agent, avec `version`, `configured` (faux : valeurs par défaut, moteur builtin) et `engines` (par usage `memory` ou `knowledge` : instance, fournisseur, isolation et capacités). Relation `viewer` sur l'agent, ou l'agent lui-même.",
    "operationId": "get_agent_policy_v1_memory_agents__agent_id__policy_get",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Agent Policy V1 Memory Agents  Agent Id  Policy Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la politique de mémoire d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   },
   "put": {
    "description": "Remplace la politique de mémoire de l'agent (nouvelle `version`, l'ancienne est gardée pour une restauration).\nContrôles : rôles `tenant_admin`, `developer` ou `application_owner` et relation `editor` sur l'agent (403) ; `global_access` exige `tenant_admin` ; `default_scope` doit être une portée d'écriture et `shared_with` des agents existants (400) ; les instances choisies doivent exister, correspondre au fournisseur et garantir `required_isolation` (409 `memory_isolation_unsatisfied` : une instance dédiée installée par le Navin Manager est nécessaire). La décision de politique `memory.policy.update` est appliquée.\n`shared_with` écrit ou supprime la relation OpenFGA `memory_reader` de chaque agent sur celui-ci. Audité sous `memory.policy.update`, plus `memory.engine.change` quand le moteur ou une instance change et `memory.share` quand le partage change.",
    "operationId": "put_agent_policy_v1_memory_agents__agent_id__policy_put",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AgentMemoryPolicyIn"
       }
      }
     },
     "description": "Choix du moteur, instances et politique de mémoire de l'agent.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Agent Policy V1 Memory Agents  Agent Id  Policy Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir la politique de mémoire d'un agent",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/policy/restore": {
   "post": {
    "description": "Applique de nouveau une ancienne version comme nouvelle version, avec les mêmes contrôles que `PUT .../policy`. 404 pour une version inconnue. Audité sous `memory.policy.restore`.",
    "operationId": "restore_agent_policy_v1_memory_agents__agent_id__policy_restore_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryPolicyRestoreIn"
       }
      }
     },
     "description": "La version à restaurer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Restore Agent Policy V1 Memory Agents  Agent Id  Policy Restore Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Restaurer une version de la politique de mémoire",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/policy/versions": {
   "get": {
    "description": "Anciennes versions de la politique de mémoire de l'agent, de la plus récente à la plus ancienne (au plus 200), chacune avec son moteur, ses instances, sa politique, son auteur et sa date. Relation `viewer` sur l'agent.",
    "operationId": "agent_policy_versions_v1_memory_agents__agent_id__policy_versions_get",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Agent Policy Versions V1 Memory Agents  Agent Id  Policy Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions de la politique de mémoire",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/agents/{agent_id}/recall": {
   "post": {
    "description": "Recherche dans les portées lisibles sur les moteurs de la politique (hybride : Mem0 et Cognee, fusionnés et dédoublonnés par contenu, chaque source gardée dans `sources`), ne garde que les résultats indexés par Navin pour une portée lisible et dans l'habilitation (contrôle des permissions avant que quoi que ce soit n'atteigne le LLM), les contrôle contre l'injection de prompt (éléments bloqués écartés) et les renvoie comme données non fiables : `framed_content` encadré par les marqueurs de contenu non fiable et `context_segments` de type `memory` pour le Context Manager, jamais comme instructions. La décision de politique `memory.search` est évaluée. Audité sous `memory.provider.read` avec des comptes seulement.",
    "operationId": "recall_v1_memory_agents__agent_id__recall_post",
    "parameters": [
     {
      "description": "Nom ou identifiant de l'agent.",
      "in": "path",
      "name": "agent_id",
      "required": true,
      "schema": {
       "title": "Agent Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryRecallIn"
       }
      }
     },
     "description": "Requête, portées, nombre de résultats et session.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Recall V1 Memory Agents  Agent Id  Recall Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rappeler des mémoires pour le LLM",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/engines": {
   "get": {
    "description": "Moteurs de mémoire utilisables par le tenant (Navin Memory Provider) : `builtin` (le magasin du Memory Manager), les instances de plateforme `mem0` et `cognee` rendues par le Navin Manager depuis ses liaisons (seulement si elles sont configurées), et les instances déclarées par l'administrateur de la plateforme (celles partagées et celles dédiées à ce tenant ; une instance dédiée à un autre tenant n'est jamais listée). Chaque entrée donne `provider`, `base_url`, `origin` (`builtin`, `manager_binding`, `declared`), `dedicated`, `credential_bound` (l'identifiant lui-même n'est jamais renvoyé), `isolation` (`logical`, `backend` ou `dedicated_instance` : la séparation que cette instance garantit pour le tenant) et `capabilities`. Navin Enterprise n'installe ni ne configure aucun moteur : c'est le rôle du Navin Manager.",
    "operationId": "list_engines_v1_memory_engines_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Engines V1 Memory Engines Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les moteurs de mémoire",
    "tags": [
     "memory",
     "memory"
    ]
   },
   "post": {
    "description": "Déclare une instance de Mem0 ou de Cognee installée et exploitée par le Navin Manager, par exemple une instance dédiée à un tenant (`tenant_id` : séparation physique, comme l'exige une banque). Réservé à `platform_admin` (403 sinon). L'identifiant de connexion n'est qu'une référence (`openbao://<chemin>` ou `env://EAI_CRED_<NOM>`), résolue côté serveur. 409 `conflict` si l'identifiant existe ou est réservé (`builtin`, `mem0`, `cognee`), 404 pour un tenant inconnu. Accepte un en-tête `Idempotency-Key`. Audité sous `memory.engine.declare`.",
    "operationId": "declare_engine_v1_memory_engines_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryEngineIn"
       }
      }
     },
     "description": "L'instance de moteur à déclarer.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Declare Engine V1 Memory Engines Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déclarer une instance de moteur de mémoire",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/engines/{engine_id}": {
   "delete": {
    "description": "Retire une instance déclarée et les identifiants de tenant qui lui sont liés. Réservé à `platform_admin`. 409 `memory_engine_in_use` tant qu'une politique d'agent ou une mémoire l'utilise ; 404 `memory_engine_not_found`. Audité sous `memory.engine.delete`.",
    "operationId": "delete_engine_v1_memory_engines__engine_id__delete",
    "parameters": [
     {
      "description": "Identifiant de l'instance de moteur.",
      "in": "path",
      "name": "engine_id",
      "required": true,
      "schema": {
       "title": "Engine Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Engine V1 Memory Engines  Engine Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer une instance de moteur de mémoire déclarée",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/engines/{engine_id}/capabilities": {
   "get": {
    "description": "Ensemble déclaratif des capacités du moteur : seulement ce que son fournisseur exécute réellement. `memory` : parmi `remember`, `recall`, `search`, `update`, `forget`, `list`, `get`, `purge`, `get_health`, `get_capabilities` ; `knowledge` : parmi `ingest_knowledge`, `build_graph`, `search_graph`, `get_relations`, `delete_dataset`. Une opération hors de cet ensemble répond 422 `memory_capability_unsupported`. Donne aussi `isolation` pour le tenant et `session_memory` (vrai pour `builtin` seulement : la mémoire de session ne va jamais vers un moteur externe). Rien n'est envoyé au moteur.",
    "operationId": "engine_capabilities_v1_memory_engines__engine_id__capabilities_get",
    "parameters": [
     {
      "description": "Identifiant de l'instance de moteur (`builtin`, `mem0`, `cognee` ou un identifiant déclaré).",
      "in": "path",
      "name": "engine_id",
      "required": true,
      "schema": {
       "title": "Engine Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Engine Capabilities V1 Memory Engines  Engine Id  Capabilities Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les capacités d'un moteur de mémoire",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/engines/{engine_id}/credential": {
   "delete": {
    "description": "Retire l'identifiant de connexion de ce tenant sur l'instance ; celui de l'instance est de nouveau utilisé. Rôle `tenant_admin`. Audité sous `memory.engine.change`.",
    "operationId": "delete_engine_credential_v1_memory_engines__engine_id__credential_delete",
    "parameters": [
     {
      "description": "Identifiant de l'instance de moteur.",
      "in": "path",
      "name": "engine_id",
      "required": true,
      "schema": {
       "title": "Engine Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Engine Credential V1 Memory Engines  Engine Id  Credential Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Délier l'identifiant du tenant",
    "tags": [
     "memory",
     "memory"
    ]
   },
   "put": {
    "description": "Lie l'identifiant de connexion de ce tenant sur une instance partagée (référence `openbao://<chemin>` vers un secret du tenant). Sur une instance Cognee avec contrôle d'accès côté moteur, c'est la clé d'API de l'utilisateur Cognee du tenant : ses jeux de données et permissions sont alors appliqués par Cognee lui-même (`isolation` devient `backend`). Sur Mem0, une clé ne sépare pas les mémoires (`logical` reste). Rôle `tenant_admin`. Audité sous `memory.engine.change`.",
    "operationId": "put_engine_credential_v1_memory_engines__engine_id__credential_put",
    "parameters": [
     {
      "description": "Identifiant de l'instance de moteur.",
      "in": "path",
      "name": "engine_id",
      "required": true,
      "schema": {
       "title": "Engine Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MemoryEngineCredentialIn"
       }
      }
     },
     "description": "Référence vers l'identifiant de connexion du tenant.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Engine Credential V1 Memory Engines  Engine Id  Credential Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lier l'identifiant du tenant sur un moteur",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/engines/{engine_id}/health": {
   "get": {
    "description": "Appelle le moteur avec l'identifiant de connexion du tenant : Mem0 `GET /auth/setup-status` puis `GET /configure/providers`, Cognee `GET /health` puis `GET /api/v1/datasets`. Répond `status` `up` (ou `degraded`), ou `down` avec `error` et `reason` (jamais un secret). Rôles `tenant_admin`, `developer`, `security_officer` ou `auditor`.",
    "operationId": "engine_health_v1_memory_engines__engine_id__health_get",
    "parameters": [
     {
      "description": "Identifiant de l'instance de moteur.",
      "in": "path",
      "name": "engine_id",
      "required": true,
      "schema": {
       "title": "Engine Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Engine Health V1 Memory Engines  Engine Id  Health Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier la santé d'un moteur de mémoire",
    "tags": [
     "memory",
     "memory"
    ]
   }
  },
  "/v1/memory/search": {
   "post": {
    "description": "Calcule le score des éléments visibles (au plus les 2000 mis à jour le plus récemment et ayant un contenu) et renvoie les `top_k` meilleurs avec `score`, `keyword_score` et `vector_score`. `keyword` : part des termes de la requête trouvés ; `vector` : similarité cosinus avec l'embedding de la requête (seulement les éléments vectorisés avec le modèle courant) ; `hybrid` (par défaut) : moitié de chaque. Le contenu rappelé est contrôlé contre l'injection de prompt ; les éléments bloqués sont écartés. La politique `memory.search` est évaluée. 400 `invalid_request` pour un mode inconnu. Audité sous `memory.search`.",
    "operationId": "search_memory_v1_memory_search_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SearchIn"
       }
      }
     },
     "description": "Requête, portée et type facultatifs, nombre de résultats et mode.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Search Memory V1 Memory Search Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rechercher dans la mémoire",
    "tags": [
     "memory"
    ]
   }
  },
  "/v1/memory/{memory_id}": {
   "delete": {
    "description": "Efface le contenu, la référence, le vecteur et les anciennes versions de l'élément et le marque supprimé. Permis pour un rédacteur ou propriétaire de l'élément, ou `tenant_admin` (sinon 403 `not_permitted_acl`). Un élément sous conservation légale répond 409 `legal_hold` (le refus est audité) ; un élément non supprimable répond 409 `conflict` sauf pour `tenant_admin`. 404 `not_found`. Audité sous `memory.delete`.",
    "operationId": "delete_memory_v1_memory__memory_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'élément de mémoire.",
      "in": "path",
      "name": "memory_id",
      "required": true,
      "schema": {
       "title": "Memory Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Memory V1 Memory  Memory Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un élément de mémoire",
    "tags": [
     "memory"
    ]
   },
   "get": {
    "description": "Renvoie l'élément sous forme de document Memory Contract avec son `history` (numéros et dates des anciennes versions). Un élément expiré, ou que l'appelant ne peut pas lire selon l'ACL ou l'habilitation, répond 404 `not_found`.",
    "operationId": "get_memory_v1_memory__memory_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'élément de mémoire.",
      "in": "path",
      "name": "memory_id",
      "required": true,
      "schema": {
       "title": "Memory Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Memory V1 Memory  Memory Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un élément de mémoire",
    "tags": [
     "memory"
    ]
   }
  },
  "/v1/meta": {
   "get": {
    "description": "Nom, version et version d'API de l'instance, langues prises en charge et langue par défaut, domaines chargés, et fonctions activées par la configuration : authentification OIDC, par clé d'API, SPIFFE et de développement, OpenBao, OpenFGA, OPA et LiteLLM (vrai quand leur adresse est renseignée). Sans authentification.",
    "operationId": "meta_v1_meta_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Meta V1 Meta Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Décrire l'instance du Control Plane",
    "tags": [
     "system"
    ]
   }
  },
  "/v1/models": {
   "get": {
    "description": "Modèles visibles par l'appelant (modèles partagés avec les surcharges du tenant appliquées, et modèles du tenant), éventuellement filtrés. Une seule page (`next_cursor` vaut toujours null). Demande un rôle de lecteur.",
    "operationId": "models_list_v1_models_get",
    "parameters": [
     {
      "description": "Seulement les modèles de ce statut : `approved`, `restricted`, `deprecated` ou `blocked`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les modèles de ce type de fournisseur, par exemple `openai` ou `vllm` (les alias sont acceptés).",
      "in": "query",
      "name": "provider",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Provider"
      }
     },
     {
      "description": "Seulement les modèles qui déclarent cette capacité, par exemple `chat`, `embeddings` ou `reasoning`.",
      "in": "query",
      "name": "capability",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Capability"
      }
     },
     {
      "description": "Seulement les modèles de ce slug de catégorie.",
      "in": "query",
      "name": "category",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Category"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les modèles enregistrés",
    "tags": [
     "llm"
    ]
   },
   "post": {
    "description": "Enregistre un modèle pour le tenant, ou un modèle partagé avec `scope: platform` (`platform_admin` seulement), et répond 201. Le corps est un Model Manifest v1 ou une spec seule ; il doit être valide (422 `invalid_manifest`) et sa catégorie doit exister. Un identifiant de modèle déjà enregistré dans la même portée répond 409 `model_exists`. Audité (`model.create`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "models_create_v1_models_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Un Model Manifest v1 (`kind: Model`, `metadata`, `spec`) ou la spec seule (`model_id`, `provider`, `provider_model_id`, `capabilities`, `context_window`, `status`...), avec les champs de la fiche, `pricing` et `scope` facultatifs.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un modèle",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/import": {
   "post": {
    "description": "Enregistre les modèles choisis lors d'une découverte. Idempotent : ceux déjà enregistrés (même identifiant, ou même fournisseur et même identifiant chez le fournisseur) sont ignorés et listés avec `already_registered`. Chaque élément est contrôlé d'abord ; un seul élément invalide refuse tout l'import avec 422 `import_invalid` et le problème de chaque élément. Chaque modèle et l'import sont audités. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "models_import_v1_models_import_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`provider_id` (identifiant ou nom du fournisseur, obligatoire), `models` (de 1 à 200 objets issus de la découverte : `provider_model_id` et, facultativement, l'identifiant du registre, la catégorie et les champs de la fiche), `scope` facultatif (`platform`).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Importer des modèles découverts",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/resolve": {
   "post": {
    "description": "Capability Resolver : traduit des paramètres d'appel normalisés (par exemple l'effort de raisonnement `none`, `low`, `medium`, `high`, `max`) en paramètres natifs du modèle, d'après ce que déclare son manifeste. Une valeur non prise en charge est refusée avec un code explicite ou ajustée avec un avertissement dans la réponse, jamais silencieusement. Modèle inconnu : 404 `model_not_found`. Demande un rôle de lecteur.",
    "operationId": "models_resolve_v1_models_resolve_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`model` (ou `model_id`) et les paramètres normalisés, soit dans `params`, soit à la racine du corps.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Résoudre les paramètres natifs d'appel",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}": {
   "delete": {
    "description": "Supprime l'entrée du modèle propre au tenant (la suppression d'une surcharge d'un modèle partagé fait réapparaître l'entrée partagée), ou l'entrée partagée s'il n'y a pas d'entrée du tenant ; l'entrée partagée demande `platform_admin` (403 `platform_only`). Répond 204 ; audité (`model.delete`). Modèle inconnu : 404 `model_not_found`. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "models_delete_v1_models__model_id__delete",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre (pas l'identifiant du modèle chez le fournisseur).",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un modèle du registre",
    "tags": [
     "llm"
    ]
   },
   "get": {
    "description": "Un modèle du registre, ou son Model Manifest v1 avec `format=manifest`. Modèle inconnu : 404 `model_not_found`. Demande un rôle de lecteur.",
    "operationId": "models_get_v1_models__model_id__get",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     },
     {
      "description": "`manifest` renvoie le Model Manifest v1 (sans les champs de la fiche) ; omettez-le pour l'entrée complète du registre.",
      "in": "query",
      "name": "format",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Format"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un modèle enregistré",
    "tags": [
     "llm"
    ]
   },
   "patch": {
    "description": "Modifie des champs de la spec du modèle, de la fiche (`category`, `display_name`, `knowledge_cutoff`) ou des prix ; l'identifiant du modèle ne change jamais. Une modification d'un modèle partagé par un tenant crée une surcharge propre au tenant. La spec fusionnée doit rester un Model Manifest v1 valide (422 `invalid_manifest`) et la catégorie doit exister. Audité (`model.update`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "models_patch_v1_models__model_id__patch",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Champs de la spec à modifier, champs de la fiche et `pricing` (par million de jetons : `input_per_mtok`, `output_per_mtok`, `cached_input_per_mtok`, `cache_write_per_mtok` ; par unité : `per_image`, `per_second`, `per_character` ; avec une devise). Utilisez `POST .../status` pour changer le statut.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un modèle enregistré",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/disable": {
   "post": {
    "description": "Kill switch du modèle pour le tenant : la passerelle le refuse immédiatement (403 `model_disabled`). Sur un modèle partagé, cela crée une surcharge propre au tenant. Audité (`model.disable`). Demande `tenant_admin`, `platform_admin`, `ai_platform_engineer` ou `security_officer`.",
    "operationId": "models_disable_v1_models__model_id__disable_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Désactiver un modèle (kill switch)",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/enable": {
   "post": {
    "description": "Lève le kill switch du modèle pour le tenant. Audité (`model.enable`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "models_enable_v1_models__model_id__enable_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réactiver un modèle désactivé",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/rollback": {
   "post": {
    "description": "Pendant un canari, l'interrompt : la version active garde tout le trafic. Sinon, rétablit la version qui était active avant la dernière publication complète. La version quittée devient `rolled_back`, l'étape est ajoutée à l'historique de déploiement et auditée (`model.rollout.rollback`). Rien à annuler : 409 `model_rollback_unavailable`. Demande un principal de tenant avec `tenant_admin` ou `platform_admin`.",
    "operationId": "versions_rollback_v1_models__model_id__rollback_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "additionalProperties": true,
          "type": "object"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "`{reason}` facultatif, inscrit dans l'historique de déploiement et le journal d'audit."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Revenir à la version précédente d'un modèle",
    "tags": [
     "llm",
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/rollout": {
   "get": {
    "description": "Version active, version canari et part du canari du modèle dans le tenant, avec tout l'historique de déploiement (publication, canari, retour arrière, acteur, raison, approbation). Demande un principal de tenant avec `tenant_admin`, `platform_admin`, `security_officer`, `finops`, `developer`, `auditor` ou `viewer`.",
    "operationId": "rollout_get_v1_models__model_id__rollout_get",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le déploiement d'un modèle",
    "tags": [
     "llm",
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/status": {
   "post": {
    "description": "Fixe le statut à `approved`, `restricted`, `deprecated` ou `blocked` (422 `status_invalid` sinon). Pour un tenant, une promotion vers `approved` n'est pas appliquée tout de suite : une demande d'approbation est ouverte et la réponse est 202 avec elle ; le statut change une fois la demande approuvée. Les autres changements s'appliquent immédiatement et sont audités (`model.status`, avec l'ancien et le nouveau statut). Demande `tenant_admin`, `platform_admin`, `ai_platform_engineer` ou `security_officer`.",
    "operationId": "models_status_v1_models__model_id__status_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`status` (`approved`, `restricted`, `deprecated` ou `blocked`) et une `reason` facultative.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Changer le statut d'un modèle",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/versions": {
   "get": {
    "description": "Versions du modèle dans le tenant, de la plus ancienne à la plus récente, avec licence, source, empreinte et taille des poids, capacités, statut (`draft`, `pending_approval`, `canary`, `active`, `superseded`, `rolled_back`) et approbation. Demande un principal de tenant avec `tenant_admin`, `platform_admin`, `security_officer`, `finops`, `developer`, `auditor` ou `viewer`.",
    "operationId": "versions_list_v1_models__model_id__versions_get",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions d'un modèle",
    "tags": [
     "llm",
     "llm"
    ]
   },
   "post": {
    "description": "Enregistre une version du modèle dans le tenant au statut `draft` et répond 201. La version demande une licence (expression SPDX, ou référence de conditions commençant par `https://` ou `LicenseRef-`) et la provenance des poids ; des champs invalides répondent 422 `model_version_invalid` avec leurs noms, une version existante 409 `model_version_exists`. Audité (`model.version.create`). Demande un principal de tenant avec `tenant_admin` ou `platform_admin`.",
    "operationId": "versions_create_v1_models__model_id__versions_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`version` (de 1 à 64 caractères : lettres, chiffres, points, tirets, soulignés, plus), `licence_spdx` ou `licence_terms_ref`, `weights` avec `source` (`https://`, `oci://`, `hf://`, `s3://` ou `registry://`), `digest` (`sha256:` et 64 caractères hexadécimaux) et `size_bytes` (entier positif), `capabilities` facultatif (liste de textes).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer une version de modèle",
    "tags": [
     "llm",
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/versions/{version}/approval": {
   "post": {
    "description": "Ouvre une demande d'approbation (action `model_version_activation`) pour une version `draft` et répond 202 avec son identifiant ; la version passe à `pending_approval`. Les approbateurs sont `tenant_admin` et `security_officer`, et le demandeur ne peut pas approuver sa propre demande (séparation des tâches). Une version qui n'est pas un brouillon répond 409 `model_version_state`. Audité. Demande un principal de tenant avec `tenant_admin` ou `platform_admin`.",
    "operationId": "versions_request_approval_v1_models__model_id__versions__version__approval_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     },
     {
      "description": "Version à approuver, telle qu'enregistrée.",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "additionalProperties": true,
          "type": "object"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "`{reason}` facultatif, présenté aux approbateurs."
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander l'approbation d'une version",
    "tags": [
     "llm",
     "llm"
    ]
   }
  },
  "/v1/models/{model_id}/versions/{version}/rollout": {
   "post": {
    "description": "Publie une version approuvée entièrement (`canary_percent` à 100, par défaut) ou en canari pour 1 à 99 pour cent du trafic, choisi de façon déterministe selon l'appelant (même session ou même principal, même version). Demande un principal de tenant avec `tenant_admin` ou `platform_admin`.\n\nÉtapes :\n\n1. L'approbation de la version doit être accordée pour ce modèle et cette version (403 `model_version_not_approved`) ; l'approbateur et la date de décision sont recopiés sur la version.\n2. Une publication complète rend la version `active` et la précédente `superseded` ; un canari demande une version active de référence (409 `model_rollout_no_baseline`).\n3. L'étape est ajoutée à l'historique de déploiement et auditée (`model.rollout.publish` ou `model.rollout.canary`).\n\nPublier la version déjà active répond 409 `model_version_state` ; une part invalide 422 `parameter_invalid`.",
    "operationId": "versions_rollout_v1_models__model_id__versions__version__rollout_post",
    "parameters": [
     {
      "description": "Identifiant du modèle dans le registre.",
      "in": "path",
      "name": "model_id",
      "required": true,
      "schema": {
       "title": "Model Id",
       "type": "string"
      }
     },
     {
      "description": "Version approuvée à publier.",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`canary_percent` (entier de 1 à 100, 100 par défaut) et une `reason` facultative.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier une version de modèle",
    "tags": [
     "llm",
     "llm"
    ]
   }
  },
  "/v1/multimodal/diarize": {
   "post": {
    "description": "Lance un travail de diarisation (qui a parlé quand) sur un média audio ou un audio fourni directement (201, le travail). Seuls `min_speakers` et `max_speakers` s'appliquent ici (`word_timestamps` répond 422 `parameter_unsupported`) ; `min_speakers` ne doit pas dépasser `max_speakers` (422 `parameter_invalid`) ; une option que le fournisseur configuré ne peut pas honorer répond 422 `parameter_unsupported`. Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "diarize_v1_multimodal_diarize_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SttIn"
       }
      }
     },
     "description": "Entrée audio et bornes du nombre de locuteurs.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Diarize V1 Multimodal Diarize Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Diariser un audio",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/dlp": {
   "post": {
    "description": "Inspecte `text`, le texte extrait d'un média enregistré, ou un contenu fourni directement (analysé d'abord), avec les règles DLP du tenant. Renvoie `source`, `extracted_text_chars`, le `text` traité, `findings`, `action` et `classification`. Rien n'est enregistré. Tout principal du tenant peut l'appeler. Audité sous `multimodal.dlp`.",
    "operationId": "multimodal_dlp_v1_multimodal_dlp_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MultimodalDlpIn"
       }
      }
     },
     "description": "Texte, média enregistré ou contenu direct, et mode DLP.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Multimodal Dlp V1 Multimodal Dlp Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exécuter la DLP sur un texte ou un média",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/images/edit": {
   "post": {
    "description": "Lance un travail de retouche d'image à partir d'un média image ou d'une image fournie directement et d'un prompt (201, le travail). L'entrée doit être une image (422 `unsupported_media`). Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "images_edit_v1_multimodal_images_edit_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ImageEditIn"
       }
      }
     },
     "description": "Image d'entrée, prompt, nombre d'images, taille et modèle.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Images Edit V1 Multimodal Images Edit Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retoucher une image",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/images/generate": {
   "post": {
    "description": "Lance un travail de génération d'images (201, le travail). Le prompt est d'abord inspecté par la DLP en mode blocage : des données personnelles répondent 422 `dlp_blocked`. L'usage est compté par image. Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "images_generate_v1_multimodal_images_generate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ImageGenIn"
       }
      }
     },
     "description": "Prompt, nombre d'images, taille et modèle.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Images Generate V1 Multimodal Images Generate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Générer des images",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/jobs": {
   "get": {
    "description": "Travaux du tenant, du plus récent au plus ancien, paginés avec `cursor`, filtrables par statut et opération.",
    "operationId": "list_jobs_v1_multimodal_jobs_get",
    "parameters": [
     {
      "description": "Seulement ce statut : `queued`, `running`, `succeeded`, `failed` ou `cancelled`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement cette opération, par exemple `stt` ou `image_generation`.",
      "in": "query",
      "name": "operation",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Operation"
      }
     },
     {
      "description": "Nombre maximal de travaux dans une page (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Jobs V1 Multimodal Jobs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les travaux multimodaux",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/jobs/{job_id}": {
   "get": {
    "description": "Renvoie le travail : opération, statut, paramètres d'entrée, sortie, erreur, adaptateur, unités et coût. 404 `not_found`.",
    "operationId": "get_job_v1_multimodal_jobs__job_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du travail.",
      "in": "path",
      "name": "job_id",
      "required": true,
      "schema": {
       "title": "Job Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Job V1 Multimodal Jobs  Job Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un travail multimodal",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/jobs/{job_id}/complete": {
   "post": {
    "description": "Appelé par les workers GPU (identités d'agent ou de service) pour signaler le résultat d'un travail en file : statut `running`, `succeeded`, `failed` ou `cancelled` (sinon 400 `invalid_request`), avec sortie, erreur et unités mesurées ; la réservation de quota est validée ou libérée. Un travail terminé répond 409 `conflict`. Demande `agent` ou `tenant_admin`. 404 `not_found`.",
    "operationId": "complete_job_v1_multimodal_jobs__job_id__complete_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du travail.",
      "in": "path",
      "name": "job_id",
      "required": true,
      "schema": {
       "title": "Job Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/JobCompleteIn"
       }
      }
     },
     "description": "Statut, sortie, erreur et unités mesurées du travail.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Complete Job V1 Multimodal Jobs  Job Id  Complete Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Signaler le résultat d'un travail en file",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/stt": {
   "post": {
    "description": "Lance un travail de transcription sur un média audio ou un audio fourni directement (201, le travail). La transcription passe par la DLP avec `dlp_mode`. Seul `word_timestamps` s'applique ici (les bornes de locuteurs répondent 422 `parameter_unsupported`). L'usage est compté en secondes d'audio (`duration_seconds`, ou lu dans un en-tête WAV). Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "stt_v1_multimodal_stt_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SttIn"
       }
      }
     },
     "description": "Entrée audio, langue, modèle, mode DLP et options.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Stt V1 Multimodal Stt Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Transcrire la parole en texte",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/tts": {
   "post": {
    "description": "Lance un travail de synthèse vocale (201, le travail). Le texte est inspecté par la DLP avec `dlp_mode` (`block` par défaut : des données personnelles répondent 422 `dlp_blocked` ; `mask` prononce le texte masqué). L'usage est compté en caractères. Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "tts_v1_multimodal_tts_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TtsIn"
       }
      }
     },
     "description": "Texte, voix, modèle et mode DLP.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Tts V1 Multimodal Tts Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Synthétiser la parole à partir d'un texte",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/video/generate": {
   "post": {
    "description": "Lance un travail de génération vidéo et répond 202 avec le travail ; suivez-le avec `GET /v1/multimodal/jobs/{job_id}`. L'usage est compté en secondes de vidéo. Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "video_generate_v1_multimodal_video_generate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/VideoGenIn"
       }
      }
     },
     "description": "Prompt, durée, taille et modèle.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Video Generate V1 Multimodal Video Generate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Générer une vidéo",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/video/process": {
   "post": {
    "description": "Extrait des images et/ou la piste audio d'un média vidéo ou d'une vidéo fournie directement (201, le travail ; mis en file quand ffmpeg n'est pas disponible). `operations` n'accepte que `frames` et `audio` (400 `invalid_request`). Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "video_process_v1_multimodal_video_process_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/VideoProcessIn"
       }
      }
     },
     "description": "Vidéo d'entrée, opérations, fréquence d'images et fréquence d'échantillonnage audio.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Video Process V1 Multimodal Video Process Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Traiter une vidéo",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/multimodal/vision": {
   "post": {
    "description": "Lance un travail d'analyse d'image sur un média image ou une image fournie directement avec un prompt (201, le travail). La réponse passe par la DLP avec `dlp_mode`. `detail` répond 422 `parameter_unsupported` quand le fournisseur configuré ne peut pas l'honorer ; une entrée qui n'est pas une image répond 422 `unsupported_media`. Demande `tenant_admin`, `developer` ou `agent`.",
    "operationId": "vision_v1_multimodal_vision_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/VisionIn"
       }
      }
     },
     "description": "Image d'entrée, prompt, modèle, mode DLP et niveau de détail.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Vision V1 Multimodal Vision Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Analyser une image",
    "tags": [
     "multimodal"
    ]
   }
  },
  "/v1/navin-library": {
   "get": {
    "description": "Version du paquet navin embarqué, nombre d'éléments par type et fichiers de règles lus dans un dépôt. 503 `navin_library_unavailable` quand le catalogue n'est pas livré.",
    "operationId": "navin_library_v1_navin_library_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Navin Library V1 Navin Library Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire le résumé de la bibliothèque",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/navin-library/{kind}": {
   "get": {
    "description": "Éléments d'un type : skills, plugins, commands, agents, mcp-presets ou channels (`q` cherche dans le nom et la description, `category` filtre les skills, plugins et presets MCP). Un type inconnu répond 404 `navin_library_kind_unknown`.",
    "operationId": "navin_library_items_v1_navin_library__kind__get",
    "parameters": [
     {
      "description": "Type d'élément : `skills`, `plugins`, `commands`, `agents`, `mcp-presets` ou `channels`.",
      "in": "path",
      "name": "kind",
      "required": true,
      "schema": {
       "title": "Kind",
       "type": "string"
      }
     },
     {
      "description": "Texte cherché dans le nom et la description.",
      "in": "query",
      "name": "q",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Q"
      }
     },
     {
      "description": "Seulement cette catégorie (skills, plugins et presets MCP).",
      "in": "query",
      "name": "category",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Category"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Navin Library Items V1 Navin Library  Kind  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les éléments d'un type",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/navin-library/{kind}/{name}": {
   "get": {
    "description": "Un élément avec son contenu (SKILL.md et fichiers d'un skill, plugin.json et README d'un plugin, Markdown d'une commande ou d'un agent, preset MCP ou contrat de canal complet). 404 `navin_library_item_not_found`.",
    "operationId": "navin_library_item_v1_navin_library__kind___name__get",
    "parameters": [
     {
      "description": "Type d'élément : `skills`, `plugins`, `commands`, `agents`, `mcp-presets` ou `channels`.",
      "in": "path",
      "name": "kind",
      "required": true,
      "schema": {
       "title": "Kind",
       "type": "string"
      }
     },
     {
      "description": "Nom de l'élément.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Navin Library Item V1 Navin Library  Kind   Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un élément de la bibliothèque",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/operations/{operation_id}": {
   "get": {
    "description": "L'opération (type, état `pending` ou `accepted`, méthode, chemin, statut HTTP, clé d'idempotence, ressource suivie, créateur et dates), sa réponse acceptée et l'état en direct de la ressource suivie. La ressource est lue avec les identifiants de l'appelant par la route de la ressource, donc l'autorisation de la ressource s'applique : si cette lecture échoue, `result` reste null et `result_error.http_status` donne le statut. `status` vaut `pending` pendant l'exécution de la requête, puis le `status` ou le `state` de la ressource. Demande un principal de tenant (403 `tenant_required`) ; une opération créée par quelqu'un d'autre répond 404 `not_found` sauf si l'appelant détient `tenant_admin`, `platform_admin`, `auditor` ou `security_officer`.",
    "operationId": "get_operation_v1_operations__operation_id__get",
    "parameters": [
     {
      "description": "Identifiant de l'opération, tel que renvoyé dans l'en-tête `Operation-Id`.",
      "in": "path",
      "name": "operation_id",
      "required": true,
      "schema": {
       "title": "Operation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Operation V1 Operations  Operation Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Suivre une opération longue",
    "tags": [
     "operations"
    ]
   }
  },
  "/v1/organizations": {
   "get": {
    "description": "Liste les organisations du tenant, paginées. Ouvert à tout membre du tenant.",
    "operationId": "list_organizations_v1_organizations_get",
    "parameters": [
     {
      "description": "Nombre maximal d'organisations dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Organizations V1 Organizations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les organisations",
    "tags": [
     "tenancy"
    ]
   },
   "post": {
    "description": "Crée une organisation dans le tenant. Exige `tenant_admin`. Un identifiant court déjà utilisé répond 409 `conflict`. Écrit l'événement d'audit `organization.create`.",
    "operationId": "create_organization_v1_organizations_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/OrganizationIn"
       }
      }
     },
     "description": "Identifiant court, nom, description et métadonnées de l'organisation.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Organization V1 Organizations Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une organisation",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/organizations/{organization_id}": {
   "patch": {
    "description": "Modifie le nom, la description, le statut (`active` ou `archived`) ou les métadonnées d'une organisation. Exige `tenant_admin`. Envoyez `expected_version` pour détecter une modification concurrente (409 `version_conflict`). Écrit l'événement d'audit `organization.update`.",
    "operationId": "patch_organization_v1_organizations__organization_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'organisation.",
      "in": "path",
      "name": "organization_id",
      "required": true,
      "schema": {
       "title": "Organization Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/OrganizationPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Organization V1 Organizations  Organization Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une organisation",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/platform-manager/backup/backups": {
   "get": {
    "description": "Sauvegardes avec leur SHA-256, leur taille et le résultat de leur dernière vérification, relayées depuis le Manager (`backup:read` côté Manager). Demande `platform_admin`.",
    "operationId": "list_backups_v1_platform_manager_backup_backups_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Backups V1 Platform Manager Backup Backups Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les sauvegardes",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/backup/jobs": {
   "get": {
    "description": "Tâches de sauvegarde du Manager avec leur statut, relayées au nom de l'utilisateur (`backup:read` côté Manager). Demande `platform_admin`.",
    "operationId": "list_backup_jobs_v1_platform_manager_backup_jobs_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Backup Jobs V1 Platform Manager Backup Jobs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les tâches de sauvegarde",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/backup/plans": {
   "get": {
    "description": "Politiques de sauvegarde (périmètre, intervalle, rétention, immuabilité) et alertes de retard ou d'échec du Manager. Demande `platform_admin`.",
    "operationId": "list_backup_plans_v1_platform_manager_backup_plans_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Backup Plans V1 Platform Manager Backup Plans Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les plans de sauvegarde",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/backup/restores": {
   "get": {
    "description": "Plans de restauration avec leur statut et leur nombre d'approbations (deux approbateurs autres que le demandeur). Demande `platform_admin`.",
    "operationId": "list_restores_v1_platform_manager_backup_restores_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Restores V1 Platform Manager Backup Restores Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les plans de restauration",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Planifie la restauration d'une sauvegarde et répond 201 avec le plan ; le plan précède toujours une exécution et demande deux approbateurs autres que le demandeur. Le Manager compare les versions cibles avec la sauvegarde. L'exécution de la restauration reste dans la console du Manager. Audité (`platform_manager.restore.plan`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "plan_restore_v1_platform_manager_backup_restores_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RestorePlanIn"
       }
      }
     },
     "description": "Sauvegarde à restaurer et environnement cible.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Plan Restore V1 Platform Manager Backup Restores Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Planifier une restauration",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/backup/restores/{plan_id}/approve": {
   "post": {
    "description": "Ajoute l'approbation de l'utilisateur à un plan de restauration ; le Manager refuse l'auto-approbation et compte deux approbateurs distincts. Audité (`platform_manager.restore.approve`) avec le nouveau statut et le nombre d'approbations. Demande `platform_admin`.",
    "operationId": "approve_restore_v1_platform_manager_backup_restores__plan_id__approve_post",
    "parameters": [
     {
      "description": "Identifiant du plan de restauration dans le Manager.",
      "in": "path",
      "name": "plan_id",
      "required": true,
      "schema": {
       "title": "Plan Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Approve Restore V1 Platform Manager Backup Restores  Plan Id  Approve Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Approuver un plan de restauration",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/catalog": {
   "get": {
    "description": "Services du catalogue du Manager (nom, version et catégories de la pile) avec leurs noms affichés, catégorie, version, composants, modes, méthodes, prérequis, mode de livraison (`install`, `connect`, `job`, `package`), couche et licence. Demande `platform_admin`.",
    "operationId": "list_catalog_v1_platform_manager_catalog_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Catalog V1 Platform Manager Catalog Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister le catalogue du Manager",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/catalog/{service}": {
   "get": {
    "description": "Descripteur d'installation d'un service du catalogue : modes avec leurs composants et cardinalités, méthodes, prérequis, sections et paramètres avec leurs valeurs par défaut. Un paramètre secret ne porte jamais de valeur par défaut et indique s'il est généré ; les gabarits et artefacts ne sortent jamais de la passerelle. Demande `platform_admin`.",
    "operationId": "get_catalog_service_v1_platform_manager_catalog__service__get",
    "parameters": [
     {
      "description": "Nom du service du catalogue.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Catalog Service V1 Platform Manager Catalog  Service  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le descripteur d'installation d'un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters": {
   "get": {
    "description": "Clusters (environnements) du Navin Manager visibles par l'utilisateur. Demande `platform_admin`.",
    "operationId": "list_clusters_v1_platform_manager_clusters_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Clusters V1 Platform Manager Clusters Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les clusters",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Crée un cluster (environnement) dans le Manager et répond 201 ; l'utilisateur délégué doit détenir `clusters:create`. Audité (`platform_manager.cluster_create`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "create_cluster_v1_platform_manager_clusters_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ClusterIn"
       }
      }
     },
     "description": "Nom, mode, description et profil du cluster.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Cluster V1 Platform Manager Clusters Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un cluster",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/enrollment-tokens": {
   "get": {
    "description": "Jetons d'amorçage d'un cluster : préfixe, échéance, utilisations et révocation (le jeton lui-même n'est jamais listé). Demande `platform_admin`.",
    "operationId": "list_enrollment_tokens_v1_platform_manager_clusters__cluster_id__enrollment_tokens_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Enrollment Tokens V1 Platform Manager Clusters  Cluster Id  Enrollment Tokens Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les jetons d'enrôlement",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Jeton d'amorçage de l'agent de nœud et commande d'enregistrement à lancer sur chaque machine, renvoyés avec 201 ; affichés une seule fois, le jeton et la commande ne sont jamais écrits dans l'audit (seuls son identifiant et son échéance). Demande `hosts:manage` dans le Manager. Demande `platform_admin`.",
    "operationId": "create_enrollment_token_v1_platform_manager_clusters__cluster_id__enrollment_tokens_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EnrollmentTokenIn"
       }
      }
     },
     "description": "Durée de vie et nombre d'utilisations du jeton.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Enrollment Token V1 Platform Manager Clusters  Cluster Id  Enrollment Tokens Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un jeton d'enrôlement",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/enrollment-tokens/{token_id}": {
   "delete": {
    "description": "Révoque un jeton d'amorçage de l'agent de nœud dans le Manager (`hosts:manage` côté Manager) et répond 204 ; les machines déjà enrôlées ne sont pas touchées. Audité (`platform_manager.enrollment_token_revoke`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "revoke_enrollment_token_v1_platform_manager_clusters__cluster_id__enrollment_tokens__token_id__delete",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant du jeton d'enrôlement, tel que listé.",
      "in": "path",
      "name": "token_id",
      "required": true,
      "schema": {
       "title": "Token Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer un jeton d'enrôlement",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/hosts/ssh-bootstrap": {
   "post": {
    "description": "Installe l'agent de nœud sur des machines par SSH (opération Manager `host.ssh_bootstrap`, `hosts:manage` côté Manager) et répond 202 avec l'opération à suivre. La clé privée est relayée au Manager, qui la garde en mémoire pour les seules tâches d'amorçage ; le Control Plane ne l'enregistre ni ne l'audite jamais (l'audit garde hôtes, utilisateur, port, sudo et empreintes). Demande `platform_admin`.",
    "operationId": "ssh_bootstrap_v1_platform_manager_clusters__cluster_id__hosts_ssh_bootstrap_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SshBootstrapIn"
       }
      }
     },
     "description": "Machines, compte SSH et empreintes des clés d'hôte.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Ssh Bootstrap V1 Platform Manager Clusters  Cluster Id  Hosts Ssh Bootstrap Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Installer l'agent de nœud par SSH",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/nodes": {
   "get": {
    "description": "Hôtes du cluster tels que le Manager les connaît (état, caractéristiques, agent). Demande `platform_admin`.",
    "operationId": "list_nodes_v1_platform_manager_clusters__cluster_id__nodes_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Nodes V1 Platform Manager Clusters  Cluster Id  Nodes Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les machines d'un cluster",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/operations": {
   "get": {
    "description": "Opérations du Manager sur le cluster (installation, démarrage, arrêt, contrôles préalables, amorçage...) avec leur cible et leur statut. Demande `platform_admin`.",
    "operationId": "list_operations_v1_platform_manager_clusters__cluster_id__operations_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Operations V1 Platform Manager Clusters  Cluster Id  Operations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les opérations d'un cluster",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services": {
   "get": {
    "description": "Services du cluster avec les cinq champs d'état (souhaité, observé, santé, opération, sécurité), l'état administratif et sa raison, le nombre d'instances démarrées et total, `drift` (l'observé diffère du souhaité), le mode de livraison et la connexion d'un service connecté. Demande `platform_admin`.",
    "operationId": "list_services_v1_platform_manager_clusters__cluster_id__services_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Services V1 Platform Manager Clusters  Cluster Id  Services Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les services d'un cluster",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Installe des services du catalogue sur les hôtes choisis, au nom de l'utilisateur, et répond 202 avec l'identifiant de l'opération ; le Manager contrôle `services:install`, les modes, méthodes, placements et la configuration, puis lance l'opération d'installation. L'audit garde services, modes, méthodes, hôtes et clés configurées, jamais les valeurs de configuration. Demande `platform_admin`.",
    "operationId": "install_services_v1_platform_manager_clusters__cluster_id__services_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__platform_manager__InstallIn"
       }
      }
     },
     "description": "Services à installer et options d'installation.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Install Services V1 Platform Manager Clusters  Cluster Id  Services Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Installer des services du catalogue",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/connect": {
   "post": {
    "description": "Enregistre un service existant du client au nom de l'utilisateur et répond 201 ; le Manager contrôle `services:install` et la section de connexion du descripteur, puis vérifie sa santé. Un service connecté n'est jamais installé, démarré ni arrêté par le Manager. Les champs secrets sont relayés et scellés par le Manager ; l'audit ne garde que le point d'accès, le TLS, la méthode et les noms des champs. Demande `platform_admin`.",
    "operationId": "connect_service_v1_platform_manager_clusters__cluster_id__services_connect_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectIn"
       }
      }
     },
     "description": "Le service à connecter.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Connect Service V1 Platform Manager Clusters  Cluster Id  Services Connect Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Connecter un service existant",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/connect/validate": {
   "post": {
    "description": "Simulation d'une connexion par le Manager : répond `ok`, les erreurs (service, champ, code, messages dans les deux langues) et le résultat du contrôle de santé ; rien n'est enregistré. Demande `platform_admin`.",
    "operationId": "validate_connect_v1_platform_manager_clusters__cluster_id__services_connect_validate_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectIn"
       }
      }
     },
     "description": "Le service à connecter, comme pour la connexion réelle.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Validate Connect V1 Platform Manager Clusters  Cluster Id  Services Connect Validate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Valider la connexion d'un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/validate": {
   "post": {
    "description": "Simulation d'une installation par le planificateur du Manager : répond `ok`, les erreurs (service, champ, code, messages dans les deux langues) et le plan qu'il exécuterait ; rien n'est installé. Demande `platform_admin`.",
    "operationId": "validate_install_v1_platform_manager_clusters__cluster_id__services_validate_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__platform_manager__InstallIn"
       }
      }
     },
     "description": "L'installation à contrôler, comme pour l'installation réelle.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Validate Install V1 Platform Manager Clusters  Cluster Id  Services Validate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Valider une installation de services",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}": {
   "get": {
    "description": "Un service avec ses champs d'état et ses nombres d'instances, son état de sécurité (TLS, nombre de secrets renseignés sur le total) et la fiche complète du Manager. Demande `platform_admin`.",
    "operationId": "get_service_v1_platform_manager_clusters__cluster_id__services__service__get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Service V1 Platform Manager Clusters  Cluster Id  Services  Service  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la fiche d'un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/config": {
   "get": {
    "description": "Version de configuration courante d'un service telle que le Manager la présente (valeurs secrètes masquées), avec recommandations, groupes et hôtes en retard. Demande `configs:read` dans le Manager ; la modification reste dans la console du Manager. Demande `platform_admin`.",
    "operationId": "service_config_v1_platform_manager_clusters__cluster_id__services__service__config_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Service Config V1 Platform Manager Clusters  Cluster Id  Services  Service  Config Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la configuration d'un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/config/diff": {
   "get": {
    "description": "Paramètres modifiés entre deux versions de configuration d'un service, une entrée par paramètre ; un paramètre secret indique seulement s'il est renseigné dans chaque version, jamais sa valeur. Demande `platform_admin`.",
    "operationId": "config_diff_v1_platform_manager_clusters__cluster_id__services__service__config_diff_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     },
     {
      "description": "Version de configuration la plus ancienne, entier d'au moins 1 (obligatoire).",
      "in": "query",
      "name": "from",
      "required": true,
      "schema": {
       "minimum": 1,
       "title": "From",
       "type": "integer"
      }
     },
     {
      "description": "Version de configuration la plus récente, entier d'au moins 1 (obligatoire).",
      "in": "query",
      "name": "to",
      "required": true,
      "schema": {
       "minimum": 1,
       "title": "To",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Config Diff V1 Platform Manager Clusters  Cluster Id  Services  Service  Config Diff Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Comparer deux versions de configuration",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/config/versions": {
   "get": {
    "description": "Historique des versions de configuration d'un service dans le Manager (`configs:read` côté Manager). Demande `platform_admin`.",
    "operationId": "service_config_versions_v1_platform_manager_clusters__cluster_id__services__service__config_versions_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Service Config Versions V1 Platform Manager Clusters  Cluster Id  Services  Service  Config Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions de configuration",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/connection": {
   "delete": {
    "description": "Oublie un service connecté (le service lui-même n'est pas touché) ; le Manager refuse tant qu'un consommateur l'utilise. Audité (`platform_manager.disconnect`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "disconnect_service_v1_platform_manager_clusters__cluster_id__services__service__connection_delete",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service connecté.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Disconnect Service V1 Platform Manager Clusters  Cluster Id  Services  Service  Connection Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déconnecter un service connecté",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/dashboard": {
   "get": {
    "description": "Tableau de bord de supervision d'un service relayé depuis le Manager (`cluster:read` côté Manager) : instances, santé et derniers contrôles, disponibilité, machines détectées avec leurs composants et métriques, alertes, opérations, configuration et, pour un service connecté, sa connexion. Demande `platform_admin`.",
    "operationId": "service_dashboard_v1_platform_manager_clusters__cluster_id__services__service__dashboard_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     },
     {
      "description": "Début de la fenêtre, ISO 8601, 64 caractères au plus. Par défaut : il y a une heure.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 64,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Fin de la fenêtre, ISO 8601, 64 caractères au plus. Par défaut : maintenant.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 64,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     },
     {
      "description": "Résolution des séries de métriques en secondes, de 10 à 86400, 60 par défaut.",
      "in": "query",
      "name": "step",
      "required": false,
      "schema": {
       "default": 60,
       "maximum": 86400,
       "minimum": 10,
       "title": "Step",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Service Dashboard V1 Platform Manager Clusters  Cluster Id  Services  Service  Dashboard Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le tableau de bord d'un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/mode": {
   "post": {
    "description": "Mode d'exploitation d'un service (drainage, mise à zéro, suspension de sécurité, arrêt d'urgence, désactivation, quarantaine, maintenance, activation), transmis au nom de l'utilisateur ; le Manager fait respecter l'état qui en résulte et peut exiger une approbation. Audité (`platform_manager.mode.<mode>`) avec l'identifiant de l'opération et l'état administratif obtenu, autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "set_mode_v1_platform_manager_clusters__cluster_id__services__service__mode_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ModeIn"
       }
      }
     },
     "description": "Le mode à appliquer et ses options.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Set Mode V1 Platform Manager Clusters  Cluster Id  Services  Service  Mode Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir le mode d'exploitation d'un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/services/{service}/operations": {
   "post": {
    "description": "Transmet une opération (démarrage, arrêt, redémarrage, redémarrage progressif, réinstallation) avec l'identité de l'utilisateur et répond 202 avec l'identifiant de l'opération ; le Manager décide (RBAC, approbations, règles d'arrêt). Audité (`platform_manager.<action>`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "request_operation_v1_platform_manager_clusters__cluster_id__services__service__operations_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du service.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/OperationIn"
       }
      }
     },
     "description": "L'opération et sa portée.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Operation V1 Platform Manager Clusters  Cluster Id  Services  Service  Operations Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander une opération sur un service",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/clusters/{cluster_id}/support-bundles": {
   "get": {
    "description": "Paquets de support assainis d'un cluster ; un paquet se télécharge depuis la console du Manager, jamais par le Control Plane. Demande `platform_admin`.",
    "operationId": "list_support_bundles_v1_platform_manager_clusters__cluster_id__support_bundles_get",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Support Bundles V1 Platform Manager Clusters  Cluster Id  Support Bundles Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les paquets de support",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Demande au Manager un paquet de support assaini (`cluster:manage` côté Manager) et répond 202 avec l'opération à suivre et l'identifiant du paquet ; le paquet se télécharge depuis la console du Manager. Audité (`platform_manager.support_bundle`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "request_support_bundle_v1_platform_manager_clusters__cluster_id__support_bundles_post",
    "parameters": [
     {
      "description": "Identifiant du cluster dans le Navin Manager.",
      "in": "path",
      "name": "cluster_id",
      "required": true,
      "schema": {
       "title": "Cluster Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Support Bundle V1 Platform Manager Clusters  Cluster Id  Support Bundles Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander un paquet de support",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/connections": {
   "get": {
    "description": "Connexions existantes des types que le portail lie (PostgreSQL, Redis, OIDC), filtrées par type si fourni ; le secret indique seulement s'il est renseigné. Demande `connections:read` dans le Manager. Demande `platform_admin`.",
    "operationId": "list_connections_v1_platform_manager_connections_get",
    "parameters": [
     {
      "description": "Seulement les connexions de ce type : `postgresql`, `redis` ou `oidc`.",
      "in": "query",
      "name": "type",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "pattern": "^(postgresql|redis|oidc)$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Type"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Connections V1 Platform Manager Connections Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les connexions",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Enregistre un PostgreSQL, un Redis ou un émetteur OIDC existant comme connexion du Manager et répond 201 (le secret est scellé par le Manager et jamais renvoyé). Les formulaires d'installation y lient ensuite un service. Demande `connections:manage` dans le Manager. L'audit garde nom, type, clés des paramètres et présence d'un secret. Demande `platform_admin`.",
    "operationId": "create_connection_v1_platform_manager_connections_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectionIn"
       }
      }
     },
     "description": "La connexion à enregistrer.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Connection V1 Platform Manager Connections Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer une connexion",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/connections/validate": {
   "post": {
    "description": "Test réel du protocole d'une connexion avant son enregistrement : répond `ok`, la latence en millisecondes, la version du serveur, le code d'erreur et les messages ; rien n'est enregistré et le secret n'est jamais renvoyé. Audité sans le secret. Demande `platform_admin`.",
    "operationId": "validate_connection_v1_platform_manager_connections_validate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectionTestIn"
       }
      }
     },
     "description": "Type, paramètres et secret de la connexion à tester.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Validate Connection V1 Platform Manager Connections Validate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Tester une connexion avant de l'enregistrer",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/connections/{connection_id}": {
   "delete": {
    "description": "Oublie une connexion et répond 204 ; le Manager refuse (409 `platform_manager_connection_in_use`) tant qu'un service y est lié. Demande `connections:manage` dans le Manager. Audité, autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "delete_connection_v1_platform_manager_connections__connection_id__delete",
    "parameters": [
     {
      "description": "Identifiant de la connexion dans le Manager.",
      "in": "path",
      "name": "connection_id",
      "required": true,
      "schema": {
       "title": "Connection Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une connexion",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/connections/{connection_id}/test": {
   "post": {
    "description": "Lance tout de suite le test du protocole d'une connexion enregistrée (son secret scellé reste dans le Manager). Audité. Demande `platform_admin`.",
    "operationId": "test_connection_v1_platform_manager_connections__connection_id__test_post",
    "parameters": [
     {
      "description": "Identifiant de la connexion dans le Manager.",
      "in": "path",
      "name": "connection_id",
      "required": true,
      "schema": {
       "title": "Connection Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Test Connection V1 Platform Manager Connections  Connection Id  Test Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Tester une connexion enregistrée",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/console": {
   "get": {
    "description": "Indique si le Manager est configuré, l'adresse de base de sa console (`EAI_PLATFORM_MANAGER_CONSOLE_URL`, sinon `EAI_PLATFORM_MANAGER_URL`) et les consoles d'exploitation (Opik, Grafana, Keycloak, LiteLLM, OpenSearch Dashboards) : depuis leur paramètre s'il est renseigné (source `setting`), sinon depuis le service du même nom installé ou connecté par le Manager (source `manager`). Une adresse de console qui n'est pas en https ou qui porte des identifiants n'est jamais renvoyée (`problem` en donne la raison). Demande `platform_admin`.",
    "operationId": "console_v1_platform_manager_console_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Console V1 Platform Manager Console Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Obtenir les liens des consoles",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/dr/exercises": {
   "get": {
    "description": "Exercices de PRA avec le RPO et le RTO observés et l'atteinte de chaque objectif (null quand non mesuré). Demande `platform_admin`.",
    "operationId": "list_dr_exercises_v1_platform_manager_dr_exercises_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Dr Exercises V1 Platform Manager Dr Exercises Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les exercices de PRA",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Demande un exercice de PRA d'un plan de PRA et répond 201 ; le Manager applique sa règle d'approbation (`dr:manage` côté Manager). La conclusion de l'exercice et une bascule réelle restent dans la console du Manager. Audité (`platform_manager.dr.exercise.request`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "request_dr_exercise_v1_platform_manager_dr_exercises_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ExerciseIn"
       }
      }
     },
     "description": "Le plan de PRA à exercer.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Dr Exercise V1 Platform Manager Dr Exercises Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander un exercice de PRA",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/dr/exercises/{exercise_id}/approve": {
   "post": {
    "description": "Ajoute l'approbation de l'utilisateur à un exercice de PRA ; le Manager refuse l'auto-approbation. Audité (`platform_manager.dr.exercise.approve`) avec le nouveau statut. Demande `platform_admin`.",
    "operationId": "approve_dr_exercise_v1_platform_manager_dr_exercises__exercise_id__approve_post",
    "parameters": [
     {
      "description": "Identifiant de l'exercice de PRA dans le Manager.",
      "in": "path",
      "name": "exercise_id",
      "required": true,
      "schema": {
       "title": "Exercise Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Approve Dr Exercise V1 Platform Manager Dr Exercises  Exercise Id  Approve Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Approuver un exercice de PRA",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/dr/plans": {
   "get": {
    "description": "Plans de PRA : mode, sites principal et secondaire, objectifs de RPO et de RTO du client (null tant qu'ils ne sont pas fixés). Demande `platform_admin`.",
    "operationId": "list_dr_plans_v1_platform_manager_dr_plans_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Dr Plans V1 Platform Manager Dr Plans Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les plans de PRA",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/hosts/{host_id}": {
   "get": {
    "description": "Une machine avec ses caractéristiques (système d'exploitation, ressources, version de l'agent, dernier signal de vie). Demande `platform_admin`.",
    "operationId": "get_host_v1_platform_manager_hosts__host_id__get",
    "parameters": [
     {
      "description": "Identifiant de la machine dans le Manager.",
      "in": "path",
      "name": "host_id",
      "required": true,
      "schema": {
       "title": "Host Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Host V1 Platform Manager Hosts  Host Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une machine",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/hosts/{host_id}/decommission": {
   "post": {
    "description": "Retire une machine sans composants (agent arrêté, certificat révoqué) et répond 202 avec l'identifiant de l'opération. Audité (`platform_manager.decommission`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "decommission_host_v1_platform_manager_hosts__host_id__decommission_post",
    "parameters": [
     {
      "description": "Identifiant de la machine dans le Manager.",
      "in": "path",
      "name": "host_id",
      "required": true,
      "schema": {
       "title": "Host Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Decommission Host V1 Platform Manager Hosts  Host Id  Decommission Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer une machine",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/hosts/{host_id}/facts/refresh": {
   "post": {
    "description": "Demande à l'agent de nœud de collecter à nouveau les caractéristiques de la machine (système d'exploitation, ressources...) et répond 202 avec l'identifiant de l'opération. Audité (`platform_manager.facts_refresh`), autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "refresh_facts_v1_platform_manager_hosts__host_id__facts_refresh_post",
    "parameters": [
     {
      "description": "Identifiant de la machine dans le Manager.",
      "in": "path",
      "name": "host_id",
      "required": true,
      "schema": {
       "title": "Host Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Refresh Facts V1 Platform Manager Hosts  Host Id  Facts Refresh Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Actualiser les caractéristiques d'une machine",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/hosts/{host_id}/preflight": {
   "get": {
    "description": "Résultats des derniers contrôles préalables d'une machine (contrôle, statut, message dans les deux langues). Demande `platform_admin`.",
    "operationId": "preflight_results_v1_platform_manager_hosts__host_id__preflight_get",
    "parameters": [
     {
      "description": "Identifiant de la machine dans le Manager.",
      "in": "path",
      "name": "host_id",
      "required": true,
      "schema": {
       "title": "Host Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Preflight Results V1 Platform Manager Hosts  Host Id  Preflight Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les résultats des contrôles préalables",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Lance les contrôles préalables d'une machine (opération Manager `host.preflight`, `hosts:manage` côté Manager) et répond 202 avec l'identifiant de l'opération ; lisez les résultats avec `GET .../preflight`. Audité, autorisé ou refusé. Demande `platform_admin`.",
    "operationId": "run_preflight_v1_platform_manager_hosts__host_id__preflight_post",
    "parameters": [
     {
      "description": "Identifiant de la machine dans le Manager.",
      "in": "path",
      "name": "host_id",
      "required": true,
      "schema": {
       "title": "Host Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Run Preflight V1 Platform Manager Hosts  Host Id  Preflight Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer les contrôles préalables",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/setup": {
   "get": {
    "description": "État de mise en route, sans jamais de secret ni d'erreur si le Manager est absent. `core` : API, PostgreSQL, migrations, Redis (facultatif) et Keycloak (facultatif), chacun avec un état (`ok`, `missing`, `down`, `error`), le paramètre à corriger et des messages en anglais et en français. `manager` : adresse et référence du jeton renseignées, identifiant trouvé, Manager joignable, utilisateur délégué connu, nombre de clusters, référence de jeton suggérée et permissions nécessaires au jeton d'API du Manager (les changements demandent un redémarrage). Demande `platform_admin`.",
    "operationId": "setup_status_v1_platform_manager_setup_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Setup Status V1 Platform Manager Setup Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire l'état de mise en route",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/summary": {
   "get": {
    "description": "Décomptes réels par cluster (environnement) : services au total, sains, en avertissement, en échec ; hôtes ; alertes ; opérations. Demande `platform_admin`.",
    "operationId": "summary_v1_platform_manager_summary_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Summary V1 Platform Manager Summary Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire la synthèse de la plateforme",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/vault/entries": {
   "get": {
    "description": "Services du coffre-fort informatique par nom, avec URL, identifiant de connexion, source (`manual` ou `installed:<service>`) et présence d'un mot de passe et d'une clé (`has_password`, `has_api_key`), jamais leurs valeurs. Demande `platform_admin`.",
    "operationId": "list_entries_v1_platform_manager_vault_entries_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Entries V1 Platform Manager Vault Entries Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les entrées du coffre-fort",
    "tags": [
     "platform-manager"
    ]
   },
   "post": {
    "description": "Ajoute un service au coffre-fort informatique (source `manual`) et répond 201 sans le mot de passe ni la clé. URL, identifiant, mot de passe et clé sont scellés ensemble en AES-256-GCM (clé dérivée de la clé maîtresse de la plateforme, identifiant de l'entrée comme données associées) ; seul le nom est enregistré en clair. Un nom déjà utilisé répond 409 `it_vault_name_taken` ; un nom vide 422 `validation_failed`. Audité (`it_vault.create`, nom seulement). Demande `platform_admin`.",
    "operationId": "create_entry_v1_platform_manager_vault_entries_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EntryIn"
       }
      }
     },
     "description": "Le service à ajouter. Les champs inconnus sont refusés.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Entry V1 Platform Manager Vault Entries Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter une entrée au coffre-fort",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/vault/entries/{entry_id}": {
   "delete": {
    "description": "Supprime un service du coffre-fort informatique et répond 204. Une entrée pré-remplie à partir des paramètres de la plateforme revient au prochain import. Audité (`it_vault.delete`, nom seulement). Identifiant inconnu : 404 `it_vault_entry_not_found`. Demande `platform_admin`.",
    "operationId": "delete_entry_v1_platform_manager_vault_entries__entry_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'entrée du coffre-fort.",
      "in": "path",
      "name": "entry_id",
      "required": true,
      "schema": {
       "title": "Entry Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une entrée du coffre-fort",
    "tags": [
     "platform-manager"
    ]
   },
   "get": {
    "description": "Un service du coffre-fort sans son mot de passe ni sa clé (utilisez `POST .../reveal` pour les obtenir). Identifiant inconnu : 404 `it_vault_entry_not_found`. Demande `platform_admin`.",
    "operationId": "get_entry_v1_platform_manager_vault_entries__entry_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'entrée du coffre-fort.",
      "in": "path",
      "name": "entry_id",
      "required": true,
      "schema": {
       "title": "Entry Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Entry V1 Platform Manager Vault Entries  Entry Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une entrée du coffre-fort",
    "tags": [
     "platform-manager"
    ]
   },
   "patch": {
    "description": "Modifie les champs fournis d'une entrée du coffre-fort et les scelle à nouveau (AES-256-GCM). Un nom utilisé par une autre entrée (sans tenir compte de la casse) répond 409 `it_vault_name_taken` ; un nom vide 422 `validation_failed`. Audité (`it_vault.update`, nom et noms des champs modifiés seulement). Demande `platform_admin`.",
    "operationId": "update_entry_v1_platform_manager_vault_entries__entry_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'entrée du coffre-fort.",
      "in": "path",
      "name": "entry_id",
      "required": true,
      "schema": {
       "title": "Entry Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EntryPatch"
       }
      }
     },
     "description": "Champs à modifier. Les champs inconnus sont refusés.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Update Entry V1 Platform Manager Vault Entries  Entry Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une entrée du coffre-fort",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/vault/entries/{entry_id}/reveal": {
   "post": {
    "description": "Mot de passe et clé d'un service en clair (affichage et copie dans la console). Chaque révélation est auditée (`it_vault.reveal`) avec le nom du service, jamais les valeurs. Identifiant inconnu : 404 `it_vault_entry_not_found`. Demande `platform_admin`.",
    "operationId": "reveal_entry_v1_platform_manager_vault_entries__entry_id__reveal_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'entrée du coffre-fort.",
      "in": "path",
      "name": "entry_id",
      "required": true,
      "schema": {
       "title": "Entry Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reveal Entry V1 Platform Manager Vault Entries  Entry Id  Reveal Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révéler les secrets d'une entrée du coffre-fort",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/platform-manager/vault/import": {
   "post": {
    "description": "Pré-remplit le coffre-fort avec les services avec lesquels la plateforme est configurée (PostgreSQL, Redis, Keycloak, OpenBao, OpenFGA, OPA, LiteLLM, Qdrant, NATS, Temporal, LiveKit, Opik, ClickHouse, S3, Platform Manager, compte d'installation du portail), à partir des paramètres `EAI_*`. Seuls les services manquants sont ajoutés : une entrée n'est jamais écrasée, une entrée supprimée revient. Répond les noms créés et ignorés. Audité (`it_vault.import`). Le même import s'exécute à chaque démarrage de l'API. Demande `platform_admin`.",
    "operationId": "import_entries_v1_platform_manager_vault_import_post",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Import Entries V1 Platform Manager Vault Import Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Importer les services configurés dans le coffre-fort",
    "tags": [
     "platform-manager"
    ]
   }
  },
  "/v1/plugins": {
   "get": {
    "description": "Plugins du tenant dans l'ordre des noms, avec version, empreinte, vérification et statut. Une seule page.",
    "operationId": "list_plugins_v1_plugins_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Plugins V1 Plugins Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les plugins",
    "tags": [
     "plugins"
    ]
   },
   "post": {
    "description": "Applique un manifeste Plugin : crée le plugin ou le met à jour (retour à `registered`, un plugin bloqué reste `blocked`), avec la signature vérifiée et enregistrée. Refus : 422 `pack_requires_marketplace` pour un pack, 422 `invalid_manifest` quand l'empreinte de `oci_ref` diffère de `artifact.digest`, qu'une liste d'autorisation manque pour `egress_allowlist` ou que les limites dépassent le maximum de la plateforme (4 CPU, 8Gi de mémoire, 3600 s, 1 GPU) ; 409 `plugin_installed` tant qu'il est installé. Demande `tenant_admin`, `developer` ou `application_owner` ; l'appelant devient propriétaire ReBAC. Audité sous `plugin.apply`.",
    "operationId": "create_plugin_v1_plugins_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Manifeste Plugin (`kind: Plugin`, `metadata`, `spec` avec description, artefact, signature et sandbox).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Plugin V1 Plugins Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un plugin",
    "tags": [
     "plugins"
    ]
   }
  },
  "/v1/plugins/{name}": {
   "get": {
    "description": "Renvoie le plugin avec son manifeste, son résultat de vérification et son déploiement généré. Demande la relation ReBAC `viewer`. 404 `plugin_not_found`.",
    "operationId": "get_plugin_v1_plugins__name__get",
    "parameters": [
     {
      "description": "Nom du plugin.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Plugin V1 Plugins  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un plugin",
    "tags": [
     "plugins"
    ]
   }
  },
  "/v1/plugins/{name}/install": {
   "post": {
    "description": "Installe le plugin dans sa sandbox. Demande `tenant_admin` et la relation ReBAC `owner`.\nÉtapes :\n1. Un plugin `blocked` répond 403 `plugin_blocked` ; un plugin déjà installé est renvoyé tel quel.\n2. La signature est revérifiée (403 `plugin_unsigned` en cas d'échec, audité comme refus).\n3. Le Deployment (classe de runtime de la sandbox, système de fichiers racine en lecture seule par défaut) et la NetworkPolicy sont générés, puis appliqués quand Kubernetes est configuré (`status: failed` avec l'erreur si l'application échoue).\nRenvoie l'état d'exécution. Audité sous `plugin.install`.",
    "operationId": "install_plugin_v1_plugins__name__install_post",
    "parameters": [
     {
      "description": "Nom du plugin.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Install Plugin V1 Plugins  Name  Install Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Installer un plugin",
    "tags": [
     "plugins"
    ]
   }
  },
  "/v1/plugins/{name}/status": {
   "get": {
    "description": "Vue d'exécution : statut, indicateur de vérification, empreinte, date d'installation, classe de runtime, objets `rendered` (générés) ou `applied` (appliqués) sur Kubernetes, Deployment et NetworkPolicy et dernière erreur. Demande la relation ReBAC `viewer`.",
    "operationId": "plugin_status_v1_plugins__name__status_get",
    "parameters": [
     {
      "description": "Nom du plugin.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Plugin Status V1 Plugins  Name  Status Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire l'état d'exécution d'un plugin",
    "tags": [
     "plugins"
    ]
   }
  },
  "/v1/plugins/{name}/uninstall": {
   "post": {
    "description": "Supprime le Deployment et la NetworkPolicy de Kubernetes s'il est configuré (502 `upstream_error` en cas d'échec) et passe le plugin à `uninstalled`. Seulement pour un plugin `installed`, `installing` ou `failed` (sinon 409 `plugin_not_installed`). Demande `tenant_admin` et la relation ReBAC `owner`. Audité sous `plugin.uninstall`.",
    "operationId": "uninstall_plugin_v1_plugins__name__uninstall_post",
    "parameters": [
     {
      "description": "Nom du plugin.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Uninstall Plugin V1 Plugins  Name  Uninstall Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Désinstaller un plugin",
    "tags": [
     "plugins"
    ]
   }
  },
  "/v1/policies": {
   "get": {
    "description": "Liste les politiques du tenant dans l'ordre d'évaluation (priorité, puis nom), avec leurs règles ou leur Rego. Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_policies_v1_policies_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Policies V1 Policies Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les politiques",
    "tags": [
     "policies"
    ]
   },
   "post": {
    "description": "Crée une politique du tenant et ajoute sa première révision. Une politique `opa` exige `rego` (422 `validation_error`) et, sauf si OPA charge des bundles signés, est poussée immédiatement dans OPA (503 `policy_engine_unavailable` quand OPA est injoignable). Un nom déjà utilisé répond 409 `conflict`. Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `policy.create`.",
    "operationId": "create_policy_v1_policies_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__policies__PolicyIn"
       }
      }
     },
     "description": "Nom, moteur, règles ou Rego, priorité et statut de la politique.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Policy V1 Policies Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/bundles": {
   "get": {
    "description": "Liste les bundles OPA publiés pour le tenant : révision, SHA-256, état de signature, politiques incluses et date. Exige `tenant_admin`, `security_officer` ou `auditor`.",
    "operationId": "list_bundles_v1_policies_bundles_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Bundles V1 Policies Bundles Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les bundles OPA",
    "tags": [
     "policies"
    ]
   },
   "post": {
    "description": "Emballe les politiques `opa` du tenant dans un bundle OPA signé et l'enregistre comme nouvelle révision `current`. Les politiques actives sont décidées par OPA ; les brouillons sont livrés comme bibliothèques ; les désactivées sont exclues. Aucune politique `opa` répond 409 `policy_bundle_empty` ; aucune clé de signature en production 503 `policy_bundle_signing_unavailable`. Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `policy.bundle_publish`. Répond 201 avec l'enregistrement du bundle.",
    "operationId": "publish_bundle_v1_policies_bundles_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/BundleIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Nom de révision et contenu de `data.json` facultatifs. Un corps vide génère la révision."
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Publish Bundle V1 Policies Bundles Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier un bundle OPA",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/bundles/{revision}/bundle.tar.gz": {
   "get": {
    "description": "Renvoie le bundle qu'OPA interroge, en `application/gzip` : modules Rego, `data.json` facultatif, `.manifest` et `.signatures.json` (JWT RS256 sur le SHA-256 de chaque fichier). L'en-tête `ETag` est son SHA-256 et `X-Bundle-Revision` sa révision. Exige `tenant_admin`, `security_officer` ou `auditor`. Répond 404 `not_found` pour une révision inconnue.",
    "operationId": "download_bundle_v1_policies_bundles__revision__bundle_tar_gz_get",
    "parameters": [
     {
      "description": "Révision du bundle, ou `current` pour la dernière publiée.",
      "in": "path",
      "name": "revision",
      "required": true,
      "schema": {
       "title": "Revision",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Télécharger un bundle OPA",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/decisions": {
   "get": {
    "description": "Liste les décisions du tenant, les plus récentes d'abord, paginées : sujet, action, ressource, contexte, autorisation, politique et version, raisons, obligations, moteur et date. Exige `tenant_admin`, `security_officer` ou `auditor`.",
    "operationId": "list_decisions_v1_policies_decisions_get",
    "parameters": [
     {
      "description": "Nombre maximal de décisions dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Uniquement les décisions portant sur cette action, par exemple `model.invoke`.",
      "in": "query",
      "name": "action",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Action"
      }
     },
     {
      "description": "`true` pour les seules décisions autorisées, `false` pour les seules refusées.",
      "in": "query",
      "name": "allow",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Allow"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Decisions V1 Policies Decisions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les décisions de politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/engines": {
   "get": {
    "description": "Renvoie les services de politique (moteur intégré, OPA, OpenFGA) et la version de politique ou la révision de bundle que chacun exécute réellement, lue auprès du moteur lui-même. Exige `tenant_admin`, `security_officer` ou `auditor`.",
    "operationId": "engines_status_v1_policies_engines_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Engines Status V1 Policies Engines Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire l'état des moteurs de politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/evaluate": {
   "post": {
    "description": "Renvoie la décision pour une demande du Policy Contract. Le corps est un Policy Contract (`{schema_version, request}`) ou seulement son objet `request` (`subject`, `action`, `resource`, `context`) ; il est validé avec le contrat `policy-contract` (422 `validation_error`).\n\n1. Sans `subject`, la demande est évaluée pour l'appelant : les aides des SDK et les intergiciels des passerelles n'envoient que l'action, la ressource et le contexte.\n2. Le sujet est toujours l'appelant, sauf que `tenant_admin` et `security_officer` peuvent évaluer pour le compte d'un autre sujet de leur propre tenant (simulation « et si » ; `platform_admin` est retiré de ses rôles). Une telle évaluation écrit l'événement d'audit `policy.simulate`.\n\nLa réponse donne `schema_version`, `request` et `decision` (autorisation, raisons, obligations, politique et version, identifiant de décision). La décision est enregistrée dans le journal des décisions.",
    "operationId": "evaluate_policy_v1_policies_evaluate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Document",
        "type": "object"
       }
      }
     },
     "description": "Document Policy Contract, ou son seul objet `request`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Evaluate Policy V1 Policies Evaluate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Évaluer une demande de politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/simulate": {
   "post": {
    "description": "Compare, entrée par entrée, les décisions des politiques actives avec celles du même ensemble où la candidate remplace `policy_id` (ou est ajoutée). Rien n'est modifié.\n\nLes entrées sont les demandes Policy Contract fournies plus, avec `recorded`, les N dernières décisions enregistrées rejouées. Chaque résultat donne `current`, `candidate` et `changed` ; `summary` compte le total, les décisions changées et les bascules `allow_to_deny` et `deny_to_allow`. Seules les candidates `builtin` peuvent être simulées (422 `simulation_engine_unsupported`). Exige `tenant_admin`, `security_officer` ou `developer`. Écrit l'événement d'audit `policy.simulate`.",
    "operationId": "simulate_policy_v1_policies_simulate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SimulateIn"
       }
      }
     },
     "description": "Politique candidate, politique remplacée, entrées et nombre de décisions enregistrées à rejouer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Simulate Policy V1 Policies Simulate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Simuler une politique candidate",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/{policy_id}": {
   "delete": {
    "description": "Supprime la politique et ajoute une révision `delete`. Une politique OPA poussée directement dans OPA en est aussi retirée (503 `policy_engine_unavailable` quand OPA est injoignable). Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `policy.delete`. Répond 204, ou 404 `not_found`.",
    "operationId": "delete_policy_v1_policies__policy_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la politique.",
      "in": "path",
      "name": "policy_id",
      "required": true,
      "schema": {
       "title": "Policy Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une politique",
    "tags": [
     "policies"
    ]
   },
   "get": {
    "description": "Renvoie une politique. Ouvert à tout membre du tenant. Répond 404 `not_found`.",
    "operationId": "get_policy_v1_policies__policy_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la politique.",
      "in": "path",
      "name": "policy_id",
      "required": true,
      "schema": {
       "title": "Policy Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Policy V1 Policies  Policy Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une politique",
    "tags": [
     "policies"
    ]
   },
   "put": {
    "description": "Remplace tous les champs de la politique et ajoute une révision `update`. Même validation qu'à la création (`rego` pour `opa`, nom unique). Une politique `opa` est poussée dans OPA sauf si OPA charge des bundles. Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `policy.update`. Répond 404 `not_found` pour une politique inconnue.",
    "operationId": "put_policy_v1_policies__policy_id__put",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la politique.",
      "in": "path",
      "name": "policy_id",
      "required": true,
      "schema": {
       "title": "Policy Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__policies__PolicyIn"
       }
      }
     },
     "description": "Nouveau contenu complet de la politique.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Policy V1 Policies  Policy Id  Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Remplacer une politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/{policy_id}/revisions": {
   "get": {
    "description": "Liste les révisions immuables de la politique (une par création, modification et suppression, écrite dans la même transaction), avec leur numéro, opération, auteur, date et contenu. Exige `tenant_admin`, `security_officer`, `auditor` ou `developer`. Répond 404 `not_found`.",
    "operationId": "list_revisions_v1_policies__policy_id__revisions_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la politique.",
      "in": "path",
      "name": "policy_id",
      "required": true,
      "schema": {
       "title": "Policy Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Revisions V1 Policies  Policy Id  Revisions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les révisions d'une politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/policies/{policy_id}/revisions/diff": {
   "get": {
    "description": "Renvoie les différences entre deux révisions de la politique. Par défaut : la révision précédente face à la dernière. La politique est d'abord recherchée (404 `not_found`), puis les révisions (404 `not_found`). Exige `tenant_admin`, `security_officer`, `auditor` ou `developer`.",
    "operationId": "diff_revisions_v1_policies__policy_id__revisions_diff_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la politique.",
      "in": "path",
      "name": "policy_id",
      "required": true,
      "schema": {
       "title": "Policy Id",
       "type": "string"
      }
     },
     {
      "description": "Numéro de la révision la plus ancienne (1 ou plus). Par défaut : celle qui précède `to_revision`.",
      "in": "query",
      "name": "from_revision",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "From Revision"
      }
     },
     {
      "description": "Numéro de la révision la plus récente (1 ou plus). Par défaut : la dernière.",
      "in": "query",
      "name": "to_revision",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "To Revision"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Diff Revisions V1 Policies  Policy Id  Revisions Diff Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Comparer deux révisions d'une politique",
    "tags": [
     "policies"
    ]
   }
  },
  "/v1/privacy/classify": {
   "post": {
    "description": "Avec `text`, détecte les entités avec les règles du tenant et renvoie `classification`, `findings` et `engine`, sans modifier le texte. Avec `media_id`, renvoie la classification, les constats et le résultat DLP déjà calculés pour ce média (404 `not_found` pour un média inconnu). L'un des deux est obligatoire (400 `invalid_request`). Ouvert à tout membre du tenant.",
    "operationId": "classify_v1_privacy_classify_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ClassifyIn"
       }
      }
     },
     "description": "Texte ou média à classer, et langue.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Classify V1 Privacy Classify Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Classer un texte ou un média",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/projects": {
   "get": {
    "description": "Liste les projets du tenant triés par identifiant court, paginés, éventuellement filtrés par organisation ou unité métier. Ouvert à tout membre du tenant.",
    "operationId": "list_projects_v1_projects_get",
    "parameters": [
     {
      "description": "Nombre maximal de projets dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Uniquement les projets rattachés à cette organisation.",
      "in": "query",
      "name": "organization_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Organization Id"
      }
     },
     {
      "description": "Uniquement les projets rattachés à cette unité métier.",
      "in": "query",
      "name": "business_unit_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Business Unit Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Projects V1 Projects Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les projets",
    "tags": [
     "tenancy"
    ]
   },
   "post": {
    "description": "Crée un projet dans le tenant de l'appelant. Exige `tenant_admin`.\n\n1. Le projet peut être rattaché à une organisation et à l'une de ses unités métier (422 `invalid_hierarchy` quand elles ne correspondent pas).\n2. Les environnements listés dans `environments` sont créés en même temps.\n3. L'appelant reçoit la relation ReBAC `owner` sur le projet.\n\nUn identifiant court déjà utilisé répond 409 `conflict`. Écrit l'événement d'audit `project.create`. La réponse porte le projet et ses environnements.",
    "operationId": "create_project_v1_projects_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__tenancy__ProjectIn"
       }
      }
     },
     "description": "Identifiant court, nom, paramètres, environnements et rattachement du projet.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Project V1 Projects Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un projet",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/projects/{project_id}": {
   "delete": {
    "description": "Supprime le projet et ses environnements. Exige `tenant_admin` et la relation `owner` sur le projet (403 `relation_required`). Des objets qui référencent encore le projet peuvent bloquer la suppression (409 `conflict`). Écrit l'événement d'audit `project.delete`. Répond 204.",
    "operationId": "delete_project_v1_projects__project_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un projet",
    "tags": [
     "tenancy"
    ]
   },
   "get": {
    "description": "Renvoie le projet avec ses environnements. Exige la relation `viewer` sur le projet (403 `relation_required`, audité). Répond 404 `not_found` pour un projet inconnu.",
    "operationId": "get_project_v1_projects__project_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Project V1 Projects  Project Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un projet",
    "tags": [
     "tenancy"
    ]
   },
   "patch": {
    "description": "Modifie le nom, la description, les paramètres, les métadonnées ou le rattachement d'un projet. Envoyer `organization_id` ou `business_unit_id` à null détache le projet. L'unité doit appartenir à l'organisation (422 `invalid_hierarchy`). Exige `tenant_admin` et la relation `editor` sur le projet. Envoyez `expected_version` pour détecter une modification concurrente (409 `version_conflict`). Écrit l'événement d'audit `project.update`.",
    "operationId": "patch_project_v1_projects__project_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__tenancy__ProjectPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Project V1 Projects  Project Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un projet",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/projects/{project_id}/environments": {
   "get": {
    "description": "Liste les environnements du projet, triés par nom. Exige la relation `viewer` sur le projet. Non paginé : `next_cursor` vaut toujours null.",
    "operationId": "list_environments_v1_projects__project_id__environments_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Environments V1 Projects  Project Id  Environments Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les environnements d'un projet",
    "tags": [
     "tenancy"
    ]
   },
   "post": {
    "description": "Ajoute un environnement (`dev`, `test`, `staging` ou `prod`) au projet. Exige `tenant_admin` et la relation `editor` sur le projet. Un environnement qui existe déjà répond 409 `conflict`. Écrit l'événement d'audit `environment.create`.",
    "operationId": "create_environment_v1_projects__project_id__environments_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EnvironmentIn"
       }
      }
     },
     "description": "Nom et paramètres de l'environnement.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Environment V1 Projects  Project Id  Environments Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un environnement",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/projects/{project_id}/mcp/instances": {
   "get": {
    "description": "Renvoie `enabled` (si le MCP Runtime tourne sur cette plateforme), la `policy` du projet (valeurs par défaut si elle n'est pas configurée), l'état du catalogue, les `templates` et les instances, de la plus récente à la plus ancienne, 500 au plus : les siennes pour un utilisateur, toutes pour `tenant_admin` et `security_officer`. Demande la relation ReBAC `viewer` sur le projet.",
    "operationId": "list_instances_v1_projects__project_id__mcp_instances_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Seulement les instances dans cet état : `provisioning`, `ready`, `sleeping`, `failed`, `terminating` ou `terminated`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "provisioning",
          "ready",
          "sleeping",
          "failed",
          "terminating",
          "terminated"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Instances V1 Projects  Project Id  Mcp Instances Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les instances MCP d'un projet",
    "tags": [
     "mcp-runtime"
    ]
   },
   "post": {
    "description": "Démarre une instance MCP isolée de l'appelant dans le projet (202) : MCPServer ToolHive propre, identifiants propres, prête quand ToolHive la signale Ready (lisez l'instance pour la suivre). Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor` sur le projet.\nContrôles : le MCP Runtime est activé (409 `mcp_runtime_disabled`) ; le modèle, ses hôtes et ses rôles de base de données sont permis par la politique du projet (403 `mcp_template_not_allowed`, `mcp_host_not_allowed`, `mcp_database_role_not_allowed`) ; un modèle à identifiants personnels demande `credential_ref` (422 `mcp_credential_required`) ; agents, outils et nom sont valides (422) ; quotas par utilisateur et par projet (409 `mcp_instance_quota`) ; aucune autre instance de l'appelant du même nom (409 `mcp_instance_exists`). Un provisionnement en échec détruit l'instance et renvoie l'erreur. Audité sous `mcp.instance.create`.",
    "operationId": "create_instance_v1_projects__project_id__mcp_instances_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/InstanceIn"
       }
      }
     },
     "description": "Modèle, nom, paramètres, identifiant personnel, agents, outils permis et temps d'inactivité.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Instance V1 Projects  Project Id  Mcp Instances Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Démarrer une instance MCP",
    "tags": [
     "mcp-runtime"
    ]
   }
  },
  "/v1/projects/{project_id}/mcp/instances/{instance_id}": {
   "delete": {
    "description": "Détruit l'instance à la fin de la tâche : identifiants révoqués, ressources supprimées, entrée de passerelle désactivée, journal conservé. Permis pour son propriétaire, ou `tenant_admin` et `security_officer` (enregistré comme destruction par un administrateur). Demande la relation ReBAC `viewer` sur le projet. Renvoie l'instance et son journal. 404 `mcp_instance_not_found`.",
    "operationId": "delete_instance_v1_projects__project_id__mcp_instances__instance_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Instance V1 Projects  Project Id  Mcp Instances  Instance Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Détruire une instance MCP",
    "tags": [
     "mcp-runtime"
    ]
   },
   "get": {
    "description": "L'instance (son état relu auprès de ToolHive tant qu'elle est en provisionnement ou prête) et son journal de cycle de vie dans `events`. `credential_active` indique si des identifiants sont actuellement émis. Visible par son propriétaire, `tenant_admin` et `security_officer` ; demande la relation ReBAC `viewer` sur le projet. 404 `mcp_instance_not_found`.",
    "operationId": "get_instance_v1_projects__project_id__mcp_instances__instance_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Instance V1 Projects  Project Id  Mcp Instances  Instance Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une instance MCP",
    "tags": [
     "mcp-runtime"
    ]
   }
  },
  "/v1/projects/{project_id}/mcp/instances/{instance_id}/start": {
   "post": {
    "description": "Redémarre maintenant une instance endormie ou en échec (compteur de pannes remis à zéro) : modèle et politique du projet revérifiés, nouvelle génération et nouveaux identifiants ; prête quand ToolHive la signale Ready (lisez l'instance pour la suivre). 409 `mcp_instance_busy` pendant une autre opération de cycle de vie. Permis pour son propriétaire, `tenant_admin` et `security_officer` ; demande la relation ReBAC `viewer` sur le projet. Renvoie l'instance et son journal (202).",
    "operationId": "start_instance_v1_projects__project_id__mcp_instances__instance_id__start_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Start Instance V1 Projects  Project Id  Mcp Instances  Instance Id  Start Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Redémarrer une instance endormie ou en échec",
    "tags": [
     "mcp-runtime"
    ]
   }
  },
  "/v1/projects/{project_id}/mcp/instances/{instance_id}/stop": {
   "post": {
    "description": "Arrête l'instance maintenant (identifiants révoqués, ressources supprimées) : elle s'endort, son entrée de passerelle reste et son prochain appel la recrée. Permis pour son propriétaire, `tenant_admin` et `security_officer` ; demande la relation ReBAC `viewer` sur le projet. Renvoie l'instance et son journal.",
    "operationId": "stop_instance_v1_projects__project_id__mcp_instances__instance_id__stop_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Stop Instance V1 Projects  Project Id  Mcp Instances  Instance Id  Stop Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Endormir une instance MCP",
    "tags": [
     "mcp-runtime"
    ]
   }
  },
  "/v1/projects/{project_id}/mcp/policy": {
   "put": {
    "description": "Remplace la politique du MCP Runtime du projet : modèles, hôtes, rôles de base de données, temps d'inactivité, durée de vie et quotas. Des modèles inconnus, des motifs d'hôte ou des noms de rôle invalides répondent 422 `mcp_policy_invalid`. Les instances nouvelles et réveillées la suivent. Demande `tenant_admin` et la relation ReBAC `editor` sur le projet. Audité sous `mcp.runtime.policy`.",
    "operationId": "put_policy_v1_projects__project_id__mcp_policy_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__mcp_runtime__routes__PolicyIn"
       }
      }
     },
     "description": "La politique complète ; les champs omis prennent leur valeur par défaut.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Policy V1 Projects  Project Id  Mcp Policy Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir la politique du runtime MCP d'un projet",
    "tags": [
     "mcp-runtime"
    ]
   }
  },
  "/v1/projects/{project_id}/navin-library": {
   "get": {
    "description": "Version de la bibliothèque, activations du projet avec le nombre par type, et serveurs MCP et canaux du projet (avec le preset dont ils viennent). Demande la relation ReBAC `viewer` sur le projet.",
    "operationId": "project_library_v1_projects__project_id__navin_library_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Project Library V1 Projects  Project Id  Navin Library Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Voir ce qu'un projet utilise de la bibliothèque",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/navin-library/activations": {
   "post": {
    "description": "Active un skill, un plugin (avec ses skills, commandes et agents), une commande ou un agent pour le projet (201). Un skill est enregistré dans le Skill Registry, vérifié par son empreinte de bibliothèque ; un nom de skill déjà pris par un skill hors bibliothèque répond 409 `navin_skill_name_taken`. Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. 422 `navin_library_kind_not_activable` ; 404 `navin_library_item_not_found`. Audité sous `navin_library.activate`.",
    "operationId": "activate_v1_projects__project_id__navin_library_activations_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__navin_library__routes__ActivationIn"
       }
      }
     },
     "description": "Type et nom de l'élément à activer.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Activate V1 Projects  Project Id  Navin Library Activations Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Activer un élément de la bibliothèque",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/navin-library/activations/{activation_id}": {
   "delete": {
    "description": "Retire une activation du projet ; un plugin emporte les skills, commandes et agents qu'il avait apportés. Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. 404 `navin_library_activation_not_found`. Audité sous `navin_library.deactivate`.",
    "operationId": "deactivate_v1_projects__project_id__navin_library_activations__activation_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) de l'activation.",
      "in": "path",
      "name": "activation_id",
      "required": true,
      "schema": {
       "title": "Activation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Deactivate V1 Projects  Project Id  Navin Library Activations  Activation Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Désactiver un élément de la bibliothèque",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/navin-library/channels/{name}/install": {
   "post": {
    "description": "Crée un canal du projet à partir d'un adaptateur de canal Navin (201). Le contrat de configuration de l'adaptateur est vérifié : des champs inconnus, secrets ou en lecture seule dans `settings` et des champs non secrets dans `secret_refs` répondent 422 `channel_field_invalid`, un type de valeur incorrect aussi, des exigences manquantes 422 `channel_field_missing` ; les secrets ne sont que des références `openbao://` ou `env://` (422 `channel_secret_ref_required`). Par défaut : auth `api_key`, limites de 65536 octets par message et 60 par minute, DLP `detect` en entrée et `mask` en sortie. Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. Audité sous `navin_library.channel_install`.",
    "operationId": "install_channel_v1_projects__project_id__navin_library_channels__name__install_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Nom de l'adaptateur de canal Navin.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ChannelInstallIn"
       }
      }
     },
     "description": "Nom du canal, agent, réglages, références de secrets, authentification, limites et DLP.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Install Channel V1 Projects  Project Id  Navin Library Channels  Name  Install Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Installer un canal depuis la bibliothèque",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/navin-library/mcp-presets/{name}/install": {
   "post": {
    "description": "Enregistre un serveur MCP du projet dans la passerelle MCP à partir d'un preset Navin (201), en HTTP streamable ou SSE. Un preset stdio s'exécute là où tourne sa commande (la page MCP du Navin Manager) : donnez son point d'accès HTTP dans `url` ; ses champs d'environnement et d'arguments sont renvoyés dans `bridge_fields`. Erreurs : 409 `mcp_preset_not_installable`, 409 `mcp_preset_needs_endpoint`, 422 `mcp_preset_field_unknown`, 422 `mcp_preset_field_missing`, 422 `mcp_preset_secret_ref_required` (un champ secret doit être une référence `openbao://` ou `env://`), 422 `mcp_preset_unsupported`. Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. Audité sous `navin_library.mcp_install`.",
    "operationId": "install_mcp_v1_projects__project_id__navin_library_mcp_presets__name__install_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Nom du preset MCP.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/McpInstallIn"
       }
      }
     },
     "description": "Nom du serveur, point d'accès, transport, champs du preset, références de secrets et outils permis.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Install Mcp V1 Projects  Project Id  Navin Library Mcp Presets  Name  Install Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Installer un serveur MCP depuis un preset",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/rules": {
   "get": {
    "description": "Règles du projet par mode puis par nom, chacune avec source, chemin source, description, globs, contenu, empreinte et taille en octets. Demande la relation ReBAC `viewer`.",
    "operationId": "list_rules_v1_projects__project_id__rules_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Rules V1 Projects  Project Id  Rules Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les règles du projet",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/rules/import": {
   "post": {
    "description": "Importe les fichiers de règles d'un dépôt (`.navin/rules`, `.cursor/rules/*.mdc` avec leur frontmatter, `.cursorrules`, `.clinerules`, `.windsurfrules`, `.github/copilot-instructions.md`) ; chaque fichier reconnu crée ou remplace une règle. Les autres fichiers sont signalés dans `skipped` avec une raison (`not_a_rule_file`, `empty`, `too_large`). 200 fichiers au plus (400 `invalid_request`). Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`.",
    "operationId": "import_rules_v1_projects__project_id__rules_import_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RulesImportIn"
       }
      }
     },
     "description": "Les fichiers à importer, chemin et contenu.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Import Rules V1 Projects  Project Id  Rules Import Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Importer les fichiers de règles d'un dépôt",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/rules/prompt": {
   "get": {
    "description": "La section de prompt que reçoit chaque run du projet : règles `always` insérées (chacune coupée à 8000 caractères), règles conditionnelles listées avec leur description et leurs globs. Renvoie `prompt`, les nombres `always` et `conditional` et l'empreinte `digest` du texte (vide si le projet n'a aucune règle). Demande la relation ReBAC `viewer`.",
    "operationId": "rules_prompt_v1_projects__project_id__rules_prompt_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Rules Prompt V1 Projects  Project Id  Rules Prompt Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la section de prompt des règles",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/projects/{project_id}/rules/{name}": {
   "delete": {
    "description": "Supprime la règle ; les runs du projet ne la reçoivent plus. Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. 404 `rule_not_found`. Audité sous `project.rule.delete`.",
    "operationId": "delete_rule_v1_projects__project_id__rules__name__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Nom de la règle.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une règle de projet",
    "tags": [
     "navin-library"
    ]
   },
   "put": {
    "description": "Crée (201) ou remplace (200) une règle du projet. Le nom compte 1 à 64 lettres, chiffres, `.`, `_` ou `-` (un suffixe `.md` est retiré, `readme` est refusé ; 422 `rule_name_invalid`). Un contenu vide répond 422 `rule_empty`, plus de 64 Kio 413 `rule_too_large` ; une règle conditionnelle demande des globs ou une description (422 `rule_scope_required`). Demande `tenant_admin`, `developer` ou `application_owner` et la relation ReBAC `editor`. Audité sous `project.rule.put`.",
    "operationId": "put_rule_v1_projects__project_id__rules__name__put",
    "parameters": [
     {
      "description": "Identifiant (UUID) ou slug du projet.",
      "in": "path",
      "name": "project_id",
      "required": true,
      "schema": {
       "title": "Project Id",
       "type": "string"
      }
     },
     {
      "description": "Nom de la règle.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__navin_library__routes__RuleIn"
       }
      }
     },
     "description": "Contenu, mode, description, globs et source de la règle.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Rule V1 Projects  Project Id  Rules  Name  Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer ou remplacer une règle de projet",
    "tags": [
     "navin-library"
    ]
   }
  },
  "/v1/prompts": {
   "get": {
    "description": "Prompts du tenant dans l'ordre des noms avec leur dernier numéro de version, paginés avec `cursor`.",
    "operationId": "list_prompts_v1_prompts_get",
    "parameters": [
     {
      "description": "Nombre maximal de prompts dans une page (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Prompts V1 Prompts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les prompts",
    "tags": [
     "prompts"
    ]
   },
   "post": {
    "description": "Crée un prompt, ou publie une nouvelle version quand le nom existe ; un modèle et des variables identiques ne sont pas dupliqués (la version courante est renvoyée). Le nom est en minuscules, chiffres, `.`, `_`, `-` (1 à 128 caractères, 400 `invalid_request` sinon). Un emplacement mal fermé, un type de variable inconnu ou un emplacement non déclaré dans `variables` répond 422 `template_error`. Demande `tenant_admin` ou `developer`. Audité sous `prompt.publish`.",
    "operationId": "create_prompt_v1_prompts_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PromptIn"
       }
      }
     },
     "description": "Nom, modèle, description, variables et indication de modèle.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Prompt V1 Prompts Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier une version de prompt",
    "tags": [
     "prompts"
    ]
   }
  },
  "/v1/prompts/{name}/render": {
   "post": {
    "description": "Remplit le modèle d'une version (par défaut : la dernière) avec les valeurs données et renvoie `text`, `chars` et `estimated_tokens` (nombre de caractères divisé par 4). Des valeurs obligatoires absentes, de mauvais types, des valeurs hors `enum`, au-delà de `max_length` et, avec `strict`, des variables inconnues répondent 422 `missing_variables` avec une entrée par problème. 404 `not_found` pour un prompt ou une version inconnus. Rien n'est enregistré.",
    "operationId": "render_prompt_v1_prompts__name__render_post",
    "parameters": [
     {
      "description": "Nom du prompt.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RenderIn"
       }
      }
     },
     "description": "Valeurs des variables, version et mode strict.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Render Prompt V1 Prompts  Name  Render Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rendre un prompt",
    "tags": [
     "prompts"
    ]
   }
  },
  "/v1/prompts/{name}/versions": {
   "get": {
    "description": "Le prompt et toutes ses versions, de la plus récente à la plus ancienne, avec modèle, variables, indication de modèle, empreinte et auteur. 404 `not_found`.",
    "operationId": "versions_v1_prompts__name__versions_get",
    "parameters": [
     {
      "description": "Nom du prompt.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Versions V1 Prompts  Name  Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions d'un prompt",
    "tags": [
     "prompts"
    ]
   }
  },
  "/v1/providers": {
   "get": {
    "description": "Fournisseurs partagés et fournisseurs du tenant de l'appelant, avec leur type, leur adresse, leur configuration, leurs régions, leur rétention, leur état et leur dernier test ; les identifiants ne sont jamais renvoyés (seulement les noms des champs renseignés). Demande un rôle de lecteur.",
    "operationId": "providers_list_v1_providers_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les fournisseurs configurés",
    "tags": [
     "llm"
    ]
   },
   "post": {
    "description": "Crée un fournisseur pour le tenant, ou un fournisseur partagé avec `scope: platform` (`platform_admin` seulement), et répond 201 sans les identifiants. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.\n\nÉtapes :\n\n1. Le type doit exister dans le catalogue (422 `missing_provider_type`, 422 `unknown_provider_type`) et les champs et identifiants requis doivent être fournis (422 avec le champ).\n2. Une adresse distante doit utiliser TLS (422 `tls_required`) et respecter les règles d'adresses sortantes.\n3. Le fournisseur est enregistré (409 `provider_exists` pour le même nom dans la même portée), puis ses identifiants sont écrits dans le magasin de secrets et seuls les noms de leurs champs sont conservés. Audité (`provider.create`).",
    "operationId": "providers_create_v1_providers_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`provider_type` (ou `type`, un type du catalogue ou un alias), `name` facultatif (le type par défaut), `api_base` (ou `base_url`), `config`, `credentials` (champs secrets), `regions`, `retention`, `scope` (`platform` pour un fournisseur partagé).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un fournisseur de modèles",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/providers/catalog": {
   "get": {
    "description": "Chaque type de fournisseur pris en charge par la plateforme, avec les champs et identifiants nécessaires, les libellés dans la langue de la requête, les fournisseurs configurés de chaque type, et la façon de trouver ses modèles (`api` : API de liste, `manual_only` : saisie par l'administrateur). Demande un rôle de lecteur.",
    "operationId": "providers_catalog_v1_providers_catalog_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire le catalogue des fournisseurs",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/providers/{pid}": {
   "delete": {
    "description": "Supprime le fournisseur et répond 204 ; un fournisseur partagé demande `platform_admin` (403 `platform_only`). Audité (`provider.delete`). Fournisseur inconnu ou invisible : 404 `provider_not_found`. Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "providers_delete_v1_providers__pid__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un fournisseur de modèles",
    "tags": [
     "llm"
    ]
   },
   "get": {
    "description": "Un fournisseur sans ses identifiants. Un fournisseur d'un autre tenant, ou un identifiant qui n'est pas un UUID, répond 404 `provider_not_found`. Demande un rôle de lecteur.",
    "operationId": "providers_get_v1_providers__pid__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un fournisseur configuré",
    "tags": [
     "llm"
    ]
   },
   "patch": {
    "description": "Modifie la configuration, l'adresse, les régions, la rétention ou les identifiants d'un fournisseur. Les nouveaux identifiants sont écrits dans le magasin de secrets et remettent à zéro l'état de connexion jusqu'au prochain test. Une adresse distante doit utiliser TLS (422 `tls_required`) et respecter les règles d'adresses sortantes ; un champ d'identifiant inconnu répond 422. Un fournisseur partagé demande `platform_admin`. Audité (`provider.update`, sans les valeurs). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "providers_patch_v1_providers__pid__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Champs à modifier : `config`, `api_base` (ou `base_url`), `credentials` (champs secrets ; un champ secret envoyé dans `config` y est déplacé), `regions`, `retention`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un fournisseur de modèles",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/providers/{pid}/disable": {
   "post": {
    "description": "Kill switch du fournisseur : la passerelle refuse immédiatement ses modèles (403 `provider_disabled`) et la découverte est refusée (409). Un fournisseur partagé demande `platform_admin`. Audité (`provider.disable`). Demande `tenant_admin`, `platform_admin`, `ai_platform_engineer` ou `security_officer`.",
    "operationId": "providers_disable_v1_providers__pid__disable_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Désactiver un fournisseur (kill switch)",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/providers/{pid}/enable": {
   "post": {
    "description": "Lève le kill switch du fournisseur. Un fournisseur partagé demande `platform_admin`. Audité (`provider.enable`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "providers_enable_v1_providers__pid__enable_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réactiver un fournisseur désactivé",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/providers/{pid}/models/discover": {
   "post": {
    "description": "Modèles que le fournisseur configuré sert réellement, d'après sa propre API de liste avec les identifiants enregistrés et à travers la garde sortante (jamais une liste inventée). Chaque candidat porte ce que le fournisseur renvoie (fenêtre de contexte, sortie maximale, modalités, prix) complété par des indications de la plateforme, un identifiant de registre et une catégorie suggérés, et s'il est déjà enregistré ; 1000 candidats au plus. Les fournisseurs sans API de liste répondent `manual_only`. Un fournisseur désactivé répond 409 `provider_disabled`. Audité (`provider.discover`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "providers_discover_v1_providers__pid__models_discover_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Découvrir les modèles d'un fournisseur",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/providers/{pid}/test": {
   "post": {
    "description": "Appelle le point de liste du fournisseur avec ses identifiants enregistrés (pour les protocoles signés comme Bedrock et Vertex, vérifie que le jeu d'identifiants est complet) et répond `ok`, le statut HTTP, un détail et les modèles vus. Le résultat est enregistré comme état de connexion et audité (`provider.test`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "providers_test_v1_providers__pid__test_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du fournisseur.",
      "in": "path",
      "name": "pid",
      "required": true,
      "schema": {
       "title": "Pid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Tester la connexion d'un fournisseur",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/quotas": {
   "get": {
    "description": "Quotas du tenant par nom, y compris ceux reproduits depuis les projets LLM (`llm-project:...`). Une seule page. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "list_quotas_v1_quotas_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Quotas V1 Quotas Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les quotas",
    "tags": [
     "finops"
    ]
   },
   "post": {
    "description": "Crée un quota du tenant et répond 201. Une limite de coût ou de jetons est obligatoire, et `scope_value` est obligatoire sauf si `scope_type` vaut `tenant` (422 `validation_error`). Sans devise, celle du tenant s'applique. Audité. Demande `finops` ou `tenant_admin`, et un principal de tenant.",
    "operationId": "create_quota_v1_quotas_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__finops__QuotaIn"
       }
      }
     },
     "description": "Le nouveau quota.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Quota V1 Quotas Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un quota",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/quotas/reserve": {
   "post": {
    "description": "Réserve un montant sur chaque quota actif correspondant à la portée (les quotas du tenant correspondent toujours ; un principal utilisateur ajoute sa propre portée utilisateur) et répond 201 avec la réservation, ses éléments par quota et fenêtre, les quotas souples dépassés et son échéance. Un quota strict qui serait dépassé répond 429 `quota_exceeded` avec `Retry-After: 60`, et rien n'est réservé. Les réservations expirées sont d'abord libérées. Audité. Demande `finops`, `tenant_admin`, `agent` ou `developer`, et un principal de tenant.",
    "operationId": "reserve_quota_v1_quotas_reserve_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReserveIn"
       }
      }
     },
     "description": "Montant, portée et durée de vie de la réservation.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reserve Quota V1 Quotas Reserve Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réserver un coût et des jetons",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/quotas/usage": {
   "get": {
    "description": "Chaque quota du tenant avec sa fenêtre courante et le coût et les jetons consommés et réservés dans celle-ci. Les quotas par exécution demandent `run` pour trouver leur fenêtre. Demande un rôle de lecteur et un principal de tenant.",
    "operationId": "quota_usage_v1_quotas_usage_get",
    "parameters": [
     {
      "description": "Projet utilisé pour résoudre les fenêtres.",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     },
     {
      "description": "Agent utilisé pour résoudre les fenêtres.",
      "in": "query",
      "name": "agent",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent"
      }
     },
     {
      "description": "Exécution d'agent dont les fenêtres des quotas `run` sont lues.",
      "in": "query",
      "name": "run",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Run"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Quota Usage V1 Quotas Usage Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la consommation des quotas",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/quotas/{quota_id}": {
   "delete": {
    "description": "Supprime un quota du tenant et répond 204. Identifiant inconnu : 404 `quota_not_found`. Audité (`finops.quota.delete`). Demande `finops` ou `tenant_admin`.",
    "operationId": "delete_quota_v1_quotas__quota_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du quota.",
      "in": "path",
      "name": "quota_id",
      "required": true,
      "schema": {
       "title": "Quota Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un quota",
    "tags": [
     "finops"
    ]
   },
   "put": {
    "description": "Remplace tous les champs d'un quota (mêmes règles qu'à la création ; sans devise, la devise actuelle est conservée). Identifiant inconnu : 404 `quota_not_found`. Audité. Demande `finops` ou `tenant_admin`, et un principal de tenant.",
    "operationId": "update_quota_v1_quotas__quota_id__put",
    "parameters": [
     {
      "description": "Identifiant (UUID) du quota.",
      "in": "path",
      "name": "quota_id",
      "required": true,
      "schema": {
       "title": "Quota Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__finops__QuotaIn"
       }
      }
     },
     "description": "Le quota complet.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Update Quota V1 Quotas  Quota Id  Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Remplacer un quota",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/quotas/{reservation_id}/commit": {
   "post": {
    "description": "Remplace le montant réservé par le montant mesuré sur chaque fenêtre de quota de la réservation et vérifie les seuils d'alerte. Une réservation close répond 409 `reservation_closed` ; une réservation inconnue 404 `reservation_not_found`. Audité. Demande `finops`, `tenant_admin`, `agent` ou `developer`, et un principal de tenant.",
    "operationId": "commit_quota_v1_quotas__reservation_id__commit_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la réservation renvoyé par `POST /v1/quotas/reserve`.",
      "in": "path",
      "name": "reservation_id",
      "required": true,
      "schema": {
       "title": "Reservation Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CommitIn"
       }
      }
     },
     "description": "Coût et jetons mesurés de l'appel.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Commit Quota V1 Quotas  Reservation Id  Commit Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Valider une réservation",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/quotas/{reservation_id}/release": {
   "post": {
    "description": "Rend le montant réservé à chaque fenêtre de quota de la réservation sans rien consommer (l'appel a échoué ou n'a pas eu lieu). Une réservation close répond 409 `reservation_closed` ; une réservation inconnue 404 `reservation_not_found`. Audité. Demande `finops`, `tenant_admin`, `agent` ou `developer`, et un principal de tenant.",
    "operationId": "release_quota_v1_quotas__reservation_id__release_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la réservation.",
      "in": "path",
      "name": "reservation_id",
      "required": true,
      "schema": {
       "title": "Reservation Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Release Quota V1 Quotas  Reservation Id  Release Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Libérer une réservation",
    "tags": [
     "finops"
    ]
   }
  },
  "/v1/rag-agent/instances": {
   "get": {
    "description": "Enregistrements d'activation du tenant, du plus récent au plus ancien, avec statut, modèle de déploiement, versions et ressources possédées (une page, `next_cursor` nul). Filtres : `status`, `project`.",
    "operationId": "list_instances_v1_rag_agent_instances_get",
    "parameters": [
     {
      "description": "Seulement les instances dans ce statut (draft, ready, failed, ...).",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 20,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les instances de ce projet (identifiant).",
      "in": "query",
      "name": "project",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 100,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Project"
      }
     },
     {
      "description": "Nombre maximal d'instances renvoyées (1 à 200, 50 par défaut).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "maximum": 200,
       "minimum": 1,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Instances V1 Rag Agent Instances Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les instances de l'agent RAG",
    "tags": [
     "rag-agent"
    ]
   },
   "post": {
    "description": "Crée l'enregistrement d'activation en `draft` (201) avec son `id` immuable (fourni, par exemple l'identifiant choisi par le runtime du RAG, ou généré). Demande un administrateur du tenant ou un propriétaire du projet (403 `forbidden`) ; projet inconnu : 404 `rag_agent_project_not_found` ; un nom ou un identifiant déjà pris : 409 `conflict`. Avec `Idempotency-Key`, une nouvelle tentative rejoue la première réponse. Audité sous `rag_agent.create`.",
    "operationId": "create_instance_v1_rag_agent_instances_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RagAgentInstanceIn"
       }
      }
     },
     "description": "Nom, projet, organisation et unité métier, modèle de déploiement, modèles de génération et d'embeddings du registre, version facultative.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Instance V1 Rag Agent Instances Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une instance brouillon de l'agent RAG",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}": {
   "delete": {
    "description": "Opération longue (202, statut `deleting` puis `deleted`) depuis `draft`, `stopped` ou `failed` (une instance prête est d'abord arrêtée, sinon 409). Ne supprime que ce que l'instance possède, tel qu'enregistré dans `resources` : sa base de connaissances et ses points Qdrant, les objets sous son préfixe de stockage, les mémoires et la politique de mémoire de son agent, ses tuples, sa clé d'API et son secret, son agent ; l'application gouvernée est retirée. Le runtime partagé, une base de données partagée, la collection Qdrant partagée par les bases du tenant et le profil de stockage sont conservés. Chaque étape liste `deleted` et `kept`. Audité sous `rag_agent.delete` et `rag_agent.delete.completed`.",
    "operationId": "delete_instance_v1_rag_agent_instances__instance_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Instance V1 Rag Agent Instances  Instance Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une instance",
    "tags": [
     "rag-agent"
    ]
   },
   "get": {
    "description": "L'enregistrement d'activation avec `serving` (vrai pour `ready` et `degraded`), l'`operation` et le `run` courants, `validation`, `last_error` et les `steps` de l'exécution courante dans l'ordre (état, tentatives, détail, journaux, horaires). C'est la route que le runtime du RAG lit au démarrage et à la demande.",
    "operationId": "get_instance_v1_rag_agent_instances__instance_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance, le même dans le Control Plane et dans le runtime du RAG.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Instance V1 Rag Agent Instances  Instance Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une instance de l'agent RAG",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/backup": {
   "post": {
    "description": "Répond 501 `rag_agent_backup_not_supported` avec `parts` et la raison de chacune : le runtime du RAG n'a pas de route d'administration pour exporter les lignes du schéma rag d'une instance, le domaine des connaissances n'a pas d'export de base de connaissances. Rien n'est simulé ; le refus est audité.",
    "operationId": "backup_instance_v1_rag_agent_instances__instance_id__backup_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Backup Instance V1 Rag Agent Instances  Instance Id  Backup Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Sauvegarder une instance (non pris en charge)",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/branding": {
   "get": {
    "description": "L'image de marque choisie dans le parcours d'activation, portée par le Control Plane : `agent_name`, `sidebar_color`, `navbar_color`, les couleurs de texte calculées pour elles (`sidebar_text_color`, `navbar_text_color`, noir ou blanc, contraste de 4,5:1 ou plus), `logo` (`content_type`, `sha256`, `size_bytes`, `url`) ou null, `version`, `updated_at`, et `app_url` (l'URL de l'application web une fois connue). Un champ null signifie la valeur par défaut du runtime. Le runtime du RAG en garde une réplique pour sa page de connexion publique et la réécrit quand `version` est plus récente. Tout principal du tenant ; l'instance d'un autre tenant répond 404.",
    "operationId": "get_branding_v1_rag_agent_instances__instance_id__branding_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Branding V1 Rag Agent Instances  Instance Id  Branding Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire l'image de marque d'une instance",
    "tags": [
     "rag-agent"
    ]
   },
   "put": {
    "description": "Écrit les champs fournis (un champ absent est conservé, null le ramène à la valeur par défaut du runtime) et renvoie l'image de marque. Les couleurs sont au format `#RRGGBB` (enregistrées en majuscules) ; le texte affiché dessus est noir ou blanc, selon le meilleur contraste (luminance relative WCAG 2.2, la règle du frontend du RAG), et une couleur dont le texte resterait sous 4,5:1 répond 422 `rag_agent_branding_contrast`. `app_url` doit être une URL https sans identifiants (422 `rag_agent_app_url_invalid`). Un changement du nom, des couleurs ou du logo incrémente `version` ; un `expected_version` différent de la version courante répond 409 `rag_agent_branding_version_conflict`. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps rejoue la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Demande un administrateur du tenant ou un propriétaire du projet (403 `forbidden`) ; refusé quand l'instance est `deleting` ou `deleted` (409). Audité sous `rag_agent.branding.update`.",
    "operationId": "put_branding_v1_rag_agent_instances__instance_id__branding_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     },
     {
      "description": "Clé facultative (200 caractères au plus), unique par instance : une nouvelle tentative avec la même clé et le même corps rejoue la première réponse.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 200,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RagAgentBrandingIn"
       }
      }
     },
     "description": "Les champs de l'image de marque à écrire.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Branding V1 Rag Agent Instances  Instance Id  Branding Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier l'image de marque d'une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/branding/logo": {
   "delete": {
    "description": "Retour au logo par défaut du runtime : l'image de marque ne pointe plus vers un objet, `version` est incrémentée et l'objet stocké est supprimé. Sans logo, renvoie l'image de marque inchangée. Mêmes droits que le PUT ; audité sous `rag_agent.branding.logo.remove`.",
    "operationId": "delete_branding_logo_v1_rag_agent_instances__instance_id__branding_logo_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Branding Logo V1 Rag Agent Instances  Instance Id  Branding Logo Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer le logo d'une instance",
    "tags": [
     "rag-agent"
    ]
   },
   "get": {
    "description": "Les octets du logo, lus par le domaine de stockage et contrôlés avec le sha256 enregistré, avec le Content-Type du format validé (`image/png` ou `image/jpeg`), `X-Content-Type-Options: nosniff`, une Content-Security-Policy commençant par `default-src 'none'` et un `ETag`. 404 `rag_agent_logo_not_found` quand l'instance n'a pas de logo. Tout principal du tenant.",
    "operationId": "get_branding_logo_v1_rag_agent_instances__instance_id__branding_logo_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "image/jpeg": {},
       "image/png": {}
      },
      "description": "The logo bytes."
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire le logo d'une instance",
    "tags": [
     "rag-agent"
    ]
   },
   "post": {
    "description": "Multipart `file` (avec les champs texte facultatifs `filename` et `mime_type`). PNG ou JPEG uniquement : le SVG et tout autre format sont refusés. La signature réelle, le Content-Type déclaré et l'extension du fichier doivent concorder ; 32 à 1024 pixels de côté, 512 Kio au plus ; sinon 422 `rag_agent_logo_invalid` avec `details.reason` (`format_not_allowed`, `signature_invalid`, `signature_mismatch`, `extension_mismatch`, `dimensions_out_of_bounds`, `too_large`, `empty`). Le fichier est ensuite analysé par le domaine de sécurité des fichiers, en échec fermé : un fichier bloqué répond 422 `file_blocked`, un analyseur indisponible 503 `file_scan_unavailable`. Il est stocké par le domaine de stockage dans le profil de stockage des documents du tenant sous `rag-agent/<tenant>/<instance>/branding/` (409 `rag_agent_branding_storage_required` sans un tel profil, 503 `rag_agent_branding_storage_unavailable` quand il est injoignable) ; l'objet remplacé est supprimé. Incrémente `version` et renvoie l'image de marque. Mêmes droits que le PUT ; audité sous `rag_agent.branding.logo.update` (un refus est audité comme refus).",
    "operationId": "upload_branding_logo_v1_rag_agent_instances__instance_id__branding_logo_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "multipart/form-data": {
       "schema": {
        "properties": {
         "file": {
          "format": "binary",
          "type": "string"
         },
         "filename": {
          "type": "string"
         },
         "mime_type": {
          "type": "string"
         }
        },
        "required": [
         "file"
        ],
        "type": "object"
       }
      }
     },
     "description": "Formulaire multipart avec le logo dans la partie `file`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upload Branding Logo V1 Rag Agent Instances  Instance Id  Branding Logo Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Téléverser le logo d'une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/health": {
   "get": {
    "description": "Contrôles réels : URL de disponibilité du runtime (`GET /readyz`), base de connaissances (ligne, vector store qdrant, `/readyz` de Qdrant) et passerelle de modèles (modèles de génération et d'embeddings dans le registre, utilisables). `status` vaut `up`, `degraded` ou `down` (runtime hors service) ; `instance_status` est le statut du cycle de vie. Chaque contrôle a `status`, `duration_ms` et `reason` quand il n'est pas `up`.",
    "operationId": "instance_health_v1_rag_agent_instances__instance_id__health_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Instance Health V1 Rag Agent Instances  Instance Id  Health Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Santé d'une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/provision": {
   "post": {
    "description": "Opération longue (202, `Operation-Id`) : statut `provisioning` puis `starting`, étapes de `validation` à `activation` exécutées par le worker. Depuis un `draft` validé (sinon 409 `rag_agent_not_validated`) ou après un provisionnement en échec, qui reprend à l'étape en échec. Répété pendant le provisionnement ou une fois prête : `changed: false`. Audité sous `rag_agent.provision`, chaque étape sous `rag_agent.step`.",
    "operationId": "provision_instance_v1_rag_agent_instances__instance_id__provision_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Provision Instance V1 Rag Agent Instances  Instance Id  Provision Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Provisionner une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/restart": {
   "post": {
    "description": "Opération longue (202) : depuis `ready`, `degraded`, `stopped` ou après un redémarrage en échec, étapes `deployment` (redémarrage par le Navin Manager pour une instance dédiée), `health`, `smoke`, `activation`. Un redémarrage en cours : `changed: false`.",
    "operationId": "restart_instance_v1_rag_agent_instances__instance_id__restart_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Restart Instance V1 Rag Agent Instances  Instance Id  Restart Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Redémarrer une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/restore": {
   "post": {
    "description": "Répond 501 `rag_agent_backup_not_supported` avec les mêmes `parts` que la sauvegarde : aucune sauvegarde d'instance ne peut encore être faite réellement. Audité.",
    "operationId": "restore_instance_v1_rag_agent_instances__instance_id__restore_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RagAgentRestoreIn"
       }
      }
     },
     "description": "La sauvegarde à restaurer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Restore Instance V1 Rag Agent Instances  Instance Id  Restore Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Restaurer une instance (non pris en charge)",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/start": {
   "post": {
    "description": "Opération longue (202) : depuis `stopped` (ou après un démarrage ou un arrêt en échec), étapes `deployment` (démarrage par le Navin Manager pour une instance dédiée, statut seulement pour une partagée), `health`, `smoke`, `activation` (application gouvernée active). Déjà en démarrage ou prête : `changed: false`.",
    "operationId": "start_instance_v1_rag_agent_instances__instance_id__start_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Start Instance V1 Rag Agent Instances  Instance Id  Start Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Démarrer une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/stop": {
   "post": {
    "description": "Opération longue (202) : depuis `ready`, `degraded` ou après un démarrage, un redémarrage, une mise à niveau ou un arrêt en échec, étapes `deployment` (arrêt par le Navin Manager pour une instance dédiée, statut seulement pour une partagée) et `activation` (application gouvernée suspendue, raison `rag_instance_stopped`) ; statut `stopped`. Déjà en arrêt ou arrêtée : `changed: false`.",
    "operationId": "stop_instance_v1_rag_agent_instances__instance_id__stop_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Stop Instance V1 Rag Agent Instances  Instance Id  Stop Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Arrêter une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/upgrade": {
   "post": {
    "description": "Opération longue (202), statut `updating` : une instance partagée suit le runtime partagé, donc l'étape de déploiement vérifie que le runtime exécute la version demandée (sinon `shared_runtime_version_mismatch`) ; une instance dédiée est réinstallée par le Navin Manager. Puis `health`, `smoke`, `activation` ; `deployed_version` devient la version. La même version déjà déployée ou en cours : `changed: false`.",
    "operationId": "upgrade_instance_v1_rag_agent_instances__instance_id__upgrade_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RagAgentUpgradeIn"
       }
      }
     },
     "description": "La version à déployer.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upgrade Instance V1 Rag Agent Instances  Instance Id  Upgrade Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Mettre à niveau une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/rag-agent/instances/{instance_id}/validate": {
   "post": {
    "description": "Contrôles réels avant le provisionnement : le projet existe et l'appelant peut l'administrer, le modèle de génération existe dans le registre avec la capacité `chat` et un statut utilisable, le modèle d'embeddings se résout dans la Model Gateway (capacité `embeddings`), le quota d'instances, Qdrant répond, le runtime partagé répond (modèle partagé) ou le Navin Manager répond (modèle dédié, un service du catalogue non publié est un avertissement). Depuis `draft` ou après une validation en échec. 200 avec `validation` et le statut `draft` ; 422 `rag_agent_validation_failed` avec les contrôles en échec (statut `failed`, étape `validation`). Audité sous `rag_agent.validate` et `rag_agent.validate.completed`.",
    "operationId": "validate_instance_v1_rag_agent_instances__instance_id__validate_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'instance.",
      "in": "path",
      "name": "instance_id",
      "required": true,
      "schema": {
       "title": "Instance Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Validate Instance V1 Rag Agent Instances  Instance Id  Validate Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Valider une instance",
    "tags": [
     "rag-agent"
    ]
   }
  },
  "/v1/realtime/sessions": {
   "post": {
    "description": "Ouvre une session (201) et la renvoie avec `session_id`, `livekit` (`url`, `token`, `room`, `configured`) et l'accès WebSocket (`ws_url`, `ws_token`, `ws_token_expires_at`). Demande `tenant_admin`, `developer` ou `agent`.\nContrôles : `pipeline` vaut `echo` ou `stt` (400 `invalid_request`) ; `config.vad` et `config.dlp_mode` sont valides (422 `parameter_invalid`) ; un appelant agent agit pour `on_behalf_of`, un utilisateur ne peut ouvrir une session que pour lui-même (403 `identity_mismatch`) ; les identifiants LiveKit sont configurés ; la politique `realtime.session.create` est évaluée. La salle prend par défaut un nom dérivé du tenant et de la session. Audité sous `realtime.session.create`.",
    "operationId": "create_session_v1_realtime_sessions_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__realtime__SessionIn"
       }
      }
     },
     "description": "Salle, pipeline, durée de vie du jeton, configuration, agent et utilisateur représenté.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Session V1 Realtime Sessions Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ouvrir une session temps réel",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}": {
   "get": {
    "description": "Renvoie la session : salle, identités, agent, pipeline, configuration, statut, secondes d'audio, jetons et usage. 404 `not_found`.",
    "operationId": "get_session_v1_realtime_sessions__session_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Session V1 Realtime Sessions  Session Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une session temps réel",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}/consents": {
   "get": {
    "description": "Consentements enregistrés pour la session, dans l'ordre de recueil, avec leur révocation le cas échéant. Une seule page.",
    "operationId": "list_consents_v1_realtime_sessions__session_id__consents_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Consents V1 Realtime Sessions  Session Id  Consents Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les consentements d'une session",
    "tags": [
     "realtime"
    ]
   },
   "post": {
    "description": "Consentement d'un participant (finalité, enregistrement, transcription, conservation, méthode), recueilli par l'appelant (201). Le participant est par défaut celui de la session. La méthode doit être `verbal`, `click`, `written` ou `api` (422 `parameter_invalid`) ; la session ne doit pas être terminée (409 `session_ended`) ; la politique `realtime.consent.record` est évaluée. Demande `tenant_admin`, `developer` ou `agent`. Audité sous `realtime.consent.record`.",
    "operationId": "record_consent_v1_realtime_sessions__session_id__consents_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConsentIn"
       }
      }
     },
     "description": "Le consentement du participant.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Record Consent V1 Realtime Sessions  Session Id  Consents Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer le consentement d'un participant",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}/consents/{consent_id}/revoke": {
   "post": {
    "description": "La révocation prend effet au prochain événement audio : transcription et enregistrement sont de nouveau refusés. Un consentement déjà révoqué ou inconnu répond 404 `not_found`. Demande `tenant_admin`, `developer` ou `agent`. Audité sous `realtime.consent.revoke`.",
    "operationId": "revoke_consent_v1_realtime_sessions__session_id__consents__consent_id__revoke_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du consentement.",
      "in": "path",
      "name": "consent_id",
      "required": true,
      "schema": {
       "title": "Consent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Revoke Consent V1 Realtime Sessions  Session Id  Consents  Consent Id  Revoke Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Révoquer un consentement",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}/end": {
   "post": {
    "description": "Termine la session et enregistre son événement d'usage (`voice_live` : secondes d'audio, jetons d'entrée et de sortie, coût mesuré). Terminer une session déjà terminée la renvoie sans changement. Demande `tenant_admin`, `developer` ou `agent`. Audité sous `realtime.session.end`.",
    "operationId": "end_session_v1_realtime_sessions__session_id__end_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response End Session V1 Realtime Sessions  Session Id  End Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Terminer une session temps réel",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}/token": {
   "post": {
    "description": "Nouveau jeton WebSocket à usage unique pour une session ouverte (reconnexion), lu sous RLS : `ws_url`, `ws_token`, `ws_token_expires_at`. 409 `session_ended` ; 404 `not_found`. Demande `tenant_admin`, `developer` ou `agent`. Audité sous `realtime.session.token`.",
    "operationId": "session_token_v1_realtime_sessions__session_id__token_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Session Token V1 Realtime Sessions  Session Id  Token Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir un nouveau jeton WebSocket",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}/transcripts": {
   "get": {
    "description": "Transcriptions conservées, déjà filtrées par la DLP (la transcription brute n'est jamais conservée), avec tour, consentement, participant, détections DLP et expiration. Demande `tenant_admin`, `developer`, `auditor` ou `security_officer`.",
    "operationId": "list_transcripts_v1_realtime_sessions__session_id__transcripts_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Transcripts V1 Realtime Sessions  Session Id  Transcripts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les transcriptions d'une session",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/realtime/sessions/{session_id}/turns": {
   "get": {
    "description": "Tours avec le participant, l'utilisateur, l'agent, l'identifiant de trace, le statut (une interruption enregistre `cancelled`) et la latence mesurée, dans l'ordre. Une seule page.",
    "operationId": "list_turns_v1_realtime_sessions__session_id__turns_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session temps réel.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Turns V1 Realtime Sessions  Session Id  Turns Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les tours d'une session",
    "tags": [
     "realtime"
    ]
   }
  },
  "/v1/redteam/runs": {
   "get": {
    "description": "Liste les campagnes de red team du tenant, identifiant le plus récent d'abord, paginées, ainsi que `probes` : les sondes intégrées (identifiant et catégorie), les familles de sondes garak et si garak est installé. Ouvert à tout membre du tenant.",
    "operationId": "list_redteam_v1_redteam_runs_get",
    "parameters": [
     {
      "description": "Nombre maximal de campagnes dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "title": "Limit",
       "type": "integer"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Redteam V1 Redteam Runs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les campagnes de red team",
    "tags": [
     "evaluation"
    ]
   },
   "post": {
    "description": "Envoie des sondes adverses à la cible et enregistre les constats. Exige `tenant_admin`, `developer`, `security_officer` ou `ai_governance_officer`.\n\n- Moteur `builtin` (par défaut) : les sondes intégrées (toutes par défaut), chacune jugée sur la réponse de la cible (fuite d'un secret témoin, du prompt système, absence de refus...).\n- Moteur `garak` : des familles de sondes garak (`promptinject` et `dan` par défaut), quand garak est installé.\n\nLa campagne finit `passed` quand le taux de réussite des attaques (constats vulnérables sur total) est au plus `max_attack_success_rate`, `failed` au-dessus, `error` quand la cible ou garak est indisponible. Des sondes ou un moteur inconnus répondent 400 `invalid_request` avec ceux qui sont disponibles. La réponse porte `Operation-Id`. Écrit l'événement d'audit `redteam.run`.",
    "operationId": "create_redteam_v1_redteam_runs_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RedTeamIn"
       }
      }
     },
     "description": "Cible, moteur, sondes et taux de réussite des attaques accepté.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Redteam V1 Redteam Runs Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer une campagne de red team",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/redteam/runs/{run_id}": {
   "get": {
    "description": "Renvoie une campagne de red team : cible, moteur, sondes, statut, synthèse (total, vulnérables, taux de réussite des attaques, seuil, par catégorie) et constats. Ouvert à tout membre du tenant. Répond 404 `not_found`.",
    "operationId": "get_redteam_v1_redteam_runs__run_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la campagne de red team.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Redteam V1 Redteam Runs  Run Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une campagne de red team",
    "tags": [
     "evaluation"
    ]
   }
  },
  "/v1/retention/erase": {
   "post": {
    "description": "Supprime les données détenues sur une personne, en une transaction. Exige `tenant_admin`, `security_officer` ou `data_steward`.\n\n1. Une personne sous gel juridique n'est pas effacée : `status` vaut `blocked_legal_hold`.\n2. Les éléments de mémoire, médias, sessions et sessions temps réel terminées de la personne sont supprimés, ainsi que les documents avec `include_documents` ; les éléments gelés sont ignorés.\n3. Les traces Opik et les données d'analyse ClickHouse de la personne sont supprimées quand ces magasins sont configurés.\n\n`status` vaut `completed`, `dry_run`, ou `incomplete` quand un magasin externe a échoué (relancez l'effacement). La réponse donne `request_id`, `deleted` et `skipped_legal_hold` par ressource, `external` par magasin et `retained` (événements d'audit et enregistrements de consommation, conservés par obligation légale). Écrit l'événement d'audit `retention.erase`, en échec quand l'effacement est incomplet.",
    "operationId": "erase_v1_retention_erase_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/EraseIn"
       }
      }
     },
     "description": "Personne, périmètre et raison de l'effacement.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Erase V1 Retention Erase Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Effacer une personne concernée",
    "tags": [
     "retention"
    ]
   }
  },
  "/v1/retention/legal-hold": {
   "post": {
    "description": "Sans `hold_id`, pose un nouveau gel sur une personne (`subject_id`) ou sur une ressource (`resource_type` et `resource_id`) ; `reason` est obligatoire (400 `invalid_request` sinon). Avec `hold_id`, active ou lève ce gel selon `active` (404 `not_found` pour un gel inconnu).\n\nL'indicateur de gel des éléments de mémoire et des médias concernés est mis à jour aussitôt ; les éléments gelés sont ignorés par les passes de conservation et les effacements jusqu'à la levée de tous leurs gels. Exige `tenant_admin`, `security_officer` ou `data_steward`. Écrit l'événement d'audit `retention.legal_hold`. Répond 201 avec le gel.",
    "operationId": "legal_hold_v1_retention_legal_hold_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/LegalHoldIn"
       }
      }
     },
     "description": "Nouveau gel, ou identifiant d'un gel à activer ou lever.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Legal Hold V1 Retention Legal Hold Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Poser ou lever un gel juridique",
    "tags": [
     "retention"
    ]
   }
  },
  "/v1/retention/policies": {
   "get": {
    "description": "Renvoie les politiques de conservation du tenant triées par type de ressource dans `items`, et les gels juridiques actifs dans `legal_holds`. Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_policies_v1_retention_policies_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Policies V1 Retention Policies Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les politiques de conservation",
    "tags": [
     "retention"
    ]
   },
   "post": {
    "description": "Définit la politique de conservation d'un type de ressource, en remplaçant l'existante. Exige `tenant_admin`, `security_officer` ou `data_steward`. Un type de ressource inconnu ou une action autre que `erase` ou `archive` répond 400 `invalid_request`. La politique s'applique à la passe de conservation suivante. Écrit l'événement d'audit `retention.policy.upsert`. Répond 201 avec la politique.",
    "operationId": "upsert_policy_v1_retention_policies_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__retention__PolicyIn"
       }
      }
     },
     "description": "Type de ressource, durée de vie et action de la politique.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upsert Policy V1 Retention Policies Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer ou modifier une politique de conservation",
    "tags": [
     "retention"
    ]
   }
  },
  "/v1/retention/run": {
   "post": {
    "description": "Applique maintenant les politiques actives, à tous les types de ressources ou à ceux de `resource_types`. Les éléments de mémoire et les médias dont l'expiration propre est passée sont purgés même sans politique ; les transcriptions et enregistrements temps réel suivent la durée de conservation du consentement des participants. Les éléments sous gel juridique sont ignorés.\n\nAvec `dry_run: true`, rien n'est supprimé et le rapport donne ce qui le serait. La réponse donne `run_id`, `dry_run`, `created_at` et `report` avec, par type de ressource, `processed` et `skipped_legal_hold`. Exige `tenant_admin`, `security_officer` ou `data_steward`. Un type de ressource inconnu répond 400 `invalid_request`. Écrit l'événement d'audit `retention.run`.",
    "operationId": "run_v1_retention_run_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/eai__domains__retention__RunIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Indicateur de simulation et types de ressources facultatifs. Un corps vide lance réellement toutes les politiques."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Run V1 Retention Run Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer les politiques de conservation",
    "tags": [
     "retention"
    ]
   }
  },
  "/v1/risk/assessments": {
   "get": {
    "description": "Liste les évaluations du tenant, les plus récentes d'abord, 500 au plus, éventuellement pour une seule cible. Chaque élément donne la cible, `eu_ai_act_category` (aussi en `risk_level`), les cas d'usage, les réponses, la justification, les obligations, le statut et le responsable. Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_assessments_v1_risk_assessments_get",
    "parameters": [
     {
      "description": "Uniquement les évaluations de cette référence de cible, par exemple un nom d'agent.",
      "in": "query",
      "name": "target_ref",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Target Ref"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Assessments V1 Risk Assessments Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les évaluations de risques",
    "tags": [
     "risk"
    ]
   },
   "post": {
    "description": "Classe la cible au titre de l'AI Act européen et enregistre l'évaluation. Exige `tenant_admin`, `security_officer`, `developer` ou `ai_governance_officer` ; le statut `approved` exige `tenant_admin`, `security_officer` ou `ai_governance_officer` (403 `forbidden`).\n\n1. Les cas d'usage et les réponses à vrai sont comparés d'abord aux pratiques interdites de l'article 5 (`unacceptable`), puis aux domaines de l'annexe III (`high`), puis aux déclencheurs de l'article 50 (`limited`) ; sinon `minimal`.\n2. La justification et les obligations de la catégorie sont enregistrées avec l'évaluation, ainsi que les preuves fournies.\n3. Pour une cible de type `agent`, le niveau de risque de l'agent est mis à jour dans le registre (`registry_synced`).\n\nUn statut autre que `draft`, `submitted`, `approved` ou `rejected` répond 400 `invalid_request`. Écrit l'événement d'audit `risk.assessment.create`.",
    "operationId": "create_assessment_v1_risk_assessments_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AssessmentIn"
       }
      }
     },
     "description": "Cible, cas d'usage, réponses, responsable, statut et preuves de l'évaluation.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Assessment V1 Risk Assessments Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Évaluer le risque d'un système d'IA",
    "tags": [
     "risk"
    ]
   }
  },
  "/v1/risk/controls": {
   "get": {
    "description": "Liste les contrôles adoptés par le tenant, triés par code, chacun avec `evidence_count`, ses `exceptions` et `active_exceptions`, ainsi que le `catalog` intégré où `adopted` indique ceux que le tenant utilise. Ouvert à tout membre du tenant.",
    "operationId": "list_controls_v1_risk_controls_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Controls V1 Risk Controls Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les contrôles",
    "tags": [
     "risk"
    ]
   },
   "post": {
    "description": "Crée ou modifie le contrôle du `code` donné. Pour un code du catalogue, le titre et les référentiels valent par défaut ceux du catalogue. Exige `tenant_admin` ou `security_officer`. Erreurs 400 `invalid_request` : statut autre que `planned`, `implemented`, `partially_implemented` ou `not_applicable` ; absence de titre pour un code personnalisé ; référentiel autre que `iso_42001`, `nist_ai_rmf`, `gdpr` ou `eu_ai_act`. Les preuves fournies sont ajoutées. Écrit l'événement d'audit `risk.control.upsert`.",
    "operationId": "upsert_control_v1_risk_controls_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ControlIn"
       }
      }
     },
     "description": "Code, titre, référentiels, statut, responsable et preuves du contrôle.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upsert Control V1 Risk Controls Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Adopter ou modifier un contrôle",
    "tags": [
     "risk"
    ]
   }
  },
  "/v1/risk/evidence": {
   "get": {
    "description": "Renvoie les preuves enregistrées sur les contrôles et les évaluations, les plus récentes d'abord, plus les preuves collectées automatiquement sur la plateforme et rattachées aux contrôles du catalogue : nombres d'agents, d'outils, de modèles, d'évaluations, de règles DLP et de politiques de conservation actives, de clés, d'événements et d'exports d'audit avec l'état de la chaîne d'audit, d'évaluations, de campagnes de red team, d'incidents et de validations. Ouvert à tout membre du tenant.",
    "operationId": "evidence_v1_risk_evidence_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Evidence V1 Risk Evidence Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les preuves de conformité",
    "tags": [
     "risk"
    ]
   }
  },
  "/v1/risk/exceptions": {
   "get": {
    "description": "Liste les dérogations du tenant triées par expiration, chacune avec son `status` calculé : `active`, `expired` ou `revoked`. Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_exceptions_v1_risk_exceptions_get",
    "parameters": [
     {
      "description": "Uniquement les dérogations dans ce statut : `active`, `expired` ou `revoked`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Exceptions V1 Risk Exceptions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les dérogations",
    "tags": [
     "risk"
    ]
   },
   "post": {
    "description": "Enregistre une dérogation limitée dans le temps sur un contrôle (`control_id` ou `control_code`) ou sur une cible (`target_ref`). Exige `tenant_admin`, `security_officer` ou `developer`.\n\nContrôles : `expires_at` dans le futur et à 365 jours au plus (422 `invalid_exception`) ; l'approbateur n'est pas le demandeur (409 `separation_of_duties`) ; le contrôle existe (404 `not_found`) ; un contrôle ou une cible est donné (400 `invalid_request`). Écrit l'événement d'audit `risk.exception.create`. La réponse porte la dérogation avec son statut.",
    "operationId": "create_exception_v1_risk_exceptions_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ExceptionIn"
       }
      }
     },
     "description": "Contrôle ou cible, raison, contrôles compensatoires, approbateur et expiration.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Exception V1 Risk Exceptions Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander une dérogation à un contrôle",
    "tags": [
     "risk"
    ]
   }
  },
  "/v1/roadmap": {
   "get": {
    "description": "Feuille de route lue dans `roadmap.yaml` du dossier de catalogue, toujours avec une liste `phases` (vide si le fichier est absent). Chargée en YAML sûr. Sans authentification.",
    "operationId": "roadmap_v1_roadmap_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Roadmap V1 Roadmap Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire la feuille de route du produit",
    "tags": [
     "system"
    ]
   }
  },
  "/v1/routes": {
   "get": {
    "description": "Règles de routage du tenant par priorité puis par nom, chacune avec ses critères, sa stratégie, ses candidats, ses replis, ses contraintes et son `target_model` (le premier candidat). Demande un rôle de lecteur.",
    "operationId": "routes_list_v1_routes_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les règles de routage",
    "tags": [
     "llm"
    ]
   },
   "post": {
    "description": "Crée une règle de routage du tenant et répond 201. Un principal de plateforme sans tenant répond 403 `platform_only` ; une stratégie inconnue 422 `parameter_invalid`. Audité (`route.create`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "routes_create_v1_routes_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "`name` (`default` par défaut), `match` (champs de la requête auxquels la règle s'applique, une valeur ou une liste de valeurs), `strategy` (`priority` par défaut, `cost`, `latency`, `quality`), `candidates` (identifiants de modèles, ou un seul `target_model`), `fallbacks`, `constraints`, `priority` (100 par défaut, la plus basse d'abord), `enabled` (vrai par défaut).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une règle de routage",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/routes/simulate": {
   "post": {
    "description": "Indique vers quel modèle une requête serait routée, sans l'appeler : la règle donnée par `route_id` ou la première règle active qui correspond, les listes d'autorisation et d'interdiction du tenant (restreintes par la requête), la classification et la politique de repli. Répond le modèle choisi, les replis, les candidats écartés avec leur raison et la règle utilisée ; aucun modèle éligible répond 422 `no_eligible_model`. Les modèles restreints ne sont admis que pour `tenant_admin` et `security_officer` avec `allow_restricted`. Demande un rôle de lecteur.",
    "operationId": "routes_simulate_v1_routes_simulate_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête à simuler : `model`, `candidates`, `strategy`, `purpose`, `capabilities`, `data_classification` (`internal` par défaut), `region_allow`, `provider_allow`, `model_allow`, `allow_restricted`, `fallback_policy` (`allowed` par défaut), `route_id` facultatif, et tout champ sur lequel portent les règles.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Simuler le routage d'une requête",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/routes/{rid}": {
   "delete": {
    "description": "Supprime une règle de routage du tenant et répond 204. Règle inconnue : 404 `route_not_found`. Audité (`route.delete`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "routes_delete_v1_routes__rid__delete",
    "parameters": [
     {
      "description": "Identifiant de la règle de routage.",
      "in": "path",
      "name": "rid",
      "required": true,
      "schema": {
       "title": "Rid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une règle de routage",
    "tags": [
     "llm"
    ]
   },
   "put": {
    "description": "Remplace les critères, la stratégie, les candidats, les replis, les contraintes, la priorité et l'indicateur d'activation d'une règle du tenant (les champs omis prennent leur valeur par défaut ; le nom est conservé). Règle inconnue : 404 `route_not_found`. Audité (`route.update`). Demande `tenant_admin`, `platform_admin` ou `ai_platform_engineer`.",
    "operationId": "routes_update_v1_routes__rid__put",
    "parameters": [
     {
      "description": "Identifiant de la règle de routage.",
      "in": "path",
      "name": "rid",
      "required": true,
      "schema": {
       "title": "Rid",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "La règle complète : mêmes champs qu'à la création sauf `name`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Remplacer une règle de routage",
    "tags": [
     "llm"
    ]
   }
  },
  "/v1/runs": {
   "get": {
    "description": "Runs du tenant, du plus récent au plus ancien, paginés avec `cursor`. Filtres : `agent_id` (identifiant ou nom ; `agent` en est un alias), `state`, `session_id`, `parent_run_id`. Un état inconnu ou un UUID mal formé répond 400 `invalid_request`.",
    "operationId": "list_runs_v1_runs_get",
    "parameters": [
     {
      "description": "Seulement les runs de cet agent (UUID ou nom).",
      "in": "query",
      "name": "agent_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent Id"
      }
     },
     {
      "description": "Alias de `agent_id`, utilisé quand `agent_id` est absent.",
      "in": "query",
      "name": "agent",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent"
      }
     },
     {
      "description": "Seulement les runs dans cet état : `pending`, `running`, `waiting_approval`, `waiting_input`, `suspended`, `succeeded`, `failed`, `cancelled` ou `timed_out`.",
      "in": "query",
      "name": "state",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "State"
      }
     },
     {
      "description": "Seulement les runs de cette session (UUID).",
      "in": "query",
      "name": "session_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Session Id"
      }
     },
     {
      "description": "Seulement les runs de sous-agents lancés par ce run parent (UUID).",
      "in": "query",
      "name": "parent_run_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Parent Run Id"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Runs V1 Runs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les runs",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}": {
   "get": {
    "description": "Renvoie l'Execution Contract v1 du run : agent et version, tâche, état, étapes, limites et consommation de budget, erreurs, politique de nouvelle tentative, informations de reprise et trace. 404 `run_not_found`.",
    "operationId": "get_run_v1_runs__run_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Run V1 Runs  Run Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/cancel": {
   "post": {
    "description": "Fait passer le run à `cancelled` avec l'erreur `cancelled` ; ses runs de sous-agents actifs sont aussi annulés. Demande `tenant_admin`, `developer`, `agent` ou `security_officer`. Un run déjà terminé répond 409 `invalid_transition`. Audité sous `run.transition`.",
    "operationId": "cancel_run_v1_runs__run_id__cancel_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/eai__domains__agents__routes__CancelIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Raison facultative de l'annulation."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Cancel Run V1 Runs  Run Id  Cancel Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Annuler un run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/checkpoints": {
   "get": {
    "description": "Points de reprise du run dans l'ordre de séquence. Une seule page. 404 `run_not_found`.",
    "operationId": "list_checkpoints_v1_runs__run_id__checkpoints_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Checkpoints V1 Runs  Run Id  Checkpoints Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les points de reprise d'un run",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Enregistre un état du run avec le numéro de séquence suivant et la séquence d'étape courante, pour que le run puisse reprendre à partir de là. Demande `tenant_admin`, `developer` ou `agent`. Refusé avec 409 `run_finished` sur un run `succeeded` ou `cancelled`. Émet `checkpoint.created` ; audité sous `run.checkpoint`.",
    "operationId": "create_checkpoint_v1_runs__run_id__checkpoints_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CheckpointIn"
       }
      }
     },
     "description": "État à enregistrer et libellé facultatif.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Checkpoint V1 Runs  Run Id  Checkpoints Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un point de reprise",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/events": {
   "get": {
    "description": "Flux Server-Sent Events (`text/event-stream`) des événements du run : `run.created`, `run.state`, `step.recorded`, `checkpoint.created`, `subagent.spawned`. Chaque message porte `id`, `event` et `data` (`type`, `data`, `at`).\nÉtapes :\n1. Les événements déjà enregistrés après `Last-Event-ID` sont envoyés.\n2. Sans `follow`, le flux se termine aussitôt ; avec `follow=true`, il interroge chaque seconde.\n3. Le flux se termine par un événement `end` portant l'état (`state`) du run quand le run est terminé, quand `timeout` est écoulé ou quand le client se déconnecte.\n404 `run_not_found`.",
    "operationId": "run_events_v1_runs__run_id__events_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     },
     {
      "description": "Si vrai, garde le flux ouvert et envoie les nouveaux événements jusqu'à la fin du run ou l'expiration de `timeout`. Faux par défaut.",
      "in": "query",
      "name": "follow",
      "required": false,
      "schema": {
       "default": false,
       "title": "Follow",
       "type": "boolean"
      }
     },
     {
      "description": "Durée maximale d'un flux suivi, en secondes (1 à 300, 30 par défaut).",
      "in": "query",
      "name": "timeout",
      "required": false,
      "schema": {
       "default": 30,
       "maximum": 300,
       "minimum": 1,
       "title": "Timeout",
       "type": "integer"
      }
     },
     {
      "description": "Identifiant numérique du dernier événement reçu ; le flux reprend après lui. En-tête standard de reconnexion SSE.",
      "in": "header",
      "name": "Last-Event-ID",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Last-Event-Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Suivre les événements d'un run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/heartbeat": {
   "post": {
    "description": "Enregistre que le runtime est vivant et lui indique s'il doit continuer : `action` vaut `continue`, ou `stop` avec une `reason` (`run_cancelled`, `run_terminal`, `run_suspended`, `agent_killed`, `agent_suspended`). La réponse donne aussi `stale_after_seconds`. Un run terminé n'est pas mis à jour. Demande `tenant_admin`, `developer` ou `agent`. Audité seulement pour le premier heartbeat d'un run et quand le runtime reçoit l'ordre d'arrêter.",
    "operationId": "heartbeat_v1_runs__run_id__heartbeat_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/HeartbeatIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Informations de progression facultatives."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Heartbeat V1 Runs  Run Id  Heartbeat Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Envoyer un heartbeat de run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/replay": {
   "get": {
    "description": "Renvoie l'historique complet d'un run pour revue : son Execution Contract, ses étapes, ses points de reprise, les événements d'audit qui le citent et une `timeline` unique triée par date (événements du run et d'audit). Demande `tenant_admin`, `developer`, `agent`, `auditor` ou `security_officer`. Lecture seule : rien n'est réexécuté.",
    "operationId": "replay_v1_runs__run_id__replay_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Replay V1 Runs  Run Id  Replay Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rejouer un run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/resume": {
   "post": {
    "description": "Ramène à `running` un run `suspended`, `failed`, `timed_out` ou `waiting_input` (sinon 409 `invalid_transition`). Demande `tenant_admin`, `developer` ou `agent` ; l'agent doit être actif (409 `agent_not_active`).\nStratégies :\n1. `from_last_checkpoint` (par défaut, sauf pour `waiting_input`) : reprendre depuis `checkpoint_id` ou le dernier point de reprise (409 `no_checkpoint` s'il n'y en a pas).\n2. `restart_step` (par défaut pour `waiting_input`) : rejouer l'étape courante.\n3. `restart_run` : tout recommencer avec la consommation de budget remise à zéro.\nUn run en échec sur `budget_exceeded`, `max_steps_exceeded`, `killed` ou `approval_rejected` ne peut utiliser que `restart_run` (409 `run_not_resumable`). Renvoie le run et le point de reprise utilisé.",
    "operationId": "resume_run_v1_runs__run_id__resume_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/ResumeIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Point de reprise et stratégie de reprise facultatifs."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Resume Run V1 Runs  Run Id  Resume Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Reprendre un run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/steps": {
   "get": {
    "description": "Étapes du run dans l'ordre, chacune avec `seq`, type, état, tentative, jetons, coût et erreur le cas échéant. Une seule page. 404 `run_not_found`.",
    "operationId": "list_steps_v1_runs__run_id__steps_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Steps V1 Runs  Run Id  Steps Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les étapes d'un run",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Enregistre une étape déclarée par le runtime et renvoie la décision du Loop Controller : `run_state`, `next_kind`, `retry`, `backoff_seconds`, `error`, `approval_id` et les budgets. Demande `tenant_admin`, `developer` ou `agent`.\nÉtapes :\n1. Le Kill Switch Registry et, pour une étape sensible, la chaîne de délégation, la session et la politique sont vérifiés.\n2. Un run `pending` passe à `running` ; tout autre état que `running` répond 409 `run_not_running`.\n3. L'usage et le coût s'ajoutent à la consommation de budget (coût dans la devise du run, sinon 400 `invalid_request`).\n4. Une étape qui correspond à `autonomy.human_approval_for` sans `approval_id` reste `pending`, une approbation est ouverte et le run passe à `waiting_approval` ; une étape de type `approval` à l'état `pending` ouvre une approbation avec `quorum`.\n5. Les budgets, le nombre maximal d'étapes et les nouvelles tentatives décident de l'état suivant (`failed` ou `timed_out` en cas de dépassement).",
    "operationId": "add_step_v1_runs__run_id__steps_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/StepIn"
       }
      }
     },
     "description": "L'étape : type, état, usage, coût, erreur et indications d'état du run.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Add Step V1 Runs  Run Id  Steps Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déclarer une étape d'un run",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/runs/{run_id}/subagents": {
   "get": {
    "description": "Résumés des runs enfants lancés par ce run, du plus ancien au plus récent. Une seule page. 404 `run_not_found`.",
    "operationId": "list_subagents_v1_runs__run_id__subagents_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run parent.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Subagents V1 Runs  Run Id  Subagents Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les runs de sous-agents",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Lance un run enfant depuis un parent `pending` ou `running` (sinon 409 `run_not_running`) et renvoie son Execution Contract. Demande `tenant_admin`, `developer` ou `agent` et le scope `agents:run`.\nContrôles, dans l'ordre : le Kill Switch Registry ; la profondeur selon `autonomy.max_depth` (1 par défaut, 409 `subagent_depth_exceeded`) ; le nombre d'enfants selon `autonomy.max_subagents` (3 par défaut, 409 `subagent_count_exceeded`) ; l'agent enfant selon la liste `subagents` du manifeste parent (403 `subagent_not_allowed`) ; la délégation du parent. Le budget de l'enfant ne peut dépasser ce qu'il reste au parent, l'enfant rejoint la session du parent et un parent en cours enregistre une étape `subagent`.",
    "operationId": "spawn_subagent_v1_runs__run_id__subagents_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du run parent.",
      "in": "path",
      "name": "run_id",
      "required": true,
      "schema": {
       "title": "Run Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SubagentIn"
       }
      }
     },
     "description": "Agent enfant, tâche et budget.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Spawn Subagent V1 Runs  Run Id  Subagents Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lancer un run de sous-agent",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/sandbox/executions": {
   "post": {
    "description": "Crée une exécution en sandbox : l'image et la commande tournent comme Job Kubernetes avec la classe de runtime du profil (gVisor, Kata, Wasm) quand Kubernetes est configuré (`status: submitted`, ou `failed` si la soumission échoue). Demande `tenant_admin`, `developer` ou `agent` ; le profil `none` demande `tenant_admin` (403). Un profil, une image ou des limites invalides répondent 422 `invalid_sandbox`. L'usage est mesuré et la demande auditée sous `sandbox.execute`.",
    "operationId": "create_sandbox_execution_v1_sandbox_executions_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SandboxIn"
       }
      }
     },
     "description": "Profil, image, commande, environnement et limites de ressources.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Sandbox Execution V1 Sandbox Executions Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exécuter une commande en sandbox",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/sandbox/executions/{execution_id}": {
   "get": {
    "description": "Renvoie l'exécution en sandbox : profil, image, commande, limites, spécification de Job Kubernetes générée, statut, exécuteur et résultat. 404 `sandbox_execution_not_found`.",
    "operationId": "get_sandbox_execution_v1_sandbox_executions__execution_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'exécution en sandbox.",
      "in": "path",
      "name": "execution_id",
      "required": true,
      "schema": {
       "title": "Execution Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Sandbox Execution V1 Sandbox Executions  Execution Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une exécution en sandbox",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/schedules": {
   "get": {
    "description": "Planifications cron du tenant, de la plus ancienne à la plus récente, éventuellement pour un seul agent. Une seule page.",
    "operationId": "list_schedules_v1_schedules_get",
    "parameters": [
     {
      "description": "Seulement les planifications de cet agent (UUID ou nom). 404 `agent_not_found` s'il est inconnu.",
      "in": "query",
      "name": "agent",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Schedules V1 Schedules Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les planifications",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Planifie des runs d'un agent à partir d'une expression cron évaluée dans un fuseau horaire ; la première échéance `next_run_at` est calculée (422 `invalid_cron`). Quand Temporal est configuré, la planification y est créée (503 `scheduler_unavailable` en cas d'échec). Demande `tenant_admin`, `developer` ou `application_owner`. 404 `agent_not_found`. Audité sous `schedule.create`.",
    "operationId": "create_schedule_v1_schedules_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ScheduleIn"
       }
      }
     },
     "description": "Agent, expression cron, fuseau horaire, entrée du run et activation.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Schedule V1 Schedules Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une planification",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/schedules/{schedule_id}": {
   "delete": {
    "description": "Supprime la planification ; elle ne lance plus aucun run. Demande `tenant_admin`, `developer` ou `application_owner`. Quand la planification est portée par Temporal, elle y est aussi supprimée (503 `scheduler_unavailable` si Temporal est injoignable). 404 `schedule_not_found`. Audité sous `schedule.delete`.",
    "operationId": "delete_schedule_v1_schedules__schedule_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la planification.",
      "in": "path",
      "name": "schedule_id",
      "required": true,
      "schema": {
       "title": "Schedule Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une planification",
    "tags": [
     "agents"
    ]
   },
   "patch": {
    "description": "Modifie l'expression cron, le fuseau horaire, l'entrée ou l'activation. La prochaine échéance est recalculée (422 `invalid_cron` pour une expression ou un fuseau invalide) et la planification Temporal est mise à jour quand Temporal la porte (503 `scheduler_unavailable`). Demande `tenant_admin`, `developer` ou `application_owner`. Audité sous `schedule.update`.",
    "operationId": "patch_schedule_v1_schedules__schedule_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la planification.",
      "in": "path",
      "name": "schedule_id",
      "required": true,
      "schema": {
       "title": "Schedule Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SchedulePatch"
       }
      }
     },
     "description": "Champs à modifier ; ceux omis sont conservés.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Schedule V1 Schedules  Schedule Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une planification",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/secrets": {
   "get": {
    "description": "Liste les métadonnées des secrets du tenant triées par chemin : `id`, `path`, `backend` (`openbao` ou `local`), `version`, `created_by`, `created_at`, `updated_at`. Jamais la valeur. Ouvert à tout membre du tenant. `next_cursor` vaut toujours null.",
    "operationId": "list_secrets_v1_secrets_get",
    "parameters": [
     {
      "description": "Uniquement les chemins qui commencent par ce texte, par exemple `connectors/`.",
      "in": "query",
      "name": "prefix",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Prefix"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Secrets V1 Secrets Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les secrets",
    "tags": [
     "secrets"
    ]
   },
   "post": {
    "description": "Crée le secret, ou remplace sa valeur quand le chemin existe (la `version` augmente de un). Exige `tenant_admin`, `security_officer` ou `developer` ; un développeur ne remplace que les secrets qu'il a écrits (403 `forbidden`). Un chemin contenant un segment `..` répond 400 `invalid_request`. La réponse (201) donne les métadonnées, jamais la valeur. Écrit l'événement d'audit `secret.write`. 502 `secret_backend_error` quand le coffre est injoignable.",
    "operationId": "create_secret_v1_secrets_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SecretIn"
       }
      }
     },
     "description": "Chemin et valeur du secret.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Secret V1 Secrets Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Écrire un secret",
    "tags": [
     "secrets"
    ]
   }
  },
  "/v1/secrets/{path}": {
   "delete": {
    "description": "Supprime le secret et, avec OpenBao, sa valeur dans le coffre. Exige `tenant_admin`, `security_officer` ou `developer` ; un développeur ne supprime que les secrets qu'il a écrits (403 `forbidden`). Les objets qui désignent encore ce chemin cessent de fonctionner. Écrit l'événement d'audit `secret.delete`. Répond 204, 404 `not_found`, ou 502 `secret_backend_error` quand le coffre est injoignable.",
    "operationId": "delete_secret_v1_secrets__path__delete",
    "parameters": [
     {
      "description": "Chemin du secret, barres obliques comprises, par exemple `connectors/crm/password`.",
      "in": "path",
      "name": "path",
      "required": true,
      "schema": {
       "title": "Path",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un secret",
    "tags": [
     "secrets"
    ]
   }
  },
  "/v1/security/posture": {
   "get": {
    "description": "Renvoie `environment`, `generated_at`, `protections` (méthodes d'authentification et autres protections en vigueur), `checks` (chacun avec son statut `pass`, `warn` ou `fail`, sa gravité et son message) et `summary` (nombre de contrôles par statut). Pour un appelant rattaché à un tenant, renvoie aussi `tenant` avec les réglages de sécurité du tenant, dont certains contrôles tiennent compte. Exige `security_officer` ou `tenant_admin`. En lecture seule.",
    "operationId": "get_posture_v1_security_posture_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Posture V1 Security Posture Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire la posture de sécurité",
    "tags": [
     "security"
    ]
   }
  },
  "/v1/security/settings": {
   "get": {
    "description": "Renvoie `tenant_id` et `settings` : les réglages de sécurité du tenant, valeurs par défaut comprises. Exige `tenant_admin` ou `security_officer` et un tenant (403 `tenant_required`).",
    "operationId": "get_security_settings_v1_security_settings_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Security Settings V1 Security Settings Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les réglages de sécurité",
    "tags": [
     "security"
    ]
   },
   "put": {
    "description": "Mise à jour partielle : seuls les réglages envoyés changent, les autres gardent leur valeur. Exige `tenant_admin` ou `security_officer` et un tenant.\n\nValidation (422 `validation_error` avec `details.field`) : réglage inconnu, `prompt_injection_mode` hors de `off`, `detect`, `block`, `untrusted_content_wrapping` qui n'est pas un booléen, `output_links` hors de `allow`, `strip_untrusted`, une liste d'hôtes de plus de 200 éléments ou contenant une valeur qui n'est pas un nom d'hôte public (préfixe `*.` autorisé). Les noms d'hôte sont mis en minuscules et dédoublonnés.\n\nLa réponse donne tous les réglages après la modification. Écrit l'événement d'audit `security.settings.update` avec les réglages modifiés.",
    "operationId": "put_security_settings_v1_security_settings_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Objet JSON avec les réglages à modifier, par exemple `{\"prompt_injection_mode\": \"block\"}`.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Security Settings V1 Security Settings Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier les réglages de sécurité",
    "tags": [
     "security"
    ]
   }
  },
  "/v1/service-identities": {
   "get": {
    "description": "Liste les identités de service du tenant, triées par nom, avec leur identifiant SPIFFE, leurs rôles, leurs portées et leur habilitation. Ouvert à tout membre du tenant. Non paginé : `next_cursor` vaut toujours null.",
    "operationId": "list_service_identities_v1_service_identities_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Service Identities V1 Service Identities Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les identités de service",
    "tags": [
     "identity"
    ]
   },
   "post": {
    "description": "Enregistre une identité de charge de travail du tenant avec son identifiant SPIFFE, ses rôles, ses portées et son habilitation. Exige `tenant_admin` ou `security_officer` ; les rôles et portées accordés suivent les mêmes règles que les clés d'API (uniquement ce que l'appelant détient). Un identifiant SPIFFE déjà enregistré répond 409 `conflict`. Écrit l'événement d'audit `service_identity.create`. Endosser l'identité exige en plus la relation explicite `can_assume` (voir la route d'endossement).",
    "operationId": "create_service_identity_v1_service_identities_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ServiceIdentityIn"
       }
      }
     },
     "description": "Nom, identifiant SPIFFE et droits de l'identité.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Service Identity V1 Service Identities Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une identité de service",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/service-identities/{identity_id}": {
   "delete": {
    "description": "Supprime l'identité de service ; elle ne peut plus être endossée ni s'authentifier avec son identifiant SPIFFE. Exige `tenant_admin` ou `security_officer`. Écrit l'événement d'audit `service_identity.delete`. Répond 204, ou 404 `not_found`.",
    "operationId": "delete_service_identity_v1_service_identities__identity_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'identité de service.",
      "in": "path",
      "name": "identity_id",
      "required": true,
      "schema": {
       "title": "Identity Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une identité de service",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/service-identities/{identity_id}/assume": {
   "post": {
    "description": "Renvoie un ExecutionContext signé et de courte durée dont le sujet est l'identité de service, pour l'appelant. Ce n'est pas un identifiant d'API : les services en aval le vérifient (`POST /v1/context/verify` ou le JWKS) et l'appelant le révoque avec `POST /v1/context/{context_id}/revoke`.\n\nContrôles, dans l'ordre :\n\n1. l'appelant détient la relation OpenFGA `can_assume` sur `service_identity:<id>`, écrite explicitement : elle n'est jamais déduite d'un rôle du tenant, administrateur compris, ni accordée à un sujet générique (403 `relation_required`, audité ; 502 `authz_backend_error` quand OpenFGA ne peut pas répondre) ;\n2. l'identité est active (409 `service_identity_disabled`) et chaque audience demandée est connue (400 `context_wrong_audience`) ;\n3. les portées demandées sont accordées par l'identité et détenues par l'appelant (403 `service_identity_scope_exceeds`) ;\n4. la politique autorise `service_identity.assume`.\n\nLes droits effectifs ne dépassent jamais ceux de l'appelant : les rôles détenus par les deux (jamais `platform_admin`), les portées ci-dessus et la plus basse des deux habilitations. L'appelant reste `on_behalf_of` dans le document signé et il est l'acteur des événements d'audit `service_identity.assume` et `context.issue`.",
    "operationId": "assume_service_identity_v1_service_identities__identity_id__assume_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'identité de service.",
      "in": "path",
      "name": "identity_id",
      "required": true,
      "schema": {
       "title": "Identity Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AssumeIn"
       }
      }
     },
     "description": "Finalité, audiences, durée de vie et portées du contexte demandé.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Assume Service Identity V1 Service Identities  Identity Id  Assume Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Endosser une identité de service",
    "tags": [
     "identity"
    ]
   }
  },
  "/v1/sessions": {
   "get": {
    "description": "Sessions, de la plus récente à la plus ancienne, paginées avec `cursor`. Un appelant voit ses propres sessions ; `tenant_admin`, `auditor` et `security_officer` voient toutes les sessions du tenant.",
    "operationId": "list_sessions_v1_sessions_get",
    "parameters": [
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Seulement les sessions ouvertes sur ce canal.",
      "in": "query",
      "name": "channel",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Channel"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Sessions V1 Sessions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les sessions",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Ouvre une session de conversation, détenue par `user_id` ou par l'appelant. Ouvrir une session pour un autre utilisateur demande `tenant_admin` ou `agent` (403 `forbidden`). Demande `tenant_admin`, `developer` ou `agent`. 404 `agent_not_found` pour un `agent_id` inconnu. Audité sous `session.create`.",
    "operationId": "create_session_v1_sessions_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__agents__routes__SessionIn"
       }
      }
     },
     "description": "Agent, propriétaire, canal, titre, métadonnées et durée de vie de la session.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Session V1 Sessions Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ouvrir une session",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/sessions/{session_id}": {
   "delete": {
    "description": "Supprime la session et ses messages. Demande `tenant_admin`, `developer` ou `agent`, et l'appelant doit voir la session (son propriétaire, ou `tenant_admin`, `auditor`, `security_officer`, `agent`) ; sinon 404 `session_not_found`. Audité sous `session.delete`.",
    "operationId": "delete_session_v1_sessions__session_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une session",
    "tags": [
     "agents"
    ]
   },
   "get": {
    "description": "Renvoie une session. Visible par son propriétaire, par `tenant_admin`, `auditor`, `security_officer` et par le rôle `agent` ; sinon 404 `session_not_found`.",
    "operationId": "get_session_v1_sessions__session_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Session V1 Sessions  Session Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une session",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/sessions/{session_id}/messages": {
   "get": {
    "description": "Messages dans l'ordre, paginés avec `cursor`. `after` (identifiant de message ou horodatage) permet à un runtime de ne récupérer que les nouveaux messages ; un identifiant inconnu ou un horodatage mal formé répond 400 `invalid_request`.",
    "operationId": "list_messages_v1_sessions__session_id__messages_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Identifiant de message ou horodatage ISO 8601 : seulement les messages plus récents.",
      "in": "query",
      "name": "after",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "message id or ISO 8601 timestamp: only newer messages",
       "title": "After"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Messages V1 Sessions  Session Id  Messages Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les messages d'une session",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Ajoute un message avec le numéro de séquence suivant. La session doit être ouverte et non expirée (409 `session_closed`). Demande `tenant_admin`, `developer` ou `agent`. L'événement d'audit `session.message.add` ne garde que le rôle et la longueur, jamais le contenu.",
    "operationId": "add_message_v1_sessions__session_id__messages_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/MessageIn"
       }
      }
     },
     "description": "Rôle, contenu et métadonnées du message.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Add Message V1 Sessions  Session Id  Messages Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter un message à une session",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/skills": {
   "get": {
    "description": "Skills du tenant dans l'ordre des noms, avec version, propriétaire, empreinte, résultat de vérification et statut. Une seule page.",
    "operationId": "list_skills_v1_skills_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Skills V1 Skills Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les skills",
    "tags": [
     "skills"
    ]
   },
   "post": {
    "description": "Applique un manifeste Skill : crée le skill ou le remplace (un skill remplacé revient à `draft`, un skill bloqué reste `blocked`). L'empreinte et la signature sont vérifiées aussitôt : le skill passe à `approved` si elles sont valides. Un artefact Git ou OCI demande `artifact.digest` (422 `invalid_manifest`). `apply_status` vaut `created` ou `updated`. Demande `tenant_admin`, `developer` ou `application_owner` ; l'appelant devient propriétaire ReBAC. Audité sous `skill.apply`.",
    "operationId": "create_skill_v1_skills_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Manifeste Skill (`kind: Skill`, `metadata` avec nom, version et propriétaire, `spec` avec description, instructions, permissions, signature et artefact facultatif).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Skill V1 Skills Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un skill",
    "tags": [
     "skills"
    ]
   }
  },
  "/v1/skills/{name}": {
   "get": {
    "description": "Renvoie le skill avec son manifeste, son contenu, son empreinte, sa signature et sa vérification. Demande la relation ReBAC `viewer`. 404 `skill_not_found`.",
    "operationId": "get_skill_v1_skills__name__get",
    "parameters": [
     {
      "description": "Nom du skill.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Skill V1 Skills  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un skill",
    "tags": [
     "skills"
    ]
   }
  },
  "/v1/skills/{name}/load": {
   "get": {
    "description": "Skill Loader : renvoie ce dont un runtime a besoin pour utiliser le skill (instructions d'un skill en ligne, artefact, point d'entrée, permissions, dépendances, signataire). Demande la relation ReBAC `can_invoke`. Refus, chacun audité comme `skill.load` refusé :\n- 403 `skill_blocked` : le skill est `blocked` ou `deprecated` ;\n- 409 `skill_digest_mismatch` : le contenu ne correspond plus à son empreinte ;\n- 403 `skill_unsigned` : la signature n'est pas valide ;\n- 403 `skill_not_granted` : avec `agent`, le skill n'est pas listé dans le manifeste de l'agent ;\n- 403 `skill_permission_denied` : le skill demande des scopes que l'agent n'a pas, ou son plafond de classification est inférieur à celui de l'agent.",
    "operationId": "load_skill_v1_skills__name__load_get",
    "parameters": [
     {
      "description": "Nom du skill.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     },
     {
      "description": "Agent (UUID ou nom) qui charge le skill ; les octrois et permissions de son manifeste sont vérifiés.",
      "in": "query",
      "name": "agent",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Agent"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Load Skill V1 Skills  Name  Load Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Charger un skill pour un agent",
    "tags": [
     "skills"
    ]
   }
  },
  "/v1/skills/{name}/status": {
   "post": {
    "description": "`approved` passe par une promotion : une approbation est ouverte pour `tenant_admin` ou `security_officer` et la réponse est 202 avec `approval_id` et `status: pending_approval`. Les autres statuts (`draft`, `restricted`, `deprecated`, `blocked`) s'appliquent aussitôt (200). Demande `tenant_admin` ou `security_officer` et la relation ReBAC `editor`. Audité sous `skill.status`.",
    "operationId": "skill_status_v1_skills__name__status_post",
    "parameters": [
     {
      "description": "Nom du skill.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SkillStatusIn"
       }
      }
     },
     "description": "Statut visé et raison.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Skill Status V1 Skills  Name  Status Post"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Changer le statut d'un skill",
    "tags": [
     "skills"
    ]
   }
  },
  "/v1/skills/{name}/verify": {
   "post": {
    "description": "Revérifie l'empreinte du contenu et la signature avec les clés de confiance et enregistre le résultat : `verified`, `method`, `signer`, `reason`. Un skill vérifié passe à `approved`, sinon à `draft` (un skill bloqué reste `blocked`). Demande `tenant_admin`, `developer`, `application_owner` ou `security_officer` et la relation ReBAC `editor`. Audité sous `skill.verify`.",
    "operationId": "verify_skill_v1_skills__name__verify_post",
    "parameters": [
     {
      "description": "Nom du skill.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify Skill V1 Skills  Name  Verify Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier la signature d'un skill",
    "tags": [
     "skills"
    ]
   }
  },
  "/v1/soc/catalog": {
   "get": {
    "description": "Renvoie les listes autorisées : `reactions`, les types de cibles admis par réaction (`targets`), les types de détections avec leur gravité de base et leur playbook (`detections`), les limites de la garde de proportionnalité (`response` : confiance minimale, rayon d'impact automatique maximal, rayon d'impact exigeant une validation manuelle, réactions toujours soumises à validation) et les rôles SOC. Ouvert à tout membre du tenant.",
    "operationId": "soc_catalog_v1_soc_catalog_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Soc Catalog V1 Soc Catalog Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire le catalogue SOC",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/detections": {
   "get": {
    "description": "Liste les détections du tenant, les plus récentes d'abord, éventuellement pour un incident ou un type. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`. La lecture est auditée.",
    "operationId": "list_detections_v1_soc_detections_get",
    "parameters": [
     {
      "description": "Uniquement les détections corrélées dans cet incident.",
      "in": "query",
      "name": "incident_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Incident Id"
      }
     },
     {
      "description": "Uniquement ce type de détection, par exemple `prompt_injection`.",
      "in": "query",
      "name": "kind",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Kind"
      }
     },
     {
      "description": "Nombre maximal de détections (100 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 100,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Detections V1 Soc Detections Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les détections",
    "tags": [
     "soc"
    ]
   },
   "post": {
    "description": "Ingère une détection. Exige un identifiant auquel la portée `soc:detections` a été accordée explicitement (la portée globale `*` ne compte pas), ou `security_officer`, `agent` ou `soc_analyst`.\n\n1. La détection est normalisée : vocabulaires fermés de types et de sources (400 `unknown_detection_kind`, 400 `invalid_request`), gravité `sev1` à `sev4` (par défaut : la gravité de base du type), confiance de 0 à 1.\n2. Elle est notée par le moteur de risque et corrélée (même utilisateur, sinon même agent, sinon niveau tenant) dans un incident sur la fenêtre de corrélation, puis scellée comme preuve de l'incident.\n3. Son playbook de réactions typées est soumis au moteur de politique de réponse ; les ordres automatiques ne s'exécutent que dans les limites de la garde.\n\nRépond 201 avec la détection, son incident et les ordres créés.",
    "operationId": "report_detection_v1_soc_detections_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DetectionIn"
       }
      }
     },
     "description": "La détection à ingérer.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Report Detection V1 Soc Detections Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Signaler une détection",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/incidents/{incident_id}/custody": {
   "get": {
    "description": "Renvoie le journal de traçabilité des preuves de l'incident (qui a scellé, lu ou exporté quoi, et quand) avec la vérification des deux chaînes de hachage. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`. La lecture elle-même est ajoutée au journal de traçabilité. Répond 404 `not_found` pour un incident inconnu.",
    "operationId": "read_custody_v1_soc_incidents__incident_id__custody_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Read Custody V1 Soc Incidents  Incident Id  Custody Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la chaîne de traçabilité",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/incidents/{incident_id}/evidence": {
   "get": {
    "description": "Renvoie les preuves scellées de l'incident (détections, ordres, accusés, notes), la vérification de leur chaîne et `custody_seq`, l'entrée de traçabilité créée par cette lecture. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`. Répond 404 `not_found`.",
    "operationId": "read_evidence_v1_soc_incidents__incident_id__evidence_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Read Evidence V1 Soc Incidents  Incident Id  Evidence Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les preuves d'un incident",
    "tags": [
     "soc"
    ]
   },
   "post": {
    "description": "Ajoute un enregistrement scellé à la chaîne de preuves de l'incident : l'empreinte du contenu et celle de l'enregistrement précédent, si bien qu'un enregistrement supprimé, réordonné ou modifié rompt la chaîne. Les enregistrements ne peuvent jamais être modifiés ni supprimés. Exige `security_officer`, `soc_analyst` ou `tenant_admin`. Écrit l'événement d'audit `soc.evidence.seal` et une entrée de traçabilité. Répond 201 avec `seq`, `kind`, `content_sha256`, `prev_hash`, `hash`, `sealed_by` et `sealed_at`.",
    "operationId": "seal_evidence_v1_soc_incidents__incident_id__evidence_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__soc__EvidenceIn"
       }
      }
     },
     "description": "Type et contenu de la preuve.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Seal Evidence V1 Soc Incidents  Incident Id  Evidence Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Sceller une preuve sur un incident",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/incidents/{incident_id}/evidence/export": {
   "post": {
    "description": "Renvoie l'incident, ses preuves, les deux chaînes vérifiées et le journal de traçabilité. Le paquet est analysé à la recherche d'identifiants secrets avant que le moindre octet ne sorte : si l'un est trouvé, l'export est refusé avec 422 `secret_detected`, le refus est inscrit dans le journal de traçabilité et une détection `secret_leak` est levée. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`.",
    "operationId": "export_evidence_v1_soc_incidents__incident_id__evidence_export_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'incident.",
      "in": "path",
      "name": "incident_id",
      "required": true,
      "schema": {
       "title": "Incident Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Export Evidence V1 Soc Incidents  Incident Id  Evidence Export Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exporter les preuves d'un incident",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/kill-switches": {
   "get": {
    "description": "Liste les coupe-circuits du tenant, les plus récents d'abord, 500 au plus : par défaut seulement ceux dont l'état voulu est `SUSPENDED`. Chacun donne réaction, cible, états voulu et observé, dernier accusé et levée. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`. La lecture est auditée.",
    "operationId": "list_kill_switches_v1_soc_kill_switches_get",
    "parameters": [
     {
      "description": "`active` (par défaut, suspendus uniquement) ou `all`.",
      "in": "query",
      "name": "state",
      "required": false,
      "schema": {
       "default": "active",
       "title": "State",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Kill Switches V1 Soc Kill Switches Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les coupe-circuits",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/kill-switches/{switch_id}/verify": {
   "post": {
    "description": "Lance la sonde du vérificateur de confinement pour le coupe-circuit : en cas de succès, l'état observé passe à `SUSPENDED_VERIFIED`. Les réactions de cluster (`ISOLATE_NETWORK`, `TERMINATE_WORKLOAD`) ne sont vérifiées que par un accusé signé depuis le cluster. Exige `security_officer`, `soc_analyst` ou `tenant_admin`. Écrit l'événement d'audit `soc.kill_switch.verify`. Répond 404 `not_found`.",
    "operationId": "verify_kill_switch_v1_soc_kill_switches__switch_id__verify_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du coupe-circuit.",
      "in": "path",
      "name": "switch_id",
      "required": true,
      "schema": {
       "title": "Switch Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify Kill Switch V1 Soc Kill Switches  Switch Id  Verify Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier un coupe-circuit",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/orders": {
   "get": {
    "description": "Liste les ordres de confinement du tenant, les plus récents d'abord, éventuellement par statut (`pending_approval`, `applied`, `rejected`...) ou par incident. Chacun donne réaction, cible, rayon d'impact, confiance, gravité, mode (`manual` ou automatique), statut et décision du moteur de politique de réponse. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`.",
    "operationId": "list_orders_v1_soc_orders_get",
    "parameters": [
     {
      "description": "Uniquement les ordres dans ce statut, par exemple `pending_approval`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Uniquement les ordres de cet incident.",
      "in": "query",
      "name": "incident_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Incident Id"
      }
     },
     {
      "description": "Nombre maximal d'ordres (100 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 100,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Orders V1 Soc Orders Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les ordres de confinement",
    "tags": [
     "soc"
    ]
   },
   "post": {
    "description": "Crée un ordre de confinement manuel. Exige `security_officer`, `soc_analyst` ou `tenant_admin`.\n\n1. La réaction doit figurer dans la liste autorisée (400 `unknown_reaction` ; `RECOVER` passe par le processus de rétablissement, 422 `recover_requires_workflow`) et accepter le type de cible (400 `target_not_allowed`).\n2. Le rayon d'impact est calculé et le moteur de politique de réponse décide : autorisé et appliqué, en attente d'un responsable SOC (`pending_approval`, toujours pour `SUSPEND_TENANT` et `EMERGENCY_LOCKDOWN`, ou au-delà des limites de rayon d'impact), ou refusé (409 `containment_refused` avec les raisons).\n3. Un ordre appliqué écrit le coupe-circuit (état voulu `SUSPENDED`) et lance son adaptateur d'application, qui associe la réaction à un chemin de code fixe.\n\nRépond 201 avec l'ordre.",
    "operationId": "create_order_v1_soc_orders_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/OrderIn"
       }
      }
     },
     "description": "Réaction, cible, incident, raison, paramètres et limites de l'ordre.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Order V1 Soc Orders Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ordonner un confinement",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/orders/{order_id}": {
   "get": {
    "description": "Renvoie l'ordre avec son dernier coupe-circuit et les accusés reçus pour lui. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`. Répond 404 `not_found`.",
    "operationId": "get_order_v1_soc_orders__order_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'ordre de confinement.",
      "in": "path",
      "name": "order_id",
      "required": true,
      "schema": {
       "title": "Order Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Order V1 Soc Orders  Order Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un ordre de confinement",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/orders/{order_id}/approve": {
   "post": {
    "description": "Approuve un ordre en attente d'une seconde personne et l'applique (coupe-circuit écrit, adaptateur lancé). Exige `security_officer` ou `tenant_admin` (responsable SOC), qui n'est pas le demandeur (409 `separation_of_duties`). Un ordre qui n'est pas en attente répond 409 `order_not_pending` ; un ordre inconnu 404.",
    "operationId": "approve_order_v1_soc_orders__order_id__approve_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'ordre de confinement.",
      "in": "path",
      "name": "order_id",
      "required": true,
      "schema": {
       "title": "Order Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Approve Order V1 Soc Orders  Order Id  Approve Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Approuver un ordre de confinement",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/orders/{order_id}/reject": {
   "post": {
    "description": "Refuse un ordre en attente de validation, avec une raison facultative. Exige `security_officer` ou `tenant_admin` (responsable SOC). Un ordre qui n'est pas en attente répond 409 `order_not_pending` ; un ordre inconnu 404.",
    "operationId": "reject_order_v1_soc_orders__order_id__reject_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'ordre de confinement.",
      "in": "path",
      "name": "order_id",
      "required": true,
      "schema": {
       "title": "Order Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DecisionNoteIn"
       }
      }
     },
     "description": "Raison facultative de la décision.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reject Order V1 Soc Orders  Order Id  Reject Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Refuser un ordre de confinement",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/receipts": {
   "post": {
    "description": "Accusé envoyé depuis le cluster (politique Cilium observée, charge de travail ramenée à zéro) pour une réaction de cluster : JSON `{kill_switch_id, verified, adapter, evidence}`. Signé comme les webhooks de la plateforme : `X-EAI-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.\" + corps)>`, une fenêtre anti-rejeu de 5 minutes, `X-EAI-Delivery` pour le dédoublonnage et `X-EAI-Tenant` (identifiant court du tenant). Sans le secret de webhook SOC configuré, toute livraison est refusée. Chaque livraison, acceptée ou non, est enregistrée et auditée. Un accusé vérifié fait passer l'état observé à `SUSPENDED_VERIFIED` ; un accusé pour une réaction qui n'en attend pas répond 409 `receipt_not_expected`. Aucun identifiant d'utilisateur n'est nécessaire : la signature authentifie l'émetteur. Répond 202 avec le coupe-circuit.",
    "operationId": "containment_receipt_v1_soc_receipts_post",
    "requestBody": {
     "content": {
      "application/octet-stream": {
       "schema": {
        "format": "binary",
        "type": "string"
       }
      }
     },
     "description": "Corps JSON brut et signé de l'accusé (`application/octet-stream`), vérifié octet par octet.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Recevoir un accusé de confinement du cluster",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/recoveries": {
   "get": {
    "description": "Liste les demandes de rétablissement du tenant, les plus récentes d'abord, 500 au plus, éventuellement par statut, avec leurs approbations. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`.",
    "operationId": "list_recoveries_v1_soc_recoveries_get",
    "parameters": [
     {
      "description": "Uniquement les demandes dans ce statut, par exemple `pending`, `approved` ou `recovered`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Recoveries V1 Soc Recoveries Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les demandes de rétablissement",
    "tags": [
     "soc"
    ]
   },
   "post": {
    "description": "Ouvre une demande de rétablissement à quatre yeux pour un coupe-circuit actif. Exige `security_officer`, `soc_analyst` ou `tenant_admin`. Un coupe-circuit inconnu répond 404, un coupe-circuit qui n'est plus suspendu 409 `kill_switch_not_active`, un coupe-circuit avec une demande ouverte 409 `recovery_already_open`. Répond 201 avec la demande.",
    "operationId": "request_recovery_v1_soc_recoveries_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/RecoveryIn"
       }
      }
     },
     "description": "Coupe-circuit et raison.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Request Recovery V1 Soc Recoveries Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Demander le rétablissement d'un coupe-circuit",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/recoveries/{recovery_id}/approve": {
   "post": {
    "description": "Enregistre une approbation d'un rétablissement en attente ; deux responsables SOC distincts sont nécessaires, aucun n'étant le demandeur (409 `separation_of_duties`), chacun approuvant une seule fois (409 `already_approved`). Exige `security_officer` ou `tenant_admin` (responsable SOC). Une demande qui n'est pas en attente répond 409 `recovery_not_pending`.",
    "operationId": "approve_recovery_v1_soc_recoveries__recovery_id__approve_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de rétablissement.",
      "in": "path",
      "name": "recovery_id",
      "required": true,
      "schema": {
       "title": "Recovery Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DecisionNoteIn"
       }
      }
     },
     "description": "Note facultative de l'approbation.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Approve Recovery V1 Soc Recoveries  Recovery Id  Approve Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Approuver un rétablissement",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/recoveries/{recovery_id}/reauthorize": {
   "post": {
    "description": "Dernière étape d'un rétablissement, par l'un des approbateurs (403 `reauthorization_by_approver` sinon) une fois les deux approbations obtenues (409 `recovery_not_approved`). Le coupe-circuit est levé, la cible est réévaluée par la politique, qui doit l'autoriser (403 `reauthorization_denied`, tout est annulé), et seulement alors le plan propriétaire est rétabli, par exemple un agent repris. Une clé d'API révoquée le reste : émettez-en une nouvelle. Exige `security_officer` ou `tenant_admin` (responsable SOC).",
    "operationId": "reauthorize_recovery_v1_soc_recoveries__recovery_id__reauthorize_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la demande de rétablissement.",
      "in": "path",
      "name": "recovery_id",
      "required": true,
      "schema": {
       "title": "Recovery Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reauthorize Recovery V1 Soc Recoveries  Recovery Id  Reauthorize Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réautoriser et rétablir",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/transactions": {
   "get": {
    "description": "Liste les appels d'outils et de connecteurs à effet de bord du tenant, les plus récents d'abord, 500 au plus : clé d'idempotence, opération, statut (`in_flight`, `committed`, `interrupted`, `reconciled`, `compensating`, `compensated`...) et rapprochement. Exige `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`.",
    "operationId": "list_transactions_v1_soc_transactions_get",
    "parameters": [
     {
      "description": "Uniquement les transactions dans ce statut, par exemple `interrupted`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Uniquement les transactions de cette exécution d'agent.",
      "in": "query",
      "name": "run_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Run Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Transactions V1 Soc Transactions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les transactions à effet de bord",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/transactions/{transaction_id}/reconcile": {
   "post": {
    "description": "Enregistre le résultat réel d'un appel à effet de bord dont l'issue est inconnue (interrompu par un coupe-circuit, une panne ou un délai dépassé) : `committed` (il a eu lieu chez le fournisseur) ou `not_executed`. Une nouvelle tentative avec la même clé d'idempotence renvoie alors le résultat enregistré au lieu de rappeler le fournisseur. Exige `security_officer` ou `tenant_admin` (responsable SOC). Un résultat inconnu répond 400, une transaction non rapprochable 409 `transaction_not_reconcilable`, une transaction inconnue 404.",
    "operationId": "reconcile_transaction_v1_soc_transactions__transaction_id__reconcile_post",
    "parameters": [
     {
      "description": "Identifiant de la transaction à effet de bord.",
      "in": "path",
      "name": "transaction_id",
      "required": true,
      "schema": {
       "title": "Transaction Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ReconcileIn"
       }
      }
     },
     "description": "Résultat, référence du fournisseur et note.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reconcile Transaction V1 Soc Transactions  Transaction Id  Reconcile Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Rapprocher une transaction",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/soc/webhooks/{source}": {
   "post": {
    "description": "Webhook des détecteurs externes : Falco, Tetragon ou OpenSearch Security Analytics. Le corps est un événement ou une liste (200 au plus traités), normalisés en détections (priorités Falco et étiquettes `eai_*` associées à des types et des gravités) et ingérés comme `POST /v1/soc/detections`. Signé comme les webhooks de la plateforme : `X-EAI-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.\" + corps)>`, une fenêtre anti-rejeu de 5 minutes, `X-EAI-Delivery` pour le dédoublonnage et `X-EAI-Tenant` (identifiant court du tenant). Sans le secret de webhook SOC configuré, toute livraison est refusée. Chaque livraison, acceptée ou non, est enregistrée et auditée. Une source inconnue répond 404. Répond 202 avec `received` et les détections.",
    "operationId": "detection_webhook_v1_soc_webhooks__source__post",
    "parameters": [
     {
      "description": "Détecteur : `falco`, `tetragon` ou `opensearch`.",
      "in": "path",
      "name": "source",
      "required": true,
      "schema": {
       "title": "Source",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/octet-stream": {
       "schema": {
        "format": "binary",
        "type": "string"
       }
      }
     },
     "description": "Corps JSON brut et signé (`application/octet-stream`) : un événement ou une liste d'événements du détecteur.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Recevoir des constats de sécurité externes",
    "tags": [
     "soc"
    ]
   }
  },
  "/v1/storage/profiles": {
   "get": {
    "description": "Profils de stockage visibles par l'appelant, triés par portée puis par nom, chacun avec ses `needed_capabilities` (capacités exigées plus celles que demandent ses usages). Une seule page : `next_cursor` vaut toujours null. Demande `platform_admin`, ou un principal de tenant avec `tenant_admin`, `security_officer`, `developer` ou `auditor`.",
    "operationId": "list_profiles_v1_storage_profiles_get",
    "parameters": [
     {
      "description": "Seulement les profils de cette portée : `tenant` ou `platform`.",
      "in": "query",
      "name": "scope",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "tenant",
          "platform"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Scope"
      }
     },
     {
      "description": "Seulement les profils de ce fournisseur, par exemple `aws_s3` ou `azure_blob`.",
      "in": "query",
      "name": "provider",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Provider"
      }
     },
     {
      "description": "Seulement les profils qui servent cet usage, par exemple `audit_worm` ou `documents`.",
      "in": "query",
      "name": "purpose",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Purpose"
      }
     },
     {
      "description": "Nombre maximal de profils, de 1 à 500, 50 par défaut.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Profiles V1 Storage Profiles Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les profils de stockage",
    "tags": [
     "storage"
    ]
   },
   "post": {
    "description": "Crée un profil au statut `unverified` et répond 201. Demande `tenant_admin` pour la portée `tenant` (dans un tenant) ou `platform_admin` pour la portée `platform` (403 `storage_scope_forbidden`), et la décision de politique `storage.profile.create`. Audité (`storage.profile.create`).\n\nValidation (422 sauf mention contraire) : aucun champ secret dans `auth`, `options` ou `encryption` (`storage_secret_in_profile`) ; fournisseur (`storage_provider_unsupported`) et méthode d'authentification (`storage_auth_method_unsupported`) pris en charge ; un `credential_ref` de la forme `openbao://...` pour un profil de tenant ou `env://EAI_CRED_...` pour un profil de plateforme (`storage_credential_ref_invalid`) ; `auth.account` pour Azure ; un nom de bucket valide ; un point d'accès HTTPS, obligatoire pour `ibm_cos`, `seaweedfs` et `s3_compatible` (`storage_endpoint_required`) ; un mode de chiffrement, des usages et des capacités connus (`storage_profile_invalid`). Un profil du même nom dans la même portée répond 409 `storage_profile_exists`.",
    "operationId": "create_profile_v1_storage_profiles_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ProfileIn"
       }
      }
     },
     "description": "Le nouveau profil de stockage. Les champs inconnus sont refusés.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Profile V1 Storage Profiles Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un profil de stockage",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/storage/profiles/{profile_id}": {
   "delete": {
    "description": "Supprime le profil (le bucket et ses objets ne sont pas touchés) et répond 204. Demande `tenant_admin` pour un profil de tenant, `platform_admin` pour un profil de plateforme (403 `storage_scope_forbidden`), et la décision de politique `storage.profile.delete`. L'événement d'audit `storage.profile.delete` enregistre le fournisseur, le bucket et le nombre d'exports d'audit qui l'utilisaient. Identifiant inconnu : 404 `storage_profile_not_found`.",
    "operationId": "delete_profile_v1_storage_profiles__profile_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un profil de stockage",
    "tags": [
     "storage"
    ]
   },
   "get": {
    "description": "Un profil de stockage avec son statut, ses capacités découvertes, sa date de vérification, ses capacités manquantes et nécessaires et sa dernière erreur. Jamais une valeur secrète. Mêmes lecteurs que la liste. Identifiant inconnu : 404 `storage_profile_not_found`.",
    "operationId": "get_profile_v1_storage_profiles__profile_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Profile V1 Storage Profiles  Profile Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un profil de stockage",
    "tags": [
     "storage"
    ]
   },
   "patch": {
    "description": "Modifie les champs fournis ; le profil fusionné est validé comme un nouveau. Un changement de fournisseur, de point d'accès, de région, de bucket, de `auth`, de `credential_ref`, de `encryption` ou d'`options` efface les capacités et remet le statut à `unverified` ; `status: disabled` désactive le profil, `status: active` sur un profil désactivé le rend `unverified`. Ajouter l'usage `audit_worm` sans un tel changement demande une capacité de verrouillage d'objet vérifiée (409 `storage_capability_missing`). Demande `tenant_admin` (profil de plateforme : `platform_admin`) et la décision de politique `storage.profile.update` ; audité avec les champs modifiés.",
    "operationId": "patch_profile_v1_storage_profiles__profile_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ProfilePatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Profile V1 Storage Profiles  Profile Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un profil de stockage",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/storage/profiles/{profile_id}/checks": {
   "get": {
    "description": "Historique des vérifications de capacités du profil, de la plus récente à la plus ancienne : statut, capacités trouvées, capacités manquantes, sonde d'écriture lancée ou non, erreur, durée en millisecondes et auteur. Une seule page (`next_cursor` vaut toujours null). Mêmes lecteurs que la liste.",
    "operationId": "list_checks_v1_storage_profiles__profile_id__checks_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     },
     {
      "description": "Nombre maximal de vérifications, de 1 à 500, 50 par défaut.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Checks V1 Storage Profiles  Profile Id  Checks Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les vérifications d'un profil",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/storage/profiles/{profile_id}/objects": {
   "get": {
    "description": "Objets rangés sous un préfixe à l'intérieur des préfixes de présignature du profil : clé relative au préfixe de clé du profil (utilisable pour la présignature et la suppression), clé stockée, taille, ETag, type de contenu, SHA-256, version, dernière modification et classe de stockage. Demande `tenant_admin`, `developer`, `agent` ou `auditor` (profil de plateforme : `platform_admin`) et la décision de politique `storage.object.list` ; la liste est auditée. Erreurs : 422 `storage_prefix_required` si `prefix` est omis et que le profil n'a pas exactement un préfixe de présignature, 403 `storage_key_out_of_scope` hors des préfixes de présignature, 409 `storage_profile_disabled`.",
    "operationId": "list_objects_v1_storage_profiles__profile_id__objects_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     },
     {
      "description": "Préfixe de clé à lister, relatif au préfixe de clé du profil et à l'intérieur d'un de ses préfixes de présignature ; 1024 caractères au plus. Par défaut, le seul préfixe de présignature du profil.",
      "in": "query",
      "name": "prefix",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 1024,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Prefix"
      }
     },
     {
      "description": "Nombre maximal d'objets dans une page, de 1 à 500, 50 par défaut.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Jeton de continuation opaque du stockage, renvoyé dans `next_cursor` par la page précédente ; 4096 caractères au plus.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 4096,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Objects V1 Storage Profiles  Profile Id  Objects Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les objets d'un profil",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/storage/profiles/{profile_id}/objects/delete": {
   "post": {
    "description": "Supprime un objet (ou une de ses versions) à l'intérieur des préfixes de présignature du profil. Un profil qui sert `audit_worm` ou `evidence` ne supprime jamais par l'API (409 `storage_object_protected`) ; un refus de rétention du stockage est renvoyé tel quel. Demande `tenant_admin`, `developer` ou `agent` (profil de plateforme : `platform_admin`) et la décision de politique `storage.object.delete` ; audité avec la clé complète et la version. Autres erreurs : 403 `storage_key_out_of_scope`, 422 `storage_key_invalid`, 409 `storage_profile_disabled`.",
    "operationId": "delete_object_v1_storage_profiles__profile_id__objects_delete_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DeleteObjectIn"
       }
      }
     },
     "description": "Clé, et éventuellement version, de l'objet à supprimer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Object V1 Storage Profiles  Profile Id  Objects Delete Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un objet d'un profil",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/storage/profiles/{profile_id}/presign": {
   "post": {
    "description": "Émet une URL présignée pour lire (`GET`) ou écrire (`PUT`) un objet directement dans le bucket, et répond 201 avec l'identifiant de l'autorisation, l'URL, la méthode, la clé complète, la durée de vie et l'échéance. La clé doit se trouver dans les préfixes de présignature (403 `storage_key_out_of_scope`) et le profil doit avoir une capacité `presign` vérifiée et récente (409 `storage_capability_missing`). La durée de vie ne peut dépasser `EAI_STORAGE_PRESIGN_MAX_SECONDS` (3600 par défaut, jamais plus de 604800 ; 422 `storage_presign_expiry_invalid`). Demande `tenant_admin`, `developer` ou `agent` (profil de plateforme : `platform_admin`) et la décision de politique `storage.presign.read` ou `storage.presign.write`. L'autorisation est enregistrée avec un SHA-256 de l'URL (jamais l'URL elle-même) et auditée.",
    "operationId": "presign_v1_storage_profiles__profile_id__presign_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PresignIn"
       }
      }
     },
     "description": "Objet, sens et durée de vie de l'URL présignée.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Presign V1 Storage Profiles  Profile Id  Presign Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Émettre une URL présignée",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/storage/profiles/{profile_id}/verify": {
   "post": {
    "description": "Découvre les capacités du bucket par de vrais appels et les enregistre. Demande `tenant_admin` ou `security_officer` (profil de plateforme : `platform_admin`) et la décision de politique `storage.profile.verify`. Un profil désactivé répond 409 `storage_profile_disabled`.\n\nÉtapes :\n\n1. L'identifiant est résolu à partir de sa référence (422 `storage_credential_invalid` s'il ne peut pas être lu).\n2. L'adaptateur interroge le bucket : versionnage, verrouillage d'objet, chiffrement, cycle de vie, réplication, un envoi en plusieurs parties commencé puis annulé, une requête présignée et, avec `probe_write`, un objet sonde écrit, relu et supprimé.\n3. Le statut devient `verified`, `degraded` (une capacité nécessaire manque) ou `failed` (le bucket n'a pas pu être joint), la vérification est ajoutée à l'historique et un événement d'audit `storage.profile.verify` est écrit.\n\nLa réponse donne le profil, le statut, les capacités manquantes, chaque capacité trouvée, l'erreur et la durée en millisecondes.",
    "operationId": "verify_profile_v1_storage_profiles__profile_id__verify_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du profil de stockage.",
      "in": "path",
      "name": "profile_id",
      "required": true,
      "schema": {
       "title": "Profile Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/eai__domains__storage__routes__VerifyIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Options de vérification ; le corps peut être omis."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Verify Profile V1 Storage Profiles  Profile Id  Verify Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Vérifier les capacités d'un profil",
    "tags": [
     "storage"
    ]
   }
  },
  "/v1/telemetry/logs": {
   "get": {
    "description": "Journal des requêtes du tenant tiré des résumés de spans, du plus récent au plus ancien : une ligne par span avec son heure, son niveau (`error` si le span a échoué, sinon `info`), son service, son nom, son message (type d'erreur, ou méthode, route et code de statut), sa durée, sa trace, son span, son exécution et ses attributs conservés. Jamais de prompt ni de sortie de modèle. Demande un principal de tenant avec `platform_admin`, `tenant_admin`, `auditor`, `viewer` ou `support_engineer` (403 `forbidden`). Un niveau inconnu ou un `from` postérieur à `to` répond 400 `invalid_request`.",
    "operationId": "list_logs_v1_telemetry_logs_get",
    "parameters": [
     {
      "description": "Seulement les lignes de ce niveau : `error` ou `info`.",
      "in": "query",
      "name": "level",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Level"
      }
     },
     {
      "description": "Seulement les spans de ce nom de service (le `service.name` OpenTelemetry), correspondance exacte.",
      "in": "query",
      "name": "service",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Service"
      }
     },
     {
      "description": "Seulement les spans de cette trace : 32 caractères hexadécimaux, insensible à la casse.",
      "in": "query",
      "name": "trace_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Trace Id"
      }
     },
     {
      "description": "Texte recherché, sans tenir compte de la casse, dans le nom du span et ses attributs conservés ; 128 caractères au plus.",
      "in": "query",
      "name": "q",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 128,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Q"
      }
     },
     {
      "description": "Début de la fenêtre, ISO 8601. Par défaut : un jour avant `to`.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Fin de la fenêtre, ISO 8601. Par défaut : maintenant.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     },
     {
      "description": "Nombre maximal de lignes, de 1 à 1000, 100 par défaut.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 100,
       "maximum": 1000,
       "minimum": 1,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Logs V1 Telemetry Logs Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister le journal des requêtes",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/overview": {
   "get": {
    "description": "Tableau de bord du tenant sur les `days` derniers jours : nombre de requêtes (spans racines), erreurs, taux d'erreur, latences p50 et p95 en millisecondes, coût total, et par jour les requêtes, les erreurs et le coût (coût des spans plus le coût mesuré ou estimé des événements d'usage LLM). Indique aussi quels exportateurs (Opik, OpenTelemetry, ClickHouse) sont configurés. Le coût total est libellé en USD. Demande un principal de tenant (403 `tenant_required`).",
    "operationId": "overview_v1_telemetry_overview_get",
    "parameters": [
     {
      "description": "Longueur de la fenêtre en jours, 7 par défaut ; les valeurs sont ramenées entre 1 et 366.",
      "in": "query",
      "name": "days",
      "required": false,
      "schema": {
       "default": 7,
       "title": "Days",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Overview V1 Telemetry Overview Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la vue d'ensemble de la télémétrie",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/slo": {
   "get": {
    "description": "Pour chaque service, sur les `window_minutes` dernières minutes : requêtes, erreurs (5xx), requêtes au-delà de la cible de latence, cible de latence et son origine (`database` ou `settings`), et pour la disponibilité et la latence la cible, le taux de mauvaises requêtes, le budget restant et le rythme de consommation. Les comptes sont ceux de la réplique qui répond. Demande `platform_admin`, `tenant_admin`, `auditor`, `viewer` ou `support_engineer` (403 `forbidden`). Un administrateur de la plateforme lit toute la plateforme ou un tenant ; les autres ne lisent que leur propre tenant (403 `slo_scope_forbidden` pour un autre). Une fenêtre hors de 1 à `EAI_SLO_RETENTION_MINUTES` répond 422 `slo_window_invalid`. Chaque lecture est auditée (`telemetry.slo.read`).",
    "operationId": "slo_v1_telemetry_slo_get",
    "parameters": [
     {
      "description": "Longueur de la fenêtre en minutes, 60 par défaut, de 1 à la rétention `EAI_SLO_RETENTION_MINUTES`.",
      "in": "query",
      "name": "window_minutes",
      "required": false,
      "schema": {
       "default": 60,
       "title": "Window Minutes",
       "type": "integer"
      }
     },
     {
      "description": "Tenant à lire. Un administrateur de la plateforme peut l'omettre pour toute la plateforme ; tout autre appelant ne peut donner que son propre tenant.",
      "in": "query",
      "name": "tenant_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Tenant Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Slo V1 Telemetry Slo Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les budgets d'erreur des SLO",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/slo/targets/{service}": {
   "put": {
    "description": "Objectifs d'un service de la plateforme, écrits en base de données où ils remplacent les valeurs par défaut `EAI_SLO_*` pour chaque réplique sous 30 secondes. Réservé à l'administrateur de la plateforme (403 `forbidden`). Un service inconnu répond 404 `slo_service_unknown`. La modification, avec les valeurs avant et après et la raison, est auditée (`telemetry.slo.target.update`) ; la réponse donne les objectifs enregistrés avec leur version.",
    "operationId": "put_target_v1_telemetry_slo_targets__service__put",
    "parameters": [
     {
      "description": "Service dont les objectifs sont définis : `control_plane_api`, `model_gateway`, `tool_gateway` ou `realtime`.",
      "in": "path",
      "name": "service",
      "required": true,
      "schema": {
       "title": "Service",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/eai__domains__observability__slo__TargetIn"
       }
      }
     },
     "description": "Nouveaux objectifs de disponibilité et de latence du service.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Target V1 Telemetry Slo Targets  Service  Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir les objectifs d'un service",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/spans": {
   "post": {
    "description": "Accepte jusqu'à 5000 spans et répond 202 avec le nombre accepté, le nombre enregistré (un span déjà connu pour les mêmes identifiants de trace et de span est ignoré) et le résultat de chaque exportateur (`ok` ou `error`). Demande un principal de tenant avec `tenant_admin`, `developer` ou `agent` (403 `forbidden`).\n\nÉtapes :\n\n1. Le corps est aplati : les `resourceSpans` OTLP (le service vient de `service.name`) et une simple liste `spans` sont tous deux lus.\n2. Chaque span est contrôlé : identifiant de trace de 32 caractères hexadécimaux, identifiant de span (et de parent) de 16, heures de début et de fin ; sinon 400 `invalid_request`.\n3. Un résumé borné est enregistré : seulement les attributs autorisés, chaînes coupées à 128 caractères, 2 Ko d'attributs au plus, nom coupé à 256 caractères.\n4. Un événement d'audit `telemetry.ingest` est écrit, puis les spans complets sont transmis, au mieux, à Opik, au collecteur OpenTelemetry et à ClickHouse s'ils sont configurés.",
    "operationId": "ingest_v1_telemetry_spans_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Body",
        "type": "object"
       }
      }
     },
     "description": "Requête d'export OTLP JSON (`resourceSpans`) ou `{spans: [...]}` où chaque span porte `trace_id`, `span_id`, `parent_span_id` facultatif, `name`, `service`, `start_time`, `end_time` (ISO 8601 ou `startTimeUnixNano` / `endTimeUnixNano`), `status`, `attributes`, et facultativement `cost {amount, currency}`, `run_id` et `session_id`.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Ingest V1 Telemetry Spans Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ingérer des spans de télémétrie",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/stream": {
   "get": {
    "description": "Server-Sent Events (`text/event-stream`) du tenant : un événement `metrics` à chaque intervalle (requêtes, erreurs, taux d'erreur et latence p95 de la dernière minute), un événement `trace` pour chaque nouveau span racine et un événement `incident` pour chaque incident créé ou modifié. Chaque événement porte un `id:` (un horodatage) que le client renvoie dans `Last-Event-ID` ou `since` pour reprendre. Le flux se ferme après `timeout` secondes (l'EventSource se reconnecte après 3 secondes) ou après `max_events` événements. Demande un principal de tenant avec `platform_admin`, `tenant_admin`, `auditor`, `viewer` ou `support_engineer`.",
    "operationId": "stream_v1_telemetry_stream_get",
    "parameters": [
     {
      "description": "Point de reprise, ISO 8601 : seules les traces et incidents enregistrés après lui sont envoyés. Par défaut : maintenant. `Last-Event-ID` l'emporte si les deux sont fournis.",
      "in": "query",
      "name": "since",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Since"
      }
     },
     {
      "description": "Secondes entre deux interrogations, de 0,05 à 60, 2 par défaut.",
      "in": "query",
      "name": "interval",
      "required": false,
      "schema": {
       "default": 2.0,
       "maximum": 60,
       "minimum": 0.05,
       "title": "Interval",
       "type": "number"
      }
     },
     {
      "description": "Secondes après lesquelles le serveur ferme le flux, de 1 à 3600, 300 par défaut.",
      "in": "query",
      "name": "timeout",
      "required": false,
      "schema": {
       "default": 300,
       "maximum": 3600,
       "minimum": 1,
       "title": "Timeout",
       "type": "integer"
      }
     },
     {
      "description": "Ferme le flux après ce nombre d'événements, de 0 à 10000 ; 0 (par défaut) signifie sans limite.",
      "in": "query",
      "name": "max_events",
      "required": false,
      "schema": {
       "default": 0,
       "maximum": 10000,
       "minimum": 0,
       "title": "Max Events",
       "type": "integer"
      }
     },
     {
      "description": "En-tête standard des Server-Sent Events : l'`id` du dernier événement reçu, renvoyé par le navigateur pour reprendre après une reconnexion.",
      "in": "header",
      "name": "Last-Event-ID",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Last-Event-Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Suivre la télémétrie en direct",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/traces": {
   "get": {
    "description": "Traces récentes du tenant, de la plus récente à la plus ancienne : une ligne par span racine avec son nom, son service, son début, sa durée, son statut, son exécution, sa session et ses nombres de spans et d'erreurs. Demande un principal de tenant avec `platform_admin`, `tenant_admin`, `auditor`, `viewer` ou `support_engineer` (403 `forbidden`). Un statut inconnu ou un `from` postérieur à `to` répond 400 `invalid_request`.",
    "operationId": "list_traces_v1_telemetry_traces_get",
    "parameters": [
     {
      "description": "Seulement les spans racines de ce statut : `ok`, `error` ou `unset`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les traces dont le span racine appartient à ce nom de service, correspondance exacte.",
      "in": "query",
      "name": "service",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Service"
      }
     },
     {
      "description": "Seulement les traces de cette exécution d'agent.",
      "in": "query",
      "name": "run_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Run Id"
      }
     },
     {
      "description": "Début de la fenêtre, ISO 8601. Par défaut : un jour avant `to`.",
      "in": "query",
      "name": "from",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "From"
      }
     },
     {
      "description": "Fin de la fenêtre, ISO 8601. Par défaut : maintenant.",
      "in": "query",
      "name": "to",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "To"
      }
     },
     {
      "description": "Nombre maximal de traces, de 1 à 500, 50 par défaut.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "default": 50,
       "maximum": 500,
       "minimum": 1,
       "title": "Limit",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Traces V1 Telemetry Traces Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les traces récentes",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telemetry/traces/{trace_id}": {
   "get": {
    "description": "Tous les spans de la trace dans l'ordre de début (avec leur coût s'il est connu), le nombre de spans, la durée totale en millisecondes, le nombre d'erreurs, les événements d'audit qui portent cet identifiant de trace, et un lien vers la trace dans Opik quand Opik est configuré. Demande un principal de tenant (403 `tenant_required`). Un identifiant mal formé répond 400 `invalid_request` ; une trace sans span ni événement d'audit répond 404 `not_found`.",
    "operationId": "trace_v1_telemetry_traces__trace_id__get",
    "parameters": [
     {
      "description": "Identifiant de trace : 32 caractères hexadécimaux (W3C trace context), insensible à la casse.",
      "in": "path",
      "name": "trace_id",
      "required": true,
      "schema": {
       "title": "Trace Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Trace V1 Telemetry Traces  Trace Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une trace",
    "tags": [
     "telemetry"
    ]
   }
  },
  "/v1/telephony/active-calls": {
   "get": {
    "description": "Appels en cours du tenant : `{items, count}`, chaque élément `linkedid`, `call_id`, `navin_call_id`, `branch`, `state` (ringing, queued, answered), `direction`, `from`, `to`, `extension`, `queue`, `started_at`, `answered_at`, `duration_seconds`, `node` et `channels` (`uniqueid`, `channel`, `state`, `node`). Tenu à jour par les événements CEL (CHAN_START, ANSWER, CHAN_END, LINKEDID_END) et les webhooks LiveKit. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_active_calls_v1_telephony_active_calls_get",
    "parameters": [
     {
      "description": "Seulement cette file (nom sans le tid).",
      "in": "query",
      "name": "queue",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Queue"
      }
     },
     {
      "description": "Seulement ce numéro d'extension (2 à 8 chiffres).",
      "in": "query",
      "name": "extension",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "pattern": "^[0-9]{2,8}$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Extension"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Active Calls V1 Telephony Active Calls Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les appels actifs",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/active-calls/{linkedid}/hangup": {
   "post": {
    "description": "Raccroche chaque canal de l'appel par l'ARI (`DELETE /ari/channels/{uniqueid}` sur le nœud de chaque canal), ou retire le participant SIP par le RoomService de LiveKit pour la branche IA. Répond `{linkedid, results}` avec un `status` (ok, gone, failed) par canal ; 502 `telephony_control_failed` quand tous les canaux ont échoué. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.call.hangup`.",
    "operationId": "hangup_call_v1_telephony_active_calls__linkedid__hangup_post",
    "parameters": [
     {
      "description": "linkedid Asterisk de l'appel actif (`linkedid` de `GET /v1/telephony/active-calls`).",
      "in": "path",
      "name": "linkedid",
      "required": true,
      "schema": {
       "title": "Linkedid",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Hangup Call V1 Telephony Active Calls  Linkedid  Hangup Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Raccrocher un appel actif",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/active-calls/{linkedid}/transfer": {
   "post": {
    "description": "Transfère un canal de l'appel : `Redirect` AMI vers `{tid}-internal` (extension du tenant, 422 `telephony_reference_invalid` quand elle n'existe pas) ou `{tid}-outbound` (numéro E.164, les permissions et règles de destination du plan de numérotation s'appliquent) ; pour la branche IA, `TransferSIPParticipant` de LiveKit (SIP REFER). `continue` et `redirect` de l'ARI ne sont pas utilisés : ils n'agissent que sur les canaux d'une application Stasis. Répond `{linkedid, target, status, via, uniqueid}` ; 502 `telephony_control_failed` en cas de refus. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.call.transfer`.",
    "operationId": "transfer_call_v1_telephony_active_calls__linkedid__transfer_post",
    "parameters": [
     {
      "description": "linkedid Asterisk de l'appel actif (`linkedid` de `GET /v1/telephony/active-calls`).",
      "in": "path",
      "name": "linkedid",
      "required": true,
      "schema": {
       "title": "Linkedid",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyTransferIn"
       }
      }
     },
     "description": "La cible et, au besoin, le canal à déplacer.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Transfer Call V1 Telephony Active Calls  Linkedid  Transfer Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Transférer un appel actif",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/agents": {
   "get": {
    "description": "Les agents humains du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_agent_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les objets actifs (true) ou inactifs (false). Par défaut : les deux.",
      "in": "query",
      "name": "enabled",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Enabled"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Agent List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les agents humains",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée un agent humain dans le tenant de l'appelant et le renvoie (201). L'extension doit appartenir au tenant. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.agent.create`.",
    "operationId": "telephony_agent_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyAgentIn"
       }
      }
     },
     "description": "L'agent humain.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Agent Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un agent humain",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/agents/{item_id}": {
   "delete": {
    "description": "Supprime l'agent humain (204) ; les moteurs le perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.agent.delete`.",
    "operationId": "telephony_agent_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un agent humain",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Un agent humain du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_agent_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Agent Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un agent humain",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie l'agent humain ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.agent.update`.",
    "operationId": "telephony_agent_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyAgentPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Agent Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un agent humain",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/blocks": {
   "get": {
    "description": "Blocages du tenant et de ses extensions, du plus récent au plus ancien : `id`, `scope`, `extension`, `reason`, `origin` (manual, automatic), `alert_id`, `active`, `kamailio_key` (clé de la table de hachage Kamailio `tenant_blocks`), `kamailio` (compte rendu de l'envoi par point JSONRPC), `created_by`, `created_at`, `lifted_by`, `lifted_at`, `lift_reason`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_blocks_v1_telephony_blocks_get",
    "parameters": [
     {
      "description": "Seulement les blocages actifs (true) ou levés (false).",
      "in": "query",
      "name": "active",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Active"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Blocks V1 Telephony Blocks Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les blocages",
    "tags": [
     "telephony",
     "telephony"
    ]
   },
   "post": {
    "description": "Écrit la clé `{tid}` ou `{tid}-{extension}` dans la table de hachage Kamailio `tenant_blocks` de chaque point JSONRPC du pool (`htable.sets`) et renvoie le blocage (201) ; un blocage actif de même portée est renvoyé tel quel. 422 `telephony_block_scope` quand la portée et l'extension ne concordent pas. La tâche `telephony_blocks` renvoie les blocages actifs après un redémarrage de Kamailio. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.block.create`, publié sous `telephony.block.created`.",
    "operationId": "create_block_v1_telephony_blocks_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyBlockIn"
       }
      }
     },
     "description": "La portée, l'extension et le motif.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Block V1 Telephony Blocks Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Bloquer le tenant ou une extension",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/blocks/{block_id}/lift": {
   "post": {
    "description": "Supprime la clé Kamailio sur chaque point JSONRPC (`htable.delete`) et marque le blocage levé. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.block.lift`, publié sous `telephony.block.lifted`.",
    "operationId": "lift_block_v1_telephony_blocks__block_id__lift_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du blocage.",
      "in": "path",
      "name": "block_id",
      "required": true,
      "schema": {
       "title": "Block Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyLiftIn"
       }
      }
     },
     "description": "Pourquoi le blocage est levé.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Lift Block V1 Telephony Blocks  Block Id  Lift Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lever un blocage",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/calls": {
   "get": {
    "description": "Historique des appels du tenant, du plus récent au plus ancien. Chaque élément : `id`, `navin_call_id`, `linkedid`, `branch` (pbx ou ai), `direction` (inbound, outbound, internal, unknown), `from`, `from_name`, `to`, `did`, `extension`, `queue`, `voice_agent_id`, `started_at`, `answered_at`, `ended_at`, `duration_seconds`, `billsec`, `wait_seconds`, `disposition`, `hangup_cause`, `hangup_cause_text`, `recording_id`, `legs`, `sip_call_id`, `livekit_room`, `pbx_instance_id`, `node`. Construit à partir des événements Cdr et CEL de l'AMI et des webhooks LiveKit. Pagination par curseur : `limit` (1 à 500, 50 par défaut) et le `cursor` opaque de `next_cursor`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_calls_v1_telephony_calls_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement cette direction.",
      "in": "query",
      "name": "direction",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "inbound",
          "outbound",
          "internal",
          "unknown"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Direction"
      }
     },
     {
      "description": "Seulement cette branche : `pbx` (Asterisk) ou `ai` (LiveKit SIP).",
      "in": "query",
      "name": "branch",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "pbx",
          "ai"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Branch"
      }
     },
     {
      "description": "Seulement ce numéro d'extension (2 à 8 chiffres).",
      "in": "query",
      "name": "extension",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "pattern": "^[0-9]{2,8}$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Extension"
      }
     },
     {
      "description": "Seulement cette file (nom sans le tid).",
      "in": "query",
      "name": "queue",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Queue"
      }
     },
     {
      "description": "Seulement les appels dont l'appelant, l'appelé ou le numéro DID est ce numéro.",
      "in": "query",
      "name": "number",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 32,
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Number"
      }
     },
     {
      "description": "Seulement cette issue du CDR Asterisk.",
      "in": "query",
      "name": "disposition",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "ANSWERED",
          "NO ANSWER",
          "BUSY",
          "FAILED",
          "CONGESTION"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Disposition"
      }
     },
     {
      "description": "Appels commencés à cet instant ou après (ISO 8601).",
      "in": "query",
      "name": "since",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "format": "date-time",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Since"
      }
     },
     {
      "description": "Appels commencés avant cet instant (ISO 8601).",
      "in": "query",
      "name": "until",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "format": "date-time",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Until"
      }
     },
     {
      "description": "true : appels répondus seulement ; false : appels sans réponse seulement.",
      "in": "query",
      "name": "answered",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Answered"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Calls V1 Telephony Calls Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les appels",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/calls/{call_id}": {
   "get": {
    "description": "Un appel avec `legs` (une par canal Asterisk, depuis les événements Cdr), `events` (événements CEL, AMI, LiveKit et du worker vocal dans l'ordre du temps : `source`, `type`, `occurred_at`, `uniqueid`, `channel`, `node`, `data`) et `recordings` (mêmes champs que `GET /v1/telephony/recordings`). Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_call_v1_telephony_calls__call_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'enregistrement d'appel, dans le tenant de l'appelant (l'appel d'un autre tenant répond 404).",
      "in": "path",
      "name": "call_id",
      "required": true,
      "schema": {
       "title": "Call Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Call V1 Telephony Calls  Call Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un appel",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/calls/{call_id}/events": {
   "post": {
    "description": "Événement d'un appel signalé par son worker vocal, écrit dans les événements de l'appel (`voice_agent.<type>`). `started` crée l'enregistrement d'appel de la branche IA quand aucune autre source ne l'a annoncé ; `answered` et `ended` le mettent à jour et publient `telephony.call.answered` et `telephony.call.ended` ; `error` garde `data.reason`. Répond `{status: recorded ou duplicate, call_id}`. Réservé à un jeton de worker vocal : une clé d'API du tenant portant explicitement la portée `telephony:voice-worker` (403 `insufficient_scope` sinon, une portée générique ne compte pas) ; l'agent vocal et l'appel doivent appartenir au tenant de la clé (404 sinon). Audité sous `telephony.voice_agent.event.<type>`.",
    "operationId": "worker_call_event_v1_telephony_calls__call_id__events_post",
    "parameters": [
     {
      "description": "Identifiant d'appel des métadonnées du job du worker : l'identifiant de l'enregistrement d'appel, le X-Navin-Call-Id ou le nom de la salle LiveKit (lettres, chiffres, `-`, `_`, 128 au plus).",
      "in": "path",
      "name": "call_id",
      "required": true,
      "schema": {
       "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$",
       "title": "Call Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyWorkerEventIn"
       }
      }
     },
     "description": "L'événement.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Worker Call Event V1 Telephony Calls  Call Id  Events Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Signaler un événement d'appel",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/calls/{call_id}/tools/{name}": {
   "post": {
    "description": "Exécute un outil de recherche de l'agent vocal par la passerelle d'outils de la plateforme (portées, politique, DLP, approbations et journal d'invocation) : le nom doit commencer par `lookup_` et figurer dans `allowed_tools` de l'agent, et un outil du registre doit porter ce nom (ou le nom avec `-` au lieu de `_`), sinon 403 `telephony_tool_not_allowed`. La clé demande aussi la portée `tools:invoke`. Répond `{result}` (texte, 8000 caractères au plus) ; 502 `telephony_tool_failed`. Réservé à un jeton de worker vocal : une clé d'API du tenant portant explicitement la portée `telephony:voice-worker` (403 `insufficient_scope` sinon, une portée générique ne compte pas) ; l'agent vocal et l'appel doivent appartenir au tenant de la clé (404 sinon). Audité sous `telephony.voice_agent.tool`.",
    "operationId": "worker_call_tool_v1_telephony_calls__call_id__tools__name__post",
    "parameters": [
     {
      "description": "Identifiant d'appel des métadonnées du job du worker : l'identifiant de l'enregistrement d'appel, le X-Navin-Call-Id ou le nom de la salle LiveKit (lettres, chiffres, `-`, `_`, 128 au plus).",
      "in": "path",
      "name": "call_id",
      "required": true,
      "schema": {
       "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$",
       "title": "Call Id",
       "type": "string"
      }
     },
     {
      "description": "Nom de l'outil de recherche (`lookup_...`).",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "pattern": "^[a-z][a-z0-9_]{0,63}$",
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyWorkerToolIn"
       }
      }
     },
     "description": "L'agent vocal et les arguments de l'outil.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Worker Call Tool V1 Telephony Calls  Call Id  Tools  Name  Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Exécuter un outil de recherche",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/calls/{call_id}/transfer": {
   "post": {
    "description": "Transfère le participant SIP de l'appel par `TransferSIPParticipant` de LiveKit (SIP REFER). `target_uri` doit être la cible de transfert de l'agent vocal (422 `telephony_worker_transfer_target`). Répond `{call_id, status, via, transfer_to}` ; 502 `telephony_control_failed` quand LiveKit refuse ou que l'appel n'a pas de participant SIP. Réservé à un jeton de worker vocal : une clé d'API du tenant portant explicitement la portée `telephony:voice-worker` (403 `insufficient_scope` sinon, une portée générique ne compte pas) ; l'agent vocal et l'appel doivent appartenir au tenant de la clé (404 sinon). Audité sous `telephony.voice_agent.transfer`.",
    "operationId": "worker_call_transfer_v1_telephony_calls__call_id__transfer_post",
    "parameters": [
     {
      "description": "Identifiant d'appel des métadonnées du job du worker : l'identifiant de l'enregistrement d'appel, le X-Navin-Call-Id ou le nom de la salle LiveKit (lettres, chiffres, `-`, `_`, 128 au plus).",
      "in": "path",
      "name": "call_id",
      "required": true,
      "schema": {
       "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$",
       "title": "Call Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyWorkerTransferIn"
       }
      }
     },
     "description": "L'agent vocal, le motif et l'URI cible.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Worker Call Transfer V1 Telephony Calls  Call Id  Transfer Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Transférer un appel vers un humain",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/conference-sessions": {
   "get": {
    "description": "Sessions des salles de conférence du tenant, depuis les événements Confbridge de l'AMI, de la plus récente à la plus ancienne : `id`, `conference_id`, `number`, `started_at`, `ended_at`, `active`, `participant_count`, `max_participants`, `total_joins`, `participants` (`uniqueid`, `channel`, `caller`, `name`, `joined_at`), `recorded`, `recording_id`. Pagination par curseur : `limit` (1 à 500, 50 par défaut) et le `cursor` opaque de `next_cursor`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_conference_sessions_v1_telephony_conference_sessions_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les sessions de cette salle de conférence.",
      "in": "query",
      "name": "conference_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "format": "uuid",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Conference Id"
      }
     },
     {
      "description": "true : sessions en cours ; false : sessions terminées.",
      "in": "query",
      "name": "active",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Active"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Conference Sessions V1 Telephony Conference Sessions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les sessions de conférence",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/conference-sessions/{session_id}": {
   "get": {
    "description": "Une session de conférence avec ses participants (mêmes champs que la liste). Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_conference_session_v1_telephony_conference_sessions__session_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la session de conférence.",
      "in": "path",
      "name": "session_id",
      "required": true,
      "schema": {
       "title": "Session Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Conference Session V1 Telephony Conference Sessions  Session Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une session de conférence",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/conferences": {
   "get": {
    "description": "Les salles de conférence du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_conference_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Conference List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les salles de conférence",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée une salle de conférence dans le tenant de l'appelant et la renvoie (201). Le code PIN est en écriture seule : la réponse porte `has_pin`. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.conference.create`.",
    "operationId": "telephony_conference_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyConferenceIn"
       }
      }
     },
     "description": "La salle de conférence.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Conference Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une salle de conférence",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/conferences/{item_id}": {
   "delete": {
    "description": "Supprime la salle de conférence (204) ; les moteurs la perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.conference.delete`.",
    "operationId": "telephony_conference_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une salle de conférence",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Une salle de conférence du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_conference_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Conference Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une salle de conférence",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie la salle de conférence ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.conference.update`.",
    "operationId": "telephony_conference_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyConferencePatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Conference Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une salle de conférence",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/config-versions/{version_id}": {
   "get": {
    "description": "Une version avec ses fichiers (secrets masqués), tables Kamailio, objets LiveKit, rapport de livraison par nœud (fichiers relus et comparés, sorties des rechargements, restauration), état et `diff` avec la version précédente. 404 quand l'appelant ne peut pas lire son pool.",
    "operationId": "get_version_v1_telephony_config_versions__version_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la version de configuration.",
      "in": "path",
      "name": "version_id",
      "required": true,
      "schema": {
       "title": "Version Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Version V1 Telephony Config Versions  Version Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une version de configuration",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/event-streams": {
   "get": {
    "description": "État de la connexion d'événements AMI de chaque nœud (tâche `telephony_events`) : `pool_id`, `node`, `connected`, `connected_at`, `events`, `reconnects`, `last_error`, `updated_at`. Demande `platform_admin`.",
    "operationId": "list_event_streams_v1_telephony_event_streams_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Event Streams V1 Telephony Event Streams Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les connexions d'événements AMI",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/extensions": {
   "get": {
    "description": "Les extensions du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_extension_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les objets actifs (true) ou inactifs (false). Par défaut : les deux.",
      "in": "query",
      "name": "enabled",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Enabled"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Extension List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les extensions",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée une extension dans le tenant de l'appelant et la renvoie (201). Le mot de passe SIP et le code PIN de messagerie sont en écriture seule : la réponse porte `has_password` et `has_voicemail_pin`. Le numéro est unique dans le tenant parmi les extensions, les files et les conférences (409 `telephony_number_in_use`) et ne peut ni commencer par un préfixe national ou international ni être un numéro d'urgence (422 `telephony_number_reserved`). Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.extension.create`.",
    "operationId": "telephony_extension_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyExtensionIn"
       }
      }
     },
     "description": "L'extension.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Extension Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une extension",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/extensions/{item_id}": {
   "delete": {
    "description": "Supprime l'extension (204) ; les moteurs la perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.extension.delete`.",
    "operationId": "telephony_extension_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une extension",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Une extension du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_extension_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Extension Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une extension",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie l'extension ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.extension.update`.",
    "operationId": "telephony_extension_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyExtensionPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Extension Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une extension",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/fraud-alerts": {
   "get": {
    "description": "Signaux de fraude du tenant, du plus récent au plus ancien : `id`, `kind` (`cps`, `concurrency`, `destination_refused`, `extension_volume`), `severity`, `extension`, `destination`, `linkedid`, `details`, `status`, `block_id`, `created_at`, `acknowledged_at`, `acknowledged_by`. Chaque alerte est aussi publiée sous `telephony.fraud.alert`. Pagination par curseur : `limit` (1 à 500, 50 par défaut) et le `cursor` opaque de `next_cursor`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_fraud_alerts_v1_telephony_fraud_alerts_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les alertes ouvertes ou acquittées.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "open",
          "acknowledged"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement ce type de signal.",
      "in": "query",
      "name": "kind",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "cps",
          "concurrency",
          "destination_refused",
          "extension_volume"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Kind"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Fraud Alerts V1 Telephony Fraud Alerts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les alertes de fraude",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/fraud-alerts/{alert_id}/acknowledge": {
   "post": {
    "description": "Marque l'alerte acquittée et la renvoie. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.fraud.acknowledge`.",
    "operationId": "acknowledge_alert_v1_telephony_fraud_alerts__alert_id__acknowledge_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'alerte de fraude.",
      "in": "path",
      "name": "alert_id",
      "required": true,
      "schema": {
       "title": "Alert Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Acknowledge Alert V1 Telephony Fraud Alerts  Alert Id  Acknowledge Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Acquitter une alerte de fraude",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/infrastructure": {
   "get": {
    "description": "Lit, par la passerelle du Platform Manager (jeton d'API du Manager, `X-NM-On-Behalf-Of`, les permissions du Manager s'appliquent), les clusters et leurs instances `asterisk`, `kamailio`, `livekit` et `livekit-sip`. Répond `{clusters, binding, problems}` : chaque cluster `{id, name, mode, services}`, chaque service `{name, catalog_service, display_name, mode, method, version, managed, state, health, admin_state, tls_enabled, config_source (manager ou defaults), config_version, bound, endpoints [{name, scheme, port, path}], nodes [{name, instance_id, host_id, hostname, fqdn, ip, component, state, desired_state, health, maintenance, host_state, addresses}], bindings {liaison: {ref, kind}}, pool ({id, name, mode} du pool PBX lié à une instance Asterisk, sinon null)}`. Adresses Asterisk `ami`, `ari`, `sip` ; Kamailio `jsonrpc` (`/RPC`, adresse d'administration du groupe de l'hôte), `sip`, `wss` avec TLS ; LiveKit `api`, `signal` ; LiveKit SIP `sip`. `bound` vaut true pour l'instance liée à navin-enterprise. `binding` ne donne que des indicateurs (`cluster_id`, `asterisk.bound`, `asterisk.ami_secret`, `asterisk.ari_secret`, `kamailio.bound`, `kamailio.ephemeral_secret`, `livekit.bound`, `livekit.api_secret`, `livekit_sip.bound`), jamais une valeur. `problems` liste les instances dont le Manager a refusé la configuration (`telephony_inventory_config_unreadable`, valeurs par défaut du catalogue utilisées). Demande `platform_admin` ; 503 `platform_manager_not_configured`, 502 `platform_manager_unreachable`, un refus du Manager est relayé.",
    "operationId": "infrastructure_v1_telephony_infrastructure_get",
    "parameters": [
     {
      "description": "Seulement ce cluster du Manager (son identifiant).",
      "in": "query",
      "name": "cluster_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maxLength": 64,
         "pattern": "^[A-Za-z0-9_-]{1,64}$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cluster Id"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Infrastructure V1 Telephony Infrastructure Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister l'infrastructure téléphonique du Navin Manager",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/infrastructure/pools": {
   "post": {
    "description": "Crée (201) ou rafraîchit (200) le pool PBX lié à une instance Asterisk du Manager (`manager_cluster`, `manager_service`) : nœuds (hôte, port AMI, adresse SIP, instance du Manager, URL ARI), `ami_tls`, points d'accès JSONRPC et adresses Kamailio (les instances Kamailio liées à cet Asterisk, ou `kamailio_service`), `livekit_binding` (`url`, `sip_host`), noms d'utilisateur AMI et ARI et références des secrets. Un secret de l'instance liée à navin-enterprise devient `env://EAI_CRED_TELEPHONY_AMI_SECRET`, `env://EAI_CRED_TELEPHONY_ARI_PASSWORD` ou `env://EAI_CRED_TELEPHONY_EPHEMERAL_SECRET` (variables rendues par le Manager dans le fichier d'environnement sensible), une référence déjà présente sur le pool est gardée, jamais une valeur. Idempotent : le même inventaire ne change rien (`changed: []`). Répond `{created, changed, pool, secrets {ami, ari, ephemeral : bound, kept ou missing}, sources {cluster_id, asterisk, kamailio, livekit, livekit_sip}, problems}`. Erreurs : 404 `telephony_inventory_not_found`, 409 `telephony_inventory_no_nodes`, 422 `telephony_inventory_too_large`, les règles des pools (422 `telephony_pool_mode`, 409 `telephony_name_taken`). Demande `platform_admin` ; audité sous `telephony.pbx_pool.sync` (plus `telephony.pbx_pool.create` ou `telephony.pbx_pool.update`).",
    "operationId": "sync_from_inventory_v1_telephony_infrastructure_pools_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyInventorySyncIn"
       }
      }
     },
     "description": "L'instance Asterisk du Manager et les options d'un nouveau pool.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Sync From Inventory V1 Telephony Infrastructure Pools Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer ou rafraîchir un pool PBX depuis l'inventaire du Manager",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-flows": {
   "get": {
    "description": "Les flux SVI du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_ivr_flow_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Ivr Flow List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les flux SVI",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée un flux SVI dans le tenant de l'appelant et le renvoie (201). Le graphe vit dans les versions du flux. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.ivr_flow.create`.",
    "operationId": "telephony_ivr_flow_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyIvrFlowIn"
       }
      }
     },
     "description": "Le flux svi.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Ivr Flow Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un flux SVI",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-flows/{flow_id}/versions": {
   "get": {
    "description": "Versions du flux avec leur état (`draft`, `published`, `archived`) et leur graphe, par identifiant. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_ivr_versions_v1_telephony_ivr_flows__flow_id__versions_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du flux SVI.",
      "in": "path",
      "name": "flow_id",
      "required": true,
      "schema": {
       "title": "Flow Id",
       "type": "string"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Ivr Versions V1 Telephony Ivr Flows  Flow Id  Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions d'un flux SVI",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Ajoute une version brouillon (numéro + 1). Le graphe est validé : un nœud `start`, chaque arête désigne un nœud existant, chaque nœud est accessible, touches DTMF uniques, chaque menu avec message, sorties `timeout` et `invalid`, attente de 1 à 30 secondes et 1 à 5 nouvelles tentatives ; les extensions, files et agents vocaux désignés existent dans le tenant. Le `prompt` d'un nœud play ou menu est un nom de son d'Asterisk ou `navin:<nom>`, un message déposé du tenant (POST /v1/telephony/ivr-prompts) que le plan de numérotation joue depuis son URL signée. Les problèmes répondent 422 avec chacun dans `details.errors`. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.ivr_flow.version.create`.",
    "operationId": "create_ivr_version_v1_telephony_ivr_flows__flow_id__versions_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du flux SVI.",
      "in": "path",
      "name": "flow_id",
      "required": true,
      "schema": {
       "title": "Flow Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyIvrVersionIn"
       }
      }
     },
     "description": "Le graphe : `start` (identifiant de nœud) et `nodes` (identifiant vers nœud avec `type` start, play, menu, time_condition, transfer, voicemail, queue, voice_agent ou hangup).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Ivr Version V1 Telephony Ivr Flows  Flow Id  Versions Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une version d'un flux SVI",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-flows/{flow_id}/versions/{version}": {
   "get": {
    "description": "Une version du flux avec son graphe ; 404 `telephony_not_found` sinon. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_ivr_version_v1_telephony_ivr_flows__flow_id__versions__version__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du flux SVI.",
      "in": "path",
      "name": "flow_id",
      "required": true,
      "schema": {
       "title": "Flow Id",
       "type": "string"
      }
     },
     {
      "description": "Numéro de version (1, 2, ...).",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Ivr Version V1 Telephony Ivr Flows  Flow Id  Versions  Version  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une version d'un flux SVI",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-flows/{flow_id}/versions/{version}/publish": {
   "post": {
    "description": "Valide à nouveau la version et en fait celle qu'utilise le compilateur ; la version publiée auparavant est archivée. Les moteurs la reçoivent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.ivr_flow.version.publish`.",
    "operationId": "publish_ivr_version_v1_telephony_ivr_flows__flow_id__versions__version__publish_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du flux SVI.",
      "in": "path",
      "name": "flow_id",
      "required": true,
      "schema": {
       "title": "Flow Id",
       "type": "string"
      }
     },
     {
      "description": "Numéro de version (1, 2, ...).",
      "in": "path",
      "name": "version",
      "required": true,
      "schema": {
       "title": "Version",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Publish Ivr Version V1 Telephony Ivr Flows  Flow Id  Versions  Version  Publish Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier une version d'un flux SVI",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-flows/{item_id}": {
   "delete": {
    "description": "Supprime le flux SVI (204) ; les moteurs le perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.ivr_flow.delete`.",
    "operationId": "telephony_ivr_flow_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un flux SVI",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Un flux SVI du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_ivr_flow_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Ivr Flow Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un flux SVI",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie le flux SVI ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.ivr_flow.update`.",
    "operationId": "telephony_ivr_flow_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyIvrFlowPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Ivr Flow Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un flux SVI",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-prompts": {
   "get": {
    "description": "Messages de SVI du tenant, du plus récent au plus ancien (champs de la réponse du dépôt, `playback_url` vide). Pagination par curseur : `limit` (1 à 500, 50 par défaut) et le `cursor` opaque de `next_cursor`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_prompts_v1_telephony_ivr_prompts_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Prompts V1 Telephony Ivr Prompts Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les messages de SVI",
    "tags": [
     "telephony",
     "telephony"
    ]
   },
   "post": {
    "description": "Corps brut : un fichier WAV PCM (`Content-Type: audio/wav`, 415 `telephony_prompt_type` sinon, 413 `telephony_prompt_size` au-delà de 10 Mo, 422 `telephony_prompt_invalid` quand il n'est pas lisible, 422 `telephony_prompt_format` quand il n'est pas en 8000 Hz, 16 bits, mono, le seul WAV que lit le format `wav` d'Asterisk). Le fichier passe par la chaîne de sécurité des fichiers (422 `file_blocked`, 503 `file_scan_unavailable`), est stocké dans la catégorie `ivr-prompts` (sans expiration) et répond 201 avec `id`, `name`, `recording_id`, `content_type`, `size_bytes`, `sha256`, `duration_seconds`, `scan_id`, `created_at` et `playback_url`, l'URL signée que joue un SVI. 409 `telephony_name_taken` quand le nom est pris. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.ivr_prompt.create`.",
    "operationId": "upload_prompt_v1_telephony_ivr_prompts_post",
    "parameters": [
     {
      "description": "Nom du message, unique dans le tenant (minuscules, chiffres, `-`).",
      "in": "query",
      "name": "name",
      "required": true,
      "schema": {
       "pattern": "^[a-z0-9][a-z0-9-]{0,40}$",
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "audio/wav": {
       "schema": {
        "format": "binary",
        "type": "string"
       }
      }
     },
     "description": "L'audio du message (WAV PCM).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Upload Prompt V1 Telephony Ivr Prompts Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déposer un message de SVI",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-prompts/{prompt_id}": {
   "delete": {
    "description": "Supprime le message et son audio (204) ; son URL de lecture répond 404 ensuite. 409 `telephony_prompt_in_use` (avec `details.ivr_flows`) tant qu'une version brouillon ou publiée d'un SVI le joue. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.ivr_prompt.delete`.",
    "operationId": "delete_prompt_v1_telephony_ivr_prompts__prompt_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du message de SVI.",
      "in": "path",
      "name": "prompt_id",
      "required": true,
      "schema": {
       "title": "Prompt Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un message de SVI",
    "tags": [
     "telephony",
     "telephony"
    ]
   },
   "get": {
    "description": "Un message de SVI ; `playback_url` n'est rempli que pour `tenant_admin` et `platform_admin`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_prompt_v1_telephony_ivr_prompts__prompt_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du message de SVI.",
      "in": "path",
      "name": "prompt_id",
      "required": true,
      "schema": {
       "title": "Prompt Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Prompt V1 Telephony Ivr Prompts  Prompt Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un message de SVI",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/ivr-prompts/{prompt_id}/download": {
   "post": {
    "description": "Répond `{url, method: GET, expires_seconds: 300, expires_at, content_type, size_bytes, sha256}` : une URL signée SeaweedFS valable 300 secondes ; aucun identifiant de stockage n'est renvoyé. 409 `telephony_recording_unavailable` quand l'objet n'est pas stocké, 503 `telephony_storage_missing` quand le profil de la plateforme manque, 409 `storage_capability_missing` quand la signature n'a pas été vérifiée. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`. Audité sous `telephony.ivr_prompt.download`.",
    "operationId": "download_prompt_v1_telephony_ivr_prompts__prompt_id__download_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du message de SVI.",
      "in": "path",
      "name": "prompt_id",
      "required": true,
      "schema": {
       "title": "Prompt Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Download Prompt V1 Telephony Ivr Prompts  Prompt Id  Download Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir un lien de téléchargement d'un message de SVI",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/livekit/webhook": {
   "post": {
    "description": "Point d'entrée du serveur LiveKit, sans authentification de la plateforme : l'en-tête `Authorization` porte le JWT que LiveKit signe avec sa clé et son secret d'API, dont la revendication `sha256` doit être le SHA-256 en base64 du corps brut (401 `invalid_signature` sinon). Événements traités : `participant_joined` et `participant_left` des participants SIP (enregistrement d'appel de la branche IA, appel actif, événements `telephony.call.*` ; relié à l'appel du PBX par l'attribut `navin.call_id` quand l'appel est passé par Asterisk) et `egress_ended` (les fichiers de LiveKit Egress sont indexés et copiés dans l'organisation du tenant). Le tenant est celui dont le tid préfixe la salle ; un attribut `navin.tenant_id` désignant un autre tenant répond 422 `telephony_tenant_mismatch`. Une livraison déjà vue répond `duplicate`.",
    "operationId": "livekit_webhook_v1_telephony_livekit_webhook_post",
    "requestBody": {
     "content": {
      "application/webhook+json": {
       "schema": {
        "type": "object"
       }
      }
     },
     "description": "Le WebhookEvent de LiveKit (JSON).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Recevoir un webhook LiveKit",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/media/prompts/{tenant_id}/{prompt_file}": {
   "get": {
    "description": "Audio (`audio/wav`) d'un message de SVI pour Asterisk : le plan de numérotation joue la `playback_url` par `Playback(https://...)` (res_http_media_cache avec res_curl). Sans session : l'URL porte un HMAC-SHA256 du tenant, du message et de son SHA-256 calculé avec une clé du tenant gardée dans le coffre ; une signature fausse, un autre tenant ou un message supprimé répondent 404.",
    "operationId": "play_prompt_v1_telephony_media_prompts__tenant_id___prompt_file__get",
    "parameters": [
     {
      "description": "Tenant du message (UUID).",
      "in": "path",
      "name": "tenant_id",
      "required": true,
      "schema": {
       "title": "Tenant Id",
       "type": "string"
      }
     },
     {
      "description": "`<identifiant du message>.wav`.",
      "in": "path",
      "name": "prompt_file",
      "required": true,
      "schema": {
       "title": "Prompt File",
       "type": "string"
      }
     },
     {
      "description": "Signature HMAC-SHA256 de l'URL (64 caractères hexadécimaux).",
      "in": "query",
      "name": "sig",
      "required": true,
      "schema": {
       "pattern": "^[0-9a-f]{64}$",
       "title": "Sig",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "audio/wav": {
        "schema": {
         "format": "binary",
         "type": "string"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Jouer un message de SVI",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/numbers": {
   "get": {
    "description": "Les numéros du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_number_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les objets actifs (true) ou inactifs (false). Par défaut : les deux.",
      "in": "query",
      "name": "enabled",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Enabled"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Number List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les numéros",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée un numéro dans le tenant de l'appelant et le renvoie (201). Le numéro E.164 est unique sur la plateforme (409 `telephony_number_taken`) ; la destination doit être un objet du tenant (422 `telephony_reference_invalid`) ; la branche `ai` demande un agent vocal. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.number.create`.",
    "operationId": "telephony_number_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyNumberIn"
       }
      }
     },
     "description": "Le numéro.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Number Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un numéro",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/numbers/{item_id}": {
   "delete": {
    "description": "Supprime le numéro (204) ; les moteurs le perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.number.delete`.",
    "operationId": "telephony_number_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un numéro",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Un numéro du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_number_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Number Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un numéro",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie le numéro ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.number.update`.",
    "operationId": "telephony_number_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyNumberPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Number Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un numéro",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools": {
   "get": {
    "description": "Tous les pools pour `platform_admin` ; pour les autres lecteurs les pools partagés et les pools dédiés à leur tenant, avec seulement `id`, `name`, `mode`, `tenant_id`, `enabled` et `dispatcher_set`. Les références de secrets ne leur sont jamais renvoyées ; les indicateurs `has_*` disent si un secret est défini. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_pools_v1_telephony_pbx_pools_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Pools V1 Telephony Pbx Pools Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les pools PBX",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Déclare un pool Asterisk installé par le Navin Manager : nœuds et leur port AMI, références des secrets AMI, ARI, JSONRPC et auth_ephemeral (`openbao://` ou `env://EAI_CRED_*`, jamais une valeur), points d'accès et adresses Kamailio, jeux du dispatcher et liaison LiveKit. Un pool partagé n'a pas de tenant, un pool dédié en a un (422 `telephony_pool_mode`). Demande `platform_admin` ; audité sous `telephony.pbx_pool.create`.",
    "operationId": "create_pool_v1_telephony_pbx_pools_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyPoolIn"
       }
      }
     },
     "description": "Le pool.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Pool V1 Telephony Pbx Pools Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Déclarer un pool PBX",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}": {
   "delete": {
    "description": "Supprime un pool qu'aucun tenant n'utilise (409 `telephony_pool_in_use` sinon) avec ses versions. Demande `platform_admin` ; audité sous `telephony.pbx_pool.delete`.",
    "operationId": "delete_pool_v1_telephony_pbx_pools__pool_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un pool PBX",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Un pool visible par l'appelant (404 sinon), champs limités sauf pour `platform_admin`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_pool_v1_telephony_pbx_pools__pool_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Pool V1 Telephony Pbx Pools  Pool Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un pool PBX",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie les champs envoyés du pool. Demande `platform_admin` ; audité sous `telephony.pbx_pool.update`.",
    "operationId": "update_pool_v1_telephony_pbx_pools__pool_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyPoolPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Update Pool V1 Telephony Pbx Pools  Pool Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un pool PBX",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}/drain": {
   "post": {
    "description": "Fixe l'état d'une destination sur chaque Kamailio du pool (`dispatcher.set_state` : `drain` inactive, `active` remise en service) et renvoie le `dispatcher.list` de chacun. Demande `platform_admin` ; audité sous `telephony.pbx_pool.drain` ou `telephony.pbx_pool.active`.",
    "operationId": "drain_destination_v1_telephony_pbx_pools__pool_id__drain_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyDrainIn"
       }
      }
     },
     "description": "La destination et son nouvel état.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Drain Destination V1 Telephony Pbx Pools  Pool Id  Drain Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Drainer ou rétablir une destination du dispatcher",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}/preview": {
   "post": {
    "description": "Compile chaque tenant du pool et valide le résultat (graphes, références, aucun contexte d'un tenant ne désignant un autre tenant, un domaine SIP pour chaque tenant qui a des extensions : `telephony_sip_domain_missing` sinon), sans livraison. Renvoie `valid`, `errors`, `files` (secrets masqués), `kamailio`, `livekit`, `sha256`, `tenants`, `current_version`, `unchanged` et `diff` (diff unifié par fichier avec la version publiée). Pool partagé : `platform_admin` ; pool dédié : son `tenant_admin`.",
    "operationId": "preview_pool_v1_telephony_pbx_pools__pool_id__preview_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Preview Pool V1 Telephony Pbx Pools  Pool Id  Preview Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Prévisualiser la configuration d'un pool",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}/publish": {
   "post": {
    "description": "Compile et valide le pool (422 avec chaque problème, `telephony_sip_domain_missing` pour un tenant qui a des extensions mais aucun domaine SIP), enregistre une nouvelle version (`publishing`) et répond 202 avec `version_id`, `version`, `sha256` et `operation_id` (en-têtes `Operation-Id`, `Operation-Location`). En arrière-plan chaque nœud du pool reçoit les fichiers par AMI (CreateConfig, UpdateConfig, rechargements, relecture GetConfig), puis les tables Kamailio et les objets LiveKit SIP sont réconciliés. La version finit `published` ou `failed` (les nœuds en échec reprennent la version précédente) ; suivez-la par `GET /v1/telephony/config-versions/{version_id}` ou l'opération. Une publication à la fois par pool (409 `telephony_publication_running`). `Idempotency-Key` prise en compte. Pool partagé : `platform_admin` ; pool dédié : son `tenant_admin`. Audité sous `telephony.config.publish` puis `telephony.config.published` ou `telephony.config.failed`.",
    "operationId": "publish_pool_v1_telephony_pbx_pools__pool_id__publish_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     },
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyPublishIn"
       }
      }
     },
     "description": "Pourquoi la configuration est publiée.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Publish Pool V1 Telephony Pbx Pools  Pool Id  Publish Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Publier la configuration d'un pool",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}/reconcile": {
   "post": {
    "description": "Compare les tables de hachage Kamailio et les trunks et règles de dispatch LiveKit SIP avec la version publiée et corrige les écarts (idempotent : rien ne change quand ils correspondent). Un Kamailio dont `core.uptime` a baissé a redémarré et reçoit à nouveau toutes les entrées (`full_resync`). 409 `telephony_nothing_published` avant la première publication. Mêmes rôles que la publication ; audité sous `telephony.config.reconcile`.",
    "operationId": "reconcile_pool_v1_telephony_pbx_pools__pool_id__reconcile_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Reconcile Pool V1 Telephony Pbx Pools  Pool Id  Reconcile Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Réconcilier le routage d'un pool",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}/rollback": {
   "post": {
    "description": "Publie à nouveau les fichiers, tables et objets LiveKit d'une version antérieure `published` ou `rolled_back` comme une nouvelle version (`rollback_of`), en opération longue comme la publication (202). Une fois publiée, les versions ultérieures deviennent `rolled_back`. 409 `telephony_rollback_target` pour un autre état. Mêmes rôles que la publication ; audité sous `telephony.config.rollback`.",
    "operationId": "rollback_pool_v1_telephony_pbx_pools__pool_id__rollback_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     },
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyRollbackIn"
       }
      }
     },
     "description": "La version à publier à nouveau et pourquoi.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Rollback Pool V1 Telephony Pbx Pools  Pool Id  Rollback Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Revenir à une version d'un pool",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/pbx-pools/{pool_id}/versions": {
   "get": {
    "description": "Versions du pool, la plus récente d'abord, avec état, auteur, raison, `sha256`, le SHA-256 de chaque fichier, `current` (la version que les moteurs exécutent) et le rapport de livraison ; `next_cursor` pagine par numéro de version. Pool partagé : `platform_admin` ; pool dédié : les lecteurs de son tenant.",
    "operationId": "list_versions_v1_telephony_pbx_pools__pool_id__versions_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du pool PBX.",
      "in": "path",
      "name": "pool_id",
      "required": true,
      "schema": {
       "title": "Pool Id",
       "type": "string"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Versions V1 Telephony Pbx Pools  Pool Id  Versions Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les versions de configuration d'un pool",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/preview": {
   "post": {
    "description": "Compile la configuration du seul tenant de l'appelant et la valide, sans aucune livraison : `valid`, `errors` (code, chemin, messages), les cinq fichiers Asterisk avec les secrets masqués, les entrées Kamailio, les objets LiveKit voulus et `sha256`. 409 `telephony_not_configured` sans paramètres. Demande `tenant_admin` ou `platform_admin`.",
    "operationId": "preview_tenant_v1_telephony_preview_post",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Preview Tenant V1 Telephony Preview Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Prévisualiser la configuration du tenant",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/queues": {
   "get": {
    "description": "Les files d'appels du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_queue_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Queue List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les files d'appels",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée une file d'appels dans le tenant de l'appelant et la renvoie (201). Son numéro facultatif suit les règles des numéros d'extension. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.queue.create`.",
    "operationId": "telephony_queue_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyQueueIn"
       }
      }
     },
     "description": "La file d'appels.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Queue Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une file d'appels",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/queues/{item_id}": {
   "delete": {
    "description": "Supprime la file d'appels (204) ; les moteurs la perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.queue.delete`.",
    "operationId": "telephony_queue_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une file d'appels",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Une file d'appels du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_queue_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Queue Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une file d'appels",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie la file d'appels ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.queue.update`.",
    "operationId": "telephony_queue_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyQueuePatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Queue Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une file d'appels",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/queues/{queue_id}/members": {
   "get": {
    "description": "Extensions et agents humains de la file avec leur pénalité. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_members_v1_telephony_queues__queue_id__members_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la file.",
      "in": "path",
      "name": "queue_id",
      "required": true,
      "schema": {
       "title": "Queue Id",
       "type": "string"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Members V1 Telephony Queues  Queue Id  Members Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les membres d'une file",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Ajoute exactement une extension ou un agent humain du tenant (422 `telephony_member_target` ou `telephony_reference_invalid`). `Idempotency-Key` prise en compte. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.queue_member.create`.",
    "operationId": "add_member_v1_telephony_queues__queue_id__members_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la file.",
      "in": "path",
      "name": "queue_id",
      "required": true,
      "schema": {
       "title": "Queue Id",
       "type": "string"
      }
     },
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyMemberIn"
       }
      }
     },
     "description": "Le membre et sa pénalité.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Add Member V1 Telephony Queues  Queue Id  Members Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Ajouter un membre à une file",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/queues/{queue_id}/members/{member_id}": {
   "delete": {
    "description": "Retire le membre (204). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.queue_member.delete`.",
    "operationId": "remove_member_v1_telephony_queues__queue_id__members__member_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la file.",
      "in": "path",
      "name": "queue_id",
      "required": true,
      "schema": {
       "title": "Queue Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du membre de la file.",
      "in": "path",
      "name": "member_id",
      "required": true,
      "schema": {
       "title": "Member Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Retirer un membre d'une file",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Fixe la pénalité du membre (0 sonne en premier). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.queue_member.update`.",
    "operationId": "update_member_v1_telephony_queues__queue_id__members__member_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) de la file.",
      "in": "path",
      "name": "queue_id",
      "required": true,
      "schema": {
       "title": "Queue Id",
       "type": "string"
      }
     },
     {
      "description": "Identifiant (UUID) du membre de la file.",
      "in": "path",
      "name": "member_id",
      "required": true,
      "schema": {
       "title": "Member Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyMemberPatch"
       }
      }
     },
     "description": "La nouvelle pénalité.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Update Member V1 Telephony Queues  Queue Id  Members  Member Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier la pénalité d'un membre de file",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/quotas": {
   "get": {
    "description": "Quotas et limites anti-fraude du tenant (valeurs par défaut avec `configured: false` s'ils n'ont jamais été définis) : appels simultanés et appels par seconde (poussés dans `tenant_limits` de Kamailio), minutes mensuelles, pays autorisés, préfixes surtaxés, durée maximale d'appel (dialplan) et conservation des enregistrements. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_quotas_v1_telephony_quotas_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Quotas V1 Telephony Quotas Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les quotas de téléphonie",
    "tags": [
     "telephony"
    ]
   },
   "put": {
    "description": "Remplace les quotas du tenant. Les préfixes sont `+` puis 1 à 8 chiffres (422 `telephony_prefix_invalid`). Demande `platform_admin` : un tenant ne relève jamais ses propres limites. Audité sous `telephony.quotas.update`.",
    "operationId": "put_quotas_v1_telephony_quotas_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyQuotasIn"
       }
      }
     },
     "description": "Les quotas complets ; les champs omis prennent leur valeur par défaut.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Quotas V1 Telephony Quotas Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir les quotas de téléphonie",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/recordings": {
   "get": {
    "description": "Index des objets de téléphonie du tenant dans SeaweedFS, du plus récent au plus ancien. Chaque élément : `id`, `category`, `status` (pending, stored, failed, deleted), `source` (asterisk, livekit, upload, export), `content_type`, `size_bytes`, `sha256`, `duration_seconds`, `encryption` (ce que le stockage a indiqué, et `profile_mode`), `call_record_id`, `navin_call_id`, `retention_until`, `legal_hold`, `created_at`, `stored_at`, `deleted_at`, `last_error`. La clé de l'objet n'est jamais renvoyée. Pagination par curseur : `limit` (1 à 500, 50 par défaut) et le `cursor` opaque de `next_cursor`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "list_recordings_v1_telephony_recordings_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement cette catégorie de l'organisation.",
      "in": "query",
      "name": "category",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "call-recordings",
          "voicemail",
          "ivr-prompts",
          "conference-recordings",
          "ai-voice-recordings",
          "audio-exports"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Category"
      }
     },
     {
      "description": "Seulement cet état.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "pending",
          "stored",
          "failed",
          "deleted"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les enregistrements de cet appel.",
      "in": "query",
      "name": "call_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "format": "uuid",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Call Id"
      }
     },
     {
      "description": "Seulement les enregistrements avec (true) ou sans (false) conservation légale.",
      "in": "query",
      "name": "legal_hold",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Legal Hold"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Recordings V1 Telephony Recordings Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les enregistrements",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/recordings/{recording_id}": {
   "delete": {
    "description": "Supprime l'objet dans SeaweedFS et marque la ligne d'index `deleted` (un message vocal qui l'utilise est supprimé aussi). 409 `telephony_legal_hold` sous conservation légale. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.recording.delete` avec le motif.",
    "operationId": "delete_recording_v1_telephony_recordings__recording_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'enregistrement, dans le tenant de l'appelant (l'enregistrement d'un autre tenant répond 404).",
      "in": "path",
      "name": "recording_id",
      "required": true,
      "schema": {
       "title": "Recording Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "anyOf": [
         {
          "$ref": "#/components/schemas/TelephonyDeleteIn"
         },
         {
          "type": "null"
         }
        ],
        "title": "Body"
       }
      }
     },
     "description": "Motif facultatif conservé dans la piste d'audit."
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Recording V1 Telephony Recordings  Recording Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un enregistrement",
    "tags": [
     "telephony",
     "telephony"
    ]
   },
   "get": {
    "description": "Un enregistrement de l'index (mêmes champs que la liste). Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_recording_v1_telephony_recordings__recording_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'enregistrement, dans le tenant de l'appelant (l'enregistrement d'un autre tenant répond 404).",
      "in": "path",
      "name": "recording_id",
      "required": true,
      "schema": {
       "title": "Recording Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Recording V1 Telephony Recordings  Recording Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un enregistrement",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/recordings/{recording_id}/download": {
   "post": {
    "description": "Répond `{url, method: GET, expires_seconds: 300, expires_at, content_type, size_bytes, sha256}` : une URL signée SeaweedFS valable 300 secondes ; aucun identifiant de stockage n'est renvoyé. 409 `telephony_recording_unavailable` quand l'objet n'est pas stocké, 503 `telephony_storage_missing` quand le profil de la plateforme manque, 409 `storage_capability_missing` quand la signature n'a pas été vérifiée. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`. Audité sous `telephony.recording.download` avec le SHA-256 de l'URL.",
    "operationId": "download_recording_v1_telephony_recordings__recording_id__download_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'enregistrement, dans le tenant de l'appelant (l'enregistrement d'un autre tenant répond 404).",
      "in": "path",
      "name": "recording_id",
      "required": true,
      "schema": {
       "title": "Recording Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Download Recording V1 Telephony Recordings  Recording Id  Download Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir un lien de téléchargement",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/recordings/{recording_id}/legal-hold": {
   "put": {
    "description": "Pose ou lève la conservation légale : un enregistrement conservé n'est jamais supprimé, ni par l'API ni par la tâche de conservation. Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.recording.legal_hold` avec le motif.",
    "operationId": "set_legal_hold_v1_telephony_recordings__recording_id__legal_hold_put",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'enregistrement, dans le tenant de l'appelant (l'enregistrement d'un autre tenant répond 404).",
      "in": "path",
      "name": "recording_id",
      "required": true,
      "schema": {
       "title": "Recording Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyLegalHoldIn"
       }
      }
     },
     "description": "La conservation et son motif.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Set Legal Hold V1 Telephony Recordings  Recording Id  Legal Hold Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir la conservation légale d'un enregistrement",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/routing-rules": {
   "get": {
    "description": "Les règles de routage du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_routing_rule_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les objets actifs (true) ou inactifs (false). Par défaut : les deux.",
      "in": "query",
      "name": "enabled",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Enabled"
      }
     },
     {
      "description": "Seulement les règles entrantes ou sortantes. Par défaut : les deux.",
      "in": "query",
      "name": "direction",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "inbound",
          "outbound"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Direction"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Routing Rule List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les règles de routage",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée une règle de routage dans le tenant de l'appelant et la renvoie (201). Les règles sortantes listent les trunks du tenant dans l'ordre de secours ; les règles entrantes demandent une destination du tenant. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.routing_rule.create`.",
    "operationId": "telephony_routing_rule_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyRuleIn"
       }
      }
     },
     "description": "La règle de routage.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Routing Rule Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer une règle de routage",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/routing-rules/{item_id}": {
   "delete": {
    "description": "Supprime la règle de routage (204) ; les moteurs la perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.routing_rule.delete`.",
    "operationId": "telephony_routing_rule_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer une règle de routage",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Une règle de routage du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_routing_rule_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Routing Rule Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire une règle de routage",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie la règle de routage ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.routing_rule.update`.",
    "operationId": "telephony_routing_rule_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyRulePatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Routing Rule Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier une règle de routage",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/runtime-settings": {
   "get": {
    "description": "Stockage de la messagerie vocale et seuils de fraude du tenant (`configured` false et les valeurs par défaut quand ils n'ont jamais été définis). Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_runtime_settings_v1_telephony_runtime_settings_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Runtime Settings V1 Telephony Runtime Settings Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les réglages d'exécution",
    "tags": [
     "telephony",
     "telephony"
    ]
   },
   "put": {
    "description": "Remplace les réglages d'exécution. `voicemail_storage` atteint Asterisk à la prochaine publication du pool (messagerie de la plateforme : Record puis l'UserEvent NavinVoicemail). Demande `tenant_admin` ou `platform_admin`. Audité sous `telephony.runtime_settings.update`.",
    "operationId": "put_runtime_settings_v1_telephony_runtime_settings_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyRuntimeSettingsIn"
       }
      }
     },
     "description": "Les réglages complets ; les champs omis prennent leur valeur par défaut.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Runtime Settings V1 Telephony Runtime Settings Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir les réglages d'exécution",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/settings": {
   "get": {
    "description": "Paramètres de téléphonie du tenant de l'appelant : modèle, domaine SIP, pool PBX, conservation des CDR, plan de numérotation, et `tid`, l'identifiant court sous lequel les moteurs connaissent le tenant. `configured: false` s'ils n'ont jamais été définis. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "get_settings_v1_telephony_settings_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Settings V1 Telephony Settings Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lire les paramètres de téléphonie",
    "tags": [
     "telephony"
    ]
   },
   "put": {
    "description": "Crée ou remplace les paramètres du tenant. Le domaine SIP est unique sur la plateforme (409 `telephony_domain_taken`). Changer `model` ou `pbx_instance_id` demande `platform_admin` (403 sinon) ; le pool doit être partagé ou dédié à ce tenant et correspondre au modèle (422 `telephony_pool_mode`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.settings.update`.",
    "operationId": "put_settings_v1_telephony_settings_put",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonySettingsIn"
       }
      }
     },
     "description": "Les paramètres complets ; les champs omis prennent leur valeur par défaut.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Put Settings V1 Telephony Settings Put",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Définir les paramètres de téléphonie",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/stats": {
   "get": {
    "description": "Chiffres de tableau de bord du tenant calculés à partir des tables d'appels : `active_calls`, `active_by_state`, `active_by_branch`, `calls`, `answered`, `answer_rate`, `average_billsec`, `average_wait_seconds`, `calls_per_hour` (`hour`, `calls`, `answered`, `inbound`, `outbound`), `queues` (`queue`, `sla_seconds`, `offered`, `answered`, `abandoned`, `waiting`, `average_wait_seconds`, `max_wait_seconds`, `sla` = part répondue dans le niveau de service de la file, `abandon_rate`), `agents` (états des membres de file : `queue`, `interface`, `extension`, `member_name`, `device_status`, `paused`, `paused_reason`, `in_call`, `calls_taken`, `updated_at`), `registrations` (`extension`, `status`, `contacts`, `updated_at`), `open_fraud_alerts`, `active_blocks`, `generated_at`, `window_hours`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "live_stats_v1_telephony_stats_get",
    "parameters": [
     {
      "description": "Fenêtre des chiffres en heures, 1 à 168 (24 par défaut).",
      "in": "query",
      "name": "window_hours",
      "required": false,
      "schema": {
       "default": 24,
       "maximum": 168,
       "minimum": 1,
       "title": "Window Hours",
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Live Stats V1 Telephony Stats Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire les statistiques en direct",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/trunks": {
   "get": {
    "description": "Les trunks SIP du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_trunk_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les objets actifs (true) ou inactifs (false). Par défaut : les deux.",
      "in": "query",
      "name": "enabled",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Enabled"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Trunk List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les trunks SIP",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée un trunk SIP dans le tenant de l'appelant et le renvoie (201). Le mot de passe est en écriture seule : la réponse porte `has_password`. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.trunk.create`.",
    "operationId": "telephony_trunk_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyTrunkIn"
       }
      }
     },
     "description": "Le trunk sip.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Trunk Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un trunk SIP",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/trunks/{item_id}": {
   "delete": {
    "description": "Supprime le trunk SIP (204) ; les moteurs le perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.trunk.delete`.",
    "operationId": "telephony_trunk_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un trunk SIP",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Un trunk SIP du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_trunk_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Trunk Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un trunk SIP",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie le trunk SIP ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.trunk.update`.",
    "operationId": "telephony_trunk_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyTrunkPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Trunk Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un trunk SIP",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/voice-agents": {
   "get": {
    "description": "Les agents vocaux IA du tenant de l'appelant, par identifiant, `limit` par page (50 par défaut, 500 au plus) avec `next_cursor` pour la page suivante. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_voice_agent_list",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement les objets actifs (true) ou inactifs (false). Par défaut : les deux.",
      "in": "query",
      "name": "enabled",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "boolean"
        },
        {
         "type": "null"
        }
       ],
       "title": "Enabled"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Voice Agent List",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les agents vocaux IA",
    "tags": [
     "telephony"
    ]
   },
   "post": {
    "description": "Crée un agent vocal IA dans le tenant de l'appelant et le renvoie (201). La file de secours doit appartenir au tenant. Avec une `Idempotency-Key`, une nouvelle tentative avec le même corps renvoie la première réponse (`Idempotency-Replayed: true`), un autre corps répond 409 `idempotency_conflict`. Les problèmes de validation répondent 422 avec chaque problème dans `details.errors` (code, chemin, `message_en`, `message_fr`). Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.voice_agent.create`.",
    "operationId": "telephony_voice_agent_create",
    "parameters": [
     {
      "description": "Clé du client qui rend une nouvelle tentative sûre : la même clé avec le même corps renvoie le premier résultat au lieu de créer un second objet.",
      "in": "header",
      "name": "Idempotency-Key",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "description": "Key that makes a retried creation return the first answer.",
       "title": "Idempotency-Key"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyVoiceAgentIn"
       }
      }
     },
     "description": "L'agent vocal ia.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Voice Agent Create",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un agent vocal IA",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/voice-agents/{item_id}": {
   "delete": {
    "description": "Supprime l'agent vocal IA (204) ; les moteurs le perdent à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.voice_agent.delete`.",
    "operationId": "telephony_voice_agent_delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un agent vocal IA",
    "tags": [
     "telephony"
    ]
   },
   "get": {
    "description": "Un agent vocal IA du tenant de l'appelant ; 404 `telephony_not_found` sinon. Les secrets ne sont jamais renvoyés, seulement les indicateurs `has_*`. Demande `tenant_admin`, `support_engineer`, `auditor` ou `platform_admin`.",
    "operationId": "telephony_voice_agent_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Voice Agent Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un agent vocal IA",
    "tags": [
     "telephony"
    ]
   },
   "patch": {
    "description": "Modifie seulement les champs envoyés et renvoie l'agent vocal IA ; les contrôles de la création s'appliquent. Un secret envoyé remplace celui conservé (nouvelle version dans le coffre). Les moteurs reçoivent la modification à la prochaine publication du pool. Demande `tenant_admin` ou `platform_admin` ; audité sous `telephony.voice_agent.update`.",
    "operationId": "telephony_voice_agent_update",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'objet, dans le tenant de l'appelant (l'objet d'un autre tenant répond 404).",
      "in": "path",
      "name": "item_id",
      "required": true,
      "schema": {
       "title": "Item Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyVoiceAgentPatch"
       }
      }
     },
     "description": "Les champs à modifier.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Telephony Voice Agent Update",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un agent vocal IA",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/telephony/voice-agents/{voice_agent_id}/runtime": {
   "get": {
    "description": "Configuration dont un worker vocal a besoin : `id`, `tenant_id`, `name`, `instructions`, `language`, `greeting`, `llm` (`model`, `base_url`), `stt` (`model`, `base_url`), `tts` (`model`, `voice`, `base_url`), `tools` (les outils `lookup_*` et `end_call` de l'agent, plus `transfer_to_human` quand une cible de transfert existe), `transfer` (`target_uri`, sip: ou tel:), `max_duration_s`, `fallback_queue`. URL de base : EAI_TELEPHONY_LLM_BASE_URL, EAI_TELEPHONY_STT_BASE_URL, EAI_TELEPHONY_TTS_BASE_URL, sinon la passerelle de modèles du Control Plane. 409 `telephony_voice_agent_disabled`. Réservé à un jeton de worker vocal : une clé d'API du tenant portant explicitement la portée `telephony:voice-worker` (403 `insufficient_scope` sinon, une portée générique ne compte pas) ; l'agent vocal et l'appel doivent appartenir au tenant de la clé (404 sinon).",
    "operationId": "voice_agent_runtime_v1_telephony_voice_agents__voice_agent_id__runtime_get",
    "parameters": [
     {
      "description": "Identifiant (UUID) de l'agent vocal, dans le tenant du jeton de worker.",
      "in": "path",
      "name": "voice_agent_id",
      "required": true,
      "schema": {
       "title": "Voice Agent Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Voice Agent Runtime V1 Telephony Voice Agents  Voice Agent Id  Runtime Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire la configuration d'exécution d'un agent vocal",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/voicemail": {
   "get": {
    "description": "Messages vocaux (état new ou read) du tenant, du plus récent au plus ancien : `id`, `mailbox`, `extension_id`, `caller_number`, `caller_name`, `navin_call_id`, `recording_id`, `duration_seconds`, `status`, `read_at`, `received_at`. Un message apparaît dès que son audio est stocké. Les lecteurs voient toutes les boîtes ; un agent humain (tout rôle) voit les boîtes des extensions auxquelles il est lié, 403 pour toute autre personne. Pagination par curseur : `limit` (1 à 500, 50 par défaut) et le `cursor` opaque de `next_cursor`.",
    "operationId": "list_voicemail_v1_telephony_voicemail_get",
    "parameters": [
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "maximum": 500,
         "minimum": 1,
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Seulement cette boîte (numéro d'extension).",
      "in": "query",
      "name": "mailbox",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "pattern": "^[0-9]{2,8}$",
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Mailbox"
      }
     },
     {
      "description": "Seulement les messages nouveaux ou lus.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "enum": [
          "new",
          "read"
         ],
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Voicemail V1 Telephony Voicemail Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les messages vocaux",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/voicemail/{message_id}": {
   "delete": {
    "description": "Supprime le message et son audio (409 `telephony_legal_hold` quand l'audio est conservé) et le renvoie avec l'état `deleted`. Permis aux lecteurs et à l'agent de la boîte. Audité sous `telephony.voicemail.delete`.",
    "operationId": "delete_voicemail_v1_telephony_voicemail__message_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du message vocal.",
      "in": "path",
      "name": "message_id",
      "required": true,
      "schema": {
       "title": "Message Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Delete Voicemail V1 Telephony Voicemail  Message Id  Delete",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un message vocal",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/voicemail/{message_id}/listen": {
   "post": {
    "description": "Répond le message (`message`) et son lien signé. Répond `{url, method: GET, expires_seconds: 300, expires_at, content_type, size_bytes, sha256}` : une URL signée SeaweedFS valable 300 secondes ; aucun identifiant de stockage n'est renvoyé. 409 `telephony_recording_unavailable` quand l'objet n'est pas stocké, 503 `telephony_storage_missing` quand le profil de la plateforme manque, 409 `storage_capability_missing` quand la signature n'a pas été vérifiée. Permis aux lecteurs et à l'agent de la boîte. Audité sous `telephony.voicemail.download`.",
    "operationId": "listen_voicemail_v1_telephony_voicemail__message_id__listen_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du message vocal.",
      "in": "path",
      "name": "message_id",
      "required": true,
      "schema": {
       "title": "Message Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Listen Voicemail V1 Telephony Voicemail  Message Id  Listen Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Écouter un message vocal",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/voicemail/{message_id}/read": {
   "post": {
    "description": "Marque le message lu (ou de nouveau nouveau avec `read=false`) et le renvoie. Permis aux lecteurs et à l'agent de la boîte. Audité sous `telephony.voicemail.read` ou `telephony.voicemail.unread`.",
    "operationId": "mark_voicemail_v1_telephony_voicemail__message_id__read_post",
    "parameters": [
     {
      "description": "Identifiant (UUID) du message vocal.",
      "in": "path",
      "name": "message_id",
      "required": true,
      "schema": {
       "title": "Message Id",
       "type": "string"
      }
     },
     {
      "description": "true (par défaut) : lu ; false : de nouveau nouveau.",
      "in": "query",
      "name": "read",
      "required": false,
      "schema": {
       "default": true,
       "title": "Read",
       "type": "boolean"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Mark Voicemail V1 Telephony Voicemail  Message Id  Read Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Marquer un message vocal lu",
    "tags": [
     "telephony",
     "telephony"
    ]
   }
  },
  "/v1/telephony/webrtc/credentials": {
   "post": {
    "description": "Identifiants SIP éphémères d'une extension WebRTC pour le module `auth_ephemeral` de Kamailio : `username` `<expiration>:<point de terminaison>`, `password` base64(HMAC-SHA1(secret partagé, username)), `expires_at` (une heure au plus), `sip_uri` `sip:<point de terminaison>@<domaine SIP>` (le softphone s'enregistre et appelle avec le nom de son point de terminaison comme utilisateur From et To, l'utilisateur que Kamailio vérifie avec les identifiants), `ws_url`, `realm`, `endpoint` (`<tid>-<extension>`) et `number` (le numéro de l'extension, pour l'affichage). Le mot de passe SIP de l'extension n'atteint jamais le navigateur. Permis pour `tenant_admin`, `platform_admin` et l'agent humain lié à l'extension (404 pour toute autre personne). 409 `telephony_not_webrtc` ou `telephony_not_configured`. Audité sous `telephony.webrtc.credentials`.",
    "operationId": "webrtc_credentials_v1_telephony_webrtc_credentials_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TelephonyWebrtcIn"
       }
      }
     },
     "description": "L'extension et la durée de vie.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Webrtc Credentials V1 Telephony Webrtc Credentials Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Obtenir des identifiants WebRTC",
    "tags": [
     "telephony"
    ]
   }
  },
  "/v1/tenants": {
   "get": {
    "description": "Liste les tenants visibles par l'appelant, triés par identifiant court, paginés : son propre tenant pour un identifiant de tenant, tous les tenants pour un administrateur de plateforme.",
    "operationId": "list_tenants_v1_tenants_get",
    "parameters": [
     {
      "description": "Nombre maximal de tenants dans une page (50 par défaut, 500 au maximum).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Tenants V1 Tenants Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les tenants",
    "tags": [
     "tenancy"
    ]
   },
   "post": {
    "description": "Crée un tenant et fait de chaque sujet de `admins` son `tenant_admin`. Exige `platform_admin`. Un identifiant court déjà utilisé répond 409 `conflict`. Écrit l'événement d'audit `tenant.create` dans la chaîne du nouveau tenant.",
    "operationId": "create_tenant_v1_tenants_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TenantIn"
       }
      }
     },
     "description": "Identifiant court, nom, résidence, paramètres et premiers administrateurs du tenant.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Tenant V1 Tenants Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un tenant",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/tenants/{tenant_id}": {
   "get": {
    "description": "Renvoie un tenant : identifiant court, nom, statut, résidence des données et paramètres. Un tenant que l'appelant ne peut pas voir répond 404 `not_found`.",
    "operationId": "get_tenant_v1_tenants__tenant_id__get",
    "parameters": [
     {
      "description": "Identifiant (UUID) du tenant.",
      "in": "path",
      "name": "tenant_id",
      "required": true,
      "schema": {
       "title": "Tenant Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Tenant V1 Tenants  Tenant Id  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un tenant",
    "tags": [
     "tenancy"
    ]
   },
   "patch": {
    "description": "Modifie le nom, la résidence des données, les paramètres ou le statut d'un tenant. Exige `tenant_admin` ; changer `status` (`active` ou `suspended`) exige un administrateur de plateforme (403 `forbidden`). Un tenant suspendu répond 403 `tenant_suspended` à tout identifiant. Écrit l'événement d'audit `tenant.update`.",
    "operationId": "patch_tenant_v1_tenants__tenant_id__patch",
    "parameters": [
     {
      "description": "Identifiant (UUID) du tenant.",
      "in": "path",
      "name": "tenant_id",
      "required": true,
      "schema": {
       "title": "Tenant Id",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TenantPatch"
       }
      }
     },
     "description": "Champs à modifier ; les champs omis gardent leur valeur.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Patch Tenant V1 Tenants  Tenant Id  Patch",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un tenant",
    "tags": [
     "tenancy"
    ]
   }
  },
  "/v1/tokenization/detokenize": {
   "post": {
    "description": "Renvoie les valeurs d'origine des jetons donnés dans `tokens` ou trouvés dans `text`.\n\n1. L'appelant doit être `tenant_admin` ou `security_officer` (403 `forbidden`, le refus étant audité).\n2. La politique décide sur `tokenization.detokenize` avec la plus haute classification des valeurs et la finalité `purpose` (403 `policy_denied`).\n\nLa réponse donne `values` (jeton, type, valeur), les jetons inconnus `unknown`, `decision_id` et, avec `text`, le texte où les jetons sont remplacés. Aucun jeton répond 400 `invalid_request`. Écrit l'événement d'audit `tokenization.detokenize` avec la décision.",
    "operationId": "detokenize_v1_tokenization_detokenize_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/DetokenizeIn"
       }
      }
     },
     "description": "Jetons ou texte contenant des jetons, et finalité.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Detokenize V1 Tokenization Detokenize Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Détokeniser des valeurs",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/tokenization/tokenize": {
   "post": {
    "description": "Remplace les valeurs sensibles par des jetons déterministes `tok_<type>_<hex>` du tenant (une même valeur donne toujours le même jeton), les valeurs étant conservées chiffrées dans le coffre. Avec `values`, renvoie `tokens` ; avec `text`, détecte les entités et renvoie le `text` tokenisé, `findings` et `classification`. L'un des deux est obligatoire (400 `invalid_request`). Ouvert à tout membre du tenant. Écrit l'événement d'audit `tokenization.tokenize` (nombre uniquement).",
    "operationId": "tokenize_v1_tokenization_tokenize_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TokenizeIn"
       }
      }
     },
     "description": "Valeurs ou texte à tokeniser.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Tokenize V1 Tokenization Tokenize Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Tokeniser des valeurs",
    "tags": [
     "privacy"
    ]
   }
  },
  "/v1/tools": {
   "get": {
    "description": "Outils du tenant dans l'ordre des noms, éventuellement d'un seul statut. Une seule page.",
    "operationId": "list_tools_v1_tools_get",
    "parameters": [
     {
      "description": "Seulement ce statut : `draft`, `approved`, `restricted`, `deprecated` ou `blocked`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Tools V1 Tools Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les outils",
    "tags": [
     "tools"
    ]
   },
   "post": {
    "description": "Applique un manifeste Tool : crée ou met à jour l'outil (`apply_status` : `created`, `updated` ou `unchanged`). Contrôles : schémas JSON d'entrée et de sortie valides, un point d'accès `http` demande une URL http(s) permise par la politique de sortie réseau, un point d'accès `mcp` demande `mcp_server`, un gestionnaire `builtin` doit être `echo` ou `time.now`, le niveau de risque `unacceptable` est refusé (422 `invalid_manifest`). Demande `tenant_admin`, `developer` ou `application_owner` ; l'appelant devient propriétaire ReBAC. Audité sous `tool.apply`.",
    "operationId": "create_tool_v1_tools_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "additionalProperties": true,
        "title": "Manifest",
        "type": "object"
       }
      }
     },
     "description": "Manifeste Tool (`kind: Tool`, `metadata`, `spec` avec description, action, schéma d'entrée, point d'accès, classification de données et options).",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Tool V1 Tools Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Enregistrer un outil",
    "tags": [
     "tools"
    ]
   }
  },
  "/v1/tools/invocations": {
   "get": {
    "description": "Invocations enregistrées par la Tool Gateway, de la plus récente à la plus ancienne, paginées avec `cursor` : outil et version, acteur, run, agent, statut, décision, approbation, arguments et résultat expurgés, détections DLP, statut HTTP, erreur et durée. Les arguments et résultats ne sont conservés qu'expurgés.",
    "operationId": "list_invocations_v1_tools_invocations_get",
    "parameters": [
     {
      "description": "Seulement les invocations de cet outil.",
      "in": "query",
      "name": "tool",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Tool"
      }
     },
     {
      "description": "Seulement ce statut : `succeeded`, `failed`, `denied`, `blocked`, `pending_approval` ou `rate_limited`.",
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Status"
      }
     },
     {
      "description": "Seulement les invocations faites par ce run.",
      "in": "query",
      "name": "run_id",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Run Id"
      }
     },
     {
      "description": "Nombre maximal d'éléments dans une page. Le serveur plafonne la valeur ; lisez la page suivante avec `cursor`.",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     },
     {
      "description": "Curseur opaque renvoyé dans `next_cursor` par la page précédente. Omettez-le pour lire la première page.",
      "in": "query",
      "name": "cursor",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Cursor"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Invocations V1 Tools Invocations Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les invocations d'outils",
    "tags": [
     "tools"
    ]
   }
  },
  "/v1/tools/{name}": {
   "delete": {
    "description": "Retire l'outil du registre. Demande `tenant_admin` et la relation ReBAC `owner`. 404 `tool_not_found`. Audité sous `tool.delete`.",
    "operationId": "delete_tool_v1_tools__name__delete",
    "parameters": [
     {
      "description": "Nom de l'outil.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un outil",
    "tags": [
     "tools"
    ]
   },
   "get": {
    "description": "Renvoie l'outil avec son manifeste, son niveau de risque, son propriétaire et son statut. Demande la relation ReBAC `viewer`. 404 `tool_not_found`.",
    "operationId": "get_tool_v1_tools__name__get",
    "parameters": [
     {
      "description": "Nom de l'outil.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Get Tool V1 Tools  Name  Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lire un outil",
    "tags": [
     "tools"
    ]
   },
   "patch": {
    "description": "Modifie le propriétaire, la description ou le statut. Un changement de statut demande `tenant_admin` ou `security_officer`, les autres champs `tenant_admin`, `developer` ou `application_owner` ; la relation ReBAC `editor` est vérifiée. `approved` passe par une promotion (202 avec `approval_id`) ; les autres statuts s'appliquent aussitôt (200). Audité sous `tool.update`.",
    "operationId": "patch_tool_v1_tools__name__patch",
    "parameters": [
     {
      "description": "Nom de l'outil.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ToolPatch"
       }
      }
     },
     "description": "Champs à modifier ; ceux omis sont conservés.",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "title": "Response Patch Tool V1 Tools  Name  Patch"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Modifier un outil",
    "tags": [
     "tools"
    ]
   }
  },
  "/v1/tools/{name}/invoke": {
   "post": {
    "description": "Exécute la chaîne de la passerelle décrite dans l'introduction du tag et renvoie l'invocation : `status`, `result` masqué, `error`, `duration_ms`, `decision_id`, détections DLP, événement d'usage et coût. Demande `tenant_admin`, `developer` ou `agent` et la relation ReBAC `can_invoke`.\nRéponses : 200 une fois exécuté (aussi quand l'amont a échoué, voir `status` et `error`) ; 202 `pending_approval` avec `approval_id` quand une validation humaine est requise (rappelez avec l'`approval_id` approuvé et les mêmes arguments) ; 202 pour un outil sandbox, avec l'identifiant de l'exécution en sandbox.\nErreurs : 403 `tool_blocked`, `insufficient_scope`, `tool_not_granted`, `classification_exceeded`, `policy_denied`, `dlp_blocked` ; 422 `invalid_arguments` ; 409 `approval_consumed` (une approbation sert un seul appel réussi) ; 429 `rate_limited` avec `Retry-After: 60` ou `quota_exceeded`.",
    "operationId": "invoke_tool_v1_tools__name__invoke_post",
    "parameters": [
     {
      "description": "Nom de l'outil.",
      "in": "path",
      "name": "name",
      "required": true,
      "schema": {
       "title": "Name",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/InvokeIn"
       }
      }
     },
     "description": "Arguments de l'appel et son contexte (run, agent, approbation, finalité, classification).",
     "required": true
    },
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Invoquer un outil via la passerelle",
    "tags": [
     "tools"
    ]
   }
  },
  "/v1/triggers": {
   "get": {
    "description": "Déclencheurs sur événement du tenant, du plus ancien au plus récent : agent, sujet et filtre. Une seule page.",
    "operationId": "list_triggers_v1_triggers_get",
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response List Triggers V1 Triggers Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     }
    },
    "summary": "Lister les déclencheurs",
    "tags": [
     "agents"
    ]
   },
   "post": {
    "description": "Lance un run de l'agent pour chaque événement publié dont le sujet correspond (jokers NATS `*` et `>` admis) et dont la charge utile passe le filtre. Demande `tenant_admin`, `developer` ou `application_owner`. 422 `invalid_subject` ou `invalid_filter` ; 404 `agent_not_found`. Audité sous `trigger.create`.",
    "operationId": "create_trigger_v1_triggers_post",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/TriggerIn"
       }
      }
     },
     "description": "Agent, sujet et filtre facultatif sur la charge utile.",
     "required": true
    },
    "responses": {
     "201": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Create Trigger V1 Triggers Post",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Créer un déclencheur",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/triggers/{trigger_id}": {
   "delete": {
    "description": "Supprime le déclencheur sur événement ; les événements correspondants ne lancent plus de run. Demande `tenant_admin`, `developer` ou `application_owner`. 404 `trigger_not_found`. Audité sous `trigger.delete`.",
    "operationId": "delete_trigger_v1_triggers__trigger_id__delete",
    "parameters": [
     {
      "description": "Identifiant (UUID) du déclencheur.",
      "in": "path",
      "name": "trigger_id",
      "required": true,
      "schema": {
       "title": "Trigger Id",
       "type": "string"
      }
     }
    ],
    "responses": {
     "204": {
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Supprimer un déclencheur",
    "tags": [
     "agents"
    ]
   }
  },
  "/v1/webhooks/deliveries": {
   "get": {
    "description": "Livraisons de webhooks reçues, de la plus récente à la plus ancienne : connecteur, identifiant de livraison, validité de la signature, statut (`accepted`, `rejected`, `duplicate`), raison, SHA-256 de la charge utile et événement publié. La charge utile elle-même n'est pas conservée ici.",
    "operationId": "deliveries_v1_webhooks_deliveries_get",
    "parameters": [
     {
      "description": "Seulement les livraisons de ce connecteur.",
      "in": "query",
      "name": "connector",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "string"
        },
        {
         "type": "null"
        }
       ],
       "title": "Connector"
      }
     },
     {
      "description": "Nombre maximal de livraisons renvoyées (50 par défaut, plafonné à 500).",
      "in": "query",
      "name": "limit",
      "required": false,
      "schema": {
       "anyOf": [
        {
         "type": "integer"
        },
        {
         "type": "null"
        }
       ],
       "title": "Limit"
      }
     }
    ],
    "responses": {
     "200": {
      "content": {
       "application/json": {
        "schema": {
         "additionalProperties": true,
         "title": "Response Deliveries V1 Webhooks Deliveries Get",
         "type": "object"
        }
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Lister les livraisons de webhooks",
    "tags": [
     "connectors"
    ]
   }
  },
  "/v1/webhooks/{connector}": {
   "post": {
    "description": "Point d'entrée du fournisseur, sans authentification de la plateforme. En-têtes : `X-EAI-Tenant` (slug du tenant, sinon 400 `tenant_required`), `X-EAI-Signature` (`t=<secondes unix>,v1=<HMAC-SHA256 hexadécimal de \"<t>.\" + corps brut>` ; `X-EAI-Timestamp` peut porter l'horodatage), `X-EAI-Delivery` facultatif (identifiant de dédoublonnage).\nÉtapes :\n1. Le connecteur doit exister, être actif et avoir les webhooks activés (404 `connector_not_found`).\n2. Signature vérifiée avec le `webhook_secret` enregistré ou la référence de secret du manifeste (401 `invalid_signature`) ; horodatage à moins de 5 minutes (401 `webhook_replay`) ; une livraison déjà acceptée répond 409 `webhook_duplicate`.\n3. Le corps JSON est contrôlé comme contenu non fiable (injection de prompt bloquée), puis publié sous `connectors.<nom>.webhook`, ce qui peut lancer des runs déclenchés.\nRépond 202 avec `delivery_id`, `event_id` et `triggered`. Chaque livraison est enregistrée et auditée sous `webhook.receive`.",
    "operationId": "webhook_v1_webhooks__connector__post",
    "parameters": [
     {
      "description": "Nom du connecteur auquel le fournisseur envoie.",
      "in": "path",
      "name": "connector",
      "required": true,
      "schema": {
       "title": "Connector",
       "type": "string"
      }
     }
    ],
    "requestBody": {
     "content": {
      "application/octet-stream": {
       "schema": {
        "format": "binary",
        "type": "string"
       }
      }
     },
     "description": "Charge utile brute du fournisseur ; la signature est vérifiée sur ces octets exacts, quel que soit le type de contenu.",
     "required": true
    },
    "responses": {
     "202": {
      "content": {
       "application/json": {
        "schema": {}
       }
      },
      "description": "Successful Response"
     },
     "422": {
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/HTTPValidationError"
        }
       }
      },
      "description": "Validation Error"
     }
    },
    "summary": "Recevoir un webhook de fournisseur",
    "tags": [
     "connectors"
    ]
   }
  }
 },
 "tags": [
  {
   "name": "agents",
   "x-displayName": "Agents",
   "description": "Registre des agents du tenant et exécution côté serveur de leurs **runs** : étapes, budgets, points de reprise, heartbeats, sous-agents, messages d'agent à agent (A2A), sessions de conversation, planifications cron, déclencheurs sur événement et exécutions en sandbox.\n\n**Concepts principaux**\n- **Agent** : un manifeste nommé et versionné (`kind: Agent`). Chaque version est immuable ; le statut de l'agent vaut `active`, `suspended` ou `killed` (kill switch).\n- **Run** : une exécution d'un agent, décrite par l'Execution Contract v1 (état, étapes, limites et consommation de budget, erreurs, politique de nouvelle tentative, trace). États : `pending`, `running`, `waiting_approval`, `waiting_input`, `suspended`, `succeeded`, `failed`, `cancelled`, `timed_out`.\n- **Étape** : une unité de travail déclarée par le runtime (`plan`, `act`, `verify`, `recover`, `model_call`, `tool_call`, `subagent`, `approval`, `checkpoint`). Le Loop Controller décide de l'état suivant du run après chaque étape.\n- **Point de reprise** : un état enregistré d'un run à partir duquel il peut reprendre.\n- **Session** : une conversation aux messages ordonnés, détenue par un utilisateur.\n\n**Parcours type**\n1. Enregistrer l'agent : `POST /v1/agents` avec son manifeste.\n2. Ouvrir une session si le run est conversationnel : `POST /v1/sessions`.\n3. Créer le run : `POST /v1/agents/{agent_id}/runs` (contrôle de politique, budget, délégation).\n4. Le runtime déclare chaque étape (`POST /v1/runs/{run_id}/steps`), envoie des heartbeats et enregistre des points de reprise.\n5. Suivre le run avec `GET /v1/runs/{run_id}/events` (Server-Sent Events) et l'auditer avec `GET /v1/runs/{run_id}/replay`.\n\n**Rôles** : écrire le registre, les planifications et les déclencheurs demande `tenant_admin`, `developer` ou `application_owner` ; exécuter et déclarer demande `tenant_admin`, `developer` ou `agent` (plus le scope `agents:run` pour lancer un run) ; le kill switch demande `tenant_admin` ou `security_officer`. Les lectures demandent un principal de tenant ; les routes d'agent vérifient aussi la relation ReBAC (`viewer`, `editor`, `owner`, `can_invoke`). Un rôle manquant répond 403 `forbidden`."
  },
  {
   "name": "applications",
   "x-displayName": "Applications",
   "description": "**Registre des applications** : les applications métier construites sur la plateforme (applications d'agents, assistants, workflows) et les **agents tiers** qui l'appellent depuis l'extérieur. Une application liste les agents, outils, serveurs MCP, modèles et plugins qu'elle peut utiliser ; ses déploiements par environnement passent par une approbation.\n\n**Concepts principaux**\n- **Application** : `kind` (`agent_app`, `assistant`, `workflow`, `external_agent`), `origin` (`internal` ou `third_party`), environnements, niveau de risque et ressources accordées. Statuts : `draft`, `approved`, `active`, `suspended`, `retired`.\n- **Déploiement** : une version dans un environnement, active seulement une fois son approbation accordée ; l'activer retire le déploiement actif précédent de cet environnement.\n- **Identité d'agent externe** : une clé d'API liée à l'application, avec le rôle `agent`. Chaque appel de passerelle fait avec elle est refusé quand l'application n'est pas active, ou quand l'outil, le serveur MCP ou le modèle n'est pas accordé (motifs permis, par exemple `crm.*`).\n\n**Parcours type**\n1. Enregistrer l'application : `POST /v1/applications`.\n2. Demander un déploiement : `POST /v1/applications/{app_id}/deployments` (ouvre une approbation).\n3. Une fois approuvé, l'activer : `POST .../deployments/{deployment_id}/activate`.\n4. Pour un agent tiers, émettre sa clé : `POST .../external-agents`.\n5. Suspendre ou retirer l'application avec `POST .../status` (utilisé aussi par le kill switch du SOC).\n\n**Rôles** : l'enregistrement et les déploiements demandent `tenant_admin`, `developer` ou `application_owner` ; les changements de statut demandent `tenant_admin` ou `security_officer` ; les identités d'agents externes demandent `tenant_admin`."
  },
  {
   "name": "approvals",
   "x-displayName": "Validations",
   "description": "Humain dans la boucle : demandes de validation des actions sensibles, avec séparation des tâches, quorum et expiration. Les exécutions d'agents qui atteignent un outil soumis à validation, les promotions de modèles, d'agents, d'outils, de compétences, de serveurs MCP ou de plugins, les accès d'urgence et les mandats créent tous leurs demandes de validation ici.\n\nCycle de vie d'une demande : `pending`, puis `approved`, `rejected`, `expired` ou `cancelled`.\n\nParcours type :\n\n1. Un demandeur (personne, agent ou service) appelle `POST /v1/approvals` avec l'action, la ressource, le quorum et les rôles autorisés à valider.\n2. Les valideurs listent les demandes en attente avec `GET /v1/approvals?status=pending`.\n3. Chaque valideur appelle `POST /v1/approvals/{approval_id}/decide`. Un seul `reject` clôt la demande ; elle passe à `approved` dès que le nombre de décisions `approve` atteint le quorum.\n4. Le sujet réagit : une exécution en attente reprend ou échoue, une promotion est appliquée, un accès d'urgence ou un mandat est activé ou refusé.\n\nSéparation des tâches : le demandeur, ainsi que la personne pour le compte de laquelle un agent agit, ne peuvent jamais valider leur propre demande (403 `separation_of_duties`). Chaque valideur décide une seule fois (409 `already_decided`). Une demande en attente dont `expires_at` est dépassé passe à `expired` à la lecture suivante. Chaque étape écrit un événement d'audit (`approval.request`, `approval.decide`, `approval.cancel`, `approval.expire`)."
  },
  {
   "name": "audit",
   "x-displayName": "Audit",
   "description": "Le journal d'audit infalsifiable du tenant.\n\nChaque modification, refus d'autorisation et lecture sensible de la plateforme écrit un événement d'audit dans la même transaction que la modification. Les événements d'un tenant forment une chaîne de hachage : `hash = sha256(prev_hash + événement canonique)`, le premier événement s'enchaînant sur 64 zéros, avec des numéros `sequence` sans trou. Chaque événement enregistre l'acteur, l'action, la ressource, la décision de politique, le résultat, l'identifiant de trace et, sous un contexte d'exécution délégué, la personne pour le compte de laquelle l'appel agit.\n\nParcours type d'un auditeur :\n\n1. `GET /v1/audit/events` recherche dans le journal (acteur, action, ressource, résultat, trace, période).\n2. `GET /v1/audit/correlations` rassemble tout ce qui partage une trace, une exécution ou une session.\n3. `GET /v1/audit/verify` recalcule la chaîne et signale la première rupture, s'il y en a une.\n4. `POST /v1/audit/export` écrit un export signé avec son manifeste dans un stockage à écriture unique (verrouillage d'objet).\n\nLes applications peuvent ajouter leurs propres événements avec `POST /v1/audit/events`, dans l'espace de noms `app.`.\n\nRôles : `auditor`, `security_officer` et `tenant_admin` lisent, vérifient et exportent ; les lectures, recherches et vérifications sont elles-mêmes inscrites dans le journal d'accès aux preuves."
  },
  {
   "name": "auth",
   "x-displayName": "Connexion au portail",
   "description": "Connexion au portail avec un compte local (identifiant et mot de passe), quand l'authentification unique n'est pas utilisée ou pas encore configurée, typiquement pour l'administrateur d'installation.\n\nNotions :\n\n- **Compte local** : un identifiant et un mot de passe conservé haché avec scrypt, rattaché à un tenant avec des rôles et une habilitation. Le premier vient de l'installation (administrateur initial) et doit changer son mot de passe à la première connexion.\n- **Session du portail** : un jeton porteur `eais_<prefix>_<secret>` renvoyé une seule fois par la connexion ; seule son empreinte est conservée. Elle expire après la durée configurée (8 heures par défaut).\n\nParcours type :\n\n1. `POST /v1/auth/login` avec l'identifiant et le mot de passe renvoie `token` et `must_change_password`.\n2. Envoyez `Authorization: Bearer <token>` dans les appels suivants.\n3. Tant que `must_change_password` est vrai, la session n'atteint que `POST /v1/auth/password`, `POST /v1/auth/logout`, `GET /v1/me` et `GET /v1/identity/whoami` ; toute autre route répond 403 `password_change_required`.\n4. `POST /v1/auth/password` définit un nouveau mot de passe ; `POST /v1/auth/logout` met fin à la session.\n\nProtections : les mauvais mots de passe verrouillent le compte après le nombre d'échecs consécutifs configuré (5 par défaut, pour 15 minutes) et comptent dans le budget d'échecs d'authentification de l'IP du client. Chaque résultat, réussite ou échec, écrit un événement d'audit (`auth.login`, `auth.password.change`, `auth.logout`). Tout le tag répond 403 `local_auth_disabled` quand les comptes locaux sont désactivés."
  },
  {
   "name": "authz",
   "x-displayName": "Autorisation par relations",
   "description": "Contrôle d'accès fondé sur les relations (ReBAC) : vérifier et gérer les tuples d'autorisation du tenant. Le moteur est OpenFGA quand il est configuré, sinon la table de tuples intégrée, avec les mêmes règles.\n\nUn tuple dit qu'un **sujet** a une **relation** sur un **objet** :\n\n- sujet : `user:alice`, `group:fin#member` (les membres d'un groupe) ou `user:*` (tout le monde) ;\n- relation : un nom en minuscules comme `viewer`, `editor`, `owner` ou `can_assume` ;\n- objet : `<type>:<id>`, par exemple `project:42` ou `service_identity:7`.\n\nLes relations sont héritées du tenant au projet puis à la ressource. Certaines relations, comme `can_assume`, ne sont accordées qu'à des sujets nommés : un sujet générique est refusé (422 `authz_wildcard_not_allowed`).\n\nParcours type :\n\n1. `POST /v1/authz/tuples` accorde une relation (administrateur du tenant ou responsable sécurité).\n2. `POST /v1/authz/check` indique si un sujet détient une relation, éventuellement avec des tuples contextuels jamais enregistrés.\n3. `DELETE /v1/authz/tuples` la retire.\n\nLe moteur refuse par défaut : quand il ne peut pas répondre, la vérification vaut refus et l'API répond 502 `authz_backend_error`. Les vérifications, écritures et suppressions écrivent les événements d'audit `authz.check`, `authz.write` et `authz.delete`."
  },
  {
   "name": "channels",
   "x-displayName": "Canaux",
   "description": "**Channel Gateway** : les points d'entrée où les utilisateurs finaux parlent aux agents (web, mobile, e-mail, Slack, Teams, WhatsApp, SMS, voix, API, et les canaux Navin Discord, Telegram, Matrix, Mattermost et Signal). Un message entrant devient un message de session et lance un run de l'agent du canal, dans des limites de taille, de débit et de sessions et avec DLP.\n\n**Concepts principaux**\n- **Manifeste de canal** (`kind: Channel`) : `type`, `agent_ref`, `auth` (`oidc`, `api_key`, `signed_webhook` avec une référence de secret, `anonymous`), `limits` (`max_message_bytes`, `rate_per_minute`, `max_concurrent_sessions`, `max_session_minutes`), `dlp` (mode `inbound`), `sessions` (`ttl_minutes`), `connection` facultative vers un adaptateur Navin.\n- **Session** : une par canal, utilisateur externe et conversation ; réutilisée tant qu'elle est ouverte.\n\n**Parcours type**\n1. Créer le canal : `POST /v1/channels` (ou en installer un depuis la bibliothèque Navin).\n2. Le pont de canal ou le fournisseur envoie chaque message : `POST /v1/channels/{name}/inbound`.\n3. Le run de l'agent répond dans la session ; suivez-le avec les routes des runs et des sessions.\n4. Désactivez le canal avec `PATCH /v1/channels/{name}` si besoin.\n\n**Rôles** : la création et la modification demandent `tenant_admin`, `developer` ou `application_owner` ; la suppression demande `tenant_admin` ; les messages entrants demandent `tenant_admin`, `developer` ou `agent`. Les lectures demandent un principal du tenant."
  },
  {
   "name": "compliance",
   "x-displayName": "Instances de l'agent de conformité et d'audit",
   "description": "**Activation et cycle de vie des instances du Navin Enterprise Compliance & Audit Agent** (ADR-0177, section 8), copie adaptée du modèle de l'agent RAG. Le Control Plane porte l'enregistrement d'activation de chaque instance (son statut, son secteur et son pack, son modèle de déploiement, ses versions et les ressources qu'elle possède), car dans le modèle dédié le runtime de conformité n'existe pas encore au moment de l'activation. Le runtime de conformité lit son activation avec `GET /v1/compliance/instances/{instance_id}`.\n\n**Secteur et pack** : `sector` vaut `generic`, `banking`, `insurance` ou `industry` ; `pack` est le pack métier chargé au-dessus du noyau générique (`banking`, `insurance` ou `industry`) ou null pour le noyau seul. Les deux sont des réglages de l'instance, transmis au Navin Manager avec la demande d'installation d'un runtime dédié.\n\n**Statuts** : `draft`, `validating`, `provisioning`, `starting`, `ready`, `degraded`, `failed`, `stopping`, `stopped`, `updating`, `deleting`, `deleted`. Le statut ne dit `ready` qu'après le succès des étapes `health` et `smoke` ; le worker suit ensuite la santé réelle (`ready` et `degraded`).\n\n**Étapes** (états `pending`, `running`, `done`, `failed`, `skipped`) : `validation`, `resources` (application gouvernée et agent de l'instance), `database`, `object_storage` (préfixe de clés dans le profil de stockage des documents), `qdrant` (base de connaissances avec `vector_store` qdrant), `memory` (politique de mémoire de l'agent), `security` (tuples OpenFGA du propriétaire, clé d'API du runtime conservée comme référence de secret), `deployment` (runtime partagé déclaré par `EAI_COMPLIANCE_AGENT_SHARED_RUNTIME_URL`, ou le Navin Manager par la passerelle platform_manager pour une instance dédiée), `health`, `smoke` (disponibilité et document OpenAPI du runtime, requête sur la base de connaissances), `activation`. Chaque étape garde ses tentatives et ses journaux ; une étape en échec se nomme avec sa raison dans `last_error`.\n\n**Transitions** : chaque action n'est acceptée que depuis les statuts de sa ligne dans la table des transitions, sinon 409 `compliance_transition_invalid` (audité comme refus). Répéter une action faite ou en cours renvoie l'enregistrement courant avec `changed: false`. Relancer l'opération en échec reprend la même exécution à son étape en échec ; les étapes déjà faites ne sont jamais rejouées. Le provisionnement, le démarrage, l'arrêt, le redémarrage, la mise à niveau et la suppression sont des opérations longues (`Operation-Id`, `Idempotency-Key`) exécutées par la tâche du worker `compliance_lifecycle`.\n\n**Modèles de déploiement** : `shared` fait pointer l'instance vers le runtime partagé existant ; démarrer et arrêter ne changent que le statut (et l'application gouvernée). `dedicated` demande au Navin Manager d'installer une instance nommée du service du catalogue `navin-compliance-agent` ; tant que ce service n'est pas publié dans le catalogue, l'étape de déploiement échoue avec la raison `service_not_published`.\n\n**Rôles** : les écritures demandent un administrateur du tenant ou un propriétaire du projet de l'instance (403 `forbidden`) ; les lectures demandent un principal du tenant. L'instance d'un autre tenant répond 404 `compliance_instance_not_found`."
  },
  {
   "name": "connectors",
   "x-displayName": "Connecteurs",
   "description": "**Connector Hub** : registre des connexions aux systèmes externes (applications SaaS, API, sources de données), leurs identifiants conservés côté serveur, les exécutions de synchronisation, le **Credential Broker** qui remet des baux de courte durée à la place des secrets, et la **Webhook Gateway** qui reçoit les événements signés des fournisseurs.\n\n**Concepts principaux**\n- **Manifeste de connecteur** (`kind: Connector`) : URL de base, `auth` (type, URL de jeton pour OAuth2, mode de délégation), `resources` avec leurs opérations, `scopes`, `sync` facultatif (mode, incrémental) et `webhooks` (signature obligatoire).\n- **Identifiants** : conservés dans le magasin de secrets par type (`oauth2_client`, `oauth2_code`, `api_key`, `service_account`, `basic`, `webhook_secret`) ; seuls leur type et les noms de champs sont renvoyés.\n- **Bail** : un jeton opaque (`token_type: eai_lease`) valable pour certains scopes et 600 secondes par défaut (30 à 3600), échangé par la plateforme pour appeler le connecteur. Désactiver un connecteur révoque ses baux.\n- **Webhook** : `POST /v1/webhooks/{connector}` vérifie une signature HMAC-SHA256 sur l'horodatage et le corps brut, refuse les rejeux et les doublons, puis publie l'événement `connectors.<nom>.webhook`.\n\n**Parcours type**\n1. Enregistrer le connecteur : `POST /v1/connectors`.\n2. Enregistrer ses identifiants : `POST /v1/connectors/{name}/credentials`.\n3. Le tester : `POST /v1/connectors/{name}/test`.\n4. Lancer une synchronisation ou laisser les agents obtenir des baux : `POST .../sync`, `POST /v1/credentials/broker/lease`.\n\n**Rôles** : l'enregistrement, la modification et le test demandent `tenant_admin`, `developer` ou `application_owner` ; les identifiants demandent `tenant_admin` ou `security_officer` ; la suppression demande `tenant_admin` ; la synchronisation et les baux demandent `tenant_admin`, `developer` ou `agent`. Le récepteur de webhooks est appelé par le fournisseur, authentifié par sa seule signature."
  },
  {
   "name": "context",
   "x-displayName": "Contexte d'exécution",
   "description": "**Context Authority** : émet, vérifie et révoque des documents **ExecutionContext** signés, la preuve qu'un appel est fait par un principal, un run d'agent et une chaîne de délégation donnés, pour une audience donnée et pour peu de temps.\n\n**Concepts principaux**\n- **Jeton** : sérialisation compacte JWS (`typ: eai-ctx+jws`), en-tête et charge en JSON canonique RFC 8785, signés en ES256 ou EdDSA. `kid` est l'empreinte RFC 7638 de la clé publique. Revendications : `iss`, `aud` (liste), `iat`, `exp`, `jti` (l'identifiant du contexte), `sub`, `tid` et `ctx`, le document Execution Context v1.\n- **Durée de vie** : 900 secondes par défaut, 3600 au plus sauf configuration différente de la plateforme.\n- **Clés** : renouvelées par la plateforme ; une clé retirée continue de vérifier les contextes qu'elle a signés jusqu'à leur expiration. Les clés publiques sont publiées sous forme de JWKS.\n- **Échec fermé** : une clé absente ou un registre de clés injoignable répond 503 `context_authority_unavailable` ; l'autorité ne signe ni n'accepte jamais un contexte qu'elle ne peut pas vérifier.\n\n**Parcours type**\n1. Un runtime ou un service demande un contexte : `POST /v1/context/issue` (éventuellement pour un run ou une délégation).\n2. Il envoie le jeton au service destinataire, qui appelle `POST /v1/context/verify` (ou vérifie la signature hors ligne avec `GET /v1/context/jwks`).\n3. Le contexte peut être révoqué avant son expiration : `POST /v1/context/{context_id}/revoke` ; révoquer une délégation révoque ses contextes.\n\n**Rôles** : émettre, vérifier et révoquer demandent un principal du tenant (révoquer le contexte d'un autre principal demande `tenant_admin` ou `security_officer`) ; le registre des clés est lisible par `security_officer` ou `auditor` ; révoquer une clé de signature demande `platform_admin`."
  },
  {
   "name": "contracts",
   "x-displayName": "Contrats et manifestes",
   "description": "Schémas JSON publiés (contrats) des documents de la plateforme, et manifestes déclaratifs qui créent ou mettent à jour des objets à partir d'un document, dans l'esprit de l'infrastructure as code.\n\n- **Contrat** : un schéma JSON (draft 2020-12) comme `agent-manifest` ou `model-manifest`. Utilisez-les pour valider les documents avant de les envoyer.\n- **Manifeste** : un document avec `kind` (`Agent`, `Model`, `Tool`, `Skill`, `Plugin`, `Connector` ou `Channel`), `metadata` (`name`, `version`, et en option `tenant`, `environment`, `labels`) et la spécification de l'objet.\n\nParcours type :\n\n1. `GET /v1/contracts` liste les contrats ; `GET /v1/contracts/{name}` renvoie un schéma.\n2. `POST /v1/contracts/{name}/validate` vérifie un document sans l'enregistrer.\n3. `POST /v1/manifests/apply` valide, vérifie la politique et crée ou met à jour l'objet.\n4. `GET /v1/manifests/{kind}/{name}` exporte le manifeste courant, par exemple pour le conserver dans Git.\n\nTout appelant authentifié lit et valide les contrats ; appliquer exige `tenant_admin` ou `developer` et écrit l'événement d'audit `manifest.apply`."
  },
  {
   "name": "delegation",
   "x-displayName": "Délégation, mandats et accès d'urgence",
   "description": "Comment un agent, une application ou un sous-agent agit pour le compte de quelqu'un, et comment des droits exceptionnels sont accordés.\n\n- **Délégation** : un principal (utilisateur ou application) délègue une partie de ses droits à un délégataire (`agent:<name>`, `app:<id>`, `service:<name>` ou `subagent:<name>`) : portées, audiences, restrictions de ressources, plafond de classification, expiration et profondeur maximale. Une délégation n'accorde jamais plus que ce que détient son délégant. Une délégation enfant (restriction) est toujours couverte par son parent. Révoquer une délégation révoque tous ses descendants et leurs contextes d'exécution.\n- **Mandat** : les exécutions planifiées et autonomes agissent sous un mandat explicite et approuvé, rattaché à une identité de service, jamais sous une identité humaine inventée. Un mandat reste en attente jusqu'à ce qu'un administrateur du tenant ou un responsable sécurité autre que le demandeur l'approuve.\n- **Accès d'urgence** (break glass) : une élévation exceptionnelle et limitée dans le temps d'une personne (rôles et portées), active seulement après l'approbation de deux approbateurs distincts, tous deux différents du demandeur et du bénéficiaire. Elle expire d'elle-même et peut être révoquée à tout moment.\n- **Autorisation** : la permission effective d'une action est utilisateur ET agent ET délégation ET ressource ET relation ET politique ; chaque couche doit autoriser.\n\nParcours type d'un agent agissant pour un utilisateur :\n\n1. L'utilisateur crée une délégation vers `agent:<name>` avec les portées nécessaires à la tâche (`POST /v1/delegations`), ou la plateforme en crée une implicite au démarrage d'une exécution.\n2. L'agent la restreint pour un sous-agent (`POST /v1/delegations/{delegation_id}/restrict`).\n3. Avant chaque action sensible, l'environnement d'exécution appelle `POST /v1/delegations/authorize`.\n4. L'utilisateur ou un administrateur la révoque (`POST /v1/delegations/{delegation_id}/revoke`), avec effet à l'action suivante.\n\nChaque étape écrit un événement d'audit (`delegation.*`, `mandate.*`, `break_glass.*`)."
  },
  {
   "name": "developer",
   "x-displayName": "Développeur",
   "description": "Kit du développeur : tout ce que la plateforme fournit à un développeur pour construire une application, un agent ou un projet, en une seule réponse et sans aucun secret.\n\nLe kit compte une ligne par ressource de la plateforme : passerelle LLM (LiteLLM), identité (Keycloak OIDC), permissions (OpenFGA et OPA), protection des données (DLP et Presidio), traçabilité IA (Opik et OTLP), outils métier (Secure MCP Gateway), accès aux documents (Qdrant et RAG sécurisé), SDK et contexte utilisateur, tests de qualité et de sécurité (Ragas, Promptfoo, garak). Chaque ligne porte ses valeurs (adresses, identifiants, modèles autorisés, outils, collections), son état (`ready`, `not_configured`, `service_not_installed`) et l'action qui la corrige (message en anglais et en français, page du portail).\n\n**Parcours type**\n\n1. Lisez le kit avec `GET /v1/developer/kit?application_id=...` (ou `agent_id`, ou `project`).\n2. Corrigez chaque ligne qui n'est pas `ready` avec son action.\n3. Créez la clé de projet sur la page de gestion LLM (affichée une seule fois) et placez-la dans `EAI_API_KEY`.\n4. Copiez les entrées `env` dans le fichier d'environnement de l'application.\n\n**Rôles** : `developer`, `application_owner` ou `tenant_admin`, dans un tenant. Lecture seule ; la sécurité au niveau des lignes du tenant s'applique."
  },
  {
   "name": "evaluation",
   "x-displayName": "Évaluation et red team",
   "description": "Qualité des systèmes d'IA du tenant : suites d'évaluation, exécutions, barrières de qualité avant promotion, comparaisons A/B avec une référence et campagnes de red team.\n\n- **Suite** : un jeu de cas, une cible (modèle, agent, prompt, chaîne RAG, compétence ou outil), un moteur (`ragas`, `promptfoo`, `garak` ou `custom`), un type (`quality`, `regression`, `red_team`, `safety`, `cost`, `latency`) et des métriques avec leurs seuils. Une suite bloquante participe aux barrières de qualité ; une suite avec `run_on_promotion` s'exécute automatiquement à la promotion de sa cible.\n- **Exécution** : un passage d'une suite ; chaque cas est envoyé à la cible (ou utilise les sorties fournies), noté, et l'exécution finit `passed`, `failed` ou `error`, avec son coût.\n- **Barrière** : les dernières exécutions des suites bloquantes d'une cible doivent toutes avoir réussi.\n- **Comparaison** : la même suite sur une référence et sur un candidat, avec les écarts par métrique et un verdict.\n- **Campagne de red team** : des sondes adverses (injection de prompt, contournement, fuites) contre une cible, avec le taux de réussite des attaques.\n\nParcours type :\n\n1. `POST /v1/evaluations/suites` avec les cas et les métriques.\n2. `POST /v1/evaluations/runs` pour l'exécuter ; suivez-la avec l'en-tête `Operation-Id` si besoin.\n3. `POST /v1/evaluations/gates/check` avant une promotion : 409 `gate_failed` la bloque.\n4. `POST /v1/evaluations/comparisons` pour comparer une nouvelle version de modèle à l'actuelle ; `POST /v1/redteam/runs` pour tester sa résistance.\n\nRôles : tout membre du tenant lit les suites, les exécutions et les campagnes de red team ; `tenant_admin`, `developer`, `security_officer` ou `ai_governance_officer` créent suites, exécutions et campagnes (`agent` peut aussi lancer des exécutions) ; les comparaisons exigent `tenant_admin`, `developer` ou `security_officer` pour être créées et ces rôles, `auditor`, `viewer` ou `finops` pour être lues. Les exécutions, barrières, comparaisons et campagnes écrivent des événements d'audit."
  },
  {
   "name": "evidence",
   "x-displayName": "Preuves",
   "description": "Couche de preuve du journal d'audit : sceaux signés, paquets vérifiables hors ligne, moniteur d'intégrité, Audit Gateway, charges utiles chiffrées, accès audités, gels juridiques et preuves de purge, et configuration d'exécution des exécutions d'agents.\n\n- **Sceau** : une signature Ed25519 sur une plage de la chaîne d'audit du tenant (racine de Merkle des événements, premières et dernières empreintes, sceau précédent). Les sceaux sont écrits par une tâche planifiée et à la demande ; sans clé de signature, rien n'est scellé (refus par défaut).\n- **Paquet** : événements, sceaux et clés publiques avec un manifeste signé, vérifiable hors ligne avec `python -m eai.domains.evidence.verify`.\n- **Alertes d'intégrité** : chaîne rompue, trou de séquence, sceau invalide, événement absent de la boîte d'envoi, décalage d'horloge. Le moniteur ne répare jamais rien.\n- **Audit Gateway** : livraison de chaque événement, dans l'ordre et sans échantillonnage, aux cibles SIEM (HTTP, NATS).\n- **Charge utile** : champs sensibles d'un événement, chiffrés avec une clé d'enveloppe ; la chaîne n'en garde que l'empreinte. Seuls `auditor` et `security_officer` les déchiffrent, jamais `platform_admin` par héritage.\n- **Journal d'accès** : chaque lecture, recherche, vérification, déchiffrement et export du journal est lui-même enregistré.\n- **Gel juridique** : fige les charges utiles d'un dossier et d'une période ; sa levée exige deux approbateurs distincts.\n- **Configuration d'exécution** : versions du modèle, du prompt, des outils, des compétences et des plugins utilisés par une exécution.\n\nParcours type d'un audit :\n\n1. `GET /v1/evidence/verify` vérifie les chaînes et les sceaux ; `GET /v1/evidence/alerts` liste les problèmes ouverts.\n2. `GET /v1/evidence/bundle` exporte ce qu'un auditeur externe vérifie hors ligne.\n3. `POST /v1/evidence/holds` gèle un dossier avant une enquête.\n\nLes lecteurs sont `auditor`, `security_officer` ou `tenant_admin` sauf mention contraire."
  },
  {
   "name": "file-security",
   "x-displayName": "Sécurité des fichiers",
   "description": "Contrôles antivirus et de contenu de chaque fichier qui entre dans la plateforme, avant qu'un analyseur, un modèle ou un magasin ne le voie.\n\n**Chaîne de traitement** : envoi, quarantaine, ClamAV, YARA-X, validation, Docling, puis RAG ou IA. Les documents de connaissance, les envois de médias, les entrées multimodales, les transcriptions audio, les paquets de la marketplace et les médias produits par la plateforme y passent tous ; `POST /v1/file-security/scans` la lance à la demande sans ingérer le fichier.\n\n**Notions**\n\n- Un **enregistrement d'analyse** est écrit d'abord `quarantined`, puis `released` (verdict `clean`) ou `blocked` (verdict `malware`, `suspicious`, `unscannable`, `policy_violation` ou `scan_error`), avec les raisons, le type détecté, le SHA-256 et la réponse de chaque moteur.\n- Des contrôles intégrés appliquent la **politique** du tenant : taille maximale, types autorisés, archives, incohérence de type, macros Office. Les **règles verrouillées** ne changent jamais : chaque fichier est analysé avant l'ingestion, les deux moteurs sont requis en mode enforce, la chaîne échoue en position fermée, les logiciels malveillants et les fichiers impossibles à inspecter sont toujours bloqués.\n- Un fichier bloqué peut garder un **échantillon** chiffré pendant `quarantine_retention_days`, pour l'analyser à nouveau plus tard ; l'envoi d'origine n'est jamais libéré.\n- Le mode est `enforce` sauf si la plateforme est configurée en mode `dev`, où un moteur non configuré est ignoré.\n\n**Parcours type**\n\n1. Vérifiez les moteurs avec `GET /v1/file-security/status`.\n2. Durcissez les règles du tenant avec `PUT /v1/file-security/policy`.\n3. Suivez les fichiers bloqués avec `GET /v1/file-security/scans?status=blocked` et `GET /v1/file-security/stats`.\n4. Après une mise à jour des signatures, analysez à nouveau un échantillon avec `POST /v1/file-security/scans/{scan_id}/rescan`, ou purgez-le avec `DELETE /v1/file-security/scans/{scan_id}/sample`.\n\n**Rôles** : les lecteurs sont `tenant_admin`, `security_officer` et `auditor` (ils voient toutes les analyses du tenant ; tout autre principal ne voit que les siennes) ; les changements de politique, les nouvelles analyses et les purges demandent `security_officer` ou `tenant_admin`. Chaque analyse, changement de politique et purge est audité."
  },
  {
   "name": "finops",
   "x-displayName": "FinOps",
   "description": "Gestion des coûts de l'usage de l'IA : grilles tarifaires, événements d'usage, estimations, quotas et réservations, répartition du coût des GPU, rapprochement de la facturation et alertes.\n\n**Notions**\n\n- Les **grilles tarifaires** (price cards) sont des révisions de prix datées par fournisseur, motif de modèle, région et type d'usage ; une nouvelle grille clôt la précédente, si bien qu'un usage passé est toujours valorisé au prix de son époque. Les **prix des médias** font de même pour les secondes audio, les images, les secondes vidéo, les secondes GPU et le stockage ; sans prix, le coût vaut `null` avec `price_unknown`, jamais zéro.\n- Les **événements d'usage** (Usage Event v1) enregistrent ce qui a été consommé ; la plateforme les valorise (coût mesuré) dans la devise du tenant, avec des instantanés des taux de change. L'ingestion est idempotente sur `event_id`.\n- Les **quotas** limitent le coût ou les jetons par tenant, projet, agent, utilisateur, modèle ou fournisseur, par requête, exécution, heure, jour ou mois, en mode `hard` (refus en 429 `quota_exceeded`) ou `soft` (alerte seulement). Les appelants **réservent** le pire cas avant un appel, puis **valident** (commit) le montant mesuré ou **libèrent** la réservation ; une réservation ouverte expire au bout de sa durée de vie.\n- Le **rapprochement** importe les exports de facturation des fournisseurs (OpenAI, Anthropic, AWS CUR, Azure, GCP, CSV générique), répartit les montants facturés sur les événements d'usage et présente l'écart. Les **alertes** se déclenchent aux seuils du budget et des quotas (50, 80 et 100 pour cent par défaut) et sur l'écart de facturation.\n\n**Parcours type**\n\n1. Fixez la devise et le budget mensuel du tenant avec `PUT /v1/finops/settings`, les taux de change et les grilles tarifaires.\n2. Créez les quotas avec `POST /v1/quotas`.\n3. Avant chaque appel, réservez avec `POST /v1/quotas/reserve` ; après, validez le coût mesuré et envoyez l'événement d'usage (la passerelle de modèles fait tout cela d'elle-même).\n4. Suivez la dépense avec `GET /v1/finops/summary` et `GET /v1/finops/alerts`, et rapprochez les factures chaque mois.\n\n**Rôles** : les lecteurs sont `finops`, `tenant_admin`, `auditor`, `developer`, `viewer` et `security_officer` ; les modifications demandent `finops` ou `tenant_admin` ; l'ingestion d'usage et les réservations demandent `finops`, `tenant_admin`, `agent` ou `developer` ; les échantillons GPU et les politiques de répartition demandent `platform_admin`. `platform_admin` passe tous les contrôles de rôle. La plupart des routes demandent un principal de tenant (403 `tenant_required`). Chaque modification est auditée."
  },
  {
   "name": "governance",
   "x-displayName": "Gouvernance",
   "description": "Registre de gouvernance : une vue agrégée des actifs d'IA du tenant, pour savoir ce qui existe, qui en est responsable et quel est son niveau de risque.\n\nLe registre rassemble les agents, les modèles, les fournisseurs LLM, les outils, les skills, les plugins et les connecteurs. Chaque élément donne son type, son identifiant, son nom, sa version, son responsable, son statut, son niveau de risque, sa portée (`platform` pour les éléments partagés, `tenant` pour ceux du tenant) et sa dernière mise à jour. Le niveau de risque est la catégorie AI Act de l'UE de la dernière évaluation des risques de l'élément, à défaut le niveau de risque déclaré sur l'élément ou dans sa spec.\n\n**Parcours type**\n\n1. Lisez `GET /v1/governance/registry` pour l'inventaire complet, ou filtrez avec `kind`.\n2. Servez-vous du `summary` pour trouver les éléments sans responsable ou sans évaluation des risques (`unassessed`).\n3. Évaluez-les ou attribuez-les dans les espaces Risques et registres.\n\n**Accès** : tout principal authentifié d'un tenant ; les lignes sont filtrées par la sécurité au niveau des lignes du tenant."
  },
  {
   "name": "identity",
   "x-displayName": "Identité",
   "description": "Qui appelle l'API et avec quels droits. Ce tag couvre l'identité de l'appelant (`whoami`), les clés d'API des scripts et des services, les identités de service (charges de travail SPIFFE) qu'un utilisateur peut endosser temporairement, et les correspondances de groupes de l'annuaire qui transforment les groupes Keycloak en appartenances à des projets et en quotas.\n\nNotions :\n\n- **Clé d'API** : identifiant `eai_<prefix>_<secret>` rattaché au tenant, avec ses propres rôles, portées, habilitation et, en option, un projet et un environnement. Le secret n'est renvoyé qu'une fois, à la création ou à la rotation ; seule son empreinte est conservée.\n- **Identité de service** : identité de charge de travail nommée (`spiffe://...`) avec des rôles et des portées. L'endosser donne un contexte d'exécution signé et de courte durée, jamais plus de droits que ceux de l'appelant.\n- **Correspondance de groupe** : un groupe de l'annuaire (revendication `groups` ou `entra_groups`) associé à un projet, un rôle et éventuellement un profil de quota. Les correspondances sont appliquées à chaque connexion OIDC et retirées quand le groupe quitte le jeton ; les appartenances accordées à la main ne sont jamais modifiées.\n- **Profil de quota** : plafond de coût et de jetons réutilisable, rattaché aux correspondances de groupes.\n\nParcours type pour une chaîne CI :\n\n1. Un développeur appelle `POST /v1/api-keys` avec les portées nécessaires à la chaîne et une date d'expiration.\n2. La chaîne range la `key` renvoyée dans son magasin de secrets et l'envoie dans `X-API-Key`.\n3. Avant l'expiration, `POST /v1/api-keys/{key_id}/rotate` avec un délai de grâce émet une nouvelle clé pendant que l'ancienne fonctionne encore.\n4. `DELETE /v1/api-keys/{key_id}` révoque une clé immédiatement.\n\nRôles : `tenant_admin`, `security_officer` et `developer` gèrent les clés d'API (les développeurs uniquement les leurs) ; `tenant_admin` et `security_officer` gèrent les identités de service ; `tenant_admin` gère les correspondances de groupes et les profils de quota, que `security_officer` et `auditor` peuvent lire. Chaque modification écrit un événement d'audit (`api_key.*`, `service_identity.*`, `identity.group_mapping.*`, `identity.quota_profile.*`)."
  },
  {
   "name": "incidents",
   "x-displayName": "Incidents",
   "description": "Incidents de sécurité et d'exploitation du tenant : gravité, statut, chronologie, actions de confinement, alertes Alertmanager et transmission au SIEM.\n\nCycle de vie : `open`, `investigating`, `contained`, `resolved`, `closed`. Gravité : `sev1` (la plus grave) à `sev4`.\n\nParcours type :\n\n1. Un incident est ouvert à la main (`POST /v1/incidents`) ou par une alerte Alertmanager (`POST /v1/incidents/alerts`).\n2. Un intervenant change son statut, sa gravité ou son responsable et ajoute des notes (`PATCH /v1/incidents/{incident_id}`).\n3. L'intervenant le confine par une action (`POST /v1/incidents/{incident_id}/actions`) : suspendre, reprendre ou arrêter un agent, suspendre ou désactiver un modèle, désactiver un fournisseur, faire tourner ou révoquer une clé. La première action exécutée fait passer un incident `open` ou `investigating` à `contained`.\n4. L'incident est résolu, puis clôturé.\n\nChaque changement est ajouté à la chronologie, transmis au SIEM configuré et inscrit comme événement d'audit (`incident.create`, `incident.update`, `incident.action.<action>`, `incident.alert.*`).\n\nRôles : tout membre du tenant lit les incidents ; `tenant_admin`, `security_officer`, `developer` et `agent` les ouvrent ; `tenant_admin` et `security_officer` interviennent."
  },
  {
   "name": "knowledge",
   "x-displayName": "Bases de connaissances",
   "description": "Bases de connaissances pour la génération augmentée par récupération (RAG) : les documents sont analysés, extraits, contrôlés pour les données personnelles et l'empoisonnement, découpés, vectorisés via le Model Gateway et recherchés avec des filtres d'ACL et d'habilitation.\n\n**Concepts principaux**\n- **Base de connaissances** : une collection nommée avec un modèle d'embedding du Model Registry et sa dimension, un réglage de découpage, une classification et une ACL (`readers`, `writers`). Deux espaces d'embedding ne sont jamais mélangés : changer de modèle est un travail de **réindexation**.\n- **Document** : un texte versionné (le même `uri` remplace la version précédente) avec sa classification, ses lecteurs, son résultat DLP et sa provenance. Un document signalé par le contrôle d'empoisonnement est mis en **quarantaine** jusqu'à ce qu'un data steward le libère ou le rejette.\n- **Requête** : recherche vectorielle ou hybride, filtrée par les identités et l'habilitation de l'appelant, éventuellement réordonnée, renvoyée avec des citations et un run de lignage.\n\n**Parcours type**\n1. Créer la base : `POST /v1/knowledge-bases`.\n2. Ajouter des documents : `POST /v1/knowledge-bases/{kb_id}/documents`.\n3. Traiter la file de quarantaine si besoin : `GET .../quarantine`, puis `POST .../documents/{doc_id}/review`.\n4. Interroger : `POST /v1/knowledge-bases/{kb_id}/query`.\n5. Passer à un autre modèle d'embedding : `POST .../reindex`, suivre avec `GET .../reindex-jobs`.\n\n**Accès** : la création et la réindexation demandent `tenant_admin` ou `developer` ; les routes sur une base vérifient aussi la relation ReBAC (`viewer`, `editor`, `can_invoke`, `owner`) et l'ACL de la base. Les lecteurs sont comparés aux identités de l'appelant (`*`, `user:<id>`, `agent:<id>`, `role:<rôle>`, `group:<groupe>`) ; les administrateurs n'ont aucun accès implicite en lecture. La revue de quarantaine demande `tenant_admin`, `security_officer` ou `data_steward`."
  },
  {
   "name": "licensing",
   "x-displayName": "Licences",
   "description": "Licence de l'installation : activation d'une licence signée, état courant et historique.\n\nUne licence est un jeton JWS compact (algorithme EdDSA, en-tête `kid`) signé par l'éditeur. Elle est vérifiée localement avec les clés publiques de confiance de l'éditeur définies dans la configuration (`EAI_LICENSE_PUBLIC_KEYS`) : aucun appel réseau n'est fait, la même vérification sert donc une installation isolée du réseau. Une licence ne porte jamais de prix, de siège, de quota ni de limite, et sa durée est de 12 ou 36 mois.\n\n**États** de l'installation, calculés à partir des licences enregistrées et de l'horloge :\n\n- `unlicensed` : aucune licence n'a encore commencé ;\n- `valid` : la licence court et sa fin est à plus de `EAI_LICENSE_EXPIRING_SOON_DAYS` jours (30 par défaut) ;\n- `expiring_soon` : la fin est plus proche que cela ;\n- `grace` : la fin est passée mais la période de grâce de la licence court encore ;\n- `expired` : la période de grâce est terminée.\n\nSeul l'état `expired` restreint quelque chose, et seulement les nouvelles mises à niveau de la distribution et les nouvelles installations depuis la marketplace (403 `license_expired`). La sécurité, l'audit, la reprise, le kill switch, les agents en cours, les nouvelles exécutions et la passerelle de modèles ne sont jamais arrêtés par l'état de la licence.\n\n**Parcours type**\n\n1. Lisez l'état avec `GET /v1/license` (tout utilisateur authentifié ; le portail affiche son `banner`).\n2. Un administrateur de la plateforme active la licence reçue de l'éditeur avec `POST /v1/license/activations`. La première licence lie l'installation à son groupe d'installations.\n3. Avant l'échéance, activez le renouvellement de la même façon : il prend effet tout seul à sa date `valid_from`, et aucun bandeau d'expiration ne s'affiche tant qu'il suit sans interruption.\n4. Consultez les activations et les refus avec `GET /v1/license/history`.\n\n**Rôles** : lire l'état demande un principal authentifié ; l'activation et l'historique demandent `platform_admin`. Chaque appel prend aussi une décision de politique (`license.read`, `license.activate`, `license.history.read`) inscrite dans la chaîne d'audit ; un refus répond 403 `policy_denied`."
  },
  {
   "name": "lineage",
   "x-displayName": "Lignage",
   "description": "Lignage des données du tenant, alimenté par des événements OpenLineage : quels traitements lisent quels jeux de données et en produisent d'autres.\n\nLe graphe a trois sortes de nœuds : les **traitements** (`job:<namespace>:<name>`), les **exécutions** d'un traitement (`run:<runId>`) et les **jeux de données** (`dataset:<namespace>:<name>`). Les arêtes sont `instance_of` (exécution vers traitement), `input_to` (jeu de données vers traitement) et `produces` (traitement vers jeu de données).\n\nParcours type :\n\n1. Les chaînes de traitement, les connecteurs et les agents envoient des RunEvents OpenLineage à `POST /v1/lineage/events`.\n2. `GET /v1/lineage/graph` renvoie le graphe autour d'un nœud, en amont, en aval ou les deux.\n3. `GET /v1/lineage/datasets/{dataset_id}` répond aux questions d'impact sur un jeu de données : qui le produit, qui le consomme.\n\nOuvert à tout membre du tenant. L'ingestion écrit l'événement d'audit `lineage.ingest`."
  },
  {
   "name": "llm",
   "x-displayName": "LLM et passerelle de modèles",
   "description": "Fournisseurs de modèles, registre des modèles, règles de routage et passerelle de modèles (Model Gateway) : le seul chemin par lequel agents et applications appellent les modèles.\n\n**Notions**\n\n- Un **fournisseur** est un service de modèles configuré (OpenAI, Anthropic, Azure OpenAI, AWS Bedrock, Mistral, vLLM, Ollama, LM Studio...). Ses identifiants vont dans le magasin de secrets et ne sont jamais renvoyés. Un fournisseur est partagé (`platform`, administrateur de la plateforme seulement) ou appartient au tenant.\n- Un **modèle** est une entrée du registre (Model Manifest v1 plus la fiche : catégorie, nom affiché, date de coupure des connaissances, prix). Son statut est `approved`, `restricted`, `deprecated` ou `blocked` : un modèle bloqué est toujours refusé, un modèle restreint ne sert que `tenant_admin`, `security_officer` ou un identifiant portant le scope `models:restricted`, un modèle déprécié répond avec un avertissement. Une modification d'un modèle partagé par un tenant crée une surcharge propre au tenant (copie à l'écriture).\n- Les **versions** portent une licence et la provenance des poids ; une version demande l'approbation d'une autre personne avant d'être publiée entièrement ou en canari, et elle peut être annulée (retour arrière).\n- Les **routes** choisissent un modèle parmi des candidats (stratégie `priority`, `cost`, `latency` ou `quality`) avec des replis. L'**isolation** (`local_only`, `air_gapped`) limite un tenant ou un projet aux fournisseurs locaux et aux hôtes internes déclarés.\n- La **passerelle** exécute chaque contrôle avant tout appel sortant : kill switches, capacité, listes d'autorisation des fournisseurs, modèles et régions, classification des données et habilitation de l'appelant, DLP (masquage ou blocage), isolation des secrets, TLS, rétention du fournisseur, détection d'injection de prompt, politique de repli, garde-fous de coût et décision de politique `model.invoke` ; puis elle appelle LiteLLM (ou directement le fournisseur), enregistre l'usage et n'audite que des empreintes. Les réponses portent `X-EAI-Decision-Id`, `X-EAI-Model`, `X-EAI-Cost-Estimated` et, s'il est connu, `X-EAI-Cost-Measured`.\n\n**Parcours type**\n\n1. Lisez le catalogue des fournisseurs avec `GET /v1/providers/catalog`, puis créez un fournisseur avec `POST /v1/providers` et testez-le.\n2. Découvrez les modèles qu'il sert avec `POST /v1/providers/{pid}/models/discover` et importez ceux choisis avec `POST /v1/models/import`.\n3. Approuvez les modèles (`POST /v1/models/{model_id}/status`), définissez les routes, puis envoyez la configuration à LiteLLM avec `POST /v1/litellm/sync`.\n4. Appelez `POST /v1/gateway/chat/completions` (ou embeddings, rerank, images, audio, vidéo) avec un identifiant qui porte le scope `models:invoke`.\n\n**Rôles** : la lecture demande l'un des rôles `tenant_admin`, `platform_admin`, `security_officer`, `finops`, `developer`, `auditor`, `viewer`, `agent`, `ai_platform_engineer`, `ai_governance_officer`, `support_engineer` ; les modifications demandent `tenant_admin`, `platform_admin` ou `ai_platform_engineer` (versions, déploiements et isolation : `tenant_admin` ou `platform_admin`) ; les kill switches (désactivation) acceptent aussi `security_officer`. Chaque modification est auditée."
  },
  {
   "name": "llm-management",
   "x-displayName": "Gestion des LLM",
   "description": "Projets LLM d'un tenant, leur quota et les clés virtuelles LiteLLM que les applications utilisent pour appeler les modèles.\n\n**Notions**\n\n- Un **projet LLM** regroupe les modèles autorisés (identifiants de modèles et catégories de modèles du registre), un responsable, un statut (`active`, `suspended`, `archived`) et un quota : budget par période, limite de jetons par période, requêtes et jetons par minute, requêtes parallèles, seuil d'alerte et arrêt strict.\n- Chaque projet est reproduit dans LiteLLM sous forme d'**équipe** (team : modèles, budget, période du budget, limites de débit). `sync_status` indique si la copie est `synced`, `pending` (LiteLLM non configuré) ou `failed`.\n- Quand le projet est lié à un projet du tenant (`project_id`) et actif, son budget et sa limite de jetons journaliers ou mensuels deviennent aussi des quotas FinOps de ce projet, appliqués par la passerelle du Control Plane (les périodes hebdomadaire et totale ne sont appliquées que par LiteLLM).\n- Une **clé** est une clé virtuelle LiteLLM du projet. Ses limites ne peuvent jamais être plus larges que celles du projet et ses modèles doivent être autorisés par le projet. La clé en clair n'est affichée qu'une fois, dans la réponse de création ; la plateforme ne garde que son empreinte, son alias, un préfixe d'affichage, les limites, le statut et l'échéance.\n\n**Parcours type**\n\n1. Vérifiez la connexion d'administration LiteLLM avec `GET /v1/llm/management/status`.\n2. Créez le projet avec `POST /v1/llm/projects`, puis définissez son quota avec `PUT /v1/llm/projects/{project_id}/quota`.\n3. Générez une clé avec `POST /v1/llm/projects/{project_id}/keys` et rangez-la tout de suite dans le secret de l'application (par exemple `EAI_API_KEY`).\n4. Suivez la dépense avec `GET /v1/llm/projects/{project_id}/keys` ; bloquez, débloquez ou révoquez une clé si besoin.\n\n**Rôles** : `tenant_admin` ou `platform_admin`, toujours dans le tenant de l'appelant (400 `tenant_required` sans tenant). Sans LiteLLM : 503 `litellm_not_configured` ; LiteLLM hors service : 502 `litellm_unreachable` ; LiteLLM qui refuse un appel : 502 `litellm_rejected`. Chaque modification est auditée ; les événements d'audit ne portent jamais de clé."
  },
  {
   "name": "marketplace",
   "x-displayName": "Marketplace",
   "description": "Catalogue de **packs** signés (ensembles d'agents, sous-agents, skills, outils, connecteurs, serveurs MCP et ressources), leurs contrôles de publication, le contrôle de mise en production, les **éditeurs** de confiance, les **installations** par environnement et le circuit de gestion des changements.\n\n**Concepts principaux**\n- **Paquet** et **version** : une archive de pack (`.tar.gz`) publiée avec sa signature, son SBOM, son analyse de vulnérabilités, ses rapports de tests et d'évaluation et sa provenance SLSA. Une version est `published`, puis `production` après le contrôle de mise en production.\n- **Installation** : une par paquet et par environnement (`dev`, `test`, `staging`, `prod`). États : `pending_approval`, `installed`, `suspended`, `failed`, `uninstalled`. Seules les permissions accordées à l'installation sont permises à l'exécution.\n- **Kill switch** : un paquet ou une installation peut être suspendu immédiatement ; la levée demande une validation à quatre yeux.\n- **Changement** : une demande de changement d'un prompt, skill, connecteur, règle, modèle, politique, outil, agent ou pack qui passe par `draft`, `test`, `security_review`, `business_approval`, `staging`, `production`, `monitoring`.\n\n**Parcours type**\n1. Enregistrer la clé de l'éditeur : `POST /v1/marketplace/publishers`.\n2. Publier une version : `POST /v1/marketplace/packages` (tous les contrôles doivent passer).\n3. La promouvoir en production si elle sera installée en `prod` : `POST .../versions/{version}/production`.\n4. Vérifier l'installation : `POST /v1/marketplace/preflight`.\n5. Demander l'installation (`POST /v1/marketplace/installations`, 202 avec une approbation), la faire approuver, puis l'appliquer (`POST .../installations/{id}/apply`).\n6. La superviser, la mettre à niveau, revenir en arrière, la suspendre ou la désinstaller.\n\n**Rôles** : la publication et les changements demandent `tenant_admin` ou `developer` ; le cycle de vie des installations demande `tenant_admin` (le preflight aussi `security_officer`) ; les actions de confiance (éditeurs, production, kill switches, rejet) demandent `tenant_admin` ou `security_officer` ; le contrôle d'exécution `authorize` accepte `tenant_admin`, `developer`, `agent` ou `service`. Chaque écriture est d'abord évaluée par le moteur de politiques (403 `policy_denied`) et les refus sont audités."
  },
  {
   "name": "mcp",
   "x-displayName": "Passerelle MCP",
   "description": "Registre des **serveurs MCP** amont (Model Context Protocol) et **MCP Gateway**, un proxy JSON-RPC 2.0 qui applique les contrôles de la plateforme à chaque appel : isolation par projet, appelants permis, listes d'autorisation d'outils et de ressources, scopes, DLP, politique, quota, contrôle d'injection de prompt, comptage et audit.\n\n**Concepts principaux**\n- **Serveur** : un nom, une URL interne (les destinations publiques sont refusées par la politique de sortie réseau), un transport (`streamable_http` ou `sse`), une authentification amont (`none`, `bearer`, `api_key`, `query_param` ou `connector`, secrets sous forme de références `openbao://` ou `env://`), `allowed_tools` et `allowed_resources` (motifs, `*` par défaut), un projet facultatif et un statut (`active`, `disabled`).\n- **Isolation par projet** : un serveur sans projet est partagé par le tenant ; un serveur de projet n'est joignable que depuis ce projet (en-tête `X-Project` ou clé d'API liée au projet), ou par `tenant_admin` et `security_officer`.\n- **Catalogue** : la découverte (`initialize`, `tools/list`) enregistre chaque schéma d'outil nouveau ou modifié comme une version en attente d'approbation.\n- **Méthodes** permises via la passerelle : `initialize`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, `prompts/list`, `ping`.\n\n**Parcours type**\n1. Enregistrer le serveur : `POST /v1/mcp/servers` (ou l'installer depuis un preset Navin).\n2. Découvrir ses outils : `POST /v1/mcp/servers/{server_id}/discover`, puis approuver les versions du catalogue.\n3. Restreindre les appelants si besoin : `PUT /v1/mcp/servers/{server_id}/access`.\n4. Les agents l'appellent via `POST /v1/mcp/gateway/{server_id}`.\n\n**Rôles** : l'enregistrement, la découverte et l'accès demandent `tenant_admin`, `developer` ou `application_owner` ; les changements de statut demandent `tenant_admin` ou `security_officer` ; la suppression et la synchronisation des serveurs hébergés demandent `tenant_admin` ; les appels de passerelle demandent `tenant_admin`, `developer` ou `agent`. Les routes sur un serveur vérifient aussi la relation ReBAC (`owner`, `editor`, `can_invoke`)."
  },
  {
   "name": "mcp-runtime",
   "x-displayName": "Runtime MCP",
   "description": "**MCP Runtime** : des **instances** isolées de serveur MCP démarrées à la demande pour un utilisateur d'un projet, à partir des modèles du catalogue de la plateforme, exécutées par ToolHive dans l'espace de noms du projet et joignables via la MCP Gateway.\n\n**Concepts principaux**\n- **Modèle** : une image de serveur MCP avec ses paramètres (hôtes, rôles de base de données...) et son type d'identifiant (un bail de base de données OpenBao dynamique, ou une référence de secret personnelle de l'utilisateur).\n- **Politique du projet** : modèles permis (vide : tous), hôtes permis (motifs comme `*.example.org`), rôles de base de données permis, temps d'inactivité, durée de vie maximale et quotas par utilisateur et par projet.\n- **Cycle de vie d'une instance** : `provisioning`, `ready` (son entrée de passerelle n'est appelable que par son propriétaire et ses agents), `sleeping` (inactive, durée de vie atteinte, arrêtée, ou détruite après une panne ; l'appel suivant la recrée avec une nouvelle génération et de nouveaux identifiants), `failed` (pannes répétées ou permanentes : les appels sont refusés jusqu'à ce que son propriétaire la redémarre), `terminating`, `terminated`. Chaque étape est inscrite dans un journal de cycle de vie en ajout seul.\n- **Secrets** : ils n'atteignent jamais une table, une réponse, un événement ou un journal ; un bail est scellé puis effacé une fois révoqué, un secret personnel n'est conservé que sous forme de référence.\n\n**Parcours type**\n1. Un administrateur du tenant fixe la politique du projet : `PUT /v1/projects/{project_id}/mcp/policy`.\n2. Un utilisateur liste les modèles et démarre une instance : `GET` puis `POST /v1/projects/{project_id}/mcp/instances`.\n3. L'utilisateur la suit jusqu'à `ready` : `GET .../instances/{instance_id}`.\n4. Les agents l'appellent via `POST /v1/mcp/gateway/{server_id}` ; elle s'endort quand elle est inactive et se réveille au prochain appel.\n5. L'utilisateur l'arrête ou la supprime à la fin de la tâche.\n\n**Accès** : les routes de projet vérifient la relation ReBAC `viewer` ou `editor` sur le projet. Un utilisateur voit et gère ses propres instances ; `tenant_admin` et `security_officer` voient toutes les instances du projet. Créer une instance demande `tenant_admin`, `developer` ou `application_owner` ; la politique demande `tenant_admin`. Quand la plateforme tourne sans MCP Runtime, la création répond 409 `mcp_runtime_disabled`."
  },
  {
   "name": "memory",
   "x-displayName": "Mémoire",
   "description": "**Memory Manager** : éléments de mémoire durables des agents et des utilisateurs, conformes au Memory Contract v1, avec ACL, durée de vie, conservation légale, versionnage et recherche hybride.\n\n**Concepts principaux**\n- **Élément** : un contenu (ou une référence vers celui-ci) avec une portée `scope` (`session`, `agent`, `user`, `project`, `tenant` et un identifiant), un type `kind` (`episodic`, `semantic`, `procedural`, `preference`, `fact`), une classification et une ACL (`readers`, `writers`, `owners`).\n- **Versions** : écrire un `memory_id` existant garde l'ancien contenu comme version et incrémente `version`.\n- **Durée de vie** : `ttl_seconds` fixe `expires_at` ; un élément expiré n'est plus renvoyé. Un élément sous `legal_hold` ne peut pas être supprimé ; un élément non `deletable` ne peut être supprimé que par `tenant_admin`.\n- **Embedding** : le contenu est vectorisé via le Model Gateway avec le modèle d'embedding mémoire de la plateforme ; la recherche ne compare que les vecteurs du modèle courant.\n\n**Parcours type**\n1. Écrire un élément : `POST /v1/memory` (la détection de données personnelles peut relever la classification).\n2. Rappeler : `POST /v1/memory/search` (les résultats sont contrôlés contre l'injection de prompt avant d'atteindre un prompt).\n3. Lire ou lister : `GET /v1/memory/{memory_id}`, `GET /v1/memory`.\n4. Oublier : `DELETE /v1/memory/{memory_id}`.\n\n**Accès** : tout principal du tenant peut appeler ces routes ; la visibilité se décide par élément selon son ACL (une des identités de l'appelant doit y figurer, les administrateurs n'ont aucun accès implicite en lecture) et selon l'habilitation de l'appelant. Les écritures et recherches passent par le moteur de politiques (`memory.write`, `memory.search`)."
  },
  {
   "name": "multimodal",
   "x-displayName": "Multimodal",
   "description": "**Médias et traitement multimodal** : stockage chiffré de médias et travaux de vision, génération et retouche d'images, traitement et génération de vidéos, transcription de la parole, diarisation, synthèse vocale et DLP multimodale.\n\n**Concepts principaux**\n- **Média** : un fichier enregistré (image, audio, vidéo, document) analysé par ClamAV et YARA-X, son texte extrait et inspecté par la DLP, chiffré avec une clé du tenant et renvoyé sous forme de document Multimodal Contract. Sa classification est relevée quand des données personnelles sont trouvées.\n- **Travail** : une opération (`vision_understanding`, `image_generation`, `image_edit`, `video_processing`, `video_generation`, `stt`, `diarization`, `tts`...). Il s'exécute directement quand un adaptateur est disponible, sinon il reste `queued` pour les workers GPU, qui en signalent le résultat avec `POST /v1/multimodal/jobs/{job_id}/complete`. Statuts : `queued`, `running`, `succeeded`, `failed`, `cancelled`.\n- Chaque opération est évaluée par la politique `multimodal.submit`, prend une réservation de quota et enregistre un événement d'usage.\n\n**Parcours type**\n1. Téléverser l'entrée : `POST /v1/media` (ou passer `content_base64` directement à l'opération).\n2. Lancer l'opération, par exemple `POST /v1/multimodal/stt` avec `media_id`.\n3. Lire le travail jusqu'à sa fin : `GET /v1/multimodal/jobs/{job_id}`.\n4. Supprimer le média quand il n'est plus utile : `DELETE /v1/media/{media_id}`.\n\n**Rôles** : le traitement et la génération demandent `tenant_admin`, `developer` ou `agent` ; la clôture d'un travail demande `agent` ou `tenant_admin` ; le téléversement, les lectures et le contrôle DLP demandent un principal du tenant ; supprimer un média demande son créateur, `tenant_admin` ou `security_officer`."
  },
  {
   "name": "navin-library",
   "x-displayName": "Bibliothèque Navin",
   "description": "La **bibliothèque Navin** est le catalogue embarqué de skills, plugins, commandes, agents, presets MCP et canaux prêts à l'emploi. Un projet active ce dont il a besoin, installe des serveurs MCP et des canaux à partir de presets et garde ses propres **règles**, les instructions durables que reçoit chaque run du projet.\n\n**Concepts principaux**\n- **Élément** : une entrée de la bibliothèque, par type (`skills`, `plugins`, `commands`, `agents`, `mcp-presets`, `channels`) et par nom, avec une empreinte.\n- **Activation** : un élément activé pour un projet. Un skill est enregistré dans le Skill Registry du tenant, vérifié par son empreinte de bibliothèque ; un plugin apporte ses skills, commandes et agents.\n- **Règle** : une instruction Markdown d'un projet, `always` (insérée dans le prompt) ou `conditional` (listée avec ses globs et sa description). Les règles peuvent être importées depuis les fichiers d'autres agents de code (`.navin/rules`, `.cursor/rules`, `.cursorrules`, `.clinerules`, `.windsurfrules`, `.github/copilot-instructions.md`).\n\n**Parcours type**\n1. Parcourir le catalogue : `GET /v1/navin-library` et `GET /v1/navin-library/{kind}`.\n2. Activer des éléments pour un projet : `POST /v1/projects/{project_id}/navin-library/activations`.\n3. Installer un serveur MCP ou un canal à partir d'un preset, avec des références de secrets uniquement.\n4. Importer ou écrire les règles du projet, puis lire la section de prompt avec `GET .../rules/prompt`.\n\n**Accès** : lire le catalogue demande un principal du tenant. Les routes de projet vérifient la relation ReBAC `viewer` (lectures) ou `editor` (écritures) sur le projet ; les écritures demandent aussi `tenant_admin`, `developer` ou `application_owner`. Un projet est désigné par son identifiant ou son slug (404 `project_not_found`)."
  },
  {
   "name": "operations",
   "x-displayName": "Opérations",
   "description": "Opérations longues : un `operation_id` durable pour chaque création longue, pour la suivre et la relancer sans risque.\n\nLes requêtes longues sont : les exécutions d'agents, les exécutions d'évaluation, les exécutions de red team, les installations et mises à niveau depuis la marketplace, la réindexation d'une base de connaissances, la synchronisation d'un connecteur et la génération de vidéo. Leur réponse porte les en-têtes `Operation-Id` et `Operation-Location` (`/v1/operations/{operation_id}`).\n\n**Idempotence** : avec un en-tête `Idempotency-Key` (1 à 200 caractères), la première réponse est conservée ; une nouvelle tentative du même principal avec le même corps la rejoue avec `Idempotency-Replayed: true` sans relancer l'opération. Un autre corps sous la même clé répond 409 `idempotency_conflict` ; une nouvelle tentative pendant que la première requête s'exécute répond 409 `operation_in_progress`. Les exécutions d'agents dédupliquent elles-mêmes et partagent une seule ligne d'opération entre leurs rejeux. Une requête en échec n'est pas conservée : elle peut être relancée avec la même clé.\n\n**Parcours type**\n\n1. Envoyez la requête longue, de préférence avec un `Idempotency-Key`.\n2. Lisez `Operation-Location` dans la réponse.\n3. Interrogez `GET /v1/operations/{operation_id}` jusqu'à ce que `status` indique l'état final de la ressource.\n\n**Accès** : un principal authentifié d'un tenant. Le créateur lit ses propres opérations ; `tenant_admin`, `platform_admin`, `auditor` et `security_officer` lisent toutes les opérations du tenant."
  },
  {
   "name": "platform-manager",
   "x-displayName": "Platform Manager",
   "description": "Espace Platform Manager de la console : la passerelle du Control Plane vers le Navin Manager, qui installe, connecte et exploite les services de la plateforme sur les machines du client, ainsi que le coffre-fort informatique des administrateurs de la plateforme.\n\n**Fonctionnement de la passerelle** : chaque appel est transmis à l'API du Navin Manager avec le jeton d'API Manager du Control Plane et l'identité de l'utilisateur du portail dans `X-NM-On-Behalf-Of` (le nom du compte local ou le nom d'utilisateur Keycloak). Le Manager reste l'autorité : ses permissions (par exemple `services:install`, `hosts:manage`, `configs:read`, `backup:read`, `restore:execute`, `dr:manage`, `connections:manage`), ses approbations et ses règles d'arrêt s'appliquent. Les valeurs secrètes (mots de passe, clés privées, jetons d'enrôlement) sont relayées au Manager, jamais enregistrées ni auditées par le Control Plane. Chaque écriture est auditée dans la chaîne du Control Plane, autorisée ou refusée.\n\n**Notions** : un **cluster** est un environnement du Manager (`production` ou `sandbox`) ; les **hôtes** sont ses machines, enrôlées avec l'agent de nœud (par SSH ou avec un jeton d'enrôlement) ; les **services** viennent du **catalogue** du Manager et sont soit installés sur des hôtes, soit connectés (un service existant du client) ; les **connexions** sont des PostgreSQL, Redis ou émetteurs OIDC existants liés à des services ; la **sauvegarde et le PRA** couvrent les plans de sauvegarde, les restaurations (deux approbateurs autres que le demandeur) et les exercices de PRA.\n\n**Parcours type**\n\n1. Vérifiez `GET /v1/platform-manager/setup` : socle de la plateforme et connexion au Manager.\n2. Créez un cluster, puis enrôlez les machines avec `POST .../hosts/ssh-bootstrap` ou un jeton d'enrôlement, et lancez les contrôles préalables.\n3. Lisez le catalogue, validez puis installez des services (`POST .../services/validate`, `POST .../services`), ou connectez des services existants.\n4. Exploitez-les (opérations, modes), suivez la synthèse, les tableaux de bord, la configuration et les opérations, et planifiez sauvegardes et restaurations.\n\n**Rôles** : chaque route demande `platform_admin` (403 `forbidden`). Erreurs de la passerelle : 503 `platform_manager_not_configured` (pas de `EAI_PLATFORM_MANAGER_URL`), 503 `platform_manager_credential_missing` (référence du jeton absente ou illisible), 502 `platform_manager_unreachable` ; un refus du Manager est relayé avec son statut HTTP et le code `platform_manager_<code du Manager>`, avec ses messages en anglais et en français."
  },
  {
   "name": "plugins",
   "x-displayName": "Plugins",
   "description": "Registre et cycle de vie d'exécution des **plugins** : des images OCI signées exécutées par la plateforme dans une sandbox (classe de runtime gVisor, Kata ou Wasm), avec un Deployment et une NetworkPolicy Kubernetes générés à partir du manifeste.\n\n**Concepts principaux**\n- **Manifeste de plugin** (`kind: Plugin`) : `artifact` (`oci_ref`, `digest`), `signature`, `sandbox` (runtime, mode réseau `none` ou `egress_allowlist` avec sa liste d'autorisation, système de fichiers racine en lecture seule, limites de ressources).\n- **Statuts** : `registered`, `installed`, `installing`, `failed`, `uninstalled`, `blocked`.\n- Un pack (`format: pack`) n'est pas un plugin : il est publié et installé via la Marketplace.\n\n**Parcours type**\n1. Enregistrer le plugin : `POST /v1/plugins` (la signature est vérifiée et le résultat enregistré).\n2. L'installer : `POST /v1/plugins/{name}/install` (signature revérifiée, objets générés et appliqués quand Kubernetes est configuré).\n3. Le suivre : `GET /v1/plugins/{name}/status`.\n4. Le désinstaller : `POST /v1/plugins/{name}/uninstall`.\n\n**Rôles** : l'enregistrement demande `tenant_admin`, `developer` ou `application_owner` ; l'installation et la désinstallation demandent `tenant_admin` et la relation ReBAC `owner` ; les lectures demandent la relation `viewer`."
  },
  {
   "name": "policies",
   "x-displayName": "Politiques",
   "description": "Politiques fondées sur les attributs (ABAC) qui décident de chaque action sensible de la plateforme, avec le Policy Contract v1, le journal des décisions, la simulation, les révisions et les bundles OPA signés.\n\nUne politique est soit `builtin` (une liste de règles évaluées par la plateforme), soit `opa` (un module Rego exécuté par Open Policy Agent). Une règle cible des actions et des types de ressources (motifs), s'applique quand toutes ses conditions sont vraies et a un effet, `allow` ou `deny`, avec d'éventuelles obligations (par exemple masquage `dlp` ou `approval`).\n\nOrdre d'évaluation, politiques actives triées par priorité puis par nom :\n\n1. une règle `deny` qui s'applique l'emporte ;\n2. sinon les règles `allow` qui s'appliquent autorisent, leurs obligations fusionnées ;\n3. si des règles d'autorisation ciblent l'action mais qu'aucune ne s'applique, la demande est refusée ;\n4. sans règle applicable, les garde-fous de la plateforme s'exécutent (habilitation face à la classification des données, plafond de classification de la ressource, régions de la ressource), puis la valeur par défaut du tenant ou de la plateforme.\n\nChaque décision est enregistrée dans le journal des décisions et inscrite dans la chaîne d'audit.\n\nParcours type :\n\n1. `POST /v1/policies/simulate` pour voir quelles décisions une politique candidate changerait.\n2. `POST /v1/policies` (ou `PUT`) pour l'activer ; chaque modification ajoute une révision.\n3. `POST /v1/policies/evaluate` depuis une application pour demander une décision ; `GET /v1/policies/decisions` pour les relire.\n4. Pour OPA, `POST /v1/policies/bundles` publie un bundle signé qu'OPA interroge.\n\nRôles : tout membre du tenant lit les politiques et évalue pour lui-même ; `tenant_admin` et `security_officer` gèrent les politiques et les bundles et peuvent évaluer pour le compte d'un autre sujet ; `developer` peut simuler et lire les révisions ; `auditor` lit les décisions, les bundles, les moteurs et les révisions."
  },
  {
   "name": "privacy",
   "x-displayName": "Vie privée et protection des données",
   "description": "Services de protection des données du tenant : classification, DLP (prévention des fuites de données), tokenisation et chiffrement par enveloppe.\n\n- **Classification** : détecte les entités personnelles et sensibles (e-mail, téléphone, personne, IBAN, carte bancaire, NIR, SIRET et SIREN, clés d'API, clés privées, mots de passe, JWT...) avec les détecteurs intégrés ou Presidio s'il est configuré, et donne la plus haute classification de données trouvée (`public` à `secret`).\n- **DLP** : applique un mode aux constats : `detect` (signaler seulement), `mask` (remplacer par `[TYPE]`), `tokenize` (remplacer par un jeton réversible) ou `block` (vider le texte). Les règles du tenant ajoutent des motifs ou des types d'entités et peuvent remplacer le mode par entité, par exemple toujours bloquer les cartes bancaires.\n- **Tokenisation** : jetons déterministes `tok_<type>_<hex>` par tenant, la valeur étant conservée chiffrée dans un coffre ; détokeniser exige un rôle d'administrateur et une décision d'autorisation de la politique.\n- **Clés de chiffrement** : des clés nommées dont les clés de données sont enveloppées par OpenBao Transit (ou localement), versionnées et tournées ; le chiffrement et le déchiffrement renvoient et acceptent des chiffrés `eaienc1:<key id>:<version>:...`.\n\nParcours type :\n\n1. `POST /v1/dlp/rules` pour ajouter les motifs de l'entreprise.\n2. `POST /v1/dlp/redact` avant d'envoyer un texte à un modèle ou à un tiers.\n3. `POST /v1/tokenization/tokenize` pour conserver une valeur pseudonymisée ; `POST /v1/tokenization/detokenize` avec une finalité pour la retrouver.\n4. `POST /v1/encryption/keys`, puis `POST /v1/encryption/encrypt` pour les données applicatives ; faites tourner les clés régulièrement.\n\nRôles : tout membre du tenant classe, inspecte, masque, tokenise, chiffre et déchiffre, et lit les règles et les clés ; `tenant_admin` ou `security_officer` gèrent les règles et les clés et détokenisent. Les appels de DLP, de tokenisation et de chiffrement écrivent des événements d'audit, jamais avec les valeurs."
  },
  {
   "name": "prompts",
   "x-displayName": "Prompts",
   "description": "**Registre de prompts** : modèles de prompt versionnés avec variables typées et rendu validé.\n\n**Concepts principaux**\n- **Prompt** : un modèle nommé. Les emplacements s'écrivent `{{ variable }}` ; chaque publication avec un modèle ou des variables différents crée une nouvelle version immuable, identifiée par une empreinte SHA-256.\n- **Variable** : nom, type (`string`, `integer`, `number`, `boolean`, `array`, `object`), caractère obligatoire, valeur par défaut, `enum` et `max_length`. Sans variables déclarées, chaque emplacement devient une variable texte obligatoire.\n- **Rendu** : les valeurs sont contrôlées selon les variables déclarées avant la production du texte ; les valeurs non textuelles sont écrites en JSON.\n\n**Parcours type**\n1. Publier : `POST /v1/prompts` (première version, ou nouvelle version d'un nom existant).\n2. Consulter l'historique : `GET /v1/prompts/{name}/versions`.\n3. Rendre une version avec des valeurs : `POST /v1/prompts/{name}/render`.\n\n**Rôles** : publier demande `tenant_admin` ou `developer` ; lire et rendre demandent un principal du tenant."
  },
  {
   "name": "rag-agent",
   "x-displayName": "Instances de l'agent RAG",
   "description": "**Activation et cycle de vie des instances du Navin Enterprise RAG Agent.** Le Control Plane porte l'enregistrement d'activation de chaque instance (son statut, son modèle de déploiement, ses versions et les ressources qu'elle possède), car dans le modèle dédié le runtime du RAG n'existe pas encore au moment de l'activation. Le runtime du RAG lit son activation avec `GET /v1/rag-agent/instances/{instance_id}` et refuse de servir des conversations tant que le statut n'est pas `ready` ou `degraded` (`instance_not_ready`).\n\n**Statuts** : `draft`, `validating`, `provisioning`, `starting`, `ready`, `degraded`, `failed`, `stopping`, `stopped`, `updating`, `deleting`, `deleted`. Le statut ne dit `ready` qu'après le succès des étapes `health` et `smoke` ; le worker suit ensuite la santé réelle (`ready` et `degraded`).\n\n**Étapes** (états `pending`, `running`, `done`, `failed`, `skipped`) : `validation`, `resources` (application gouvernée et agent de l'instance), `database`, `object_storage` (préfixe de clés dans le profil de stockage des documents), `qdrant` (base de connaissances avec `vector_store` qdrant), `memory` (politique de mémoire de l'agent), `security` (tuples OpenFGA du propriétaire, clé d'API du runtime conservée comme référence de secret), `deployment` (runtime partagé déclaré par `EAI_RAG_AGENT_SHARED_RUNTIME_URL`, ou le Navin Manager par la passerelle platform_manager pour une instance dédiée), `health`, `smoke`, `activation`. Chaque étape garde ses tentatives et ses journaux ; une étape en échec se nomme avec sa raison dans `last_error`.\n\n**Transitions** : chaque action n'est acceptée que depuis les statuts de sa ligne dans la table des transitions, sinon 409 `rag_agent_transition_invalid` (audité comme refus). Répéter une action faite ou en cours renvoie l'enregistrement courant avec `changed: false`. Relancer l'opération en échec reprend la même exécution à son étape en échec ; les étapes déjà faites ne sont jamais rejouées. Le provisionnement, le démarrage, l'arrêt, le redémarrage, la mise à niveau et la suppression sont des opérations longues (`Operation-Id`, `Idempotency-Key`) exécutées par la tâche du worker `rag_agent_lifecycle`.\n\n**Modèles de déploiement** : `shared` fait pointer l'instance vers le runtime partagé existant ; démarrer et arrêter ne changent que le statut (et l'application gouvernée). `dedicated` demande au Navin Manager d'installer une instance nommée du service du catalogue `navin-rag-agent` ; tant que ce service n'est pas publié dans le catalogue, l'étape de déploiement échoue avec la raison `service_not_published`.\n\n**Rôles** : les écritures demandent un administrateur du tenant ou un propriétaire du projet de l'instance (403 `forbidden`) ; les lectures demandent un principal du tenant. L'instance d'un autre tenant répond 404 `rag_agent_instance_not_found`."
  },
  {
   "name": "realtime",
   "x-displayName": "Voix en temps réel",
   "description": "Sessions de **voix en temps réel** : un jeton de salle LiveKit et un protocole d'événements WebSocket pour diffuser de l'audio vers un agent, avec consentement des participants, tours de parole, transcriptions filtrées par la DLP, enregistrements facultatifs et comptage d'usage.\n\n**Concepts principaux**\n- **Session** : une salle, un pipeline (`echo` : transcription par l'adaptateur STT ou l'indice du client, audio renvoyé ; `stt` : adaptateur STT requis), une configuration (`dlp_mode`, `vad`, `record`...), l'identité de l'appelant, l'agent et l'utilisateur pour qui il agit. Statut `open` puis `ended`.\n- **WebSocket** : `GET /v1/realtime/ws/{session_id}?token=...` (absente de cette référence, car c'est une route WebSocket). Événements client : `session.update`, `input_audio.append` (PCM16 en base64), `input_audio.commit`, `input_audio.speech_started` (interruption), `response.create`, `response.cancel`, `session.end`. Les événements serveur incluent `session.created`, `transcript.delta`, `response.audio.delta`, `response.done`, `response.cancelled`, `recording.saved`, `usage.update`, `error`, `session.ended`. Le jeton est de courte durée et à usage unique.\n- **Consentement** : par participant, finalité, enregistrement et transcription permis, conservation en jours et méthode de recueil. L'enregistrement demande un consentement qui l'autorise ; quand la plateforme exige le consentement, la transcription aussi (erreur `consent_required`). Transcriptions et enregistrements expirent avec la durée de conservation du consentement.\n- **Transcriptions** : enregistrées seulement après la DLP ; la transcription brute n'est jamais conservée.\n\n**Parcours type**\n1. Ouvrir une session : `POST /v1/realtime/sessions` (jeton LiveKit et jeton WebSocket).\n2. Enregistrer le consentement de chaque participant : `POST .../consents`.\n3. Diffuser sur le WebSocket ; se reconnecter avec un nouveau jeton obtenu par `POST .../token`.\n4. Terminer la session : `POST .../end` (usage enregistré).\n5. Consulter `turns` et `transcripts`.\n\n**Rôles** : les écritures demandent `tenant_admin`, `developer` ou `agent` ; les transcriptions demandent `tenant_admin`, `developer`, `auditor` ou `security_officer` ; les autres lectures demandent un principal du tenant (lignes lues sous la sécurité au niveau des lignes du tenant)."
  },
  {
   "name": "retention",
   "x-displayName": "Conservation",
   "description": "Durée de conservation des données du tenant, leur purge, l'effacement d'une personne concernée (article 17 du RGPD) et les gels juridiques.\n\n- **Politique de conservation** : par type de ressource (`memory`, `sessions`, `media`, `usage_raw`, `telemetry`, `documents`, `realtime`), une durée de vie en jours et une action, `erase` ou `archive` (copie chiffrée conservée avant suppression, mémoire uniquement).\n- **Passe de conservation** : applique maintenant les politiques actives, ou les simule avec `dry_run`.\n- **Effacement** : supprime tout ce que la plateforme détient sur une personne (mémoire, médias, sessions, sessions temps réel terminées, éventuellement documents, traces Opik et données d'analyse ClickHouse). Le journal d'audit et les enregistrements de consommation sont conservés, comme la loi l'exige ; la réponse l'indique dans `retained`.\n- **Gel juridique** : fige une ressource ou toutes les ressources d'une personne ; les éléments gelés sont ignorés par les passes et les effacements.\n\nParcours type :\n\n1. `POST /v1/retention/policies` fixe la durée de vie de chaque type de ressource.\n2. `POST /v1/retention/run` avec `dry_run: true` montre ce qui serait purgé, puis sans lui purge.\n3. Sur demande d'une personne concernée, `POST /v1/retention/erase` ; en cas de litige, `POST /v1/retention/legal-hold`.\n\nRôles : tout membre du tenant lit les politiques ; `tenant_admin`, `security_officer` et `data_steward` les modifient, lancent les passes, effacent et gèlent. Chaque modification écrit un événement d'audit (`retention.policy.upsert`, `retention.run`, `retention.erase`, `retention.legal_hold`)."
  },
  {
   "name": "risk",
   "x-displayName": "Risques et conformité",
   "description": "Gestion des risques des systèmes d'IA du tenant : évaluations au titre de l'AI Act européen, contrôles rattachés à l'ISO/IEC 42001, au NIST AI RMF, au RGPD et à l'AI Act, preuves et dérogations limitées dans le temps.\n\n- **Évaluation** : classe une cible (agent, modèle, application...) à partir de ses cas d'usage et de ses réponses déclarés dans une catégorie de l'AI Act : `unacceptable` (pratique interdite de l'article 5), `high` (domaine de l'annexe III ou composant de sécurité), `limited` (transparence de l'article 50) ou `minimal`, avec la justification et les obligations de cette catégorie. L'évaluation d'un agent met aussi à jour son niveau de risque dans le registre.\n- **Contrôle** : un contrôle adopté par le tenant, issu du catalogue intégré (`EAI-GOV-01`, `EAI-RISK-01`, `EAI-DATA-01`, `EAI-DATA-02`, `EAI-SEC-01`, `EAI-LOG-01`, `EAI-QA-01`, `EAI-QA-02`, `EAI-OPS-01`, `EAI-HUM-01`) ou personnalisé, avec son statut et son responsable.\n- **Preuve** : documents enregistrés sur un contrôle ou une évaluation, plus les preuves collectées automatiquement sur la plateforme (inventaire, règles DLP, conservation, clés, chaîne d'audit, évaluations, campagnes de red team, incidents, validations).\n- **Dérogation** : une exemption limitée dans le temps d'un contrôle ou d'une cible, 365 jours au plus, approuvée par une autre personne que le demandeur.\n\nParcours type :\n\n1. `POST /v1/risk/assessments` avant de déployer un système d'IA.\n2. `POST /v1/risk/controls` pour adopter les contrôles du catalogue, avec leurs preuves.\n3. `GET /v1/risk/evidence` pour préparer un audit.\n4. `POST /v1/risk/exceptions` quand un contrôle ne peut pas encore être respecté, avec des contrôles compensatoires.\n\nRôles : tout membre du tenant lit ; `tenant_admin`, `security_officer`, `developer` et `ai_governance_officer` évaluent (seuls les administrateurs et les responsables de la gouvernance de l'IA approuvent une évaluation) ; `tenant_admin` et `security_officer` gèrent les contrôles ; eux et `developer` demandent des dérogations. Les modifications écrivent des événements d'audit (`risk.assessment.create`, `risk.control.upsert`, `risk.exception.create`)."
  },
  {
   "name": "secrets",
   "x-displayName": "Secrets",
   "description": "Magasin en écriture seule des secrets du tenant : clés de fournisseurs, mots de passe de connecteurs, jetons de serveurs MCP. Les valeurs sont chiffrées (dans OpenBao quand il est configuré, sinon en AES-GCM dans la base) et ne sont jamais renvoyées par l'API. Les autres objets désignent un secret par son chemin ; la plateforme ne lit la valeur côté serveur que lorsqu'elle en a besoin.\n\nParcours type :\n\n1. `POST /v1/secrets` avec un `path` comme `connectors/crm/password` et la `value`.\n2. Référencez le chemin dans un connecteur, un fournisseur de modèles ou un serveur MCP.\n3. Écrivez de nouveau le même chemin pour faire tourner la valeur (la `version` augmente) ; `DELETE /v1/secrets/{path}` la supprime.\n\nRôles : `tenant_admin`, `security_officer` et `developer` écrivent et suppriment les secrets ; un développeur ne remplace ou ne supprime que les secrets qu'il a écrits. Tout membre du tenant liste les métadonnées. Les écritures et suppressions écrivent les événements d'audit `secret.write` et `secret.delete`, jamais avec la valeur."
  },
  {
   "name": "security",
   "x-displayName": "Sécurité",
   "description": "Posture de sécurité du déploiement et réglages de sécurité du tenant.\n\n- **Posture** : les protections en vigueur (méthodes d'authentification, limites, en-têtes) et une liste de contrôles, chacun `pass`, `warn` ou `fail` avec une gravité, par exemple si l'authentification par en-têtes de développement est désactivée.\n- **Réglages** du tenant, enregistrés avec le tenant et fusionnés avec les valeurs par défaut :\n  - `prompt_injection_mode` : `off`, `detect` ou `block` ;\n  - `untrusted_content_wrapping` : encapsuler le contenu des outils et du RAG comme des données avant qu'il n'atteigne un modèle (vrai ou faux) ;\n  - `output_links` : `allow` ou `strip_untrusted` (liens et images vers des hôtes inconnus retirés de la sortie des modèles) ;\n  - `output_link_allowlist` : noms d'hôte conservés dans la sortie des modèles ;\n  - `egress_allowed_hosts` : quand la liste n'est pas vide, les outils, connecteurs, serveurs MCP et fournisseurs du tenant ne peuvent appeler que ces hôtes. Seuls des noms d'hôte publics sont acceptés (pas d'adresse IP, pas de suffixe interne comme `.internal` ou `.local`) ; l'accès privé relève d'une décision de plateforme.\n\nRôles : `tenant_admin` et `security_officer`. Les modifications écrivent l'événement d'audit `security.settings.update`."
  },
  {
   "name": "skills",
   "x-displayName": "Skills",
   "description": "**Skill Registry** et **Skill Loader**. Un skill est un ensemble d'instructions signé et versionné (en ligne, ou artefact Git ou OCI) avec les permissions dont il a besoin ; les agents chargent les skills accordés dans leur manifeste.\n\n**Concepts principaux**\n- **Skill** : un manifeste de type `Skill` avec `instructions`, un `artifact` facultatif (`git_ref` ou `oci_ref` et `digest`), une `signature` et des `permissions` (`scopes`, `data_classification_max`).\n- **Vérification** : l'empreinte du contenu et la signature sont contrôlées avec les clés d'éditeurs de confiance. Un skill vérifié passe à `approved`, un skill non vérifié reste `draft`.\n- **Statuts** : `draft`, `approved`, `restricted`, `deprecated`, `blocked`. Un skill `blocked` ou `deprecated` ne peut pas être chargé.\n\n**Parcours type**\n1. Enregistrer le skill : `POST /v1/skills` avec son manifeste (la vérification s'exécute aussitôt).\n2. Le revérifier quand les clés changent : `POST /v1/skills/{name}/verify`.\n3. Un agent le charge à l'exécution : `GET /v1/skills/{name}/load?agent=<agent>`.\n4. Le restreindre, le déprécier ou le bloquer avec `POST /v1/skills/{name}/status` ; l'approbation passe par une promotion.\n\n**Rôles** : l'enregistrement demande `tenant_admin`, `developer` ou `application_owner` ; la vérification accepte aussi `security_officer` ; les changements de statut demandent `tenant_admin` ou `security_officer`. Les routes sur un skill vérifient la relation ReBAC (`viewer`, `editor`, `can_invoke`)."
  },
  {
   "name": "soc",
   "x-displayName": "SOC et réponse aux incidents IA",
   "description": "Opérations de sécurité pour l'IA (AI-SDR) : détections, ordres de confinement typés, registre des coupe-circuits (Kill Switch Registry), accusés de confinement, rétablissement à quatre yeux, preuves scellées des incidents et rapprochement des transactions à effet de bord.\n\nNotions :\n\n- **Détection** : un constat normalisé (garde interne, DLP, sortie réseau, quotas, plugins, environnement d'exécution, autorisation, politique, audit, connaissances, MCP, ou constats externes Falco, Tetragon et OpenSearch), noté, corrélé en un incident et scellé comme preuve. Son playbook de réactions typées est soumis au moteur de politique de réponse.\n- **Ordre de confinement** : une réaction typée (jamais une commande) sur une cible typée, par exemple `SUSPEND_AGENT` sur un agent. Le moteur de politique de réponse décide s'il est autorisé, automatique ou en attente d'une seconde personne (garde de proportionnalité : confiance, rayon d'impact, réactions toujours soumises à validation).\n- **Coupe-circuit** : l'enregistrement d'un confinement (état voulu `SUSPENDED`), lu par l'authentification, la politique et l'environnement d'exécution des agents ; son état observé ne passe à `SUSPENDED_VERIFIED` que sur un accusé vérifié.\n- **Rétablissement** : lever un coupe-circuit exige une demande, deux responsables SOC distincts et une nouvelle autorisation de la politique.\n- **Preuves et traçabilité** : des enregistrements en ajout seul, chaînés par empreintes pour chaque incident, et une seconde chaîne qui enregistre chaque scellement, lecture et export.\n- **Transactions** : les appels à effet de bord (paiements, écritures dans un ERP) interrompus par un incident sont rapprochés par une personne.\n\nParcours type :\n\n1. Une détection arrive (`POST /v1/soc/detections` ou un webhook signé) et ouvre ou rejoint un incident.\n2. Son playbook, ou un analyste (`POST /v1/soc/orders`), ordonne un confinement ; un responsable l'approuve si nécessaire.\n3. Le coupe-circuit est vérifié (`POST /v1/soc/kill-switches/{switch_id}/verify`, ou un accusé signé depuis le cluster).\n4. Une fois la menace écartée, `POST /v1/soc/recoveries`, deux approbations, puis `reauthorize`.\n\nRôles : les analystes sont `security_officer`, `soc_analyst` ou `tenant_admin` pour les ordres ; les responsables sont `security_officer` ou `tenant_admin` (responsable SOC) ; les lecteurs sont `security_officer`, `tenant_admin`, `auditor` ou `soc_analyst`. Un accès refusé est audité (`soc.access.deny`) et chaque lecture est auditée (`soc.access`)."
  },
  {
   "name": "storage",
   "x-displayName": "Stockage",
   "description": "Profils de stockage : les buckets et conteneurs du client dans lesquels la plateforme écrit (exports d'audit, preuves, documents, médias, exports, sauvegardes), avec leur référence d'identifiants et leurs capacités découvertes par de vrais appels.\n\n**Notions**\n\n- Un **profil** indique où vont les données (fournisseur `aws_s3`, `ibm_cos`, `seaweedfs`, `s3_compatible`, `azure_blob` ou `gcs` ; point d'accès, région, bucket, préfixe de clé, chiffrement, options) et comment s'authentifier (`auth` avec des identifiants non secrets, `credential_ref` vers le secret). Les valeurs secrètes sont refusées dans le contenu (422 `storage_secret_in_profile`) et jamais renvoyées.\n- Un profil de **tenant** (portée `tenant`) utilise une référence `openbao://` vers un secret du tenant ; un profil de **plateforme** (portée `platform`) utilise une variable d'opérateur `env://EAI_CRED_<NOM>` ou l'identité de charge de travail.\n- Les **capacités** (versionnage, verrouillage d'objet, conservation légale, rétention, envoi en plusieurs parties, URL présignées, chiffrement, KMS, classes de stockage, cycle de vie, réplication, intégrité) sont découvertes par la vérification et enregistrées avec leur date. Un usage qui demande une capacité absente, inconnue ou trop ancienne (plus vieille que `EAI_STORAGE_CAPABILITY_MAX_AGE_SECONDS`, un jour par défaut) est refusé avec 409 `storage_capability_missing`. Les usages `audit_worm` et `evidence` demandent le verrouillage d'objet et l'intégrité (`audit_worm` aussi la rétention).\n- Les **préfixes de présignature** bornent les clés accessibles par l'API : URL présignées, liste et suppression d'objets ne fonctionnent qu'à l'intérieur.\n\n**Parcours type**\n\n1. Rangez l'identifiant dans le magasin de secrets, puis créez le profil avec `POST /v1/storage/profiles` (statut `unverified`).\n2. Vérifiez-le avec `POST /v1/storage/profiles/{profile_id}/verify` : statut `verified`, `degraded` (une capacité nécessaire manque) ou `failed`.\n3. Émettez des URL présignées avec `POST /v1/storage/profiles/{profile_id}/presign`, listez et supprimez des objets à l'intérieur des préfixes de présignature.\n4. Un changement de fournisseur, de point d'accès, de région, de bucket, d'authentification, de chiffrement ou d'options efface les capacités : vérifiez à nouveau.\n\n**Rôles** : les profils de tenant sont gérés par `tenant_admin` (la vérification aussi par `security_officer`) et lus par `tenant_admin`, `security_officer`, `developer` et `auditor` ; les URL présignées et la suppression d'objets par `tenant_admin`, `developer` et `agent` ; la liste des objets par `tenant_admin`, `developer`, `agent` et `auditor`. Les profils de plateforme sont gérés par `platform_admin` seulement. Chaque action prend d'abord une décision de politique (403 `policy_denied`) et est auditée."
  },
  {
   "name": "system",
   "x-displayName": "Système",
   "description": "Points techniques du Control Plane : sondes de vie et de disponibilité, description de l'instance, identité de l'appelant, catalogue des briques de la plateforme et feuille de route du produit.\n\n**Parcours type**\n\n1. Les orchestrateurs sondent `GET /healthz` (processus vivant) et `GET /readyz` (base de données joignable et migrée).\n2. Un client lit `GET /v1/meta` pour connaître la version de l'API, les langues, les domaines chargés et les intégrations configurées.\n3. Après connexion, le client lit `GET /v1/me` pour connaître son principal, son tenant, ses rôles et ses scopes.\n\n**Accès** : `/healthz`, `/readyz`, `/v1/meta`, le catalogue de briques et la feuille de route ne demandent aucune authentification, et les sondes échappent à la limitation de débit ; `/v1/me` demande un principal authentifié."
  },
  {
   "name": "telemetry",
   "x-displayName": "Télémétrie",
   "description": "Traces, journal des requêtes, flux en direct et objectifs de niveau de service (SLO) de la plateforme.\n\n**Spans** : les applications et les agents envoient des spans OpenTelemetry (OTLP JSON `resourceSpans` ou une simple liste `{spans: [...]}`). PostgreSQL conserve un résumé borné de chaque span : identifiants de trace et de span, exécution, session, durée, statut, coût, jetons et seulement une courte liste autorisée d'attributs (aucun prompt, aucune sortie de modèle ni aucun contenu métier). Le span complet est transmis, au mieux, aux exportateurs configurés (Opik, collecteur OpenTelemetry, ClickHouse) ; c'est le journal d'audit, et non ces magasins, qui fait foi.\n\n**SLO** : chaque requête servie par l'API est comptée par minute et par service (`control_plane_api`, `model_gateway`, `tool_gateway`, `realtime`) : requêtes, erreurs (5xx) et requêtes plus lentes que la cible de latence. Les objectifs d'un service viennent de la base de données dès qu'un administrateur de la plateforme les a écrits, sinon des paramètres `EAI_SLO_*`. Les compteurs appartiennent à la réplique qui a servi la requête ; la vue de l'ensemble sur 30 jours vient des règles d'enregistrement Prometheus.\n\n**Parcours type**\n\n1. Envoyez les spans avec `POST /v1/telemetry/spans`.\n2. Lisez le tableau de bord avec `GET /v1/telemetry/overview`, la liste avec `GET /v1/telemetry/traces` et une trace avec `GET /v1/telemetry/traces/{trace_id}`.\n3. Suivez l'activité en direct avec les Server-Sent Events de `GET /v1/telemetry/stream`.\n4. Vérifiez les budgets d'erreur avec `GET /v1/telemetry/slo` et ajustez-les avec `PUT /v1/telemetry/slo/targets/{service}`.\n\n**Rôles** : l'ingestion demande `tenant_admin`, `developer` ou `agent` ; la liste des traces, le journal, le flux et les SLO demandent `platform_admin`, `tenant_admin`, `auditor`, `viewer` ou `support_engineer` ; la vue d'ensemble et une trace demandent un principal de tenant quelconque ; modifier un objectif demande `platform_admin`. Les lignes sont toujours celles du tenant de l'appelant."
  },
  {
   "name": "telephony",
   "x-displayName": "Téléphonie",
   "description": "**Téléphonie** (ADR-0173) : PBX d'entreprise, bord SIP et agents vocaux IA du tenant. Navin Enterprise garde les données (trunks, extensions, numéros, règles de routage, flux SVI, files, agents humains et IA, conférences, quotas), compile les fichiers gérés d'Asterisk, les tables de routage Kamailio et les objets LiveKit SIP par pool PBX, et les publie vers les moteurs par AMI, JSONRPC et Twirp avec versions et retour arrière ; il n'exécute jamais de commande système. Le Navin Manager installe et exploite les moteurs.\n\n**Parcours type**\n1. `platform_admin` déclare le pool (`POST /v1/telephony/pbx-pools`) et l'attribue (`PUT /v1/telephony/settings`), puis fixe les quotas (`PUT /v1/telephony/quotas`).\n2. L'administrateur du tenant crée trunks, extensions, files, flux SVI (et publie une version), agents vocaux et numéros.\n3. `POST /v1/telephony/preview` ou `POST .../pbx-pools/{pool_id}/preview` montre le résultat compilé et ses problèmes.\n4. `POST .../pbx-pools/{pool_id}/publish` le livre (202, opération longue) ; `GET /v1/telephony/config-versions/{version_id}` le suit ; `POST .../rollback` revient en arrière.\n\n**Isolation** : chaque objet appartient au tenant de l'appelant ; l'objet d'un autre tenant répond 404 et une référence vers lui 422. Sur les moteurs chaque nom commence par le `tid` du tenant et le compilateur refuse un contexte qui désigne un autre tenant. **Secrets** (mots de passe SIP, codes PIN, identifiants des moteurs) en écriture seule, conservés dans le coffre ; les réponses portent des indicateurs `has_*`. **Rôles** : lecteurs `tenant_admin`, `support_engineer`, `auditor` ; rédacteurs `tenant_admin` ; `platform_admin` pour les pools, les quotas, la publication d'un pool partagé et le drain. Chaque modification est auditée."
  },
  {
   "name": "tenancy",
   "x-displayName": "Tenants et organisation",
   "description": "Les tenants, leur hiérarchie, les projets, les environnements et les membres.\n\nNotions :\n\n- **Tenant** : un espace client isolé. Chaque objet de la plateforme appartient à un tenant ; un identifiant ne voit que le sien. Les tenants sont créés par un administrateur de plateforme.\n- **Organisation** et **unité métier** : la hiérarchie au sein d'un tenant. Une organisation contient des unités métier, qui peuvent être imbriquées (`parent_id`, fixé à la création pour qu'aucun cycle ne puisse apparaître).\n- **Projet** : l'espace de travail qui porte les agents, les bases de connaissances, les quotas et les secrets. Il peut être rattaché à une organisation et à l'une de ses unités. Les appelants le choisissent avec l'en-tête `X-Project`.\n- **Environnement** : `dev`, `test`, `staging` ou `prod`, au plus un de chaque par projet.\n- **Membre** : un utilisateur (sujet OIDC) avec un rôle et une habilitation dans le tenant, éventuellement limité à un projet.\n\nParcours type :\n\n1. Un administrateur de plateforme crée le tenant avec `POST /v1/tenants` et désigne ses premiers administrateurs dans `admins`.\n2. L'administrateur du tenant crée les organisations et les unités métier, puis les projets avec leurs environnements.\n3. L'administrateur du tenant ajoute les membres avec `POST /v1/members`, ou laisse les correspondances de groupes de l'annuaire (tag Identité) le faire à la connexion.\n\nRôles : `platform_admin` crée les tenants et change leur statut ; `tenant_admin` gère le reste. Lire un projet exige aussi la relation ReBAC `viewer` sur le projet ; le modifier `editor` ; le supprimer `owner` (le créateur reçoit `owner`). Les objets de la hiérarchie portent `version` : envoyez `expected_version` dans un PATCH pour ne pas écraser une modification concurrente. Chaque modification écrit un événement d'audit (`tenant.*`, `organization.*`, `business_unit.*`, `project.*`, `environment.create`, `member.*`)."
  },
  {
   "name": "tools",
   "x-displayName": "Outils",
   "description": "**Tool Registry** et **Tool Gateway**. Un outil est une action déclarée avec un schéma d'entrée, un point d'accès (`http`, `mcp`, `builtin`, `sandbox`), une classification de données, un niveau de risque et, en option, une approbation, une limite de débit et un coût. Chaque appel passe par la passerelle, qui applique les contrôles et enregistre une invocation.\n\n**Chaîne de la passerelle** (`POST /v1/tools/{name}/invoke`)\n1. Statut : un outil `blocked` ou `deprecated` est refusé ; une application tierce doit être active et avoir reçu l'outil.\n2. Scopes : `tools:invoke` plus les scopes exigés par l'outil.\n3. Arguments : validés selon le `input_schema` de l'outil.\n4. Octroi à l'agent et classification : avec `agent_id`, l'outil doit être listé dans le manifeste de l'agent et la classification rester sous le plafond de l'agent.\n5. Politique `tool.invoke`, puis DLP sur les arguments.\n6. Validation humaine quand l'outil, l'octroi de l'agent, une obligation de politique ou `autonomy.human_approval_for` l'exige.\n7. Limite de débit, réservation de quota, exécution avec des identifiants côté serveur, DLP et contrôle d'injection de prompt sur le résultat, événement d'usage.\n\n**Rôles** : l'enregistrement demande `tenant_admin`, `developer` ou `application_owner` ; les changements de statut demandent `tenant_admin` ou `security_officer` ; la suppression demande `tenant_admin` ; l'invocation demande `tenant_admin`, `developer` ou `agent`. Les routes sur un outil vérifient la relation ReBAC (`viewer`, `editor`, `owner`, `can_invoke`)."
  }
 ],
 "x-tagGroups": [
  {
   "name": "Identité et accès",
   "tags": [
    "identity",
    "auth",
    "authz",
    "delegation",
    "context",
    "secrets"
   ]
  },
  {
   "name": "Tenants et organisation",
   "tags": [
    "tenancy"
   ]
  },
  {
   "name": "Agents et IA",
   "tags": [
    "agents",
    "llm",
    "llm-management",
    "prompts",
    "skills",
    "memory",
    "multimodal",
    "realtime",
    "evaluation"
   ]
  },
  {
   "name": "Connaissances et données",
   "tags": [
    "knowledge",
    "storage",
    "lineage",
    "navin-library"
   ]
  },
  {
   "name": "Outils et intégrations",
   "tags": [
    "tools",
    "mcp",
    "mcp-runtime",
    "connectors",
    "channels",
    "plugins",
    "marketplace",
    "applications",
    "developer"
   ]
  },
  {
   "name": "Gouvernance et conformité",
   "tags": [
    "governance",
    "policies",
    "approvals",
    "contracts",
    "risk",
    "privacy",
    "retention",
    "evidence",
    "audit"
   ]
  },
  {
   "name": "Sécurité",
   "tags": [
    "security",
    "file-security",
    "soc",
    "incidents"
   ]
  },
  {
   "name": "FinOps",
   "tags": [
    "finops"
   ]
  },
  {
   "name": "Exploitation de la plateforme",
   "tags": [
    "operations",
    "platform-manager",
    "licensing",
    "telemetry",
    "system"
   ]
  },
  {
   "name": "Autres",
   "tags": [
    "compliance",
    "rag-agent",
    "telephony"
   ]
  }
 ]
}
