{
  "openapi": "3.0.0",
  "info": {
    "title": "Hubdoc API",
    "description": "API de la GED Hubdoc : workspaces, dossiers, documents, éditique\n(Markdown/Typst), uploads, annuaire (users, contacts, groupes) et\npermissions.\n\n## Authentification\n\nToutes les requêtes exigent un token `Bearer` OAuth2 (ou PAT) avec le\nscope adapté au verbe HTTP : `read` pour les `GET`, `write` pour les\nmutations. L'obtention des tokens (flows OAuth2, device flow CLI,\ntoken exchange Edifice, PAT) est documentée sur la page\n[Authentification](../authentication/).\n",
    "version": "1.0.0",
    "contact": {
      "name": "API Support",
      "email": "support@sinoia.fr"
    }
  },
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Serveur de développement"
    },
    {
      "url": "https://rec.hubdoc.sinoia.cloud",
      "description": "Serveur de recette"
    }
  ],
  "tags": [
    {
      "name": "Workspaces",
      "description": "Workspace management",
      "x-displayName": "Workspaces"
    },
    {
      "name": "Folders",
      "description": "Folder management",
      "x-displayName": "Folders"
    },
    {
      "name": "Documents",
      "description": "Document management",
      "x-displayName": "Documents"
    },
    {
      "name": "Composed Documents",
      "description": "Composed Documents — éditique Markdown/Typst. Permet de créer des\ndocuments à partir d'un template, gérer les parts (sections), et\ncompiler en PDF via le pipeline Typst.\n",
      "x-displayName": "Composed Documents"
    },
    {
      "name": "Document Templates",
      "description": "Document Templates — modèles Typst/Markdown réutilisables.\nRead-only pour tous les users, CRUD réservé aux admins.\n",
      "x-displayName": "Document Templates"
    },
    {
      "name": "BulkUploads",
      "description": "Bulk upload operations",
      "x-displayName": "BulkUploads"
    },
    {
      "name": "ChunkedUploads",
      "description": "Chunked upload operations",
      "x-displayName": "ChunkedUploads"
    },
    {
      "name": "Contacts",
      "description": "Contact management",
      "x-displayName": "Contacts"
    },
    {
      "name": "Groups",
      "description": "Group management",
      "x-displayName": "Groups"
    },
    {
      "name": "Group Members",
      "description": "Membres d'un groupe (ajout, retrait, liste)",
      "x-displayName": "Group Members"
    },
    {
      "name": "Permissions",
      "description": "Permission management",
      "x-displayName": "Permissions"
    },
    {
      "name": "Public Access",
      "description": "Accès public — la **troisième voie d'accès**, à côté des ACL nominatives\net des liens de partage. Rendre public, rendre privé, repropager,\nexclure, lever une exclusion, et lire l'état qui en résulte.\n\n## Deux modèles de propagation, délibérément différents\n\n|  | ACL nominatives | Accès public |\n|---|---|---|\n| Mécanisme | ascendance évaluée **à la lecture** | **estampillage explicite**, ligne par ressource |\n| Document ajouté après | hérite immédiatement | **pas public** tant qu'on n'a pas repropagé |\n| Document déplacé hors du sous-arbre | perd le droit | concession périmée |\n| Retrait sur une ressource | impossible sans permission négative | marqueur d'exclusion, transitif |\n\nLa divergence est **volontaire** : fail-closed assumé, pour qu'aucune\nressource ne soit publique sans qu'un dossier l'annonce. Elle surprend,\nd'où `subtree.unstamped` et `reason` dans l'état rendu.\n\n## Ce que l'accès public N'ouvre PAS\n\n- aucune **écriture** : le court-circuit est borné à la lecture, un accès\n  public ne sert donc jamais à obtenir le droit de publier ;\n- aucune **indexation** : une ressource publique n'est délibérément pas\n  cherchable ;\n- la racine d'une publication est toujours un **dossier**.\n",
      "x-displayName": "Public Access"
    },
    {
      "name": "Users",
      "description": "User management",
      "x-displayName": "Users"
    },
    {
      "name": "Mass Communications",
      "description": "Mass communication campaigns",
      "x-displayName": "Mass Communications"
    },
    {
      "name": "Loctavia — Mandataires",
      "x-displayName": "Mandataires",
      "description": "Point d'entrée de l'API Loctavia : liste les mandataires (tenants)\naccessibles à l'utilisateur. L'`id` retourné alimente l'en-tête\n`X-Mandataire-Id` requis par tous les autres endpoints Loctavia.\nAuth Bearer (PAT, ex. via le device flow CLI).\n"
    },
    {
      "name": "Loctavia — Baux",
      "x-displayName": "Baux",
      "description": "Baux (leases) — création avec conditions financières par lot, terme d'indexation et locataire."
    },
    {
      "name": "Loctavia — Quittancements",
      "x-displayName": "Quittancements",
      "description": "Quittancements (billing runs). Cycle : `POST /billing_runs` (calcul\nasynchrone — poller le détail jusqu'au statut `computed`/`review`),\nrelecture des lignes (`/lines`, exclusion ou acquittement d'anomalies),\npuis `validate` et `apply`.\n"
    },
    {
      "name": "Loctavia — Mandats",
      "x-displayName": "Mandats",
      "description": "Mandats de gestion (propriétaire × immeuble, honoraires)."
    },
    {
      "name": "Loctavia — Patrimoine",
      "x-displayName": "Patrimoine",
      "description": "Immeubles (properties) et lots (units)."
    },
    {
      "name": "Loctavia — Tiers",
      "x-displayName": "Tiers",
      "description": "Propriétaires et locataires (recherche texte libre via `q`)."
    },
    {
      "name": "Loctavia — Paiements",
      "x-displayName": "Paiements",
      "description": "Encaissements (lecture seule)."
    },
    {
      "name": "Loctavia — Incidents",
      "x-displayName": "Incidents",
      "description": "Incidents locataires (lecture seule — la déclaration passe par le portail)."
    },
    {
      "name": "Loctavia — Comptabilité",
      "x-displayName": "Comptabilité",
      "description": "Écritures comptables en partie double (immuables, correction par\nextourne, cinq journaux : LOYERS, CHARGES, BANQUE, PROPRIO, OD) et\nexport FEC réglementaire (arrêté du 29 juillet 2013). La génération\ndu FEC est synchrone : le `201` du `POST /fec_exports` signifie que\nle fichier est prêt au téléchargement.\n"
    },
    {
      "name": "Octopia",
      "description": "Octopia Knowledge Graph API — ingestion de données externes via connecteurs",
      "x-displayName": "Octopia"
    },
    {
      "name": "Octopia — Mémoire",
      "x-displayName": "Mémoire",
      "description": "Cerveau d'entreprise : mémoire sémantique partagée entre devs et agents.\nChaque mémoire porte son auteur (`user_id`), est recherchable\nsémantiquement par tous, et se rattache à des entités du graphe (client,\nprojet…) via `links` — ces entités sont les points de convergence\n(documents hubdoc, mémoires…) qui rendent le graphe naviguable.\n"
    },
    {
      "name": "Authentication",
      "description": "OAuth2 authentication endpoints",
      "x-displayName": "Authentication"
    },
    {
      "name": "CLI Auth",
      "description": "Endpoints du device flow CLI (RFC 8628). Permettent au binaire\n`hubdoc` (cf sinoia/hubdoc-tools) de s'authentifier sans\nmanipulation manuelle de tokens : `hubdoc login` ouvre le\nnavigateur, l'user confirme, le CLI ramasse son PAT\nautomatiquement.\n",
      "x-displayName": "CLI Auth"
    }
  ],
  "paths": {
    "/api/v1/documents/workspaces": {
      "get": {
        "summary": "Lister les espaces de travail",
        "description": "Récupérer la liste de tous les espaces de travail",
        "tags": [
          "Workspaces"
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Workspace"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un espace de travail",
        "description": "Créer un nouvel espace de travail",
        "tags": [
          "Workspaces"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Espace de travail créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/workspaces/{id}": {
      "get": {
        "summary": "Récupérer un espace de travail",
        "description": "Récupérer un espace de travail spécifique par identifiant",
        "tags": [
          "Workspaces"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour un espace de travail",
        "description": "Mettre à jour un espace de travail existant",
        "tags": [
          "Workspaces"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Espace de travail mis à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un espace de travail",
        "description": "Supprimer un espace de travail existant",
        "tags": [
          "Workspaces"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Espace de travail supprimé avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/folders": {
      "get": {
        "summary": "Lister les dossiers",
        "description": "Récupère une liste de dossiers selon les paramètres de filtrage fournis.\n\n**Filtrage par contexte :**\n- Sans paramètres : tous les dossiers accessibles à l'utilisateur\n- `workspace_id` : dossiers d'un workspace spécifique\n- `documents_folder_id` : sous-dossiers d'un dossier parent spécifique\n\n**Filtrage avancé avec Ransack :**\n- Utilisez le paramètre `q` pour des recherches avancées\n- Exemple : `q[name_cont]=admin` pour chercher les dossiers contenant \"admin\"\n",
        "tags": [
          "Folders"
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "description": "ID du workspace pour filtrer les dossiers",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "documents_folder_id",
            "in": "query",
            "description": "ID du dossier parent pour filtrer les sous-dossiers",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Champ de tri (name, updated_at, created_at, etc.)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "updated_at"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Direction du tri",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Nombre d'éléments par page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Numéro de page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filtres Ransack pour recherche avancée.\nExemples :\n- `q[name_cont]=projet` : nom contenant \"projet\"\n- `q[description_present]=true` : avec description\n- `q[color_eq]=#FF0000` : couleur exacte en hexadecimal\n",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des dossiers récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Folder"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un dossier",
        "description": "Créer un nouveau dossier",
        "tags": [
          "Folders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FolderMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dossier créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Folder"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/folders/{id}": {
      "get": {
        "summary": "Récupérer un dossier avec ses éléments",
        "description": "Récupérer un dossier spécifique par identifiant avec son contenu (sous-dossiers et fichiers)",
        "tags": [
          "Folders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Folder"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "oneOf": [
                              {
                                "allOf": [
                                  {
                                    "$ref": "#/components/schemas/Folder"
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "type": {
                                        "type": "string",
                                        "enum": [
                                          "folder"
                                        ]
                                      }
                                    }
                                  }
                                ]
                              },
                              {
                                "allOf": [
                                  {
                                    "$ref": "#/components/schemas/Document"
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "type": {
                                        "type": "string",
                                        "enum": [
                                          "file"
                                        ]
                                      }
                                    }
                                  }
                                ]
                              }
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour un dossier",
        "description": "Mettre à jour un dossier existant",
        "tags": [
          "Folders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FolderMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dossier mis à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Folder"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un dossier",
        "description": "Supprimer un dossier existant",
        "tags": [
          "Folders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Dossier supprimé avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/documents": {
      "get": {
        "summary": "Lister les documents",
        "description": "Récupère une liste de documents selon les paramètres de filtrage fournis.\n\n**Filtrage par contexte :**\n- Sans paramètres : tous les documents accessibles à l'utilisateur\n- `workspace_id` : documents d'un workspace spécifique\n- `documents_folder_id` : documents d'un dossier spécifique\n\n**Filtrage avancé avec Ransack :**\n- Utilisez le paramètre `q` pour des recherches avancées\n- Exemple : `q[name_cont]=rapport` pour chercher les documents contenant \"rapport\"\n",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "description": "ID du workspace pour filtrer les documents",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "documents_folder_id",
            "in": "query",
            "description": "ID du dossier pour filtrer les documents",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Champ de tri (name, updated_at, created_at, etc.)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "updated_at"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Direction du tri",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Nombre d'éléments par page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Numéro de page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filtres Ransack pour recherche avancée.\nExemples :\n- `q[name_cont]=test` : nom contenant \"test\"\n- `q[description_present]=true` : avec description\n",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des documents récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Document"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un document",
        "description": "Créer un document en uploadant un fichier (classique ou chunked).\n\n**Deux modes d'upload :**\n- **Mode classique** : Upload direct d'un fichier via `uploaded_file`\n- **Mode chunked** : Référence à un upload chunked complété via `chunked_upload_id`\n\n**Deux scénarios :**\n- **Sans bulk_upload_id** : Crée automatiquement un BulkUpload pour traçabilité\n- **Avec bulk_upload_id** : Attache le fichier à un BulkUpload existant (cas hubdoc-tools)\n\n**Mode fusion (bulk_upload avec `merge_to_pdf`)** : le fichier est stagé\n(réponse `202 Accepted`, aucun document individuel créé) à sa `position`\n(obligatoire, unique, à partir de 0). Quand `total_files` sources ont été\nreçues, elles sont fusionnées en un seul document PDF dans l'ordre des\npositions ; son ID est exposé par `GET /bulk_uploads/:id`\n(`merged_document_id`). Formats acceptés : PDF, JPEG, PNG.\n\n**Fonctionnalités :**\n- Classification automatique optionnelle\n- Métadonnées utilisateur personnalisées\n- Source automatique basée sur l'application OAuth\n",
        "tags": [
          "Documents"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "bulk_upload_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID d'un BulkUpload existant (optionnel, pour uploads groupés)"
                  },
                  "workspace_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID du workspace"
                  },
                  "documents_folder_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID du dossier parent"
                  },
                  "domain_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID du domaine"
                  },
                  "document_type_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID du type de document"
                  },
                  "auto_classify": {
                    "type": "boolean",
                    "description": "Active la classification automatique des documents",
                    "default": false
                  },
                  "metadata_user": {
                    "type": "object",
                    "description": "Métadonnées utilisateur personnalisées"
                  },
                  "uploaded_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Fichier à uploader (soit uploaded_file, soit chunked_upload_id requis)"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Position du fichier dans le PDF fusionné (requis si le BulkUpload est en mode merge_to_pdf)"
                  },
                  "pages": {
                    "type": "string",
                    "description": "Réservé (sélection de plages de pages, non implémenté — envoyer null)"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Pour uploads chunked uniquement",
                "properties": {
                  "bulk_upload_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID d'un BulkUpload existant (optionnel)"
                  },
                  "chunked_upload_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID d'un upload chunked complété"
                  },
                  "workspace_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID du workspace"
                  },
                  "documents_folder_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "ID du dossier parent"
                  },
                  "auto_classify": {
                    "type": "boolean",
                    "default": false
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Position du fichier dans le PDF fusionné (requis si le BulkUpload est en mode merge_to_pdf)"
                  }
                },
                "required": [
                  "chunked_upload_id"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Document"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "bulk_upload_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "ID du BulkUpload associé"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Fichier stagé pour fusion (BulkUpload en mode merge_to_pdf), aucun document individuel créé",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "staged": {
                      "type": "boolean",
                      "description": "Toujours true — le fichier attend la fusion"
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Position du fichier dans le PDF fusionné"
                    },
                    "bulk_upload": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "received_files": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "total_files": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "in_progress",
                            "completed",
                            "failed"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "received_files",
                        "total_files",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "staged",
                    "position",
                    "bulk_upload"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/documents/{id}": {
      "get": {
        "summary": "Récupérer un document",
        "description": "Récupérer un document spécifique par identifiant",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour un document",
        "description": "Mettre à jour un document existant.\n\n**Deux modes :**\n- **application/json** : Mise à jour des métadonnées uniquement (name, description, etc.)\n- **multipart/form-data** : Remplacement du fichier via `uploaded_file`\n",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentMutation"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/DocumentMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document mis à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un document",
        "description": "Supprimer un document existant",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Document supprimé avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/documents/{id}/versions": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer une nouvelle version d'un document",
        "description": "Crée une nouvelle **version** du document : le contenu courant est d'abord\narchivé comme version (historique), puis remplacé par le fichier uploadé.\nL'identifiant du document reste inchangé.\n\nÀ utiliser lorsqu'un livrable déjà présent doit être **mis à jour** sans\ncréer de doublon ni casser les liens existants (ex. resync d'un artefact\nmodifié par un agent Hubdoc/Synapse).\n",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "uploaded_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Nouveau contenu du document (soit uploaded_file, soit chunked_upload_id requis)"
                  },
                  "chunked_upload_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Référence à un upload chunked complété (alternative à uploaded_file)"
                  },
                  "comment": {
                    "type": "string",
                    "description": "Commentaire optionnel associé à la version créée"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Pour versions à partir d'un upload chunked uniquement",
                "properties": {
                  "chunked_upload_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Référence à un upload chunked complété"
                  },
                  "comment": {
                    "type": "string",
                    "description": "Commentaire optionnel associé à la version créée"
                  }
                },
                "required": [
                  "chunked_upload_id"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Nouvelle version créée ; le document (contenu courant mis à jour) est renvoyé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/folders/{folder_id}/public_access": {
      "get": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Lire l'état d'accès public d'un dossier",
        "description": "Rend l'état complet : publique ou non, la racine qui l'explique, les\nexclusions, et le bilan du sous-arbre — **dont les ressources non\nestampillées**, celles qu'une repropagation rendrait publiques.\n\nExige le droit d'**écriture** sur le dossier, et non de lecture : l'état\nénumère nommément le complément privé du sous-arbre publié. L'ouvrir en\nlecture rendrait un dossier public utilisable comme annuaire de ce qu'il\nne publie pas.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FolderIdParameter"
          },
          {
            "$ref": "#/components/parameters/PublicAccessLimitParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "État d'accès public du dossier",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Publier un dossier (rendre public)",
        "description": "Publie le dossier comme **racine publique** et propage la concession sur\ntout son sous-arbre. Geste explicite, tracé et révocable.\n\nC'est la porte générique du « rendre public » : elle remplace le détour\npar `POST /api/v1/documents/folders/{id}/site`, qui reste servi le temps\nque le CLI publié migre (hubdoc-tools#29) mais ne doit plus être employé\npour ce geste.\n\n**La racine d'une publication est toujours un dossier** : rendre un\ndocument isolé public n'existe pas. Les documents sont couverts par la\npropagation depuis un dossier.\n\nIdempotent : republier un dossier déjà publié rend `200` et ne ressuscite\naucune concession que personne n'a reprise. Publier lève l'exclusion\nportée par le dossier lui-même (celles de ses descendants restent).\n\nAction à conséquence : la portée doit être annoncée à l'utilisateur\n**avant** validation, jamais après.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FolderIdParameter"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "propagate": {
                    "type": "boolean",
                    "default": true,
                    "description": "`false` publie la racine SANS estampiller le sous-arbre : seul\nle dossier devient public. Réservé aux publications en deux\ntemps ; la propagation reste alors à faire explicitement.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dossier déjà publié — état inchangé (idempotence)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "201": {
            "description": "Dossier publié",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Dépublier un dossier (rendre privé)",
        "description": "Retire la racine publique. **Une seule écriture** : toutes les concessions\ndu sous-arbre sont conditionnées à la présence de cette racine, elles\npériment donc d'un coup, sans dépropagation ni tâche de fond.\n\nIdempotent. L'état rendu après coup n'est pas redondant : un dossier\nimbriqué dans un AUTRE sous-arbre publié reste public une fois dépublié,\net seul l'état le dit.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FolderIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "État d'accès public après dépublication",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/folders/{folder_id}/public_access/propagate": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Repropager l'accès public sur le sous-arbre",
        "description": "Réestampille tout le sous-arbre d'une racine publiée : l'existant est\nréestampillé, les ajouts sont couverts. Idempotent.\n\n**C'est la contrepartie assumée du fail-closed.** Un document déposé dans\nun dossier publié n'est pas public tant que ce geste n'a pas eu lieu :\n`subtree.unstamped` le compte, ce geste le résorbe. Un agent qui régénère\nun site doit enchaîner régénération et repropagation.\n\nLes ressources explicitement exclues ne sont jamais réestampillées — sans\nquoi l'exclusion serait illusoire dès la première régénération.\n\nÉchoue en `422` si le dossier n'est pas publié comme racine : propager\nn'est pas un raccourci pour publier.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FolderIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "État d'accès public après repropagation (`granted_count` renseigné)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/folders/{folder_id}/public_access/exclusion": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Exclure un dossier de l'accès public",
        "description": "Pose le marqueur « jamais public ici » sur le dossier et coupe l'accès\n**immédiatement** : les concessions qu'il portait sont retirées, celles de\nson sous-arbre aussi.\n\n**La portée est TRANSITIVE** : exclure un dossier exclut tout ce qu'il\ncontient, aujourd'hui et demain. Elle doit être annoncée à l'utilisateur\navant validation — sans quoi « jamais public ici » ne protégerait que le\nlibellé du dossier pendant que son contenu resterait lisible par URL\ndirecte.\n\nLe marqueur **survit aux repropagations** et **voyage avec la ressource** :\nun dossier exclu déplacé dans un autre sous-arbre public reste exclu. Le\nretirer est un geste explicite (`DELETE`).\n\nExclure un dossier qui est lui-même une racine publiée le dépublie : c'est\nbien le sens du geste.\n\nIdempotent : `200` si l'exclusion existait déjà, `201` sinon.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FolderIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Exclusion déjà en place (idempotence)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "201": {
            "description": "Exclusion posée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Lever l'exclusion d'un dossier",
        "description": "Retire le marqueur « jamais public ici ». **Ne rend rien public** : cela\nrend seulement le dossier à nouveau éligible à une propagation, qui reste\nun geste distinct.\n\nIdempotent.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FolderIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "État d'accès public après levée de l'exclusion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/documents/{file_id}/public_access": {
      "get": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Lire l'état d'accès public d'un document",
        "description": "Rend l'état complet du document : publique ou non, **quelle racine**\nl'explique, s'il est exclu et par quoi.\n\nLe champ `reason` porte le diagnostic quand le document n'est pas public.\n`stale` y est le cas le plus instructif : le document a été estampillé un\njour, mais sa racine a été dépubliée ou lui-même déplacé — la concession\nest périmée, sans qu'aucune écriture n'ait été nécessaire.\n\n`subtree` est toujours `null` : un document n'a pas de sous-arbre.\n\nExige le droit d'**écriture** sur le document (cf. l'endpoint dossier).\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FileIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "État d'accès public du document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/documents/{file_id}/public_access/exclusion": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Exclure un document de l'accès public",
        "description": "Pose le marqueur « jamais public ici » sur le document et coupe l'accès\nimmédiatement : la concession qu'il portait est retirée.\n\nC'est le SEUL moyen de retirer une ressource d'un sous-arbre public sans\ndépublier la racine entière.\n\nLe marqueur **survit aux repropagations** et **voyage avec le document** :\ndéplacé dans un autre sous-arbre public, il reste exclu — un marqueur de\nconfidentialité qui s'évaporerait au déplacement serait le pire des deux\nmondes.\n\nIdempotent : `200` si l'exclusion existait déjà, `201` sinon.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FileIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Exclusion déjà en place (idempotence)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "201": {
            "description": "Exclusion posée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Lever l'exclusion d'un document",
        "description": "Retire le marqueur « jamais public ici ». **Ne rend rien public** : le\ndocument redevient seulement éligible à une propagation, qu'il faut\ndéclencher explicitement sur la racine (`POST …/public_access/propagate`).\n\nIdempotent.\n",
        "tags": [
          "Public Access"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FileIdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "État d'accès public après levée de l'exclusion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAccessState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/composed_documents": {
      "get": {
        "summary": "Lister les documents composés",
        "description": "Liste les `ComposedDocument` accessibles à l'utilisateur (scopés\npar `ComposedDocumentPolicy::Scope`).\n\nFiltres optionnels :\n- `workspace_id` : workspace propriétaire\n- `folder_id` : dossier propriétaire\n- `status` : draft / in_progress / ready / exported\n",
        "tags": [
          "Composed Documents"
        ],
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "folder_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "in_progress",
                "ready",
                "exported"
              ]
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste paginée",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "composed_documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ComposedDocument"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un document composé à partir d'un template",
        "description": "Instancie un `ComposedDocument` depuis un `DocumentTemplate`\nidentifié par sa `key`. Le typst_source, variables_schema et\ndefault_parts sont hérités du template ; les `variables` du body\nremplissent les placeholders.\n\nPipeline d'interactor : `Documents::ComposedDocuments::CreateFromTemplate`.\n",
        "tags": [
          "Composed Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ComposedDocumentMutation"
                  },
                  {
                    "type": "object",
                    "required": [
                      "template_key"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document composé créé",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "composed_document": {
                      "$ref": "#/components/schemas/ComposedDocument"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/api/v1/documents/composed_documents/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdParameter"
        }
      ],
      "get": {
        "summary": "Récupérer un document composé",
        "tags": [
          "Composed Documents"
        ],
        "responses": {
          "200": {
            "description": "Document composé",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "composed_document": {
                      "$ref": "#/components/schemas/ComposedDocument"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Modifier un document composé",
        "description": "Met à jour les attributs modifiables : `name`, `description`,\n`variables`, `page_settings`, `metadata_user`. Le `typst_source`\nn'est pas modifié directement par cette route — pour modifier le\ncontenu, utiliser les endpoints `/parts`.\n",
        "tags": [
          "Composed Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComposedDocumentMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mis à jour",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "composed_document": {
                      "$ref": "#/components/schemas/ComposedDocument"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un document composé",
        "tags": [
          "Composed Documents"
        ],
        "responses": {
          "204": {
            "description": "Supprimé"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/documents/composed_documents/{id}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdParameter"
        }
      ],
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Compiler le document composé en PDF",
        "description": "Déclenche le pipeline `Documents::ComposedDocuments::AssembleAndCompile` :\n\n1. Assemble les `parts` (markdown converti, typst concaténé)\n2. Compile via `Documents::TypstCompiler` → PDF\n3. Attache le PDF au `Documents::File` du composed_document\n4. Passe le status à `exported`\n5. Republie pour ré-indexation search\n\n**Synchrone** : la réponse n'arrive qu'une fois le PDF compilé et\nattaché — pas de polling nécessaire côté client.\n\nRetourne une structure PLATE dédiée au résultat d'export (le PDF est\nen tête via `pdf_file_id`). Le détail du composé reste accessible via\n`GET /composed_documents/:id`.\n",
        "tags": [
          "Composed Documents"
        ],
        "parameters": [
          {
            "name": "include_draft",
            "in": "query",
            "description": "Inclure les parts en statut `draft` dans le rendu (défaut false).",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Compilation réussie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "composed_document_id",
                    "pdf_file_id"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "composed_document_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "pdf_file_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "pdf_url": {
                      "type": "string",
                      "example": "https://hq.sinoia.cloud/documents/browse/files/019e6ec8-86ee-7518-9d1e-25e6a2f746bf"
                    },
                    "pdf_byte_size": {
                      "type": "integer",
                      "nullable": true,
                      "example": 12415202
                    },
                    "took_ms": {
                      "type": "integer",
                      "example": 22341
                    },
                    "warnings": {
                      "type": "array",
                      "description": "Avertissements non bloquants remontés par l'assembler (#463) —\np.ex. un `header:`/`footer:`/`margin:` défini dans le\n`typst_source` qui aurait été écrasé par une part ou par\npage_settings : la directive concurrente est ignorée et un\nwarning est émis ici.\n",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Échec de compilation Typst",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/documents/composed_documents/{id}/parts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdParameter"
        }
      ],
      "get": {
        "summary": "Lister les parts d'un document composé",
        "description": "Retourne toutes les `Documents::Part` du composed_document,\nordonnées par `position`.\n",
        "tags": [
          "Composed Documents"
        ],
        "responses": {
          "200": {
            "description": "Liste des parts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Part"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/api/v1/documents/composed_documents/{id}/parts/{part_id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "ID du composed_document parent",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "part_id",
          "in": "path",
          "required": true,
          "description": "ID de la part",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "summary": "Récupérer une part",
        "tags": [
          "Composed Documents"
        ],
        "responses": {
          "200": {
            "description": "Part",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "part": {
                      "$ref": "#/components/schemas/Part"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Modifier une part",
        "description": "Met à jour les attributs modifiables d'une part : title, content,\ncontent_format, position, status, alignment, page_break, etc.\n\nLes transitions de status sont validées par\n`Documents::Part::VALID_TRANSITIONS`.\n",
        "tags": [
          "Composed Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Part mise à jour",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "part": {
                      "$ref": "#/components/schemas/Part"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer une part",
        "tags": [
          "Composed Documents"
        ],
        "responses": {
          "204": {
            "description": "Supprimée"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/documents/composed_documents/{id}/parts/reorder": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdParameter"
        }
      ],
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Réordonner les parts d'un document composé",
        "description": "Met à jour les positions de toutes les parts en un seul appel.\nLe body est un array `[{ id: uuid, position: int }, ...]`.\n\nToutes les parts du document doivent être incluses (sinon les\nomises restent à leur position actuelle, mais on recommande de\ntoujours envoyer la liste complète).\n",
        "tags": [
          "Composed Documents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "parts"
                ],
                "properties": {
                  "parts": {
                    "type": "array",
                    "description": "Liste {id, position} pour chaque part à repositionner",
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "position"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Parts réordonnées",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Part"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/api/v1/documents/document_templates": {
      "get": {
        "summary": "Lister les templates de document actifs",
        "description": "Retourne tous les `DocumentTemplate` actifs, triés par nom.\n\nTemplates = modèles de documents Typst/Markdown servant de base à\nla création de `ComposedDocument`. Read-only pour tout user\nauthentifié, CRUD réservé aux admins.\n",
        "tags": [
          "Document Templates"
        ],
        "responses": {
          "200": {
            "description": "Liste des templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DocumentTemplate"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un nouveau template (admin only)",
        "description": "Crée un `DocumentTemplate`. Réservé aux administrateurs (Pundit\n`DocumentTemplatePolicy#create?` = `administrator?`).\n\nLe `typst_source` et la `key` sont obligatoires. `key` doit être\nunique.\n",
        "tags": [
          "Document Templates"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentTemplateMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Template créé",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "template": {
                      "$ref": "#/components/schemas/DocumentTemplate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/document_templates/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdParameter"
        }
      ],
      "get": {
        "summary": "Récupérer un template",
        "tags": [
          "Document Templates"
        ],
        "responses": {
          "200": {
            "description": "Template récupéré",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "template": {
                      "$ref": "#/components/schemas/DocumentTemplate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Modifier un template (admin only)",
        "description": "Met à jour les attributs d'un template. Seuls les champs présents\ndans le body sont touchés. Réservé aux administrateurs.\n",
        "tags": [
          "Document Templates"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentTemplateMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template mis à jour",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "template": {
                      "$ref": "#/components/schemas/DocumentTemplate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un template (admin only)",
        "description": "Supprime un template. Refuse si des `composed_documents` y sont\nrattachés (422). Passer `?force=true` pour nullifier les\nréférences au lieu de bloquer.\n",
        "tags": [
          "Document Templates"
        ],
        "parameters": [
          {
            "name": "force",
            "in": "query",
            "description": "Nullifier les composed_documents rattachés au lieu de bloquer",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Template supprimé"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/api/v1/documents/bulk_uploads": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un BulkUpload",
        "description": "Créer un BulkUpload pour regrouper plusieurs fichiers uploadés séparément.\n\n**Workflow typique** :\n1. Créer un BulkUpload avec le nombre total de fichiers\n2. Uploader chaque fichier via POST /api/v1/documents/documents en passant le bulk_upload_id\n3. Récupérer le statut final via GET /api/v1/documents/bulk_uploads/:id\n\n**Cas d'usage** :\n- Import en masse depuis hubdoc-tools\n- Upload parallélisé de gros volumes\n- Traçabilité d'un lot d'uploads\n\n**Mode fusion PDF** (`merge_to_pdf: true` + `merged_file_name`) : les\nfichiers du lot ne créent pas de documents individuels ; chacun est envoyé\navec une `position` explicite, puis toutes les sources sont fusionnées en\nun seul document PDF dans l'ordre des positions dès que `total_files`\nsources ont été reçues. Suivre `GET /bulk_uploads/:id` jusqu'au statut\n`completed` pour récupérer `merged_document_id`. Formats acceptés : PDF,\nJPEG, PNG (les images deviennent une page A4).\n",
        "tags": [
          "BulkUploads"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkUploadMutation"
              },
              "examples": {
                "basic": {
                  "summary": "BulkUpload basique",
                  "value": {
                    "total_files": 50,
                    "source": "hubdoc-tools"
                  }
                },
                "with_folder": {
                  "summary": "Avec dossier de destination",
                  "value": {
                    "total_files": 25,
                    "documents_folder_id": "123e4567-e89b-12d3-a456-426614174000",
                    "workspace_id": "987e6543-e21b-12d3-a456-426614174000",
                    "auto_classify": true,
                    "source": "hubdoc-tools"
                  }
                },
                "merge_to_pdf": {
                  "summary": "Fusion du lot en un seul PDF",
                  "value": {
                    "total_files": 3,
                    "documents_folder_id": "123e4567-e89b-12d3-a456-426614174000",
                    "merge_to_pdf": true,
                    "merged_file_name": "dossier_complet",
                    "source": "hubdoc-tools"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "BulkUpload créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkUpload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/bulk_uploads/{id}": {
      "get": {
        "summary": "Récupérer un BulkUpload",
        "description": "Récupère les informations et le statut d'un BulkUpload.\n\n**Utilisation** :\n- Suivre la progression d'un import en masse\n- Vérifier le statut final après tous les uploads\n- Obtenir le nombre de fichiers en succès/échec\n",
        "tags": [
          "BulkUploads"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "ID du BulkUpload"
          }
        ],
        "responses": {
          "200": {
            "description": "BulkUpload récupéré avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkUpload"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/api/v1/documents/chunked_uploads": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Initialiser une session d'upload par chunks",
        "description": "Crée une session d'upload pour un fichier volumineux qui sera uploadé par morceaux (chunks).\n\n**Workflow typique** :\n1. Initialiser la session avec ce endpoint (retourne un upload_id)\n2. Uploader chaque chunk via PATCH /api/v1/documents/chunked_uploads/{upload_id}/chunks/{chunk_number}\n3. Finaliser l'upload via POST /api/v1/documents/chunked_uploads/{upload_id}/complete\n4. Créer le document final via POST /api/v1/documents/documents avec le chunked_upload_id\n\n**Limites** :\n- Taille max de fichier : 5 GB\n- Taille de chunk recommandée : 5 MB\n- Durée de vie de la session : 24 heures\n\n**Cas d'usage** :\n- Upload de fichiers > 10 MB\n- Upload avec suivi de progression granulaire\n- Upload avec reprise en cas d'interruption\n",
        "tags": [
          "ChunkedUploads"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChunkedUploadMutation"
              },
              "examples": {
                "basic": {
                  "summary": "Session basique",
                  "value": {
                    "filename": "document.pdf",
                    "file_size": 52428800,
                    "content_type": "application/pdf",
                    "chunk_size": 5242880
                  }
                },
                "with_folder": {
                  "summary": "Avec dossier de destination",
                  "value": {
                    "filename": "presentation.pptx",
                    "file_size": 104857600,
                    "content_type": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
                    "chunk_size": 5242880,
                    "workspace_id": "987e6543-e21b-12d3-a456-426614174000",
                    "documents_folder_id": "123e4567-e89b-12d3-a456-426614174000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session créée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ChunkedUploadSessionResponse"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "upload_id": "a1b2c3d4e5f6",
                    "chunk_size": 5242880,
                    "total_chunks": 10,
                    "expires_at": "2024-11-10T10:00:00Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/chunked_uploads/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifiant unique de la session d'upload (upload_id)",
          "schema": {
            "type": "string"
          },
          "example": "a1b2c3d4e5f6"
        }
      ]
    },
    "/api/v1/documents/chunked_uploads/{id}/chunks/{chunk_number}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifiant unique de la session d'upload (upload_id)",
          "schema": {
            "type": "string"
          },
          "example": "a1b2c3d4e5f6"
        },
        {
          "name": "chunk_number",
          "in": "path",
          "required": true,
          "description": "Numéro du chunk à uploader (1-indexed)",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 5
        }
      ],
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Uploader un chunk",
        "description": "Upload un chunk spécifique d'un fichier dans une session d'upload par chunks.\n\n**Important** :\n- Les chunks sont numérotés à partir de 1\n- Le body doit contenir les données binaires brutes du chunk\n- Content-Type doit être application/octet-stream\n- Les chunks peuvent être uploadés dans n'importe quel ordre\n- Les chunks déjà uploadés retournent une réponse idempotente (pas de re-upload)\n\n**Retry** :\n- En cas d'échec, le client peut retry le même chunk\n- L'API gère l'idempotence automatiquement\n\n**Progression** :\n- La réponse inclut la progression globale\n- Utilisez GET /api/v1/documents/chunked_uploads/{id}/status pour un statut détaillé\n",
        "tags": [
          "ChunkedUploads"
        ],
        "requestBody": {
          "required": true,
          "description": "Données binaires du chunk",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chunk uploadé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ChunkedUploadChunkResponse"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "chunk_number": 5,
                    "upload_id": "a1b2c3d4e5f6",
                    "progress": 50,
                    "uploaded_chunks": 5,
                    "total_chunks": 10,
                    "status": "processing"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Paramètres invalides (chunk_number invalide, etc.)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Invalid chunk number"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "410": {
            "description": "Session expirée",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Upload session has expired"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Upload du chunk échoué",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Failed to upload chunk to storage"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/chunked_uploads/{id}/complete": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifiant unique de la session d'upload (upload_id)",
          "schema": {
            "type": "string"
          },
          "example": "a1b2c3d4e5f6"
        }
      ],
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Finaliser l'upload par chunks",
        "description": "Finalise une session d'upload par chunks en assemblant tous les chunks.\n\n**Prérequis** :\n- Tous les chunks doivent avoir été uploadés\n- La session ne doit pas être expirée\n\n**Processus** :\n1. Vérifie que tous les chunks sont présents\n2. Assemble les chunks dans le storage (MinIO/S3)\n3. Vérifie le checksum si fourni (recommandé)\n4. Marque la session comme \"completed\"\n5. Retourne l'object_key pour référence future\n\n**Après finalisation** :\n- Utilisez l'upload_id pour créer le document final via POST /api/v1/documents/documents\n- Le fichier assemblé est disponible dans le storage\n",
        "tags": [
          "ChunkedUploads"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChunkedUploadCompleteRequest"
              },
              "example": {
                "checksum": "1B2M2Y8AsgTpgAmY7PhCfg=="
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload finalisé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ChunkedUploadCompleteResponse"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "upload_id": "a1b2c3d4e5f6",
                    "status": "completed",
                    "assembled_file_size": 52428800,
                    "object_key": "uploads/abc123/document.pdf"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "Impossible de finaliser l'upload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Not all chunks have been uploaded"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/chunked_uploads/{id}/status": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifiant unique de la session d'upload (upload_id)",
          "schema": {
            "type": "string"
          },
          "example": "a1b2c3d4e5f6"
        }
      ],
      "get": {
        "summary": "Obtenir le statut d'un upload",
        "description": "Récupère le statut actuel d'une session d'upload par chunks.\n\n**Informations retournées** :\n- Statut actuel (pending, processing, completed, failed, cancelled, expired)\n- Progression en pourcentage\n- Nombre de chunks uploadés vs total\n- Date d'expiration\n- Indicateur d'expiration\n\n**Cas d'usage** :\n- Suivi de progression pendant l'upload\n- Vérification avant de finaliser\n- Reprise après interruption\n- Monitoring d'uploads parallèles\n",
        "tags": [
          "ChunkedUploads"
        ],
        "responses": {
          "200": {
            "description": "Statut récupéré avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ChunkedUploadStatusResponse"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "upload_id": "a1b2c3d4e5f6",
                    "status": "processing",
                    "progress": 50,
                    "uploaded_chunks": 5,
                    "total_chunks": 10,
                    "expires_at": "2024-11-10T10:00:00Z",
                    "expired": false
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Session d'upload introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Upload session not found"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/api/v1/documents/chunked_uploads/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifiant unique de la session d'upload (upload_id)",
          "schema": {
            "type": "string"
          },
          "example": "a1b2c3d4e5f6"
        }
      ],
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Annuler un upload",
        "description": "Annule une session d'upload par chunks en cours.\n\n**Actions effectuées** :\n- Annule le multipart upload sur le storage\n- Supprime tous les chunks uploadés\n- Marque la session comme \"cancelled\"\n\n**Restrictions** :\n- Ne peut pas annuler un upload déjà complété\n- Les sessions expirées sont automatiquement nettoyées\n\n**Cas d'usage** :\n- Annulation volontaire de l'utilisateur\n- Erreur détectée côté client\n- Upload obsolète ou erroné\n",
        "tags": [
          "ChunkedUploads"
        ],
        "responses": {
          "200": {
            "description": "Upload annulé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "cancelled"
                          ],
                          "example": "cancelled"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "status": "cancelled"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Session d'upload introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Upload session not found"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Impossible d'annuler l'upload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string",
                      "example": "Cannot cancel completed upload"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/documents/typst/fonts": {
      "get": {
        "summary": "Lister les polices Typst disponibles",
        "description": "Catalogue des polices effectivement résolvables par le compilateur\nTypst (mêmes chemins que la compilation des `ComposedDocument`).\nPermet de découvrir les `font_family` valides avant de configurer\n`page_settings.font_family`.\n\nRéponse cachée 1h (les polices ne changent qu'au déploiement / à\nl'installation de paquets).\n",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "include_system",
            "in": "query",
            "description": "Inclure les polices système (/usr/share/fonts) en plus des polices embarquées.",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "with_variants",
            "in": "query",
            "description": "Inclure la liste des variantes (graisses/styles) par famille.",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Catalogue des polices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "fonts",
                    "total",
                    "font_paths"
                  ],
                  "properties": {
                    "fonts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "family",
                          "variants"
                        ],
                        "properties": {
                          "family": {
                            "type": "string",
                            "example": "Plus Jakarta Sans"
                          },
                          "variants": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "example": [
                              "Regular",
                              "Bold",
                              "Italic",
                              "Bold Italic"
                            ]
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer",
                      "example": 238
                    },
                    "font_paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "/usr/share/fonts",
                        "engines/documents/fonts"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/api/v1/contacts": {
      "get": {
        "summary": "Lister les contacts",
        "description": "Récupère une liste de contacts selon les paramètres de filtrage fournis.\n\n**Filtrage avancé avec Ransack :**\n- Utilisez le paramètre `q` pour des recherches avancées\n- Exemple : `q[email_address_cont]=example.com` pour chercher les contacts avec une adresse contenant \"example.com\"\n",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "Champ de tri (email_address, first_name, last_name, created_at, updated_at, etc.)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "updated_at"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Direction du tri",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Nombre d'éléments par page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Numéro de page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filtres Ransack pour recherche avancée.\nExemples :\n- `q[email_address_cont]=example.com` : email contenant \"example.com\"\n- `q[first_name_eq]=John` : prénom exacte\n- `q[last_name_start]=Doe` : nom commençant par \"Doe\"\n- `q[external_id_eq]=ext-123` : identifiant externe exact\n",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des contacts récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Contact"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un contact",
        "description": "Créer un nouveau contact",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/contacts/{id}": {
      "get": {
        "summary": "Récupérer un contact",
        "description": "Récupérer un contact spécifique par identifiant",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour un contact",
        "description": "Mettre à jour un contact existant",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact mis à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un contact",
        "description": "Supprimer un contact existant",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Contact supprimé avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/groups": {
      "get": {
        "summary": "Lister les groupes",
        "description": "Récupère une liste de groupes selon les paramètres de filtrage fournis.\n\n**Filtrage avancé avec Ransack :**\n- Utilisez le paramètre `q` pour des recherches avancées\n- Exemple : `q[name_cont]=admin` pour chercher les groupes contenant \"admin\"\n",
        "tags": [
          "Groups"
        ],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "Champ de tri (name, description, created_at, updated_at, etc.)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "updated_at"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Direction du tri",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Nombre d'éléments par page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Numéro de page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filtres Ransack pour recherche avancée.\nExemples :\n- `q[name_cont]=admin` : nom contenant \"admin\"\n- `q[description_present]=true` : avec description\n- `q[external_id_eq]=ext-group` : identifiant externe exact\n",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des groupes récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Group"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un groupe",
        "description": "Créer un nouveau groupe",
        "tags": [
          "Groups"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Groupe créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/groups/{id}": {
      "get": {
        "summary": "Récupérer un groupe",
        "description": "Récupérer un groupe spécifique par identifiant",
        "tags": [
          "Groups"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour un groupe",
        "description": "Mettre à jour un groupe existant",
        "tags": [
          "Groups"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Groupe mis à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un groupe",
        "description": "Supprimer un groupe existant",
        "tags": [
          "Groups"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Groupe supprimé avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/groups/{group_id}/members": {
      "get": {
        "summary": "Lister les membres d'un groupe",
        "description": "Récupère la liste des membres d'un groupe spécifique.\nRequiert les droits administrateur.\n",
        "tags": [
          "Group Members"
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "description": "Identifiant du groupe",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des membres récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GroupMember"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Ajouter un membre à un groupe",
        "description": "Ajoute un utilisateur comme membre d'un groupe via son identifiant externe.\nL'opération est idempotente : si l'utilisateur est déjà membre, retourne le membership existant avec un statut 200.\nRequiert les droits administrateur.\n",
        "tags": [
          "Group Members"
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "description": "Identifiant du groupe",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMemberMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Le membre existait déjà dans le groupe",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupMember"
                }
              }
            }
          },
          "201": {
            "description": "Membre ajouté avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupMember"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/groups/{group_id}/members/{id}": {
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Retirer un membre d'un groupe",
        "description": "Retire un membre d'un groupe par son identifiant de membership.\nAlternativement, le paramètre `user_external_id` peut être passé pour identifier le membre à retirer.\nRequiert les droits administrateur.\n",
        "tags": [
          "Group Members"
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "description": "Identifiant du groupe",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifiant du membership",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          }
        ],
        "responses": {
          "204": {
            "description": "Membre retiré du groupe avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/permissions": {
      "get": {
        "summary": "Lister les permissions",
        "description": "Récupère une liste de permissions selon les paramètres de filtrage fournis.\n\n**Filtrage avancé avec Ransack :**\n- Utilisez le paramètre `q` pour des recherches avancées\n- Exemple : `q[level_eq]=admin` pour chercher les permissions de niveau admin\n",
        "tags": [
          "Permissions"
        ],
        "parameters": [
          {
            "name": "permissible_type",
            "in": "query",
            "description": "Type de la ressource (ex. Documents::File, Documents::Folder, Documents::Workspace)",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Documents::File"
            }
          },
          {
            "name": "permissible_id",
            "in": "query",
            "description": "ID de la ressource",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Champ de tri (level, actor_type, permissible_type, created_at, updated_at, etc.)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "updated_at"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Direction du tri",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Nombre d'éléments par page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Numéro de page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filtres Ransack pour recherche avancée.\nExemples :\n- `q[level_eq]=admin` : niveau de permission exact\n- `q[actor_type_eq]=User` : type d'acteur exact\n- `q[permissible_type_eq]=Documents::File` : type de ressource exact\n- `q[actor_id_eq]=019951a3-...` : acteur spécifique\n- `q[permissible_id_eq]=019951a3-...` : ressource spécifique\n",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des permissions récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Permission"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer une permission",
        "description": "Créer une nouvelle permission",
        "tags": [
          "Permissions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PermissionMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Permission créée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Permission"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/permissions/{id}": {
      "get": {
        "summary": "Récupérer une permission",
        "description": "Récupérer une permission spécifique par identifiant",
        "tags": [
          "Permissions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Permission"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour une permission",
        "description": "Mettre à jour une permission existante",
        "tags": [
          "Permissions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PermissionMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Permission mise à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Permission"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer une permission",
        "description": "Supprimer une permission existante",
        "tags": [
          "Permissions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Permission supprimée avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/users": {
      "get": {
        "summary": "Lister les utilisateurs",
        "description": "Récupère une liste d'utilisateurs selon les paramètres de filtrage fournis.\n\n**Filtrage avancé avec Ransack :**\n- Utilisez le paramètre `q` pour des recherches avancées\n- Exemple : `q[role_eq]=admin` pour chercher les utilisateurs administrateurs\n",
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "Champ de tri (email_address, first_name, last_name, role, created_at, updated_at, etc.)",
            "required": false,
            "schema": {
              "type": "string",
              "default": "updated_at"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "description": "Direction du tri",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Nombre d'éléments par page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Numéro de page",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filtres Ransack pour recherche avancée.\nExemples :\n- `q[email_address_cont]=john` : email contenant \"john\"\n- `q[role_eq]=admin` : rôle exact\n- `q[first_name_or_last_name_cont]=doe` : prénom ou nom contenant \"doe\"\n",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des utilisateurs récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/User"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un utilisateur",
        "description": "Créer un nouvel utilisateur",
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Utilisateur créé avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/users/{id}": {
      "get": {
        "summary": "Récupérer un utilisateur",
        "description": "Récupérer un utilisateur spécifique par identifiant",
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Réponse réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour un utilisateur",
        "description": "Mettre à jour un utilisateur existant",
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserMutation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Utilisateur mis à jour avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Supprimer un utilisateur",
        "description": "Supprimer un utilisateur existant",
        "tags": [
          "Users"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Utilisateur supprimé avec succès"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/communications/mass_communications": {
      "get": {
        "summary": "Lister les communications de masse",
        "description": "Récupère la liste des communications de masse de l'utilisateur.\n",
        "tags": [
          "Mass Communications"
        ],
        "parameters": [
          {
            "name": "q[name_cont]",
            "in": "query",
            "description": "Filtrer par nom (contient)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q[status_eq]",
            "in": "query",
            "description": "Filtrer par statut",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sending",
                "sent",
                "failed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des communications de masse",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mass_communications": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MassCommunication"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer une communication de masse",
        "description": "Crée une nouvelle communication de masse avec ses destinataires et pièces jointes.\n\n**Modes de création:**\n- **Brouillon** (par défaut): La communication est créée mais pas envoyée\n- **Envoi immédiat** (`send=true`): La communication est créée et envoyée immédiatement\n\n**Destinataires:**\nLes destinataires peuvent être fournis de deux façons:\n- Directement dans le JSON avec email, prénom, nom\n- Via un fichier CSV/Excel uploadé dans le champ `file`\n\n**Variables de template:**\nLe corps du message supporte les variables Liquid:\n- `{{ recipient.first_name }}` - Prénom du destinataire\n- `{{ recipient.last_name }}` - Nom du destinataire\n- `{{ recipient.email }}` - Email du destinataire\n- `{{ recipient.custom_attributes.xxx }}` - Attributs personnalisés\n",
        "tags": [
          "Mass Communications"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MassCommunicationMutation"
              },
              "example": {
                "mass_communication": {
                  "name": "Newsletter Janvier 2024",
                  "subject": "Actualités du mois",
                  "body": "Bonjour {{ recipient.first_name }},\n\nVoici les actualités...",
                  "communication_type": "email"
                },
                "recipients": [
                  {
                    "email": "john@example.com",
                    "first_name": "John",
                    "last_name": "Doe"
                  },
                  {
                    "email": "jane@example.com",
                    "first_name": "Jane",
                    "last_name": "Smith"
                  }
                ],
                "send": false
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "mass_communication[name]": {
                    "type": "string"
                  },
                  "mass_communication[subject]": {
                    "type": "string"
                  },
                  "mass_communication[body]": {
                    "type": "string"
                  },
                  "recipients[][email]": {
                    "type": "string"
                  },
                  "recipients[][first_name]": {
                    "type": "string"
                  },
                  "recipients[][last_name]": {
                    "type": "string"
                  },
                  "recipients[][file]": {
                    "type": "string",
                    "format": "binary",
                    "description": "Fichier CSV/Excel contenant les destinataires"
                  },
                  "attachments[][file]": {
                    "type": "string",
                    "format": "binary"
                  },
                  "attachments[][name]": {
                    "type": "string"
                  },
                  "send": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Communication de masse créée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MassCommunicationResponse"
                },
                "example": {
                  "mass_communication": {
                    "id": "550e8400-e29b-41d4-a716-446655440000",
                    "name": "Newsletter Janvier 2024",
                    "subject": "Actualités du mois",
                    "status": "draft",
                    "recipients_count": 2,
                    "created_at": "2024-01-16T10:00:00Z"
                  },
                  "recipients": [
                    {
                      "id": "660e8400-e29b-41d4-a716-446655440001",
                      "email": "john@example.com",
                      "first_name": "John",
                      "last_name": "Doe",
                      "status": "pending"
                    }
                  ],
                  "attachments": [],
                  "skipped_recipients": [],
                  "sending": false,
                  "enqueued_count": 0
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "Erreur de validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "name_required": {
                    "summary": "Nom requis",
                    "value": {
                      "message": "Le nom est requis",
                      "error": "unprocessable_entity"
                    }
                  },
                  "recipients_required": {
                    "summary": "Destinataires requis",
                    "value": {
                      "message": "Au moins un destinataire est requis",
                      "error": "unprocessable_entity"
                    }
                  },
                  "subject_required_for_send": {
                    "summary": "Sujet requis pour l'envoi",
                    "value": {
                      "message": "Le sujet est requis pour envoyer la communication",
                      "error": "unprocessable_entity"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/communications/mass_communications/{id}": {
      "get": {
        "summary": "Récupérer une communication de masse",
        "description": "Récupère les détails d'une communication de masse avec ses destinataires et pièces jointes.\n",
        "tags": [
          "Mass Communications"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Communication de masse récupérée avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MassCommunicationResponse"
                },
                "example": {
                  "mass_communication": {
                    "id": "550e8400-e29b-41d4-a716-446655440000",
                    "name": "Newsletter Janvier 2024",
                    "subject": "Actualités du mois",
                    "body": "Bonjour {{ recipient.first_name }}...",
                    "communication_type": "email",
                    "status": "sent",
                    "recipients_count": 150,
                    "sent_count": 148,
                    "failed_count": 2,
                    "created_at": "2024-01-16T10:00:00Z",
                    "sent_at": "2024-01-16T10:05:00Z"
                  },
                  "recipients": [
                    {
                      "id": "660e8400-e29b-41d4-a716-446655440001",
                      "email": "john@example.com",
                      "first_name": "John",
                      "last_name": "Doe",
                      "status": "sent",
                      "sent_at": "2024-01-16T10:05:01Z"
                    }
                  ],
                  "attachments": [
                    {
                      "id": "770e8400-e29b-41d4-a716-446655440002",
                      "name": "document.pdf",
                      "scope_type": "all"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/mandataires": {
      "get": {
        "summary": "Lister les mandataires accessibles à l'utilisateur",
        "operationId": "listMandataires",
        "tags": [
          "Loctavia — Mandataires"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Mandataire"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/leases": {
      "get": {
        "summary": "Lister les baux",
        "operationId": "listLeases",
        "tags": [
          "Loctavia — Baux"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Lease"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un bail",
        "operationId": "createLease",
        "tags": [
          "Loctavia — Baux"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaseMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Lease"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/leases/{id}": {
      "get": {
        "summary": "Détail d'un bail",
        "operationId": "getLease",
        "tags": [
          "Loctavia — Baux"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaseDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/billing_runs": {
      "get": {
        "summary": "Lister les quittancements",
        "operationId": "listBillingRuns",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BillingRun"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un quittancement (calcul asynchrone)",
        "operationId": "createBillingRun",
        "description": "Le calcul est asynchrone (job). Le run renvoyé démarre en draft/scheduled\navec linesCount 0 ; interroger GET /billing_runs/{id} jusqu au statut\ncomputed/review avant de valider.\n",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingRunMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/billing_runs/{id}": {
      "get": {
        "summary": "Détail d'un quittancement",
        "operationId": "getBillingRun",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingRunDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/billing_runs/{id}/lines": {
      "get": {
        "summary": "Lignes d'un quittancement",
        "operationId": "listBillingRunLines",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "tenant",
                "owner"
              ],
              "default": "tenant"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total de lignes",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BillingLine"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/billing_runs/{id}/lines/{line_id}": {
      "patch": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Mettre à jour une ligne (exclure/inclure ou acquitter)",
        "operationId": "updateBillingLine",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "name": "line_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingLineUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingLine"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/billing_runs/{id}/validate": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Valider un quittancement",
        "operationId": "validateBillingRun",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          },
          {
            "name": "force",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Valide malgré des anomalies non acquittées (présence du paramètre = activé)"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/billing_runs/{id}/apply": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Appliquer un quittancement (génère les appels de loyer)",
        "operationId": "applyBillingRun",
        "tags": [
          "Loctavia — Quittancements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingRun"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/mandates": {
      "get": {
        "summary": "Lister les mandats",
        "operationId": "listMandates",
        "tags": [
          "Loctavia — Mandats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Mandate"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un mandat",
        "operationId": "createMandate",
        "tags": [
          "Loctavia — Mandats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MandateMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Mandate"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/mandates/{id}": {
      "get": {
        "summary": "Détail d'un mandat",
        "operationId": "getMandate",
        "tags": [
          "Loctavia — Mandats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MandateDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/properties": {
      "get": {
        "summary": "Lister les immeubles",
        "operationId": "listProperties",
        "tags": [
          "Loctavia — Patrimoine"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Property"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un immeuble",
        "operationId": "createPropertie",
        "tags": [
          "Loctavia — Patrimoine"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PropertyMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Property"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/properties/{id}": {
      "get": {
        "summary": "Détail d'un immeuble",
        "operationId": "getProperty",
        "tags": [
          "Loctavia — Patrimoine"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/units": {
      "get": {
        "summary": "Lister les lots",
        "operationId": "listUnits",
        "tags": [
          "Loctavia — Patrimoine"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Unit"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un lot",
        "operationId": "createUnit",
        "tags": [
          "Loctavia — Patrimoine"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnitMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Unit"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/units/{id}": {
      "get": {
        "summary": "Détail d'un lot",
        "operationId": "getUnit",
        "tags": [
          "Loctavia — Patrimoine"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnitDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/owners": {
      "get": {
        "summary": "Lister les propriétaires",
        "operationId": "listOwners",
        "tags": [
          "Loctavia — Tiers"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Recherche texte libre (prénom, nom, raison sociale, SIRET)"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Owner"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Créer un propriétaire",
        "operationId": "createOwner",
        "tags": [
          "Loctavia — Tiers"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OwnerMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Owner"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/owners/{id}": {
      "get": {
        "summary": "Détail d'un propriétaire",
        "description": "Fiche complète du propriétaire : identité, coordonnées, comptes\nbancaires, biens détenus, ownerships, mandats de gestion, redditions\n(owner payments) et CGP courant. Le paramètre `id` est l'identifiant de\nl'acteur propriétaire (le même que le champ `id` du payload de liste).\n",
        "operationId": "getOwner",
        "tags": [
          "Loctavia — Tiers"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OwnerDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/tenants": {
      "get": {
        "summary": "Lister les locataires",
        "operationId": "listTenants",
        "tags": [
          "Loctavia — Tiers"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Recherche texte libre (prénom, nom, email)"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Tenant"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/tenants/{id}": {
      "get": {
        "summary": "Détail d'un locataire",
        "description": "Fiche complète du locataire : identité, coordonnées, comptes bancaires,\nbaux (multi-baux consolidés), appels de loyer (rent calls), paiements et\ninformations de facturation. Le paramètre `id` est l'identifiant de\nl'acteur locataire (le même que le champ `id` du payload de liste).\n",
        "operationId": "getTenant",
        "tags": [
          "Loctavia — Tiers"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/payments": {
      "get": {
        "summary": "Lister les encaissements",
        "operationId": "listPayments",
        "tags": [
          "Loctavia — Paiements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Payment"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/payments/{id}": {
      "get": {
        "summary": "Détail d'un encaissement",
        "operationId": "getPayment",
        "tags": [
          "Loctavia — Paiements"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/incidents": {
      "get": {
        "summary": "Lister les incidents",
        "operationId": "listIncidents",
        "tags": [
          "Loctavia — Incidents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Incident"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/incidents/{id}": {
      "get": {
        "summary": "Détail d'un incident",
        "operationId": "getIncident",
        "tags": [
          "Loctavia — Incidents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncidentDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/journal_entries": {
      "get": {
        "summary": "Lister les écritures comptables",
        "operationId": "listJournalEntries",
        "description": "Écritures en partie double, immuables (correction par extourne),\ngénérées automatiquement par les quittancements, encaissements et\nreversements. Cinq journaux : LOYERS, CHARGES, BANQUE, PROPRIO, OD.\nFiltres fins via Ransack : `q[status_eq]=posted`,\n`q[accounting_date_gteq]=2026-01-01`…\n",
        "tags": [
          "Loctavia — Comptabilité"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "name": "journal",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "LOYERS",
                "CHARGES",
                "BANQUE",
                "PROPRIO",
                "OD"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "Current-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "Page-Items": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/JournalEntry"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/journal_entries/{id}": {
      "get": {
        "summary": "Détail d'une écriture et de ses lignes",
        "operationId": "getJournalEntry",
        "tags": [
          "Loctavia — Comptabilité"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JournalEntryDetails"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/loctavia/api/v1/fec_exports": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Générer un export FEC pour un exercice",
        "operationId": "createFecExport",
        "description": "Fichier des Écritures Comptables au format réglementaire (18 colonnes,\narrêté du 29 juillet 2013). Périmètre : écritures `validated` + `posted`\nde l'exercice civil. La génération est synchrone : la réponse `201`\nsignifie que le fichier est prêt, à récupérer via\n`GET /fec_exports/{id}/download`.\n",
        "tags": [
          "Loctavia — Comptabilité"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/ContentTypeHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FecExportMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Créé (fichier prêt au téléchargement)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FecExport"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/loctavia/api/v1/fec_exports/{id}/download": {
      "get": {
        "summary": "Télécharger le fichier FEC",
        "operationId": "downloadFecExport",
        "description": "Renvoie le fichier `.txt` FEC en pièce jointe (`Content-Disposition:\nattachment`, nom réglementaire `<siren>FEC<AAAA1231>.txt`).\n",
        "tags": [
          "Loctavia — Comptabilité"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MandataireIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "Contenu FEC (18 colonnes séparées par des pipes)"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ]
      }
    },
    "/octopia/api/ingest": {
      "post": {
        "summary": "Ingérer des données externes dans le Knowledge Graph",
        "description": "Importe des entités et des relations dans le Knowledge Graph Octopia depuis un connecteur externe.\n\n**Authentification** : via header `X-Connector-Key` (clé API du connecteur, pas OAuth2).\n\n**Workflow typique** :\n1. Créer un connecteur via l'interface Octopia (qui génère une clé API)\n2. Envoyer les données à cet endpoint avec la clé dans le header\n3. Les entités sont créées ou mises à jour (upsert par nom)\n4. Les relations sont créées entre les entités\n\n**Types d'entités supportés** : person, company, team, skill, tag, project, contract, product, service, location, event, role\n\n**Cas d'usage** :\n- Synchronisation depuis un CRM (Salesforce, HubSpot)\n- Import depuis un ERP (SAP, Sage)\n- Enrichissement depuis des sources externes (API, CSV)\n",
        "tags": [
          "Octopia"
        ],
        "security": [
          {
            "ConnectorApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OctopiaIngestionRequest"
              },
              "examples": {
                "complete": {
                  "summary": "Import complet (entités + relations)",
                  "value": {
                    "entities": [
                      {
                        "type": "company",
                        "name": "ACME Corporation",
                        "attributes": {
                          "industry": "technologie",
                          "city": "Paris",
                          "country": "FR"
                        }
                      },
                      {
                        "type": "person",
                        "name": "Jean Dupont",
                        "attributes": {
                          "email": "jean.dupont@acme.fr"
                        }
                      },
                      {
                        "type": "project",
                        "name": "Migration Cloud 2025",
                        "attributes": {
                          "status": "active"
                        }
                      }
                    ],
                    "relations": [
                      {
                        "from_type": "person",
                        "from_name": "Jean Dupont",
                        "to_type": "company",
                        "to_name": "ACME Corporation",
                        "edge_type": "works_in"
                      },
                      {
                        "from_type": "person",
                        "from_name": "Jean Dupont",
                        "to_type": "project",
                        "to_name": "Migration Cloud 2025",
                        "edge_type": "works_on_project"
                      }
                    ]
                  }
                },
                "minimal": {
                  "summary": "Import minimal (entités seules)",
                  "value": {
                    "entities": [
                      {
                        "type": "company",
                        "name": "Société XYZ"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ingestion réussie",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OctopiaIngestionResponse"
                },
                "examples": {
                  "success": {
                    "summary": "Import réussi",
                    "value": {
                      "imported": 3,
                      "updated": 0,
                      "errors": []
                    }
                  },
                  "partial": {
                    "summary": "Import partiel avec erreurs",
                    "value": {
                      "imported": 2,
                      "updated": 1,
                      "errors": [
                        "Unknown entity type: unknown_type",
                        "Cannot create relation works_in: missing nodes"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Clé de connecteur invalide ou manquante",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "unauthorized",
                  "message": "Invalid or missing connector key"
                }
              }
            }
          },
          "422": {
            "description": "Erreur lors de l'ingestion (échec de l'interactor)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "ingestion_error",
                  "message": "Connector is not active"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/octopia/memories": {
      "get": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Lister les mémoires du cerveau d'entreprise",
        "operationId": "listMemories",
        "tags": [
          "Octopia — Mémoire"
        ],
        "parameters": [
          {
            "name": "memory_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "fact",
                "preference",
                "context",
                "insight"
              ]
            }
          },
          {
            "name": "scope[type]",
            "in": "query",
            "required": false,
            "description": "Type d'entité de rattachement (ex. `client`, `project`) — restreint la liste.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope[name]",
            "in": "query",
            "required": false,
            "description": "Nom de l'entité de rattachement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Total-Count": {
                "description": "Nombre total d'éléments",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Total-Pages": {
                "description": "Nombre total de pages",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Page": {
                "description": "Page courante",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Per-Page": {
                "description": "Éléments par page",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Has-More": {
                "description": "Page suivante disponible",
                "schema": {
                  "type": "boolean"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Memory"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Stocker une mémoire et la rattacher à des entités du graphe",
        "operationId": "storeMemory",
        "tags": [
          "Octopia — Mémoire"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryMutation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Mémoire stockée (avec les entités effectivement rattachées).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Memory"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "links": {
                          "type": "array",
                          "description": "Entités effectivement rattachées.",
                          "items": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/EntityLink"
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "description": "Clé du nœud dans le graphe."
                                  }
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/octopia/memories/{id}": {
      "get": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Détail d'une mémoire (avec ses entités liées)",
        "operationId": "showMemory",
        "tags": [
          "Octopia — Mémoire"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Memory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "security": [
          {
            "OAuth2Password": [
              "write"
            ]
          },
          {
            "OAuth2AuthCode": [
              "write"
            ]
          }
        ],
        "summary": "Oublier une mémoire (supprime le record et son nœud graphe)",
        "operationId": "forgetMemory",
        "tags": [
          "Octopia — Mémoire"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdParameter"
          }
        ],
        "responses": {
          "204": {
            "description": "Mémoire oubliée."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/octopia/memories/search": {
      "post": {
        "security": [
          {
            "OAuth2Password": [
              "read"
            ]
          },
          {
            "OAuth2AuthCode": [
              "read"
            ]
          }
        ],
        "summary": "Recherche sémantique dans le cerveau d'entreprise",
        "description": "Recherche sémantique (pgvector) sur **tous les auteurs** (mémoire commune),\nenrichie des entités liées du graphe. Optionnellement restreinte à une\nentité via `scope`.\n",
        "operationId": "searchMemories",
        "tags": [
          "Octopia — Mémoire"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemorySearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemorySearchResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "summary": "Obtenir un token d'accès OAuth2",
        "description": "Obtenir un token d'accès OAuth2 en utilisant différents flows :\n\n- **password** : Pour applications de confiance (mobile, CLI, tests) - envoie email/mot de passe directement\n- **authorization_code** : Pour applications frontend/tierce - échange un code d'autorisation contre un token\n- **refresh_token** : Pour rafraîchir un token expiré\n",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "password",
                      "authorization_code",
                      "refresh_token"
                    ],
                    "description": "Type d'autorisation OAuth2",
                    "example": "authorization_code"
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Identifiant client OAuth2",
                    "example": "Q0OEEVE5kLDll1uAek5B-rJcXJCtkIoEdPwLvDiQ888"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Secret client OAuth2",
                    "example": "jnZuQF8PFv42WmA5vLLBdPrhBAwAqLuBTERGMUERUPE"
                  },
                  "username": {
                    "type": "string",
                    "description": "Adresse email de l'utilisateur (requis pour grant_type=password)",
                    "example": "admin@example.com"
                  },
                  "password": {
                    "type": "string",
                    "description": "Mot de passe de l'utilisateur (requis pour grant_type=password)",
                    "example": "MyPassword123"
                  },
                  "code": {
                    "type": "string",
                    "description": "Code d'autorisation (requis pour grant_type=authorization_code)",
                    "example": "AUTH_CODE_FROM_AUTHORIZE_ENDPOINT"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri",
                    "description": "URI de redirection (requis pour grant_type=authorization_code)",
                    "example": "http://localhost:8082/callback"
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "Jeton de rafraîchissement (requis pour grant_type=refresh_token)",
                    "example": "def50200cde4321cfa9..."
                  },
                  "scope": {
                    "type": "string",
                    "description": "Portée des permissions demandées (optionnelle)",
                    "example": "read write"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token d'accès généré avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "summary": "Initier l'autorisation OAuth2 (flow authorization_code)",
        "description": "Redirige l'utilisateur vers l'interface d'autorisation OAuth2.\nL'utilisateur doit s'authentifier et autoriser l'application.\nAprès autorisation, l'utilisateur est redirigé vers l'URI spécifiée avec un code d'autorisation.\n",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ],
              "example": "code"
            },
            "description": "Type de réponse OAuth2 (toujours \"code\")"
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Q0OEEVE5kLDll1uAek5B-rJcXJCtkIoEdPwLvDiQ888"
            },
            "description": "Identifiant client OAuth2"
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri",
              "example": "http://localhost:3000/callback"
            },
            "description": "URI de redirection après autorisation"
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "read write"
            },
            "description": "Portée des permissions demandées"
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "random_state_string"
            },
            "description": "Valeur aléatoire pour prévenir les attaques CSRF"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirection vers l'interface d'autorisation ou vers l'URI de callback",
            "headers": {
              "Location": {
                "description": "URL de redirection",
                "schema": {
                  "type": "string",
                  "example": "http://localhost:3000/callback?code=AUTH_CODE&state=random_state_string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "summary": "Révoquer un token OAuth2",
        "description": "Révoque un token d'accès ou un refresh token OAuth2.\nConforme à RFC 7009 (OAuth 2.0 Token Revocation).\n\nUne fois révoqué, le token ne peut plus être utilisé pour accéder aux ressources protégées.\n",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "Token à révoquer (access_token ou refresh_token)",
                    "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "access_token",
                      "refresh_token"
                    ],
                    "description": "Indice sur le type de token (optionnel, améliore les performances)",
                    "example": "access_token"
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Identifiant client OAuth2",
                    "example": "Q0OEEVE5kLDll1uAek5B-rJcXJCtkIoEdPwLvDiQ888"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Secret client OAuth2",
                    "example": "jnZuQF8PFv42WmA5vLLBdPrhBAwAqLuBTERGMUERUPE"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token révoqué avec succès (ou token déjà invalide/inexistant)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "Token revoked successfully"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/oauth/token_exchange": {
      "post": {
        "summary": "Échanger un JWT Edifice contre un token d'accès",
        "description": "Permet à Edifice d'authentifier ses utilisateurs dans Corex via un échange de token JWT.\n\n**Flow:**\n1. Edifice génère un JWT signé (RS256) contenant les informations utilisateur\n2. Corex vérifie la signature via JWKS (JSON Web Key Set)\n3. Si l'utilisateur n'existe pas, il est créé automatiquement\n4. Un token d'accès Doorkeeper est retourné\n\n**JWT Claims attendus:**\n- `sub` - Identifiant utilisateur Edifice\n- `email` - Email de l'utilisateur (requis)\n- `first_name` - Prénom\n- `last_name` - Nom\n- `iss` - Doit être \"edifice\"\n- `aud` - Doit être \"corex\"\n- `exp` - Timestamp d'expiration\n",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token d'accès généré avec succès",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenExchangeResponse"
                },
                "example": {
                  "access_token": "abc123def456...",
                  "token_type": "Bearer",
                  "expires_in": 7200,
                  "created_at": 1705410000
                }
              }
            }
          },
          "400": {
            "description": "Requête invalide (grant_type non supporté ou assertion manquante)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "examples": {
                  "unsupported_grant_type": {
                    "summary": "Grant type non supporté",
                    "value": {
                      "error": "unsupported_grant_type"
                    }
                  },
                  "missing_assertion": {
                    "summary": "Assertion manquante",
                    "value": {
                      "error": "invalid_request",
                      "error_description": "assertion required"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification échouée (client invalide ou JWT invalide)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "examples": {
                  "invalid_client": {
                    "summary": "Client OAuth invalide",
                    "value": {
                      "error": "invalid_client"
                    }
                  },
                  "invalid_grant": {
                    "summary": "JWT invalide",
                    "value": {
                      "error": "invalid_grant",
                      "error_description": "JWT verification failed: signature invalid"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/cli/auth/device": {
      "post": {
        "security": [],
        "summary": "Démarrer un device flow CLI (RFC 8628)",
        "description": "Initie une session d'authentification \"device flow\" pour le CLI\nHubdoc. Endpoint public — pas d'auth Doorkeeper requise.\n\nLe flow complet :\n\n1. CLI appelle ce endpoint → reçoit `device_code` (secret) + `user_code`\n   (lisible humain, format `XXXX-XXXX`) + `verification_uri`.\n2. CLI affiche le user_code à l'utilisateur et ouvre la\n   `verification_uri_complete` dans son navigateur.\n3. L'user (déjà loggé sur Hubdoc) confirme le code sur la page\n   `/cli/activation`.\n4. CLI poll `/api/v1/cli/auth/device/poll` jusqu'à approval.\n5. À l'approval, le poll retourne un `access_token` (un Personal\n   Access Token utilisable comme Bearer sur toute l'API).\n\nRate-limited à 20 appels par 5 minutes (par IP).\n",
        "tags": [
          "CLI Auth"
        ],
        "responses": {
          "200": {
            "description": "Device flow démarré",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "device_code",
                    "user_code",
                    "verification_uri",
                    "expires_in",
                    "interval"
                  ],
                  "properties": {
                    "device_code": {
                      "type": "string",
                      "description": "Secret à présenter dans le polling. 256 bits d'entropie.",
                      "example": "8a3f2e7b9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f"
                    },
                    "user_code": {
                      "type": "string",
                      "description": "Code lisible à présenter à l'utilisateur (8 chars, alphabet sans 0/O/1/I/L)",
                      "example": "WDJB-MJHT"
                    },
                    "verification_uri": {
                      "type": "string",
                      "description": "URL où l'utilisateur saisit le user_code",
                      "example": "https://hubdoc.example.com/cli/activation/new"
                    },
                    "verification_uri_complete": {
                      "type": "string",
                      "description": "URL pré-remplie avec le user_code en query string",
                      "example": "https://hubdoc.example.com/cli/activation/new?code=WDJB-MJHT"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Durée de validité du flow en secondes (10 min)",
                      "example": 600
                    },
                    "interval": {
                      "type": "integer",
                      "description": "Intervalle de polling recommandé en secondes",
                      "example": 5
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit dépassé (20 / 5 min)"
          }
        }
      }
    },
    "/api/v1/cli/auth/device/poll": {
      "post": {
        "security": [],
        "summary": "Poll pour récupérer l'access_token (device flow CLI)",
        "description": "Polling endpoint du device flow. Le CLI appelle ce endpoint avec\nson `device_code` jusqu'à recevoir un access_token (succès) ou\nune erreur définitive.\n\nCodes d'erreur (compatibles RFC 6749 §5.2) :\n- `authorization_pending` (400) : pas encore approuvé, retry\n- `expired_token` (400) : device_code expiré ou inconnu → recommencer le flow\n- `access_denied` (400) : session révoquée entre l'approval et le pickup\n- `already_consumed` (410) : token déjà ramassé par un précédent poll → NE PAS retry\n",
        "tags": [
          "CLI Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_code"
                ],
                "properties": {
                  "device_code": {
                    "type": "string",
                    "description": "Le secret reçu lors du `start`"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approval réussie — token retourné",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "access_token",
                    "token_type"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "description": "Personal Access Token (PAT) à utiliser comme\n`Authorization: Bearer <access_token>` pour toute\nrequête API. Visible et révocable dans\n`/profile/personal-access-tokens`.\n"
                    },
                    "token_type": {
                      "type": "string",
                      "example": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Durée de validité en secondes (30 jours)"
                    },
                    "user": {
                      "type": "object",
                      "description": "Infos basiques du user authentifié",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email_address": {
                          "type": "string"
                        },
                        "first_name": {
                          "type": "string"
                        },
                        "last_name": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `error: invalid_request` — device_code manquant\n- `error: authorization_pending` — retry plus tard\n- `error: expired_token` — recommencer le flow\n- `error: access_denied` — session révoquée\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "`error: already_consumed` — token déjà ramassé. Le CLI ne doit\nPAS retry, il faut recommencer un device flow.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "IdParameter": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identifiant de la ressource",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
      },
      "ContentTypeHeader": {
        "name": "Content-Type",
        "in": "header",
        "required": true,
        "description": "Type de contenu de la requête",
        "schema": {
          "type": "string",
          "enum": [
            "application/json"
          ]
        },
        "example": "application/json"
      },
      "FolderIdParameter": {
        "name": "folder_id",
        "in": "path",
        "required": true,
        "description": "Identifiant du dossier",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
      },
      "PublicAccessLimitParameter": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Borne le tableau `subtree.unstamped_items`. Défaut 50, maximum 500.\nLe décompte `subtree.unstamped` reste exact quelle que soit la borne.\n",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "maximum": 500,
          "default": 50
        }
      },
      "FileIdParameter": {
        "name": "file_id",
        "in": "path",
        "required": true,
        "description": "Identifiant du document",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
      },
      "MandataireIdHeader": {
        "name": "X-Mandataire-Id",
        "in": "header",
        "required": true,
        "description": "Identifiant UUID du mandataire (tenant) sur lequel opérer. Renvoie 403 si\nl'utilisateur n'est pas membre du mandataire indiqué. Obligatoire sauf sur\nGET /loctavia/api/v1/mandataires (qui sert justement à le découvrir).\nTolérances côté serveur, non recommandées pour les clients générés :\nomission acceptée quand l'utilisateur n'a qu'un seul mandataire, et le\nparamètre de requête `mandataire_id` est accepté en équivalent du header.\n",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "schemas": {
      "Workspace": {
        "type": "object",
        "required": [
          "id",
          "label",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique de l'espace de travail",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "label": {
            "type": "string",
            "description": "Libellé de l'espace de travail",
            "example": "Project Alpha"
          },
          "description": {
            "type": "string",
            "description": "Description de l'espace de travail",
            "example": "Main project workspace"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-workspace-12345"
          },
          "classification_plan_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du plan de classement associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "classification_plan_label": {
            "type": "string",
            "nullable": true,
            "description": "Libellé du plan de classement associé",
            "example": "Standard Plan"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du domaine associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "completion_percentage": {
            "type": "number",
            "description": "Pourcentage d'avancement de l'espace de travail",
            "example": 75.5
          },
          "settings": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "nullable": true,
                "description": "Code de l'espace de travail",
                "example": "PROJ-001"
              },
              "start_date": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Date de début du projet",
                "example": "2023-01-01"
              },
              "color": {
                "type": "string",
                "nullable": true,
                "description": "Couleur de l'espace de travail",
                "example": "#3498db"
              },
              "icon": {
                "type": "string",
                "nullable": true,
                "description": "Icône de l'espace de travail",
                "example": "folder"
              },
              "address": {
                "type": "string",
                "nullable": true,
                "description": "Adresse du projet",
                "example": "123 Main St"
              },
              "postal_code": {
                "type": "string",
                "nullable": true,
                "description": "Code postal du projet",
                "example": "12345"
              },
              "city": {
                "type": "string",
                "nullable": true,
                "description": "Ville du projet",
                "example": "Paris"
              },
              "agency": {
                "type": "string",
                "nullable": true,
                "description": "Agence du projet",
                "example": "Construction Agency"
              },
              "project_type": {
                "type": "string",
                "nullable": true,
                "description": "Type de projet",
                "example": "Construction"
              },
              "status": {
                "type": "string",
                "nullable": true,
                "description": "Statut du projet",
                "example": "active"
              },
              "end_date": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Date de fin du projet",
                "example": "2023-12-31"
              },
              "validation": {
                "type": "object",
                "properties": {
                  "default_deadline_days": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Délai par défaut en jours",
                    "example": 30
                  },
                  "auto_reminder_days": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "nullable": true,
                    "description": "Rappel automatique en jours",
                    "example": [
                      7,
                      14
                    ]
                  },
                  "super_validator_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Identifiants des super validateurs",
                    "example": [
                      "019951a3-01b7-7eb9-88bb-f872a01ed886",
                      "019951a3-01b7-7eb9-88bb-f872a01ed887"
                    ]
                  }
                }
              }
            }
          },
          "metadata_user": {
            "type": "object",
            "description": "Métadonnées définies par l'utilisateur"
          },
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "Folder": {
        "type": "object",
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du dossier",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "name": {
            "type": "string",
            "description": "Nom du dossier",
            "example": "Documents"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description du dossier",
            "example": "Main documents folder"
          },
          "color": {
            "type": "string",
            "nullable": true,
            "description": "Couleur du dossier",
            "example": "#e74c3c"
          },
          "icon": {
            "type": "string",
            "nullable": true,
            "description": "Icône du dossier",
            "example": "folder-open"
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du dossier parent",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'espace de travail associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-folder-12345"
          },
          "classification_plan_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du plan de classement (délégué via workspace)",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed891"
          },
          "workspace": {
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/Workspace"
              }
            ],
            "nullable": true,
            "description": "Espace de travail associé (si présent)"
          },
          "parent": {
            "type": "object",
            "nullable": true,
            "description": "Dossier parent (si présent)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
              },
              "name": {
                "type": "string",
                "example": "Parent Folder"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "Document": {
        "type": "object",
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du fichier",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "name": {
            "type": "string",
            "description": "Nom du fichier",
            "example": "document.pdf"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description du fichier",
            "example": "Important document"
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du dossier parent",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'espace de travail associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du domaine associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed889"
          },
          "document_type_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du type de document associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed890"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-doc-12345"
          },
          "classification_plan_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du plan de classement (délégué via workspace)",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed891"
          },
          "byte_size": {
            "type": "integer",
            "format": "int64",
            "nullable": true,
            "description": "Taille en octets du fichier attaché. `null` pour une note sans fichier. Exposée pour les connecteurs natifs (Cloud Filter API Windows / File Provider macOS), qui doivent dimensionner le placeholder avant hydratation.",
            "example": 342502
          },
          "content_type": {
            "type": "string",
            "nullable": true,
            "description": "Type MIME du fichier attaché. `null` pour une note sans fichier.",
            "example": "application/pdf"
          },
          "metadata_user": {
            "type": "object",
            "description": "Métadonnées définies par l'utilisateur"
          },
          "workspace": {
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/Workspace"
              }
            ],
            "nullable": true,
            "description": "Espace de travail associé (si présent)"
          },
          "folder": {
            "type": "object",
            "nullable": true,
            "description": "Dossier parent (si présent)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
              },
              "name": {
                "type": "string",
                "example": "Documents"
              }
            }
          },
          "domain": {
            "type": "object",
            "nullable": true,
            "description": "Domaine associé (si présent)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "019951a3-01b7-7eb9-88bb-f872a01ed889"
              },
              "name": {
                "type": "string",
                "example": "Legal"
              }
            }
          },
          "document_type": {
            "type": "object",
            "nullable": true,
            "description": "Type de document associé (si présent)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "019951a3-01b7-7eb9-88bb-f872a01ed890"
              },
              "name": {
                "type": "string",
                "example": "Contract"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "User": {
        "type": "object",
        "required": [
          "id",
          "email_address",
          "role",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant utilisateur",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "email_address": {
            "type": "string",
            "format": "email",
            "description": "Adresse email de l'utilisateur",
            "example": "john.doe@example.com"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "description": "Prénom de l'utilisateur",
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "nullable": true,
            "description": "Nom de famille de l'utilisateur",
            "example": "Doe"
          },
          "display_name": {
            "type": "string",
            "description": "Nom complet de l'utilisateur",
            "example": "John Doe"
          },
          "initials": {
            "type": "string",
            "description": "Initiales de l'utilisateur",
            "example": "JD"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "manager",
              "admin",
              "super_admin"
            ],
            "description": "Rôle de l'utilisateur",
            "example": "user"
          },
          "externa_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-user-12345"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "BulkUpload": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du BulkUpload"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "failed"
            ],
            "description": "Statut du traitement du lot"
          },
          "source": {
            "type": "string",
            "description": "Source de l'upload (api, hubdoc-tools, manual, etc.)"
          },
          "total_files": {
            "type": "integer",
            "minimum": 0,
            "description": "Nombre total de fichiers dans le lot"
          },
          "processed_files": {
            "type": "integer",
            "minimum": 0,
            "description": "Nombre de fichiers traités"
          },
          "successful_files": {
            "type": "integer",
            "minimum": 0,
            "description": "Nombre de fichiers uploadés avec succès"
          },
          "failed_files": {
            "type": "integer",
            "minimum": 0,
            "description": "Nombre de fichiers en échec"
          },
          "progress": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 100,
            "description": "Pourcentage de progression (processed_files / total_files * 100)"
          },
          "auto_classify": {
            "type": "boolean",
            "description": "Classification automatique activée"
          },
          "merge_to_pdf": {
            "type": "boolean",
            "description": "Lot en mode fusion (toutes les sources sont fusionnées en un seul PDF)"
          },
          "merged_file_name": {
            "type": "string",
            "nullable": true,
            "description": "Nom du document PDF fusionné (mode fusion uniquement)"
          },
          "received_files": {
            "type": "integer",
            "minimum": 0,
            "description": "Nombre de sources déjà reçues (présent uniquement en mode fusion)"
          },
          "merged_document_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du document PDF fusionné, disponible quand le lot est complété (mode fusion uniquement)"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date et heure de début du traitement"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date et heure de fin du traitement"
          },
          "folder_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du dossier de destination"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du workspace"
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID de l'utilisateur ayant initié l'upload"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Métadonnées supplémentaires"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de dernière modification"
          }
        },
        "required": [
          "id",
          "status",
          "total_files",
          "processed_files",
          "successful_files",
          "failed_files",
          "user_id",
          "created_at",
          "updated_at"
        ]
      },
      "ChunkedUpload": {
        "type": "object",
        "description": "Session d'upload par chunks pour fichiers volumineux",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique de la session d'upload"
          },
          "upload_id": {
            "type": "string",
            "description": "Identifiant de la session d'upload (format hex)",
            "example": "a1b2c3d4e5f6"
          },
          "filename": {
            "type": "string",
            "description": "Nom du fichier à uploader",
            "example": "document.pdf"
          },
          "file_size": {
            "type": "integer",
            "format": "int64",
            "description": "Taille totale du fichier en octets",
            "example": 52428800
          },
          "content_type": {
            "type": "string",
            "description": "Type MIME du fichier",
            "example": "application/pdf"
          },
          "chunk_size": {
            "type": "integer",
            "description": "Taille de chaque chunk en octets",
            "example": 5242880
          },
          "total_chunks": {
            "type": "integer",
            "description": "Nombre total de chunks",
            "example": 10
          },
          "uploaded_chunks": {
            "type": "integer",
            "description": "Nombre de chunks déjà uploadés",
            "example": 5
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed",
              "cancelled",
              "expired"
            ],
            "description": "Statut de l'upload:\n- pending: En attente, aucun chunk uploadé\n- processing: Upload en cours\n- completed: Upload terminé et assemblé\n- failed: Échec de l'upload\n- cancelled: Upload annulé\n- expired: Session expirée\n",
            "example": "processing"
          },
          "progress": {
            "type": "number",
            "format": "float",
            "description": "Pourcentage de progression (0-100)",
            "example": 50
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date d'expiration de la session (24h par défaut)",
            "example": "2024-11-10T10:00:00Z"
          },
          "expired": {
            "type": "boolean",
            "description": "Indique si la session est expirée",
            "example": false
          },
          "checksum": {
            "type": "string",
            "nullable": true,
            "description": "Checksum MD5 du fichier complet (base64)",
            "example": "1B2M2Y8AsgTpgAmY7PhCfg=="
          },
          "storage_object_key": {
            "type": "string",
            "nullable": true,
            "description": "Clé de l'objet dans le storage après assemblage",
            "example": "uploads/abc123/document.pdf"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du workspace de destination"
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du dossier de destination"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de dernière modification"
          }
        },
        "required": [
          "id",
          "upload_id",
          "filename",
          "file_size",
          "chunk_size",
          "total_chunks",
          "uploaded_chunks",
          "status",
          "expires_at"
        ]
      },
      "Contact": {
        "type": "object",
        "required": [
          "id",
          "email_address",
          "type",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du contact",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "email_address": {
            "type": "string",
            "format": "email",
            "description": "Adresse email du contact",
            "example": "contact@example.com"
          },
          "first_name": {
            "type": "string",
            "nullable": true,
            "description": "Prénom du contact",
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "nullable": true,
            "description": "Nom du contact",
            "example": "Doe"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-12345"
          },
          "display_name": {
            "type": "string",
            "description": "Nom d'affichage du contact (email ou prénom/nom)",
            "example": "John Doe"
          },
          "full_name": {
            "type": "string",
            "nullable": true,
            "description": "Nom complet du contact (prénom + nom)",
            "example": "John Doe"
          },
          "type": {
            "type": "string",
            "description": "Type d'acteur",
            "example": "Contact"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "Group": {
        "type": "object",
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du groupe",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "name": {
            "type": "string",
            "description": "Nom du groupe",
            "example": "Administrators"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description du groupe",
            "example": "Group for system administrators"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-admin-group"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "GroupMember": {
        "type": "object",
        "required": [
          "id",
          "group_id",
          "member_id",
          "member_type",
          "role",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique de l'adhésion au groupe",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "group_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du groupe",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "member_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du membre",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          },
          "member_type": {
            "type": "string",
            "description": "Type du membre (User ou Group)",
            "example": "User",
            "enum": [
              "User",
              "Group"
            ]
          },
          "role": {
            "type": "string",
            "description": "Rôle du membre dans le groupe",
            "example": "member",
            "enum": [
              "member",
              "admin",
              "owner"
            ]
          },
          "user_external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe de l'utilisateur (si le membre est un User)",
            "example": "ext-user-123"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "Permission": {
        "type": "object",
        "required": [
          "id",
          "level",
          "actor_type",
          "permissible_type",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique de la permission",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "level": {
            "type": "string",
            "enum": [
              "read",
              "write",
              "admin"
            ],
            "description": "Niveau de permission (read, write, admin)",
            "example": "read"
          },
          "actor_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'acteur (User, Contact, Group)",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "actor_type": {
            "type": "string",
            "description": "Type d'acteur (User, Contact, Group)",
            "example": "User"
          },
          "permissible_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de la ressource protégée",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          },
          "permissible_type": {
            "type": "string",
            "description": "Type de ressource protégée (Documents::File, Documents::Folder, etc.)",
            "example": "Documents::File"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-perm-12345"
          },
          "actor": {
            "type": "object",
            "nullable": true,
            "description": "Acteur ayant la permission (si présent)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
              },
              "display_name": {
                "type": "string",
                "example": "John Doe"
              },
              "type": {
                "type": "string",
                "example": "User"
              }
            }
          },
          "permissible": {
            "type": "object",
            "nullable": true,
            "description": "Ressource protégée (si présent)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
              },
              "name": {
                "type": "string",
                "example": "document.pdf"
              },
              "type": {
                "type": "string",
                "example": "Documents::File"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création",
            "example": "2023-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification",
            "example": "2023-01-01T00:00:00Z"
          }
        }
      },
      "ComposedDocument": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "user_id",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed999"
          },
          "name": {
            "type": "string",
            "description": "Nom du document composé",
            "example": "Facture Sinoia 2026-05"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "in_progress",
              "ready",
              "exported"
            ],
            "description": "Cycle de vie : draft → in_progress → ready → exported. Le passage\nà `exported` est piloté par l'action POST /export qui compile\nen PDF et attache le résultat.\n"
          },
          "assembly_mode": {
            "type": "string",
            "enum": [
              "composed",
              "pure_typst"
            ],
            "description": "Mode d'assemblage du PDF (#463) :\n- `composed` (défaut) : l'assembler combine page_settings + variables\n  + typst_source + header/footer + parts.\n- `pure_typst` : le `typst_source` EST le document, aucune injection\n  structurelle (seules les substitutions `{{var:x}}` et `{{image:slug}}`\n  sont appliquées). Évite les effets de couplage (header/margin écrasés).\n"
          },
          "typst_source": {
            "type": "string",
            "nullable": true,
            "description": "Source Typst (héritée du template à la création, modifiable). En mode\n`pure_typst`, c'est l'unique source du rendu.\n"
          },
          "variables": {
            "type": "object",
            "additionalProperties": true,
            "description": "Valeurs des variables du template (clé → valeur)",
            "example": {
              "client": "Sinoia",
              "montant": 1500
            }
          },
          "page_settings": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "metadata_ai": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "metadata_user": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "document_template_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Template d'origine (nullable si template supprimé avec force)"
          },
          "file_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Fichier Documents::File résultant du compile (PDF). Renseigné\nautomatiquement après le premier appel à POST /export.\n"
          },
          "folder_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "Créateur"
          },
          "parts": {
            "type": "array",
            "description": "Parts (sections) composant le document",
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Part": {
        "type": "object",
        "required": [
          "id",
          "title",
          "content_format",
          "part_type",
          "position",
          "status",
          "composed_document_id",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "description": "Titre de la part (visible en édition)",
            "example": "Annexe — Conditions générales"
          },
          "content": {
            "type": "string",
            "description": "Contenu de la part. Si `content_format` = \"markdown\", parsé en\nHTML/PDF via la pipeline markdown. Si \"typst\", concaténé tel\nquel dans le source assemblé.\n"
          },
          "content_format": {
            "type": "string",
            "enum": [
              "typst",
              "markdown"
            ],
            "description": "Format du contenu",
            "default": "typst"
          },
          "content_source": {
            "type": "string",
            "enum": [
              "human",
              "agent",
              "template"
            ],
            "description": "Source du contenu : édité par humain, généré par agent IA, ou\nhérité du template par défaut.\n",
            "default": "human"
          },
          "part_type": {
            "type": "string",
            "enum": [
              "content",
              "header",
              "footer",
              "signature",
              "appendix"
            ],
            "description": "Rôle structurel de la part",
            "default": "content"
          },
          "position": {
            "type": "integer",
            "description": "Ordre dans le document (ascending)"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "editing",
              "ready",
              "validated",
              "locked"
            ],
            "description": "Cycle de vie de la part. Transitions valides limitées\n(cf. Documents::Part::VALID_TRANSITIONS).\n"
          },
          "alignment": {
            "type": "string",
            "enum": [
              "left",
              "center",
              "right"
            ],
            "default": "left"
          },
          "bsize": {
            "type": "integer",
            "description": "Largeur en grid columns (1-12)",
            "default": 12
          },
          "page_break": {
            "type": "boolean",
            "description": "Forcer un saut de page avant cette part",
            "default": false
          },
          "row_group": {
            "type": "integer",
            "nullable": true,
            "description": "Regroupement de parts sur une même ligne (grid layout)"
          },
          "key": {
            "type": "string",
            "nullable": true,
            "description": "Clé fonctionnelle (référence dans le template)"
          },
          "instructions": {
            "type": "string",
            "nullable": true,
            "description": "Instructions pour l'humain ou l'agent qui édite cette part"
          },
          "assignee_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "assignee_type": {
            "type": "string",
            "nullable": true,
            "description": "Type polymorphique : User ou Agents::Agent"
          },
          "markdown_file_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Si le contenu vient d'un fichier markdown éditable (live edit\nvia le markdown editor), référence le Documents::File source.\n"
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "composed_document_id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DocumentTemplate": {
        "type": "object",
        "required": [
          "id",
          "key",
          "name",
          "typst_source",
          "category",
          "active",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du template",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed999"
          },
          "key": {
            "type": "string",
            "description": "Slug unique du template (utilisé pour création depuis CLI/API)",
            "example": "facture-edf"
          },
          "name": {
            "type": "string",
            "description": "Nom affiché du template",
            "example": "Facture EDF"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description longue du template",
            "example": "Modèle de facture standard EDF"
          },
          "category": {
            "type": "string",
            "description": "Catégorie fonctionnelle (billing, contract, report...)",
            "example": "billing"
          },
          "locale": {
            "type": "string",
            "description": "Locale de référence du template",
            "default": "fr",
            "example": "fr"
          },
          "typst_source": {
            "type": "string",
            "description": "Source Typst du template. Peut contenir des `{{placeholders}}` qui\nseront remplacés par les `variables` d'un ComposedDocument\ninstancié depuis ce template.\n",
            "example": "#set page(paper: \"a4\")\n= Facture {{client}}\n"
          },
          "variables_schema": {
            "type": "object",
            "nullable": true,
            "description": "JSON Schema décrivant les variables attendues. Format simplifié :\n`{ \"properties\": { \"key\": { \"type\": \"string\", \"title\": \"Label\" } }, \"required\": [\"key\"] }`\n",
            "additionalProperties": true,
            "example": {
              "properties": {
                "client": {
                  "type": "string",
                  "title": "Nom du client"
                },
                "montant": {
                  "type": "number"
                }
              },
              "required": [
                "client"
              ]
            }
          },
          "default_parts": {
            "type": "array",
            "description": "Liste des parts par défaut du template (header, footer, content,\nsignature, appendix). Chaque part est un objet libre avec au\nmoins title/content/content_format/position. Stockage JSONB direct\nsur le template (pas une relation séparée).\n",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "example": [
              {
                "title": "Header",
                "content": "Logo EDF",
                "content_format": "typst",
                "part_type": "header",
                "position": 1
              },
              {
                "title": "Corps",
                "content": "Facture pour {{client}} : {{montant}}€",
                "content_format": "typst",
                "part_type": "content",
                "position": 2
              }
            ]
          },
          "page_settings": {
            "type": "object",
            "nullable": true,
            "description": "Configuration page (margins, headers, font...)",
            "additionalProperties": true
          },
          "active": {
            "type": "boolean",
            "description": "Le template est-il actif (listable via index)",
            "default": true,
            "example": true
          },
          "solution_instance_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Solution Pack qui a fourni ce template (si applicable)"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de création"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Horodatage de dernière modification"
          }
        }
      },
      "PublicAccessState": {
        "type": "object",
        "description": "État d'accès public d'une ressource — la réponse de TOUS les endpoints\nd'accès public, lecture comme mutations : après un geste, on rend l'état\nqui en résulte.\n\n## Pourquoi un état, et pas un booléen\n\nL'accès public est une **concession estampillée et périssable** : elle est\nposée explicitement sur chaque ressource lors d'une propagation depuis un\ndossier racine, et elle cesse de valoir dès que la racine est dépubliée ou\nque la ressource change de place. Conséquence assumée (fail-closed) : **un\ndocument déposé après la propagation n'est PAS public** tant qu'on n'a pas\nrepropagé (`POST …/public_access/propagate`).\n\nC'est le piège le plus probable du modèle : sans `reason` et sans\n`subtree`, un appelant ne peut ni expliquer ni réparer un refus, et le\nfail-closed passe pour un bug.\n\n## Les deux références nullables\n\n- `root` : le dossier racine dont la ressource tient son accès public.\n  `null` si la ressource n'est pas publique.\n- `excluded_by` : le dossier ancêtre exclu le plus proche, qui coupe\n  l'accès de tout son contenu. `null` si aucun ancêtre n'est exclu.\n",
        "required": [
          "resource",
          "public",
          "published_root",
          "excluded"
        ],
        "properties": {
          "resource": {
            "$ref": "#/components/schemas/PublicAccessResourceRef"
          },
          "public": {
            "type": "boolean",
            "description": "La ressource est-elle lisible anonymement, ici et maintenant ?",
            "example": true
          },
          "reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "not_stamped",
              "excluded",
              "excluded_by_ancestor",
              "stale",
              "trashed",
              null
            ],
            "description": "`null` quand la ressource est publique. Sinon, la cause :\n\n| Valeur | Sens |\n|---|---|\n| `not_stamped` | jamais estampillée — dépôt après propagation, ou dossier jamais publié |\n| `excluded` | marqueur « jamais public ici » posé sur la ressource elle-même |\n| `excluded_by_ancestor` | un dossier ancêtre est exclu ; l'exclusion est transitive |\n| `stale` | concession PÉRIMÉE : racine dépubliée, ou ressource déplacée |\n| `trashed` | ressource à la corbeille |\n",
            "example": "not_stamped"
          },
          "published_root": {
            "type": "boolean",
            "description": "La ressource est-elle elle-même une racine publiée ? Toujours `false`\npour un document : la racine d'une publication est toujours un dossier.\n",
            "example": true
          },
          "root": {
            "$ref": "#/components/schemas/PublicAccessResourceRef"
          },
          "excluded": {
            "type": "boolean",
            "description": "Un marqueur « jamais public ici » est-il posé sur la ressource elle-même ?",
            "example": false
          },
          "excluded_by": {
            "$ref": "#/components/schemas/PublicAccessResourceRef"
          },
          "granted_count": {
            "type": "integer",
            "description": "Nombre de ressources estampillées par le geste qui vient d'avoir lieu.\nPrésent uniquement sur les réponses de publication et de propagation.\n",
            "example": 42
          },
          "subtree": {
            "type": "object",
            "nullable": true,
            "description": "Bilan du sous-arbre. Présent uniquement pour un **dossier qui participe\nà une publication** (racine publiée, ou dossier lui-même publiquement\nlisible) : ailleurs, « non estampillé » ne veut rien dire. Toujours\n`null` pour un document.\n\nLes trois catégories sont disjointes : `total = granted + excluded + unstamped`.\n",
            "required": [
              "total",
              "granted",
              "excluded",
              "unstamped",
              "unstamped_items"
            ],
            "properties": {
              "total": {
                "type": "integer",
                "description": "Ressources du sous-arbre, corbeille exclue",
                "example": 120
              },
              "granted": {
                "type": "integer",
                "description": "Ressources porteuses d'une concession valide et non exclues",
                "example": 118
              },
              "excluded": {
                "type": "integer",
                "description": "Ressources exclues, directement ou par un dossier ancêtre",
                "example": 1
              },
              "unstamped": {
                "type": "integer",
                "description": "Ressources ni estampillées ni exclues — celles qu'une repropagation\nrendrait publiques. Un nombre non nul signale une propagation en\nretard, pas une erreur.\n",
                "example": 1
              },
              "unstamped_items": {
                "type": "array",
                "description": "Échantillon des ressources non estampillées, borné par `limit`\n(défaut 50, maximum 500). `unstamped` reste le décompte exact.\n",
                "items": {
                  "$ref": "#/components/schemas/PublicAccessResourceRef"
                }
              }
            }
          }
        }
      },
      "PublicAccessResourceRef": {
        "type": "object",
        "nullable": true,
        "description": "Référence courte vers une ressource estampillable (dossier ou document).\n\nLe schéma est déclaré `nullable` parce que les champs `root` et\n`excluded_by` de `PublicAccessState` valent `null` quand aucune racine\nn'explique l'accès et qu'aucun ancêtre n'est exclu — le cas courant. Là où\nla référence est obligatoire (`resource`, éléments de `unstamped_items`),\nelle n'est jamais nulle : `nullable` y est un sur-ensemble inoffensif, et\nc'est le prix d'un OAS 3.0 qui ne sait pas rendre un `$ref` nullable au\npoint d'usage.\n",
        "required": [
          "id",
          "type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de la ressource",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "type": {
            "type": "string",
            "enum": [
              "folder",
              "file"
            ],
            "description": "Nature de la ressource",
            "example": "folder"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Libellé de la ressource",
            "example": "Rapports publics"
          },
          "path": {
            "type": "string",
            "nullable": true,
            "description": "Chemin ltree ABSOLU de la ressource, construit sur des identifiants\n(`ltree_id`) et non sur des noms — un renommage ne le change pas.\n",
            "example": "019951a3.019951a4.019951a5"
          }
        }
      },
      "WorkspaceMutation": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "description": "Libellé de l'espace de travail",
            "example": "Project Alpha"
          },
          "description": {
            "type": "string",
            "description": "Description de l'espace de travail",
            "example": "Main project workspace"
          },
          "classification_plan_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du plan de classement associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "classification_plan_csv": {
            "type": "string",
            "description": "Données CSV du plan de classement"
          },
          "classification_plan_label": {
            "type": "string",
            "description": "Libellé du plan de classement",
            "example": "Standard Plan"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du domaine associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed886"
          },
          "settings": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Code de l'espace de travail",
                "example": "PROJ-001"
              },
              "start_date": {
                "type": "string",
                "format": "date",
                "description": "Date de début du projet",
                "example": "2023-01-01"
              },
              "color": {
                "type": "string",
                "description": "Couleur de l'espace de travail",
                "example": "#3498db"
              },
              "icon": {
                "type": "string",
                "description": "Icône de l'espace de travail",
                "example": "folder"
              },
              "address": {
                "type": "string",
                "description": "Adresse du projet",
                "example": "123 Main St"
              },
              "postal_code": {
                "type": "string",
                "description": "Code postal du projet",
                "example": "12345"
              },
              "city": {
                "type": "string",
                "description": "Ville du projet",
                "example": "Paris"
              },
              "agency": {
                "type": "string",
                "description": "Agence du projet",
                "example": "Construction Agency"
              },
              "project_type": {
                "type": "string",
                "description": "Type de projet",
                "example": "Construction"
              },
              "status": {
                "type": "string",
                "description": "Statut du projet",
                "example": "active"
              },
              "end_date": {
                "type": "string",
                "format": "date",
                "description": "Date de fin du projet",
                "example": "2023-12-31"
              },
              "validation": {
                "type": "object",
                "properties": {
                  "default_deadline_days": {
                    "type": "integer",
                    "description": "Délai par défaut en jours",
                    "example": 30
                  },
                  "auto_reminder_days": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Rappel automatique en jours",
                    "example": [
                      7,
                      14
                    ]
                  },
                  "super_validator_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Identifiants des super validateurs",
                    "example": [
                      "019951a3-01b7-7eb9-88bb-f872a01ed886",
                      "019951a3-01b7-7eb9-88bb-f872a01ed887"
                    ]
                  }
                }
              }
            }
          },
          "metadata_user": {
            "type": "object",
            "description": "Métadonnées définies par l'utilisateur"
          }
        }
      },
      "FolderMutation": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nom du dossier",
            "example": "Documents"
          },
          "description": {
            "type": "string",
            "description": "Description du dossier",
            "example": "Main documents folder"
          },
          "color": {
            "type": "string",
            "description": "Couleur du dossier",
            "example": "#e74c3c"
          },
          "icon": {
            "type": "string",
            "description": "Icône du dossier",
            "example": "folder-open"
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du dossier parent",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'espace de travail associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          }
        }
      },
      "DocumentMutation": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nom du fichier",
            "example": "document.pdf"
          },
          "description": {
            "type": "string",
            "description": "Description du fichier",
            "example": "Important document"
          },
          "uploaded_file": {
            "type": "string",
            "format": "binary",
            "description": "Fichier à télécharger"
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du dossier parent",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'espace de travail associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du domaine associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed889"
          },
          "document_type_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du type de document associé",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed890"
          },
          "metadata_user": {
            "type": "object",
            "description": "Métadonnées définies par l'utilisateur"
          },
          "auto_classify": {
            "type": "boolean",
            "description": "Active la classification automatique du document",
            "default": false,
            "example": true
          }
        }
      },
      "BulkUploadMutation": {
        "type": "object",
        "properties": {
          "total_files": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Nombre total de fichiers qui seront uploadés"
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID du dossier de destination"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID du workspace"
          },
          "auto_classify": {
            "type": "boolean",
            "default": false,
            "description": "Activer la classification automatique des documents"
          },
          "source": {
            "type": "string",
            "description": "Source de l'upload (sera auto-détecté depuis OAuth si non fourni)",
            "example": "hubdoc-tools"
          },
          "merge_to_pdf": {
            "type": "boolean",
            "default": false,
            "description": "Fusionner toutes les sources du lot en un seul document PDF.\nChaque fichier doit alors être envoyé avec une `position` explicite ;\nla fusion démarre quand `total_files` sources ont été reçues.\nFormats acceptés : PDF, JPEG, PNG (les images deviennent une page).\n"
          },
          "merged_file_name": {
            "type": "string",
            "description": "Nom du document PDF fusionné (requis si merge_to_pdf est vrai, l'extension .pdf est ajoutée si absente)",
            "example": "dossier_complet"
          }
        },
        "required": [
          "total_files"
        ]
      },
      "ChunkedUploadMutation": {
        "type": "object",
        "description": "Paramètres pour créer une session d'upload par chunks",
        "properties": {
          "filename": {
            "type": "string",
            "description": "Nom du fichier à uploader",
            "example": "document.pdf"
          },
          "file_size": {
            "type": "integer",
            "format": "int64",
            "description": "Taille totale du fichier en octets",
            "minimum": 1,
            "maximum": 5368709120,
            "example": 52428800
          },
          "content_type": {
            "type": "string",
            "description": "Type MIME du fichier",
            "example": "application/pdf"
          },
          "chunk_size": {
            "type": "integer",
            "description": "Taille de chaque chunk en octets (optionnel, défaut 5MB)",
            "default": 5242880,
            "minimum": 1048576,
            "maximum": 104857600,
            "example": 5242880
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID du workspace de destination (optionnel)",
            "nullable": true
          },
          "documents_folder_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID du dossier de destination (optionnel)",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "description": "Métadonnées additionnelles (optionnel)",
            "additionalProperties": true,
            "nullable": true
          }
        },
        "required": [
          "filename",
          "file_size",
          "content_type"
        ]
      },
      "ChunkedUploadSessionResponse": {
        "type": "object",
        "description": "Réponse de création de session d'upload par chunks",
        "properties": {
          "upload_id": {
            "type": "string",
            "description": "Identifiant unique de la session d'upload",
            "example": "a1b2c3d4e5f6"
          },
          "chunk_size": {
            "type": "integer",
            "description": "Taille de chaque chunk en octets",
            "example": 5242880
          },
          "total_chunks": {
            "type": "integer",
            "description": "Nombre total de chunks à uploader",
            "example": 10
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date d'expiration de la session (24h)",
            "example": "2024-11-10T10:00:00Z"
          }
        },
        "required": [
          "upload_id",
          "chunk_size",
          "total_chunks",
          "expires_at"
        ]
      },
      "ChunkedUploadChunkResponse": {
        "type": "object",
        "description": "Réponse d'upload d'un chunk",
        "properties": {
          "chunk_number": {
            "type": "integer",
            "description": "Numéro du chunk uploadé",
            "example": 5
          },
          "upload_id": {
            "type": "string",
            "description": "Identifiant de la session d'upload",
            "example": "a1b2c3d4e5f6"
          },
          "progress": {
            "type": "number",
            "format": "float",
            "description": "Pourcentage de progression (0-100)",
            "example": 50
          },
          "uploaded_chunks": {
            "type": "integer",
            "description": "Nombre de chunks uploadés",
            "example": 5
          },
          "total_chunks": {
            "type": "integer",
            "description": "Nombre total de chunks",
            "example": 10
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing"
            ],
            "description": "Statut actuel de l'upload",
            "example": "processing"
          }
        },
        "required": [
          "chunk_number",
          "upload_id",
          "progress",
          "uploaded_chunks",
          "total_chunks",
          "status"
        ]
      },
      "ChunkedUploadCompleteRequest": {
        "type": "object",
        "description": "Paramètres pour finaliser un upload par chunks",
        "properties": {
          "checksum": {
            "type": "string",
            "description": "Checksum MD5 du fichier complet en base64 (optionnel mais recommandé)",
            "example": "1B2M2Y8AsgTpgAmY7PhCfg==",
            "nullable": true
          }
        }
      },
      "ChunkedUploadCompleteResponse": {
        "type": "object",
        "description": "Réponse de finalisation d'upload par chunks",
        "properties": {
          "upload_id": {
            "type": "string",
            "description": "Identifiant de la session d'upload",
            "example": "a1b2c3d4e5f6"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ],
            "description": "Statut de l'upload (toujours \"completed\" en cas de succès)",
            "example": "completed"
          },
          "assembled_file_size": {
            "type": "integer",
            "format": "int64",
            "description": "Taille du fichier assemblé en octets",
            "example": 52428800
          },
          "object_key": {
            "type": "string",
            "description": "Clé de l'objet dans le storage",
            "example": "uploads/abc123/document.pdf"
          }
        },
        "required": [
          "upload_id",
          "status",
          "object_key"
        ]
      },
      "ChunkedUploadStatusResponse": {
        "type": "object",
        "description": "Réponse du statut d'upload par chunks",
        "properties": {
          "upload_id": {
            "type": "string",
            "description": "Identifiant de la session d'upload",
            "example": "a1b2c3d4e5f6"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed",
              "cancelled",
              "expired"
            ],
            "description": "Statut actuel de l'upload",
            "example": "processing"
          },
          "progress": {
            "type": "number",
            "format": "float",
            "description": "Pourcentage de progression (0-100)",
            "example": 50
          },
          "uploaded_chunks": {
            "type": "integer",
            "description": "Nombre de chunks uploadés",
            "example": 5
          },
          "total_chunks": {
            "type": "integer",
            "description": "Nombre total de chunks",
            "example": 10
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date d'expiration de la session",
            "example": "2024-11-10T10:00:00Z"
          },
          "expired": {
            "type": "boolean",
            "description": "Indique si la session est expirée",
            "example": false
          }
        },
        "required": [
          "upload_id",
          "status",
          "progress",
          "uploaded_chunks",
          "total_chunks",
          "expires_at",
          "expired"
        ]
      },
      "ContactMutation": {
        "type": "object",
        "required": [
          "email_address"
        ],
        "properties": {
          "email_address": {
            "type": "string",
            "format": "email",
            "description": "Adresse email du contact",
            "example": "contact@example.com"
          },
          "first_name": {
            "type": "string",
            "description": "Prénom du contact",
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "description": "Nom du contact",
            "example": "Doe"
          },
          "external_id": {
            "type": "string",
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-12345"
          }
        }
      },
      "GroupMutation": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nom du groupe",
            "example": "Administrators"
          },
          "description": {
            "type": "string",
            "description": "Description du groupe",
            "example": "Group for system administrators"
          },
          "external_id": {
            "type": "string",
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-admin-group"
          }
        }
      },
      "GroupMemberMutation": {
        "type": "object",
        "required": [
          "user_external_id"
        ],
        "properties": {
          "user_external_id": {
            "type": "string",
            "description": "Identifiant externe de l'utilisateur à ajouter au groupe",
            "example": "ext-user-123"
          },
          "role": {
            "type": "string",
            "description": "Rôle à attribuer au membre (par défaut \"member\")",
            "example": "member",
            "enum": [
              "member",
              "admin",
              "owner"
            ],
            "default": "member"
          }
        }
      },
      "PermissionMutation": {
        "type": "object",
        "required": [
          "level",
          "actor_id",
          "actor_type",
          "permissible_id",
          "permissible_type"
        ],
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "read",
              "write",
              "admin"
            ],
            "description": "Niveau de permission (read, write, admin)",
            "example": "read"
          },
          "actor_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'acteur (User, Contact, Group)",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed887"
          },
          "actor_type": {
            "type": "string",
            "description": "Type d'acteur (User, Contact, Group)",
            "example": "User"
          },
          "permissible_id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de la ressource protégée",
            "example": "019951a3-01b7-7eb9-88bb-f872a01ed888"
          },
          "permissible_type": {
            "type": "string",
            "description": "Type de ressource protégée (Documents::File, Documents::Folder, etc.)",
            "example": "Documents::File"
          },
          "external_id": {
            "type": "string",
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-perm-12345"
          }
        }
      },
      "UserMutation": {
        "type": "object",
        "required": [
          "email_address"
        ],
        "properties": {
          "email_address": {
            "type": "string",
            "format": "email",
            "description": "Adresse email de l'utilisateur",
            "example": "john.doe@example.com"
          },
          "password": {
            "type": "string",
            "format": "password",
            "minLength": 6,
            "description": "Mot de passe de l'utilisateur (minimum 6 caractères)",
            "example": "securePassword123"
          },
          "first_name": {
            "type": "string",
            "maxLength": 50,
            "description": "Prénom de l'utilisateur",
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "maxLength": 50,
            "description": "Nom de famille de l'utilisateur",
            "example": "Doe"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "manager",
              "admin",
              "super_admin"
            ],
            "description": "Rôle de l'utilisateur",
            "example": "user",
            "default": "user"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe pour l'intégration avec des systèmes tiers",
            "example": "ext-user-12345"
          }
        }
      },
      "ComposedDocumentMutation": {
        "type": "object",
        "properties": {
          "template_key": {
            "type": "string",
            "description": "(Création seulement) Slug d'un DocumentTemplate. Le composed\ndocument hérite du typst_source, variables_schema et default_parts\ndu template. La création utilise l'interactor\n`Documents::ComposedDocuments::CreateFromTemplate`.\n",
            "example": "facture-edf"
          },
          "name": {
            "type": "string",
            "description": "Nom du document (par défaut, nom du template)",
            "example": "Facture Sinoia 2026-05"
          },
          "description": {
            "type": "string"
          },
          "variables": {
            "type": "object",
            "additionalProperties": true,
            "description": "Valeurs des variables du template",
            "example": {
              "client": "Sinoia",
              "montant": 1500
            }
          },
          "page_settings": {
            "type": "object",
            "additionalProperties": true
          },
          "typst_source": {
            "type": "string",
            "description": "(Update) Source Typst complète. Combiné à `assembly_mode: pure_typst`\npour piloter le rendu sans injection (#463). Côté CLI : `set-typst`.\n"
          },
          "assembly_mode": {
            "type": "string",
            "enum": [
              "composed",
              "pure_typst"
            ],
            "description": "(Update) Bascule le mode d'assemblage (#463). Un mode invalide est\nrejeté en 422.\n"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "folder_id": {
            "type": "string",
            "format": "uuid"
          },
          "metadata_user": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "PartMutation": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "content_format": {
            "type": "string",
            "enum": [
              "typst",
              "markdown"
            ]
          },
          "content_source": {
            "type": "string",
            "enum": [
              "human",
              "agent",
              "template"
            ]
          },
          "part_type": {
            "type": "string",
            "enum": [
              "content",
              "header",
              "footer",
              "signature",
              "appendix"
            ]
          },
          "position": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "editing",
              "ready",
              "validated",
              "locked"
            ]
          },
          "alignment": {
            "type": "string",
            "enum": [
              "left",
              "center",
              "right"
            ]
          },
          "bsize": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "page_break": {
            "type": "boolean"
          },
          "row_group": {
            "type": "integer"
          },
          "key": {
            "type": "string"
          },
          "instructions": {
            "type": "string"
          },
          "assignee_id": {
            "type": "string",
            "format": "uuid"
          },
          "assignee_type": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "DocumentTemplateMutation": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Slug unique du template",
            "example": "facture-edf"
          },
          "name": {
            "type": "string",
            "description": "Nom affiché du template",
            "example": "Facture EDF"
          },
          "description": {
            "type": "string",
            "description": "Description longue"
          },
          "category": {
            "type": "string",
            "description": "Catégorie fonctionnelle",
            "example": "billing"
          },
          "locale": {
            "type": "string",
            "default": "fr",
            "example": "fr"
          },
          "typst_source": {
            "type": "string",
            "description": "Source Typst du template (placeholders {{var}} supportés)"
          },
          "variables_schema": {
            "type": "object",
            "additionalProperties": true,
            "description": "JSON Schema simplifié décrivant les variables attendues"
          },
          "default_parts": {
            "type": "array",
            "description": "Parts par défaut du template (JSONB array d'objets)",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "page_settings": {
            "type": "object",
            "additionalProperties": true,
            "description": "Configuration page"
          },
          "active": {
            "type": "boolean",
            "description": "Le template est-il actif",
            "default": true
          }
        }
      },
      "MassCommunication": {
        "type": "object",
        "description": "Communication de masse (email broadcast)",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "name": {
            "type": "string",
            "description": "Nom de la communication",
            "example": "Newsletter Janvier 2024"
          },
          "subject": {
            "type": "string",
            "description": "Sujet de l'email",
            "example": "Actualités du mois"
          },
          "body": {
            "type": "string",
            "description": "Corps du message (supporte les variables Liquid)",
            "example": "Bonjour {{ recipient.first_name }},\n\nVoici les actualités..."
          },
          "communication_type": {
            "type": "string",
            "description": "Type de communication",
            "enum": [
              "email",
              "sms"
            ],
            "example": "email"
          },
          "status": {
            "type": "string",
            "description": "Statut de la communication",
            "enum": [
              "draft",
              "sending",
              "sent",
              "failed",
              "cancelled"
            ],
            "example": "draft"
          },
          "template_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du template utilisé"
          },
          "settings": {
            "type": "object",
            "description": "Paramètres additionnels",
            "additionalProperties": true
          },
          "attribute_definitions": {
            "type": "array",
            "description": "Définitions des attributs personnalisés pour les destinataires",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "placeholder": {
                  "type": "string"
                }
              }
            }
          },
          "recipients_count": {
            "type": "integer",
            "description": "Nombre total de destinataires",
            "example": 150
          },
          "sent_count": {
            "type": "integer",
            "description": "Nombre de messages envoyés avec succès",
            "example": 148
          },
          "failed_count": {
            "type": "integer",
            "description": "Nombre d'envois échoués",
            "example": 2
          },
          "created_by_id": {
            "type": "string",
            "format": "uuid",
            "description": "ID de l'utilisateur créateur"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création",
            "example": "2024-01-16T10:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de dernière modification"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'envoi",
            "example": "2024-01-16T10:05:00Z"
          }
        }
      },
      "MassCommunicationMutation": {
        "type": "object",
        "description": "Données pour créer une communication de masse",
        "required": [
          "mass_communication",
          "recipients"
        ],
        "properties": {
          "mass_communication": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "Nom de la communication",
                "example": "Newsletter Janvier 2024"
              },
              "subject": {
                "type": "string",
                "description": "Sujet de l'email (requis pour l'envoi)",
                "example": "Actualités du mois"
              },
              "body": {
                "type": "string",
                "description": "Corps du message (supporte les variables Liquid)",
                "example": "Bonjour {{ recipient.first_name }},\n\nVoici les actualités..."
              },
              "communication_type": {
                "type": "string",
                "description": "Type de communication",
                "enum": [
                  "email",
                  "sms"
                ],
                "default": "email"
              },
              "template_id": {
                "type": "string",
                "format": "uuid",
                "description": "ID du template à utiliser"
              },
              "settings": {
                "type": "object",
                "description": "Paramètres additionnels",
                "additionalProperties": true
              },
              "attribute_definitions": {
                "type": "array",
                "description": "Définitions des attributs personnalisés",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "placeholder": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "recipients": {
            "type": "array",
            "description": "Liste des destinataires",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "email"
              ],
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email",
                  "description": "Adresse email du destinataire",
                  "example": "john@example.com"
                },
                "first_name": {
                  "type": "string",
                  "description": "Prénom",
                  "example": "John"
                },
                "last_name": {
                  "type": "string",
                  "description": "Nom",
                  "example": "Doe"
                },
                "external_id": {
                  "type": "string",
                  "description": "Identifiant externe"
                },
                "custom_attributes": {
                  "type": "object",
                  "description": "Attributs personnalisés",
                  "additionalProperties": true
                }
              }
            }
          },
          "attachments": {
            "type": "array",
            "description": "Pièces jointes",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Nom du fichier"
                },
                "scope_type": {
                  "type": "string",
                  "description": "Portée de la pièce jointe",
                  "enum": [
                    "all",
                    "actor",
                    "group"
                  ],
                  "default": "all"
                },
                "actor_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "ID de l'acteur (si scope_type=actor)"
                },
                "group_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "ID du groupe (si scope_type=group)"
                }
              }
            }
          },
          "send": {
            "type": "boolean",
            "description": "Envoyer immédiatement après création",
            "default": false
          }
        }
      },
      "MassCommunicationResponse": {
        "type": "object",
        "description": "Réponse contenant une communication de masse avec ses relations",
        "properties": {
          "mass_communication": {
            "$ref": "#/components/schemas/MassCommunication"
          },
          "recipients": {
            "type": "array",
            "description": "Liste des destinataires",
            "items": {
              "$ref": "#/components/schemas/MassCommunicationRecipient"
            }
          },
          "attachments": {
            "type": "array",
            "description": "Liste des pièces jointes",
            "items": {
              "$ref": "#/components/schemas/MassCommunicationAttachment"
            }
          },
          "skipped_recipients": {
            "type": "array",
            "description": "Destinataires ignorés lors de la création",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "description": "Email du destinataire ignoré"
                },
                "reason": {
                  "type": "string",
                  "description": "Code de la raison",
                  "enum": [
                    "invalid_email_format",
                    "duplicate_email",
                    "missing_required_field"
                  ]
                },
                "message": {
                  "type": "string",
                  "description": "Message explicatif"
                }
              }
            }
          },
          "sending": {
            "type": "boolean",
            "description": "Indique si l'envoi est en cours",
            "example": false
          },
          "enqueued_count": {
            "type": "integer",
            "description": "Nombre de messages mis en file d'attente pour envoi",
            "example": 0
          }
        }
      },
      "MassCommunicationRecipient": {
        "type": "object",
        "description": "Destinataire d'une communication de masse",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique",
            "example": "660e8400-e29b-41d4-a716-446655440001"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Adresse email",
            "example": "john@example.com"
          },
          "first_name": {
            "type": "string",
            "description": "Prénom",
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "description": "Nom",
            "example": "Doe"
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe"
          },
          "status": {
            "type": "string",
            "description": "Statut de l'envoi",
            "enum": [
              "pending",
              "sent",
              "failed",
              "bounced",
              "opened",
              "clicked"
            ],
            "example": "pending"
          },
          "custom_attributes": {
            "type": "object",
            "description": "Attributs personnalisés",
            "additionalProperties": true
          },
          "actor": {
            "type": "object",
            "nullable": true,
            "description": "Acteur lié (si le destinataire est un utilisateur existant)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "type": {
                "type": "string"
              }
            }
          },
          "group": {
            "type": "object",
            "nullable": true,
            "description": "Groupe lié (si le destinataire fait partie d'un groupe)",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'envoi"
          },
          "opened_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'ouverture"
          },
          "error_message": {
            "type": "string",
            "nullable": true,
            "description": "Message d'erreur en cas d'échec"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création"
          }
        }
      },
      "MassCommunicationAttachment": {
        "type": "object",
        "description": "Pièce jointe d'une communication de masse",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique",
            "example": "770e8400-e29b-41d4-a716-446655440002"
          },
          "name": {
            "type": "string",
            "description": "Nom du fichier",
            "example": "document.pdf"
          },
          "scope_type": {
            "type": "string",
            "description": "Portée de la pièce jointe",
            "enum": [
              "all",
              "actor",
              "group"
            ],
            "example": "all"
          },
          "actor_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID de l'acteur (si scope_type=actor)"
          },
          "group_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "ID du groupe (si scope_type=group)"
          },
          "file_url": {
            "type": "string",
            "format": "uri",
            "description": "URL de téléchargement du fichier"
          },
          "content_type": {
            "type": "string",
            "description": "Type MIME du fichier",
            "example": "application/pdf"
          },
          "file_size": {
            "type": "integer",
            "description": "Taille du fichier en octets",
            "example": 102400
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Type d'erreur",
            "example": "validation_error"
          },
          "message": {
            "type": "string",
            "description": "Message d'erreur lisible",
            "example": "The request contains invalid parameters"
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Détails supplémentaires de l'erreur",
            "example": [
              "Name is required",
              "Workspace ID must be a valid integer"
            ]
          }
        }
      },
      "LoctaviaMandataire": {
        "$ref": "#/components/schemas/Mandataire"
      },
      "LoctaviaLease": {
        "$ref": "#/components/schemas/Lease"
      },
      "LoctaviaLeaseDetails": {
        "$ref": "#/components/schemas/LeaseDetails"
      },
      "LoctaviaLeaseMutation": {
        "$ref": "#/components/schemas/LeaseMutation"
      },
      "LoctaviaBillingRun": {
        "$ref": "#/components/schemas/BillingRun"
      },
      "LoctaviaBillingRunDetails": {
        "$ref": "#/components/schemas/BillingRunDetails"
      },
      "LoctaviaBillingRunMutation": {
        "$ref": "#/components/schemas/BillingRunMutation"
      },
      "LoctaviaBillingLine": {
        "$ref": "#/components/schemas/BillingLine"
      },
      "LoctaviaBillingLineUpdate": {
        "$ref": "#/components/schemas/BillingLineUpdate"
      },
      "LoctaviaMandate": {
        "$ref": "#/components/schemas/Mandate"
      },
      "LoctaviaMandateDetails": {
        "$ref": "#/components/schemas/MandateDetails"
      },
      "LoctaviaMandateMutation": {
        "$ref": "#/components/schemas/MandateMutation"
      },
      "LoctaviaProperty": {
        "$ref": "#/components/schemas/Property"
      },
      "LoctaviaPropertyDetails": {
        "$ref": "#/components/schemas/PropertyDetails"
      },
      "LoctaviaPropertyMutation": {
        "$ref": "#/components/schemas/PropertyMutation"
      },
      "LoctaviaUnit": {
        "$ref": "#/components/schemas/Unit"
      },
      "LoctaviaUnitDetails": {
        "$ref": "#/components/schemas/UnitDetails"
      },
      "LoctaviaUnitMutation": {
        "$ref": "#/components/schemas/UnitMutation"
      },
      "LoctaviaOwner": {
        "$ref": "#/components/schemas/Owner"
      },
      "LoctaviaOwnerDetails": {
        "$ref": "#/components/schemas/OwnerDetails"
      },
      "LoctaviaOwnerMutation": {
        "$ref": "#/components/schemas/OwnerMutation"
      },
      "LoctaviaTenant": {
        "$ref": "#/components/schemas/Tenant"
      },
      "LoctaviaTenantDetails": {
        "$ref": "#/components/schemas/TenantDetails"
      },
      "LoctaviaPayment": {
        "$ref": "#/components/schemas/Payment"
      },
      "LoctaviaPaymentDetails": {
        "$ref": "#/components/schemas/PaymentDetails"
      },
      "LoctaviaIncident": {
        "$ref": "#/components/schemas/Incident"
      },
      "LoctaviaIncidentDetails": {
        "$ref": "#/components/schemas/IncidentDetails"
      },
      "LoctaviaJournalEntry": {
        "$ref": "#/components/schemas/JournalEntry"
      },
      "LoctaviaJournalEntryDetails": {
        "$ref": "#/components/schemas/JournalEntryDetails"
      },
      "LoctaviaJournalEntryLine": {
        "$ref": "#/components/schemas/JournalEntryLine"
      },
      "LoctaviaMoney": {
        "$ref": "#/components/schemas/Money"
      },
      "LoctaviaFecExport": {
        "$ref": "#/components/schemas/FecExport"
      },
      "LoctaviaFecExportMutation": {
        "$ref": "#/components/schemas/FecExportMutation"
      },
      "Mandataire": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "accountingCode": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Money": {
        "type": "object",
        "nullable": true,
        "description": "Montant monétaire tel que sérialisé par Loctavia (`MoneySerializer`).\n`null` quand le montant n'est pas défini.\n",
        "properties": {
          "cents": {
            "type": "integer",
            "description": "Montant en centimes (entier, devise dans `currency`).",
            "example": 120000
          },
          "currency": {
            "type": "string",
            "description": "Code devise ISO 4217.",
            "example": "EUR"
          },
          "formatted": {
            "type": "string",
            "description": "Montant formaté pour affichage (séparateur de milliers, symbole).",
            "example": "1 200,00 €"
          }
        }
      },
      "Lease": {
        "type": "object",
        "additionalProperties": true,
        "description": "Bail en payload de liste (attributs légers). Version condensée retournée par l'endpoint d'index des baux.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du bail",
            "example": "3f8a1c2e-9b4d-4e5f-8a1b-2c3d4e5f6a7b"
          },
          "reference": {
            "type": "string",
            "description": "Référence lisible du bail",
            "example": "BL-2024-00042"
          },
          "leaseType": {
            "type": "string",
            "description": "Type de bail (habitation, commercial, professionnel…)",
            "example": "residential"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "terminated",
              "renewed",
              "closed_pending"
            ],
            "description": "Statut du bail",
            "example": "active"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de début du bail",
            "example": "2024-01-01"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de fin du bail",
            "example": "2027-01-01"
          },
          "tenantName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du locataire principal",
            "example": "Jean Dupont"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'entité locataire principale (partyable)",
            "example": "7a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"
          },
          "unitIdentifier": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant du premier lot rattaché au bail",
            "example": "A-101"
          },
          "propertyName": {
            "type": "string",
            "nullable": true,
            "description": "Nom de l'immeuble du premier lot",
            "example": "Résidence Les Tilleuls"
          },
          "propertyId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'immeuble du premier lot",
            "example": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
          },
          "monthlyRent": {
            "$ref": "#/components/schemas/Money"
          },
          "paymentTiming": {
            "type": "string",
            "nullable": true,
            "description": "Terme de paiement (à échoir / échu)",
            "example": "in_advance"
          },
          "units": {
            "type": "array",
            "description": "Lots rattachés au bail (aperçu)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "identifier": {
                  "type": "string",
                  "description": "Identifiant du lot",
                  "example": "A-101"
                },
                "propertyName": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom de l'immeuble du lot",
                  "example": "Résidence Les Tilleuls"
                }
              }
            }
          }
        }
      },
      "LeaseMutation": {
        "type": "object",
        "required": [
          "lease"
        ],
        "properties": {
          "lease": {
            "type": "object",
            "required": [
              "lease_type",
              "start_date"
            ],
            "properties": {
              "lease_type": {
                "type": "string"
              },
              "reference": {
                "type": "string"
              },
              "start_date": {
                "type": "string",
                "format": "date"
              },
              "end_date": {
                "type": "string",
                "format": "date"
              },
              "signature_date": {
                "type": "string",
                "format": "date"
              },
              "payment_day": {
                "type": "integer"
              },
              "payment_frequency": {
                "type": "string",
                "enum": [
                  "monthly",
                  "quarterly",
                  "annual"
                ]
              },
              "payment_timing": {
                "type": "string",
                "enum": [
                  "advance",
                  "arrears"
                ]
              },
              "auto_renewal": {
                "type": "boolean"
              },
              "notice_months": {
                "type": "integer"
              },
              "status": {
                "type": "string",
                "enum": [
                  "draft",
                  "active"
                ]
              }
            }
          },
          "term": {
            "type": "object",
            "properties": {
              "revision_month": {
                "type": "integer",
                "minimum": 1,
                "maximum": 12
              },
              "effective_from": {
                "type": "string",
                "format": "date"
              },
              "effective_to": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "units": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "base_rent_cents": {
                  "type": "integer"
                },
                "charge_provision_cents": {
                  "type": "integer"
                },
                "vat_applicable": {
                  "type": "boolean"
                },
                "vat_rate": {
                  "type": "number"
                },
                "index_type": {
                  "type": "string",
                  "enum": [
                    "irl",
                    "ilc",
                    "ilat",
                    "icc"
                  ]
                }
              }
            }
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "LeaseDetails": {
        "type": "object",
        "additionalProperties": true,
        "description": "Bail en payload de détail (attributs complets). Déroule toutes les structures imbriquées : termes, parties, lots, indicateur de révision, révisions, dépôt de garantie, polices GLI, dates de rupture, franchises, plafonnements et mesures d'accompagnement.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique du bail",
            "example": "3f8a1c2e-9b4d-4e5f-8a1b-2c3d4e5f6a7b"
          },
          "reference": {
            "type": "string",
            "description": "Référence lisible du bail",
            "example": "BL-2024-00042"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe (système d'origine / reprise)",
            "example": "LEGACY-88213"
          },
          "leaseType": {
            "type": "string",
            "description": "Type de bail (habitation, commercial, professionnel…)",
            "example": "residential"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "terminated",
              "renewed",
              "closed_pending"
            ],
            "description": "Statut du bail",
            "example": "active"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de début du bail",
            "example": "2024-01-01"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de fin du bail",
            "example": "2027-01-01"
          },
          "signatureDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de signature du bail",
            "example": "2023-12-15"
          },
          "revisionMode": {
            "type": "string",
            "nullable": true,
            "description": "Mode de révision (indexation) du bail",
            "example": "automatic"
          },
          "initialBalance": {
            "$ref": "#/components/schemas/Money"
          },
          "cafAllocataireNumber": {
            "type": "string",
            "nullable": true,
            "description": "Numéro d'allocataire CAF",
            "example": "1234567"
          },
          "paymentDay": {
            "type": "integer",
            "nullable": true,
            "description": "Jour du mois de prélèvement / échéance",
            "example": 5
          },
          "paymentFrequency": {
            "type": "string",
            "nullable": true,
            "description": "Fréquence de paiement",
            "example": "monthly"
          },
          "paymentTiming": {
            "type": "string",
            "nullable": true,
            "description": "Terme de paiement (à échoir / échu)",
            "example": "in_advance"
          },
          "noticeMonths": {
            "type": "integer",
            "nullable": true,
            "description": "Durée de préavis en mois",
            "example": 3
          },
          "terms": {
            "type": "array",
            "description": "Termes du bail (ordonnés du plus récent au plus ancien)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du terme"
                },
                "baseRent": {
                  "$ref": "#/components/schemas/Money"
                },
                "chargeProvision": {
                  "$ref": "#/components/schemas/Money"
                },
                "totalMonthly": {
                  "$ref": "#/components/schemas/Money"
                },
                "effectiveFrom": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date d'entrée en vigueur du terme",
                  "example": "2024-01-01"
                },
                "effectiveTo": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de fin du terme",
                  "example": "2024-12-31"
                },
                "revisionMonth": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Mois de révision (1-12)",
                  "example": 1
                },
                "revisionFrequencyYears": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Fréquence de révision en années",
                  "example": 1
                }
              }
            }
          },
          "parties": {
            "type": "array",
            "description": "Parties au bail (locataires, garants, propriétaires…)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la partie"
                },
                "partyType": {
                  "type": "string",
                  "description": "Type de partie (tenant, owner, guarantor…)",
                  "example": "tenant"
                },
                "partyableType": {
                  "type": "string",
                  "description": "Type polymorphe de l'entité rattachée",
                  "example": "Loctavia::Contact"
                },
                "role": {
                  "type": "string",
                  "description": "Rôle de la partie dans le bail",
                  "example": "main"
                },
                "name": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom affichable de l'entité rattachée",
                  "example": "Jean Dupont"
                },
                "email": {
                  "type": "string",
                  "nullable": true,
                  "description": "Adresse email de l'entité rattachée",
                  "example": "jean.dupont@example.com"
                },
                "partyableId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Identifiant de l'entité rattachée"
                },
                "shareNumerator": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Numérateur de la quote-part",
                  "example": 1
                },
                "shareDenominator": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Dénominateur de la quote-part",
                  "example": 2
                },
                "validFrom": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Début de validité de la partie",
                  "example": "2024-01-01"
                },
                "validTo": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Fin de validité de la partie",
                  "example": "2027-01-01"
                },
                "tenantProfileId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Identifiant du profil locataire associé"
                }
              }
            }
          },
          "units": {
            "type": "array",
            "description": "Lots rattachés au bail (détail par lot)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du lot de bail (lease_unit)"
                },
                "unitId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du lot (unit)"
                },
                "identifier": {
                  "type": "string",
                  "description": "Identifiant lisible du lot",
                  "example": "A-101"
                },
                "propertyName": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom de l'immeuble du lot",
                  "example": "Résidence Les Tilleuls"
                },
                "propertyId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Identifiant de l'immeuble du lot"
                },
                "areaSqm": {
                  "type": "number",
                  "nullable": true,
                  "description": "Surface du lot en mètres carrés",
                  "example": 45.5
                },
                "entryDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date d'entrée du lot dans le bail",
                  "example": "2024-01-01"
                },
                "exitDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de sortie du lot du bail",
                  "example": null
                },
                "baseRentCents": {
                  "type": "integer",
                  "description": "Loyer de base du lot en centimes",
                  "example": 60000
                },
                "annualAmountCents": {
                  "type": "integer",
                  "description": "Montant annuel du lot en centimes",
                  "example": 720000
                },
                "chargeProvisionCents": {
                  "type": "integer",
                  "description": "Provision de charges du lot en centimes",
                  "example": 10000
                },
                "vatApplicable": {
                  "type": "boolean",
                  "description": "TVA applicable sur le lot",
                  "example": false
                },
                "vatRate": {
                  "type": "number",
                  "nullable": true,
                  "description": "Taux de TVA appliqué",
                  "example": 20
                },
                "indexType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type d'indice de révision (ILC, ILAT, IRL…)",
                  "example": "IRL"
                },
                "indexBaseDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de base de l'indice",
                  "example": "2023-01-01"
                },
                "indexBaseValue": {
                  "type": "number",
                  "nullable": true,
                  "description": "Valeur de base de l'indice",
                  "example": 138.61
                }
              }
            }
          },
          "revisionIndicator": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Indicateur de révision (indexation). Nul si l'indexation est désactivée ou si aucun terme / indice effectif n'est disponible.",
            "properties": {
              "indexCode": {
                "type": "string",
                "description": "Code de l'indice retenu (premier lot en multi-indices)",
                "example": "IRL"
              },
              "revisionDue": {
                "type": "boolean",
                "description": "Une révision est due selon la fréquence contractuelle",
                "example": true
              },
              "lastRevisionDate": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Date de la dernière révision effectuée",
                "example": "2024-01-01"
              },
              "estimatedIncrease": {
                "type": "number",
                "nullable": true,
                "description": "Augmentation estimée en pourcentage à partir des derniers indices",
                "example": 3.5
              },
              "revisionMonth": {
                "type": "integer",
                "nullable": true,
                "description": "Mois de révision du terme courant",
                "example": 1
              },
              "indexAvailable": {
                "type": "boolean",
                "description": "L'indice de révision le plus récent est disponible",
                "example": true
              },
              "latestPeriod": {
                "type": "string",
                "nullable": true,
                "description": "Dernière période d'indice attendue (ex. 2024-Q1)",
                "example": "2024-Q1"
              },
              "nextPublicationInfo": {
                "type": "object",
                "nullable": true,
                "additionalProperties": true,
                "description": "Prochaine publication d'indice attendue (si indice indisponible)",
                "properties": {
                  "period": {
                    "type": "string",
                    "description": "Période concernée",
                    "example": "2024-Q2"
                  },
                  "expectedDate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true,
                    "description": "Date de publication attendue",
                    "example": "2024-07-15"
                  }
                }
              }
            }
          },
          "revisions": {
            "type": "array",
            "description": "Révisions du bail (5 plus récentes, ordonnées par date décroissante)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la révision"
                },
                "revisionDate": {
                  "type": "string",
                  "format": "date",
                  "description": "Date d'effet de la révision",
                  "example": "2024-01-01"
                },
                "indexCode": {
                  "type": "string",
                  "nullable": true,
                  "description": "Code de l'indice (nul en révision multi-indices)",
                  "example": "IRL"
                },
                "indexCodes": {
                  "type": "array",
                  "description": "Codes d'indices distincts couverts par la révision",
                  "items": {
                    "type": "string",
                    "example": "IRL"
                  }
                },
                "multiIndex": {
                  "type": "boolean",
                  "description": "La révision couvre plusieurs indices distincts",
                  "example": false
                },
                "referencePeriod": {
                  "type": "string",
                  "nullable": true,
                  "description": "Période de référence de l'indice",
                  "example": "2023-Q1"
                },
                "revisionPeriod": {
                  "type": "string",
                  "nullable": true,
                  "description": "Période de révision de l'indice",
                  "example": "2024-Q1"
                },
                "referenceValue": {
                  "type": "number",
                  "nullable": true,
                  "description": "Valeur de l'indice de référence",
                  "example": 138.61
                },
                "revisionValue": {
                  "type": "number",
                  "nullable": true,
                  "description": "Valeur de l'indice de révision",
                  "example": 143.46
                },
                "effectiveRatio": {
                  "type": "number",
                  "nullable": true,
                  "description": "Ratio de révision effectivement appliqué",
                  "example": 1.035
                },
                "oldRent": {
                  "$ref": "#/components/schemas/Money"
                },
                "newRent": {
                  "$ref": "#/components/schemas/Money"
                },
                "increasePercent": {
                  "type": "number",
                  "nullable": true,
                  "description": "Pourcentage d'augmentation",
                  "example": 3.5
                },
                "computationTrace": {
                  "type": "object",
                  "nullable": true,
                  "additionalProperties": true,
                  "description": "Trace de calcul détaillée de la révision (audit du calcul)"
                },
                "lines": {
                  "type": "array",
                  "description": "Détail de la révision par lot",
                  "items": {
                    "type": "object",
                    "additionalProperties": true,
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant de la ligne de révision"
                      },
                      "leaseUnitId": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Identifiant du lot de bail concerné"
                      },
                      "unitIdentifier": {
                        "type": "string",
                        "nullable": true,
                        "description": "Identifiant lisible du lot",
                        "example": "A-101"
                      },
                      "indexCode": {
                        "type": "string",
                        "nullable": true,
                        "description": "Code de l'indice appliqué à ce lot",
                        "example": "IRL"
                      },
                      "referencePeriod": {
                        "type": "string",
                        "nullable": true,
                        "description": "Période de référence de l'indice",
                        "example": "2023-Q1"
                      },
                      "revisionPeriod": {
                        "type": "string",
                        "nullable": true,
                        "description": "Période de révision de l'indice",
                        "example": "2024-Q1"
                      },
                      "referenceValue": {
                        "type": "number",
                        "nullable": true,
                        "description": "Valeur de l'indice de référence",
                        "example": 138.61
                      },
                      "revisionValue": {
                        "type": "number",
                        "nullable": true,
                        "description": "Valeur de l'indice de révision",
                        "example": 143.46
                      },
                      "oldCents": {
                        "type": "integer",
                        "nullable": true,
                        "description": "Ancien loyer du lot en centimes",
                        "example": 60000
                      },
                      "newCents": {
                        "type": "integer",
                        "nullable": true,
                        "description": "Nouveau loyer du lot en centimes",
                        "example": 62100
                      },
                      "increasePercent": {
                        "type": "number",
                        "nullable": true,
                        "description": "Pourcentage d'augmentation du lot",
                        "example": 3.5
                      }
                    }
                  }
                },
                "createdBy": {
                  "type": "object",
                  "nullable": true,
                  "additionalProperties": true,
                  "description": "Auteur de la révision (nul si non renseigné)",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant de l'utilisateur"
                    },
                    "name": {
                      "type": "string",
                      "description": "Nom affichable de l'utilisateur",
                      "example": "Marie Martin"
                    }
                  }
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true,
                  "description": "Date de création de la révision",
                  "example": "2024-01-02T09:30:00Z"
                }
              }
            }
          },
          "securityDeposit": {
            "$ref": "#/components/schemas/Money"
          },
          "gliPolicies": {
            "type": "array",
            "description": "Polices d'assurance garantie loyers impayés (GLI)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la police"
                },
                "insurerName": {
                  "type": "string",
                  "description": "Nom de l'assureur",
                  "example": "Assur'Loyer"
                },
                "policyNumber": {
                  "type": "string",
                  "description": "Numéro de police",
                  "example": "GLI-2024-001"
                },
                "premium": {
                  "$ref": "#/components/schemas/Money"
                },
                "premiumFrequency": {
                  "type": "string",
                  "description": "Fréquence de la prime",
                  "example": "monthly"
                },
                "coverageRate": {
                  "type": "number",
                  "nullable": true,
                  "description": "Taux de couverture",
                  "example": 3.5
                },
                "startDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début de la police",
                  "example": "2024-01-01"
                },
                "endDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de fin de la police",
                  "example": "2025-01-01"
                },
                "status": {
                  "type": "string",
                  "description": "Statut de la police",
                  "example": "active"
                },
                "expiringSoon": {
                  "type": "boolean",
                  "description": "La police expire sous 30 jours",
                  "example": false
                }
              }
            }
          },
          "breakDates": {
            "type": "array",
            "description": "Dates de rupture / faculté de résiliation du bail",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la date de rupture"
                },
                "breakDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de rupture",
                  "example": "2025-01-01"
                },
                "breakType": {
                  "type": "string",
                  "description": "Type de rupture (triennale, dérogatoire…)",
                  "example": "triennial"
                },
                "noticeDeadline": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date limite de préavis",
                  "example": "2024-07-01"
                },
                "status": {
                  "type": "string",
                  "description": "Statut de la date de rupture",
                  "example": "upcoming"
                },
                "daysUntilBreak": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Nombre de jours avant la date de rupture",
                  "example": 180
                },
                "noticePeriodStarted": {
                  "type": "boolean",
                  "description": "Le préavis a commencé",
                  "example": false
                }
              }
            }
          },
          "franchisePeriods": {
            "type": "array",
            "description": "Périodes de franchise (loyer réduit / offert)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la période de franchise"
                },
                "startDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début de la franchise",
                  "example": "2024-01-01"
                },
                "endDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de fin de la franchise",
                  "example": "2024-03-31"
                },
                "franchiseType": {
                  "type": "string",
                  "description": "Type de franchise (totale, partielle)",
                  "example": "total"
                },
                "reductionPercent": {
                  "type": "number",
                  "nullable": true,
                  "description": "Pourcentage de réduction",
                  "example": 100
                },
                "reason": {
                  "type": "string",
                  "nullable": true,
                  "description": "Motif de la franchise",
                  "example": "Travaux d'aménagement"
                },
                "scopeType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Périmètre d'application de la franchise",
                  "example": "rent"
                }
              }
            }
          },
          "cappingRules": {
            "type": "array",
            "description": "Règles de plafonnement (Pinel, dérogatoire, encadrement…)",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la règle de plafonnement"
                },
                "cappingType": {
                  "type": "string",
                  "description": "Type de plafonnement",
                  "example": "cap_pinel"
                },
                "referenceAmount": {
                  "$ref": "#/components/schemas/Money"
                },
                "maxIncreasePercent": {
                  "type": "number",
                  "nullable": true,
                  "description": "Pourcentage maximal d'augmentation autorisé",
                  "example": 3.5
                },
                "startDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début d'application de la règle",
                  "example": "2024-01-01"
                },
                "zone": {
                  "type": "string",
                  "nullable": true,
                  "description": "Zone géographique concernée",
                  "example": "A_bis"
                }
              }
            }
          },
          "supportMeasures": {
            "type": "array",
            "description": "Mesures d'accompagnement (modificateurs hors indexation et ajustement de charges), ordonnées par priorité.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du modificateur"
                },
                "modifierType": {
                  "type": "string",
                  "description": "Type de modificateur",
                  "example": "discount"
                },
                "category": {
                  "type": "string",
                  "description": "Catégorie (franchise, capping, discount, adjustment, penalty, other)",
                  "example": "discount"
                },
                "status": {
                  "type": "string",
                  "description": "Statut de la mesure (active, cancelled, expired, upcoming)",
                  "example": "active"
                },
                "label": {
                  "type": "string",
                  "nullable": true,
                  "description": "Libellé de la mesure (motif ou libellé auto-généré)",
                  "example": "Remise commerciale"
                },
                "validFrom": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début de validité",
                  "example": "2024-01-01"
                },
                "validTo": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de fin de validité",
                  "example": "2024-06-30"
                },
                "priority": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Priorité d'application du modificateur",
                  "example": 10
                },
                "impactLabel": {
                  "type": "string",
                  "nullable": true,
                  "description": "Libellé de l'impact (ex. \"-10%\", \"+50,00 €/mois\")",
                  "example": "-10%"
                },
                "configuration": {
                  "type": "object",
                  "nullable": true,
                  "additionalProperties": true,
                  "description": "Configuration brute du modificateur"
                },
                "sourceType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type de la source du modificateur (démodulisé)",
                  "example": "SupportMeasure"
                },
                "sourceId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Identifiant de la source du modificateur"
                }
              }
            }
          },
          "autoRenewal": {
            "type": "boolean",
            "nullable": true,
            "description": "Reconduction tacite activée",
            "example": true
          },
          "renewalNoticePeriod": {
            "type": "integer",
            "nullable": true,
            "description": "Durée du préavis de reconduction (mois)",
            "example": 6
          },
          "renewalNoticeDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de préavis de reconduction",
            "example": "2026-07-01"
          }
        }
      },
      "BillingRun": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du run de facturation"
          },
          "reference": {
            "type": "string",
            "description": "Référence du run",
            "example": "FACT-2026-05"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "validated",
              "applied",
              "cancelled",
              "scheduled",
              "computed"
            ],
            "description": "Statut du run de facturation"
          },
          "periodStart": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Début de la période facturée"
          },
          "periodEnd": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Fin de la période facturée"
          },
          "totalTenantAmount": {
            "type": "number",
            "description": "Montant total locataires"
          },
          "totalOwnerAmount": {
            "type": "number",
            "description": "Montant total propriétaires"
          },
          "linesCount": {
            "type": "integer",
            "description": "Nombre de lignes de quittancement (appels de loyer locataires)"
          },
          "anomaliesCount": {
            "type": "integer",
            "description": "Nombre d'anomalies détectées"
          },
          "validatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de validation du run"
          },
          "validatedBy": {
            "type": "string",
            "nullable": true,
            "description": "Auteur de la validation du run"
          },
          "appliedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'application du run"
          },
          "billingFrequency": {
            "type": "string",
            "nullable": true,
            "description": "Fréquence de facturation du run"
          },
          "editable": {
            "type": "boolean",
            "description": "Indique si le run est modifiable"
          },
          "cancellable": {
            "type": "boolean",
            "description": "Indique si le run est annulable"
          },
          "currentStep": {
            "type": "integer",
            "nullable": true,
            "description": "Étape courante dans le workflow du run"
          },
          "allAnomaliesAcknowledged": {
            "type": "boolean",
            "description": "Indique si toutes les anomalies ont été acquittées"
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de planification du run"
          },
          "computedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de calcul du run"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de création du run"
          },
          "totalRentAmount": {
            "type": "number",
            "description": "Montant total des loyers (hors provisions de charges)"
          },
          "totalChargesAmount": {
            "type": "number",
            "description": "Montant total des provisions de charges"
          }
        }
      },
      "BillingRunMutation": {
        "type": "object",
        "required": [
          "period_start"
        ],
        "properties": {
          "period_start": {
            "type": "string",
            "format": "date",
            "description": "Premier jour du mois facturé (YYYY-MM-DD)"
          },
          "grouping_mode": {
            "type": "string",
            "enum": [
              "per_lease",
              "per_tenant"
            ]
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "schedule": {
            "type": "boolean"
          },
          "frequencies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "monthly",
                "quarterly",
                "annual"
              ]
            }
          },
          "timings": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "advance",
                "arrears"
              ]
            }
          },
          "lease_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "lease_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "exclude_lease_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "BillingRunDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du run de facturation"
          },
          "reference": {
            "type": "string",
            "description": "Référence du run",
            "example": "FACT-2026-05"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "review",
              "validated",
              "applied",
              "cancelled",
              "scheduled",
              "computed"
            ],
            "description": "Statut du run de facturation"
          },
          "periodStart": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Début de la période facturée"
          },
          "periodEnd": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Fin de la période facturée"
          },
          "totalTenantAmount": {
            "type": "number",
            "description": "Montant total locataires"
          },
          "totalOwnerAmount": {
            "type": "number",
            "description": "Montant total propriétaires"
          },
          "linesCount": {
            "type": "integer",
            "description": "Nombre de lignes de quittancement (appels de loyer locataires)"
          },
          "anomaliesCount": {
            "type": "integer",
            "description": "Nombre d'anomalies détectées"
          },
          "validatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de validation du run"
          },
          "validatedBy": {
            "type": "string",
            "nullable": true,
            "description": "Auteur de la validation du run"
          },
          "appliedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'application du run"
          },
          "billingFrequency": {
            "type": "string",
            "nullable": true,
            "description": "Fréquence de facturation du run"
          },
          "editable": {
            "type": "boolean",
            "description": "Indique si le run est modifiable"
          },
          "cancellable": {
            "type": "boolean",
            "description": "Indique si le run est annulable"
          },
          "currentStep": {
            "type": "integer",
            "nullable": true,
            "description": "Étape courante dans le workflow du run"
          },
          "allAnomaliesAcknowledged": {
            "type": "boolean",
            "description": "Indique si toutes les anomalies ont été acquittées"
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de planification du run"
          },
          "computedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de calcul du run"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de création du run"
          },
          "totalRentAmount": {
            "type": "number",
            "description": "Montant total des loyers (hors provisions de charges)"
          },
          "totalChargesAmount": {
            "type": "number",
            "description": "Montant total des provisions de charges"
          },
          "groupingMode": {
            "type": "string",
            "nullable": true,
            "description": "Mode de regroupement des lignes du run"
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'annulation du run"
          },
          "cancellationReason": {
            "type": "string",
            "nullable": true,
            "description": "Motif d'annulation du run"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Métadonnées libres du run"
          },
          "ownerPaymentsSummary": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Synthèse des reversements propriétaires",
            "properties": {
              "count": {
                "type": "integer",
                "description": "Nombre de reversements"
              },
              "totalGross": {
                "type": "number",
                "description": "Montant brut total des reversements"
              },
              "totalNet": {
                "type": "number",
                "description": "Montant net total des reversements"
              }
            }
          },
          "billingNoticesSummary": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Synthèse des avis de facturation (mode groupé par locataire)",
            "properties": {
              "count": {
                "type": "integer",
                "description": "Nombre d'avis"
              },
              "totalAmountCents": {
                "type": "integer",
                "description": "Montant total des avis en centimes"
              },
              "notices": {
                "type": "array",
                "description": "Liste des avis de facturation",
                "items": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant de l'avis"
                    },
                    "reference": {
                      "type": "string",
                      "description": "Référence de l'avis"
                    },
                    "tenantName": {
                      "type": "string",
                      "nullable": true,
                      "description": "Nom du locataire"
                    },
                    "totalAmountCents": {
                      "type": "integer",
                      "description": "Montant total de l'avis en centimes"
                    },
                    "linesCount": {
                      "type": "integer",
                      "description": "Nombre de lignes de l'avis"
                    }
                  }
                }
              }
            }
          },
          "receiptsMetadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Métadonnées des quittances (extrait de metadata.receipts)"
          },
          "scopeSummary": {
            "type": "object",
            "additionalProperties": true,
            "description": "Synthèse du périmètre du run",
            "properties": {
              "period": {
                "type": "string",
                "nullable": true,
                "description": "Libellé de la période (mois année)",
                "example": "mai 2026"
              },
              "frequencies": {
                "type": "array",
                "description": "Fréquences incluses dans le périmètre",
                "items": {
                  "type": "string"
                }
              },
              "timings": {
                "type": "array",
                "description": "Temporalités incluses dans le périmètre",
                "items": {
                  "type": "string"
                }
              },
              "leaseTypes": {
                "type": "array",
                "description": "Types de baux inclus dans le périmètre",
                "items": {
                  "type": "string"
                }
              },
              "propertyId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Identifiant du bien filtré, le cas échéant"
              },
              "linesCount": {
                "type": "integer",
                "description": "Nombre total de lignes locataires du périmètre"
              },
              "excludedCount": {
                "type": "integer",
                "description": "Nombre de lignes locataires exclues"
              },
              "totalLeases": {
                "type": "integer",
                "description": "Nombre de baux distincts du périmètre"
              }
            }
          },
          "vatBreakdown": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Ventilation de la TVA calculée pour le run"
          }
        }
      },
      "BillingLine": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lineType": {
            "type": "string",
            "enum": [
              "tenant",
              "owner"
            ]
          },
          "billingCategory": {
            "type": "string"
          },
          "computedAmount": {
            "type": "number"
          },
          "baseAmount": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "validated",
              "anomaly",
              "excluded"
            ]
          },
          "leaseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "unitIdentifier": {
            "type": "string",
            "nullable": true
          },
          "hasAnomaly": {
            "type": "boolean"
          },
          "anomalyType": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "BillingLineUpdate": {
        "type": "object",
        "description": "Fournir soit `excluded` (inclure/exclure), soit `review_status` (acquitter une anomalie).",
        "properties": {
          "excluded": {
            "type": "boolean"
          },
          "review_status": {
            "type": "string",
            "enum": [
              "ok",
              "flagged"
            ]
          },
          "comment": {
            "type": "string"
          }
        }
      },
      "Mandate": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du mandat de gestion"
          },
          "reference": {
            "type": "string",
            "description": "Référence du mandat",
            "example": "MG-2026-0042"
          },
          "mandateType": {
            "type": "string",
            "description": "Type de mandat de gestion",
            "example": "gestion_locative"
          },
          "status": {
            "type": "string",
            "description": "Statut du mandat",
            "example": "active"
          },
          "ownerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du propriétaire (Owner) rattaché au mandat"
          },
          "ownerActorId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'acteur propriétaire sous-jacent"
          },
          "ownerName": {
            "type": "string",
            "nullable": true,
            "description": "Nom affiché du propriétaire",
            "example": "SCI Les Tilleuls"
          },
          "propertyId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du bien géré"
          },
          "propertyName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du bien géré",
            "example": "Résidence Bellevue"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de début du mandat"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de fin du mandat"
          },
          "managementFeeRate": {
            "type": "number",
            "nullable": true,
            "description": "Taux d'honoraires de gestion",
            "example": 7.5
          },
          "paymentTiming": {
            "type": "string",
            "nullable": true,
            "description": "Modalité de temporalité des paiements",
            "example": "term_echu"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de création du mandat"
          }
        }
      },
      "MandateMutation": {
        "type": "object",
        "required": [
          "owner_profile_id",
          "reference",
          "start_date"
        ],
        "properties": {
          "owner_profile_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string",
            "example": "MAN-2026-001"
          },
          "mandate_type": {
            "type": "string",
            "enum": [
              "full_management",
              "rental_only",
              "charges_only"
            ],
            "default": "full_management"
          },
          "start_date": {
            "type": "string",
            "format": "date"
          },
          "end_date": {
            "type": "string",
            "format": "date"
          },
          "management_fee_rate": {
            "type": "number",
            "example": 0.08
          }
        }
      },
      "MandateDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du mandat de gestion"
          },
          "reference": {
            "type": "string",
            "description": "Référence du mandat",
            "example": "MG-2026-0042"
          },
          "mandateType": {
            "type": "string",
            "description": "Type de mandat de gestion",
            "example": "gestion_locative"
          },
          "status": {
            "type": "string",
            "description": "Statut du mandat",
            "example": "active"
          },
          "ownerId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du propriétaire (Owner) rattaché au mandat"
          },
          "ownerActorId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'acteur propriétaire sous-jacent"
          },
          "ownerName": {
            "type": "string",
            "nullable": true,
            "description": "Nom affiché du propriétaire",
            "example": "SCI Les Tilleuls"
          },
          "propertyId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du bien géré"
          },
          "propertyName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du bien géré",
            "example": "Résidence Bellevue"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de début du mandat"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de fin du mandat"
          },
          "managementFeeRate": {
            "type": "number",
            "nullable": true,
            "description": "Taux d'honoraires de gestion",
            "example": 7.5
          },
          "paymentTiming": {
            "type": "string",
            "nullable": true,
            "description": "Modalité de temporalité des paiements",
            "example": "term_echu"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de création du mandat"
          },
          "mandataireId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du mandataire gestionnaire"
          },
          "signatureDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de signature du mandat"
          },
          "vatRateOnFees": {
            "type": "number",
            "nullable": true,
            "description": "Taux de TVA appliqué sur les honoraires",
            "example": 20
          },
          "renewalMode": {
            "type": "string",
            "nullable": true,
            "description": "Mode de renouvellement du mandat",
            "example": "tacit"
          },
          "noticeMonths": {
            "type": "integer",
            "nullable": true,
            "description": "Durée du préavis en mois",
            "example": 3
          },
          "specialConditions": {
            "type": "string",
            "nullable": true,
            "description": "Conditions particulières du mandat"
          },
          "distributionMode": {
            "type": "string",
            "nullable": true,
            "description": "Mode de reversement au propriétaire"
          },
          "distributionFrequency": {
            "type": "string",
            "nullable": true,
            "description": "Fréquence de reversement au propriétaire"
          },
          "distributionDay": {
            "type": "integer",
            "nullable": true,
            "description": "Jour du mois pour le reversement",
            "example": 5
          },
          "distributionMarginDays": {
            "type": "integer",
            "nullable": true,
            "description": "Nombre de jours de marge avant reversement"
          },
          "depotGarantieDetention": {
            "type": "string",
            "nullable": true,
            "description": "Détenteur du dépôt de garantie"
          },
          "retenueDestination": {
            "type": "string",
            "nullable": true,
            "description": "Destination des retenues"
          },
          "accountingMode": {
            "type": "string",
            "nullable": true,
            "description": "Mode comptable configuré"
          },
          "effectiveAccountingMode": {
            "type": "string",
            "nullable": true,
            "description": "Mode comptable effectif résolu"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Métadonnées libres du mandat"
          },
          "mandateServices": {
            "type": "array",
            "description": "Prestations (services) associées au mandat, ordonnées",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la prestation"
                },
                "serviceType": {
                  "type": "string",
                  "description": "Type de prestation"
                },
                "label": {
                  "type": "string",
                  "description": "Libellé de la prestation"
                },
                "billingMode": {
                  "type": "string",
                  "description": "Mode de facturation de la prestation"
                },
                "rate": {
                  "type": "number",
                  "nullable": true,
                  "description": "Taux appliqué"
                },
                "amountCents": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Montant en centimes"
                },
                "calculationBase": {
                  "type": "string",
                  "nullable": true,
                  "description": "Base de calcul"
                },
                "frequency": {
                  "type": "string",
                  "nullable": true,
                  "description": "Fréquence de facturation"
                },
                "triggerEvent": {
                  "type": "string",
                  "nullable": true,
                  "description": "Événement déclencheur"
                },
                "target": {
                  "type": "string",
                  "nullable": true,
                  "description": "Cible de la prestation"
                },
                "deliveryMode": {
                  "type": "string",
                  "nullable": true,
                  "description": "Mode de délivrance"
                },
                "vatApplicable": {
                  "type": "boolean",
                  "nullable": true,
                  "description": "Indique si la TVA s'applique"
                },
                "vatRate": {
                  "type": "number",
                  "nullable": true,
                  "description": "Taux de TVA de la prestation"
                },
                "priority": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Priorité d'application"
                },
                "active": {
                  "type": "boolean",
                  "nullable": true,
                  "description": "Indique si la prestation est active"
                },
                "configuration": {
                  "type": "object",
                  "additionalProperties": true,
                  "nullable": true,
                  "description": "Configuration spécifique de la prestation"
                },
                "position": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Position d'ordre d'affichage"
                }
              }
            }
          }
        }
      },
      "Property": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'immeuble"
          },
          "name": {
            "type": "string",
            "description": "Nom de l'immeuble",
            "example": "Résidence Les Tilleuls"
          },
          "propertyType": {
            "type": "string",
            "nullable": true,
            "description": "Type d'immeuble",
            "example": "residence"
          },
          "address": {
            "type": "string",
            "nullable": true,
            "description": "Adresse de l'immeuble",
            "example": "12 rue des Lilas"
          },
          "city": {
            "type": "string",
            "nullable": true,
            "description": "Ville",
            "example": "Lyon"
          },
          "postalCode": {
            "type": "string",
            "nullable": true,
            "description": "Code postal",
            "example": "69003"
          },
          "hasElevator": {
            "type": "boolean",
            "nullable": true,
            "description": "Présence d'un ascenseur"
          },
          "floorCount": {
            "type": "integer",
            "nullable": true,
            "description": "Nombre d'étages",
            "example": 5
          },
          "active": {
            "type": "boolean",
            "nullable": true,
            "description": "Immeuble actif"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe (système source / reprise)"
          },
          "shareKeys": {
            "type": "array",
            "description": "Clés de répartition (tantièmes) de l'immeuble",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la clé de répartition"
                },
                "name": {
                  "type": "string",
                  "description": "Libellé de la clé de répartition",
                  "example": "Charges générales"
                },
                "total": {
                  "type": "integer",
                  "description": "Total des tantièmes (dénominateur de la clé)",
                  "example": 10000
                },
                "chargeNatureId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Nature de charge rattachée à la clé"
                },
                "distributionStrategy": {
                  "type": "integer",
                  "description": "Stratégie de distribution (enum manual, by_area, by_floors_climbed, by_habitable_area, by_unit_count, by_residential_lots)",
                  "example": 0
                },
                "excludedUnitTypes": {
                  "type": "array",
                  "description": "Types de lots exclus de la répartition",
                  "items": {
                    "type": "string"
                  }
                },
                "lastModifiedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true,
                  "description": "Date de la dernière modification d'une allocation"
                },
                "activeVersionsCount": {
                  "type": "integer",
                  "description": "Nombre d'allocations en version active à ce jour"
                },
                "historicalVersionsCount": {
                  "type": "integer",
                  "description": "Nombre d'allocations historiques (versions clôturées)"
                }
              }
            }
          },
          "unitsCount": {
            "type": "integer",
            "description": "Nombre de lots de l'immeuble",
            "example": 24
          },
          "activeLeaseCount": {
            "type": "integer",
            "description": "Nombre de baux actifs",
            "example": 18
          },
          "occupiedCount": {
            "type": "integer",
            "description": "Nombre de lots occupés",
            "example": 18
          },
          "vacantCount": {
            "type": "integer",
            "description": "Nombre de lots vacants",
            "example": 6
          },
          "totalSurface": {
            "type": "number",
            "description": "Surface totale de l'immeuble en m²",
            "example": 1240.5
          },
          "occupancyRate": {
            "type": "number",
            "description": "Taux d'occupation en pourcentage",
            "example": 75
          }
        }
      },
      "PropertyMutation": {
        "type": "object",
        "required": [
          "name",
          "address"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "Résidence de la Paix"
          },
          "address": {
            "type": "string",
            "example": "1 rue de la Paix, 75002 Paris"
          },
          "city": {
            "type": "string"
          },
          "postal_code": {
            "type": "string"
          },
          "property_type": {
            "type": "string",
            "enum": [
              "residential",
              "commercial",
              "mixed"
            ],
            "default": "commercial"
          },
          "has_elevator": {
            "type": "boolean"
          },
          "floor_count": {
            "type": "integer"
          }
        }
      },
      "PropertyDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'immeuble"
          },
          "name": {
            "type": "string",
            "description": "Nom de l'immeuble",
            "example": "Résidence Les Tilleuls"
          },
          "propertyType": {
            "type": "string",
            "nullable": true,
            "description": "Type d'immeuble",
            "example": "residence"
          },
          "address": {
            "type": "string",
            "nullable": true,
            "description": "Adresse de l'immeuble",
            "example": "12 rue des Lilas"
          },
          "city": {
            "type": "string",
            "nullable": true,
            "description": "Ville",
            "example": "Lyon"
          },
          "postalCode": {
            "type": "string",
            "nullable": true,
            "description": "Code postal",
            "example": "69003"
          },
          "hasElevator": {
            "type": "boolean",
            "nullable": true,
            "description": "Présence d'un ascenseur"
          },
          "floorCount": {
            "type": "integer",
            "nullable": true,
            "description": "Nombre d'étages",
            "example": 5
          },
          "active": {
            "type": "boolean",
            "nullable": true,
            "description": "Immeuble actif"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe (système source / reprise)"
          },
          "shareKeys": {
            "type": "array",
            "description": "Clés de répartition (tantièmes) de l'immeuble",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la clé de répartition"
                },
                "name": {
                  "type": "string",
                  "description": "Libellé de la clé de répartition",
                  "example": "Charges générales"
                },
                "total": {
                  "type": "integer",
                  "description": "Total des tantièmes (dénominateur de la clé)",
                  "example": 10000
                },
                "chargeNatureId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Nature de charge rattachée à la clé"
                },
                "distributionStrategy": {
                  "type": "integer",
                  "description": "Stratégie de distribution (enum manual, by_area, by_floors_climbed, by_habitable_area, by_unit_count, by_residential_lots)",
                  "example": 0
                },
                "excludedUnitTypes": {
                  "type": "array",
                  "description": "Types de lots exclus de la répartition",
                  "items": {
                    "type": "string"
                  }
                },
                "lastModifiedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true,
                  "description": "Date de la dernière modification d'une allocation"
                },
                "activeVersionsCount": {
                  "type": "integer",
                  "description": "Nombre d'allocations en version active à ce jour"
                },
                "historicalVersionsCount": {
                  "type": "integer",
                  "description": "Nombre d'allocations historiques (versions clôturées)"
                }
              }
            }
          },
          "units": {
            "type": "array",
            "description": "Lots de l'immeuble (détail complet, cf. UnitDetails)",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du lot"
                },
                "identifier": {
                  "type": "string",
                  "description": "Identifiant fonctionnel du lot",
                  "example": "A-102"
                },
                "unitType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type de lot",
                  "example": "apartment"
                },
                "areaSqm": {
                  "type": "number",
                  "nullable": true,
                  "description": "Surface en m²",
                  "example": 45.5
                },
                "floor": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Étage du lot",
                  "example": 2
                },
                "lotNumber": {
                  "type": "string",
                  "nullable": true,
                  "description": "Numéro de lot au règlement de copropriété",
                  "example": "102"
                },
                "active": {
                  "type": "boolean",
                  "nullable": true,
                  "description": "Lot actif"
                },
                "externalId": {
                  "type": "string",
                  "nullable": true,
                  "description": "Identifiant externe"
                },
                "dateReception": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de réception du lot"
                },
                "allocations": {
                  "type": "array",
                  "description": "Répartitions de tantièmes actives (version courante)",
                  "items": {
                    "type": "object",
                    "properties": {
                      "shareKeyId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant de la clé de répartition"
                      },
                      "shareKeyName": {
                        "type": "string",
                        "description": "Libellé de la clé de répartition",
                        "example": "Charges générales"
                      },
                      "shares": {
                        "type": "integer",
                        "description": "Nombre de tantièmes attribués",
                        "example": 120
                      }
                    }
                  }
                },
                "description": {
                  "type": "string",
                  "nullable": true,
                  "description": "Description libre du lot"
                },
                "owners": {
                  "type": "array",
                  "description": "Propriétaires actifs du lot",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant du propriétaire"
                      },
                      "name": {
                        "type": "string",
                        "description": "Nom affiché du propriétaire",
                        "example": "SCI Les Tilleuls"
                      },
                      "shareRatio": {
                        "type": "string",
                        "description": "Quote-part de propriété (numérateur/dénominateur)",
                        "example": "1/2"
                      }
                    }
                  }
                },
                "activeLease": {
                  "type": "object",
                  "nullable": true,
                  "description": "Bail actif en cours sur le lot",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant du bail"
                    },
                    "reference": {
                      "type": "string",
                      "description": "Référence du bail",
                      "example": "BAIL-2026-014"
                    },
                    "status": {
                      "type": "string",
                      "description": "Statut du bail",
                      "example": "active"
                    }
                  }
                },
                "propertyId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de l'immeuble parent"
                },
                "propertyName": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom de l'immeuble parent",
                  "example": "Résidence Les Tilleuls"
                },
                "ownerships": {
                  "type": "array",
                  "description": "Propriétés actives (quotes-parts en cours)",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant de la propriété"
                      },
                      "ownerId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant du propriétaire"
                      },
                      "ownerName": {
                        "type": "string",
                        "description": "Nom affiché du propriétaire",
                        "example": "SCI Les Tilleuls"
                      },
                      "shareNumerator": {
                        "type": "integer",
                        "description": "Numérateur de la quote-part",
                        "example": 1
                      },
                      "shareDenominator": {
                        "type": "integer",
                        "description": "Dénominateur de la quote-part",
                        "example": 2
                      },
                      "shareRatio": {
                        "type": "number",
                        "description": "Quote-part de propriété (ratio décimal)",
                        "example": 0.5
                      },
                      "sharePercent": {
                        "type": "number",
                        "description": "Quote-part de propriété en pourcentage",
                        "example": 50
                      },
                      "validFrom": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "description": "Date de début de validité"
                      },
                      "acquisitionType": {
                        "type": "string",
                        "nullable": true,
                        "description": "Type d'acquisition",
                        "example": "purchase"
                      }
                    }
                  }
                },
                "ownershipHistory": {
                  "type": "array",
                  "description": "Historique des propriétés clôturées",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant de la propriété"
                      },
                      "ownerId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant du propriétaire"
                      },
                      "ownerName": {
                        "type": "string",
                        "description": "Nom affiché du propriétaire",
                        "example": "SCI Les Tilleuls"
                      },
                      "shareNumerator": {
                        "type": "integer",
                        "description": "Numérateur de la quote-part",
                        "example": 1
                      },
                      "shareDenominator": {
                        "type": "integer",
                        "description": "Dénominateur de la quote-part",
                        "example": 2
                      },
                      "shareRatio": {
                        "type": "number",
                        "description": "Quote-part de propriété (ratio décimal)",
                        "example": 0.5
                      },
                      "sharePercent": {
                        "type": "number",
                        "description": "Quote-part de propriété en pourcentage",
                        "example": 50
                      },
                      "validFrom": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "description": "Date de début de validité"
                      },
                      "acquisitionType": {
                        "type": "string",
                        "nullable": true,
                        "description": "Type d'acquisition",
                        "example": "purchase"
                      },
                      "validTo": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "description": "Date de fin de validité"
                      }
                    }
                  }
                },
                "sharesComplete": {
                  "type": "boolean",
                  "description": "Indique si la somme des quotes-parts actives couvre 100 %"
                },
                "totalSharePercent": {
                  "type": "number",
                  "description": "Somme des quotes-parts actives en pourcentage",
                  "example": 100
                },
                "leases": {
                  "type": "array",
                  "description": "Baux rattachés au lot (les plus récents d'abord)",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant du bail"
                      },
                      "leaseUnitId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Identifiant du lien bail-lot"
                      },
                      "reference": {
                        "type": "string",
                        "description": "Référence du bail",
                        "example": "BAIL-2026-014"
                      },
                      "status": {
                        "type": "string",
                        "description": "Statut du bail",
                        "example": "active"
                      },
                      "startDate": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "description": "Date de début du bail"
                      },
                      "endDate": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "description": "Date de fin du bail"
                      }
                    }
                  }
                },
                "qrCode": {
                  "type": "object",
                  "nullable": true,
                  "description": "QR code rattaché au lot",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant du QR code"
                    },
                    "token": {
                      "type": "string",
                      "description": "Jeton du QR code"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Date de génération du QR code"
                    }
                  }
                },
                "dpe": {
                  "type": "object",
                  "description": "Diagnostic de performance énergétique du lot",
                  "properties": {
                    "rating": {
                      "type": "string",
                      "nullable": true,
                      "description": "Étiquette DPE",
                      "example": "D"
                    },
                    "issuedOn": {
                      "type": "string",
                      "format": "date",
                      "nullable": true,
                      "description": "Date d'établissement du DPE"
                    },
                    "degraded": {
                      "type": "boolean",
                      "description": "DPE dégradé (étiquette F ou G)"
                    },
                    "daysUntilBan": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Nombre de jours avant interdiction de location (loi Climat)",
                      "example": 540
                    },
                    "documentId": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true,
                      "description": "Identifiant du document DPE"
                    },
                    "documentUrl": {
                      "type": "string",
                      "nullable": true,
                      "description": "URL de téléchargement du document DPE",
                      "example": "/documents/files/2b1c.../download"
                    },
                    "documentName": {
                      "type": "string",
                      "nullable": true,
                      "description": "Nom du document DPE"
                    }
                  }
                },
                "cgpOverride": {
                  "type": "object",
                  "nullable": true,
                  "description": "Surcharge du CGP au niveau du lot",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant du contact CGP"
                    },
                    "displayName": {
                      "type": "string",
                      "description": "Nom affiché du CGP",
                      "example": "Jean Dupont"
                    },
                    "email": {
                      "type": "string",
                      "nullable": true,
                      "description": "Adresse e-mail du CGP"
                    },
                    "phoneNumber": {
                      "type": "string",
                      "nullable": true,
                      "description": "Numéro de téléphone du CGP"
                    },
                    "companyName": {
                      "type": "string",
                      "nullable": true,
                      "description": "Raison sociale du CGP"
                    },
                    "validFrom": {
                      "type": "string",
                      "format": "date",
                      "nullable": true,
                      "description": "Date de début de validité de la surcharge"
                    }
                  }
                },
                "effectiveCgp": {
                  "type": "object",
                  "nullable": true,
                  "description": "CGP effectif à ce jour",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant du contact CGP"
                    },
                    "displayName": {
                      "type": "string",
                      "description": "Nom affiché du CGP",
                      "example": "Jean Dupont"
                    },
                    "companyName": {
                      "type": "string",
                      "nullable": true,
                      "description": "Raison sociale du CGP"
                    },
                    "source": {
                      "type": "string",
                      "description": "Origine du CGP effectif",
                      "enum": [
                        "unit_override",
                        "owner_referent"
                      ],
                      "example": "owner_referent"
                    }
                  }
                }
              }
            }
          },
          "entrances": {
            "type": "array",
            "description": "Entrées / cages d'escalier de l'immeuble",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de l'entrée"
                },
                "name": {
                  "type": "string",
                  "description": "Nom de l'entrée",
                  "example": "Bâtiment A"
                },
                "address": {
                  "type": "string",
                  "nullable": true,
                  "description": "Adresse de l'entrée"
                },
                "postalCode": {
                  "type": "string",
                  "nullable": true,
                  "description": "Code postal de l'entrée"
                },
                "city": {
                  "type": "string",
                  "nullable": true,
                  "description": "Ville de l'entrée"
                },
                "active": {
                  "type": "boolean",
                  "nullable": true,
                  "description": "Entrée active"
                },
                "propertyId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de l'immeuble parent"
                },
                "unitsCount": {
                  "type": "integer",
                  "description": "Nombre de lots visibles rattachés à l'entrée",
                  "example": 8
                },
                "patrimonyCode": {
                  "type": "string",
                  "nullable": true,
                  "description": "Code patrimoine de l'entrée"
                },
                "qrCode": {
                  "type": "object",
                  "nullable": true,
                  "description": "QR code rattaché à l'entrée",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifiant du QR code"
                    },
                    "token": {
                      "type": "string",
                      "description": "Jeton du QR code"
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Date de génération du QR code"
                    }
                  }
                }
              }
            }
          },
          "patrimonyCode": {
            "type": "string",
            "nullable": true,
            "description": "Code patrimoine de l'immeuble"
          },
          "qrCode": {
            "type": "object",
            "nullable": true,
            "description": "QR code rattaché à l'immeuble",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant du QR code"
              },
              "token": {
                "type": "string",
                "description": "Jeton du QR code"
              },
              "generatedAt": {
                "type": "string",
                "format": "date-time",
                "description": "Date de génération du QR code"
              }
            }
          }
        }
      },
      "Unit": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du lot"
          },
          "identifier": {
            "type": "string",
            "description": "Identifiant fonctionnel du lot (numéro / repère)",
            "example": "A-102"
          },
          "unitType": {
            "type": "string",
            "nullable": true,
            "description": "Type de lot (appartement, cave, parking, commerce…)",
            "example": "apartment"
          },
          "areaSqm": {
            "type": "number",
            "nullable": true,
            "description": "Surface en m²",
            "example": 45.5
          },
          "floor": {
            "type": "integer",
            "nullable": true,
            "description": "Étage du lot",
            "example": 2
          },
          "lotNumber": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de lot au règlement de copropriété",
            "example": "102"
          },
          "active": {
            "type": "boolean",
            "nullable": true,
            "description": "Lot actif"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe (système source / reprise)"
          },
          "dateReception": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de réception du lot"
          },
          "allocations": {
            "type": "array",
            "description": "Répartitions de tantièmes actives (version courante)",
            "items": {
              "type": "object",
              "properties": {
                "shareKeyId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la clé de répartition"
                },
                "shareKeyName": {
                  "type": "string",
                  "description": "Libellé de la clé de répartition",
                  "example": "Charges générales"
                },
                "shares": {
                  "type": "integer",
                  "description": "Nombre de tantièmes attribués au lot pour cette clé",
                  "example": 120
                }
              }
            }
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description libre du lot"
          },
          "owners": {
            "type": "array",
            "description": "Propriétaires actifs du lot",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du propriétaire"
                },
                "name": {
                  "type": "string",
                  "description": "Nom affiché du propriétaire",
                  "example": "SCI Les Tilleuls"
                },
                "shareRatio": {
                  "type": "string",
                  "description": "Quote-part de propriété (numérateur/dénominateur)",
                  "example": "1/2"
                }
              }
            }
          },
          "activeLease": {
            "type": "object",
            "nullable": true,
            "description": "Bail actif en cours sur le lot, le cas échéant",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant du bail"
              },
              "reference": {
                "type": "string",
                "description": "Référence du bail",
                "example": "BAIL-2026-014"
              },
              "status": {
                "type": "string",
                "description": "Statut du bail",
                "example": "active"
              }
            }
          }
        }
      },
      "UnitMutation": {
        "type": "object",
        "required": [
          "property_id",
          "identifier"
        ],
        "properties": {
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "identifier": {
            "type": "string",
            "example": "Lot 12"
          },
          "unit_type": {
            "type": "string",
            "enum": [
              "apartment",
              "office",
              "retail",
              "parking",
              "storage"
            ],
            "default": "retail"
          },
          "area_sqm": {
            "type": "number"
          },
          "floor": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "UnitDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du lot"
          },
          "identifier": {
            "type": "string",
            "description": "Identifiant fonctionnel du lot (numéro / repère)",
            "example": "A-102"
          },
          "unitType": {
            "type": "string",
            "nullable": true,
            "description": "Type de lot (appartement, cave, parking, commerce…)",
            "example": "apartment"
          },
          "areaSqm": {
            "type": "number",
            "nullable": true,
            "description": "Surface en m²",
            "example": 45.5
          },
          "floor": {
            "type": "integer",
            "nullable": true,
            "description": "Étage du lot",
            "example": 2
          },
          "lotNumber": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de lot au règlement de copropriété",
            "example": "102"
          },
          "active": {
            "type": "boolean",
            "nullable": true,
            "description": "Lot actif"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant externe (système source / reprise)"
          },
          "dateReception": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date de réception du lot"
          },
          "allocations": {
            "type": "array",
            "description": "Répartitions de tantièmes actives (version courante)",
            "items": {
              "type": "object",
              "properties": {
                "shareKeyId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la clé de répartition"
                },
                "shareKeyName": {
                  "type": "string",
                  "description": "Libellé de la clé de répartition",
                  "example": "Charges générales"
                },
                "shares": {
                  "type": "integer",
                  "description": "Nombre de tantièmes attribués au lot pour cette clé",
                  "example": 120
                }
              }
            }
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description libre du lot"
          },
          "owners": {
            "type": "array",
            "description": "Propriétaires actifs du lot",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du propriétaire"
                },
                "name": {
                  "type": "string",
                  "description": "Nom affiché du propriétaire",
                  "example": "SCI Les Tilleuls"
                },
                "shareRatio": {
                  "type": "string",
                  "description": "Quote-part de propriété (numérateur/dénominateur)",
                  "example": "1/2"
                }
              }
            }
          },
          "activeLease": {
            "type": "object",
            "nullable": true,
            "description": "Bail actif en cours sur le lot, le cas échéant",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant du bail"
              },
              "reference": {
                "type": "string",
                "description": "Référence du bail",
                "example": "BAIL-2026-014"
              },
              "status": {
                "type": "string",
                "description": "Statut du bail",
                "example": "active"
              }
            }
          },
          "propertyId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'immeuble parent"
          },
          "propertyName": {
            "type": "string",
            "nullable": true,
            "description": "Nom de l'immeuble parent",
            "example": "Résidence Les Tilleuls"
          },
          "ownerships": {
            "type": "array",
            "description": "Propriétés actives (quotes-parts en cours)",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la propriété"
                },
                "ownerId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du propriétaire"
                },
                "ownerName": {
                  "type": "string",
                  "description": "Nom affiché du propriétaire",
                  "example": "SCI Les Tilleuls"
                },
                "shareNumerator": {
                  "type": "integer",
                  "description": "Numérateur de la quote-part",
                  "example": 1
                },
                "shareDenominator": {
                  "type": "integer",
                  "description": "Dénominateur de la quote-part",
                  "example": 2
                },
                "shareRatio": {
                  "type": "number",
                  "description": "Quote-part de propriété (ratio décimal)",
                  "example": 0.5
                },
                "sharePercent": {
                  "type": "number",
                  "description": "Quote-part de propriété en pourcentage",
                  "example": 50
                },
                "validFrom": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début de validité de la propriété"
                },
                "acquisitionType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type d'acquisition",
                  "example": "purchase"
                }
              }
            }
          },
          "ownershipHistory": {
            "type": "array",
            "description": "Historique des propriétés clôturées",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la propriété"
                },
                "ownerId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du propriétaire"
                },
                "ownerName": {
                  "type": "string",
                  "description": "Nom affiché du propriétaire",
                  "example": "SCI Les Tilleuls"
                },
                "shareNumerator": {
                  "type": "integer",
                  "description": "Numérateur de la quote-part",
                  "example": 1
                },
                "shareDenominator": {
                  "type": "integer",
                  "description": "Dénominateur de la quote-part",
                  "example": 2
                },
                "shareRatio": {
                  "type": "number",
                  "description": "Quote-part de propriété (ratio décimal)",
                  "example": 0.5
                },
                "sharePercent": {
                  "type": "number",
                  "description": "Quote-part de propriété en pourcentage",
                  "example": 50
                },
                "validFrom": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début de validité de la propriété"
                },
                "acquisitionType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type d'acquisition",
                  "example": "purchase"
                },
                "validTo": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de fin de validité de la propriété"
                }
              }
            }
          },
          "sharesComplete": {
            "type": "boolean",
            "description": "Indique si la somme des quotes-parts actives couvre 100 %"
          },
          "totalSharePercent": {
            "type": "number",
            "description": "Somme des quotes-parts actives en pourcentage",
            "example": 100
          },
          "leases": {
            "type": "array",
            "description": "Baux rattachés au lot (les plus récents d'abord)",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du bail"
                },
                "leaseUnitId": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant du lien bail-lot"
                },
                "reference": {
                  "type": "string",
                  "description": "Référence du bail",
                  "example": "BAIL-2026-014"
                },
                "status": {
                  "type": "string",
                  "description": "Statut du bail",
                  "example": "active"
                },
                "startDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de début du bail"
                },
                "endDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de fin du bail"
                }
              }
            }
          },
          "qrCode": {
            "type": "object",
            "nullable": true,
            "description": "QR code rattaché au lot",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant du QR code"
              },
              "token": {
                "type": "string",
                "description": "Jeton du QR code"
              },
              "generatedAt": {
                "type": "string",
                "format": "date-time",
                "description": "Date de génération du QR code"
              }
            }
          },
          "dpe": {
            "type": "object",
            "description": "Diagnostic de performance énergétique du lot",
            "properties": {
              "rating": {
                "type": "string",
                "nullable": true,
                "description": "Étiquette DPE",
                "example": "D"
              },
              "issuedOn": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Date d'établissement du DPE"
              },
              "degraded": {
                "type": "boolean",
                "description": "DPE dégradé (étiquette F ou G)"
              },
              "daysUntilBan": {
                "type": "integer",
                "nullable": true,
                "description": "Nombre de jours avant interdiction de location (loi Climat)",
                "example": 540
              },
              "documentId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Identifiant du document DPE"
              },
              "documentUrl": {
                "type": "string",
                "nullable": true,
                "description": "URL de téléchargement du document DPE",
                "example": "/documents/files/2b1c.../download"
              },
              "documentName": {
                "type": "string",
                "nullable": true,
                "description": "Nom du document DPE"
              }
            }
          },
          "cgpOverride": {
            "type": "object",
            "nullable": true,
            "description": "Surcharge du conseiller en gestion de patrimoine au niveau du lot",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant du contact CGP"
              },
              "displayName": {
                "type": "string",
                "description": "Nom affiché du CGP",
                "example": "Jean Dupont"
              },
              "email": {
                "type": "string",
                "nullable": true,
                "description": "Adresse e-mail du CGP"
              },
              "phoneNumber": {
                "type": "string",
                "nullable": true,
                "description": "Numéro de téléphone du CGP"
              },
              "companyName": {
                "type": "string",
                "nullable": true,
                "description": "Raison sociale du CGP"
              },
              "validFrom": {
                "type": "string",
                "format": "date",
                "nullable": true,
                "description": "Date de début de validité de la surcharge"
              }
            }
          },
          "effectiveCgp": {
            "type": "object",
            "nullable": true,
            "description": "Conseiller en gestion de patrimoine effectif à ce jour",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant du contact CGP"
              },
              "displayName": {
                "type": "string",
                "description": "Nom affiché du CGP",
                "example": "Jean Dupont"
              },
              "companyName": {
                "type": "string",
                "nullable": true,
                "description": "Raison sociale du CGP"
              },
              "source": {
                "type": "string",
                "description": "Origine du CGP effectif",
                "enum": [
                  "unit_override",
                  "owner_referent"
                ],
                "example": "owner_referent"
              }
            }
          }
        }
      },
      "Owner": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'acteur propriétaire (Actor)."
          },
          "ownerProfileId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du profil propriétaire (OwnerProfile) par mandataire."
          },
          "displayName": {
            "type": "string",
            "description": "Nom d'affichage du propriétaire.",
            "example": "SCI Les Tilleuls"
          },
          "firstName": {
            "type": "string",
            "nullable": true,
            "description": "Prénom."
          },
          "lastName": {
            "type": "string",
            "nullable": true,
            "description": "Nom de famille."
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Adresse e-mail.",
            "example": "contact@lestilleuls.fr"
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de téléphone."
          },
          "ownerType": {
            "type": "string",
            "description": "Type de propriétaire (personne physique / morale)."
          },
          "companyName": {
            "type": "string",
            "nullable": true,
            "description": "Raison sociale."
          },
          "siret": {
            "type": "string",
            "nullable": true,
            "description": "Numéro SIRET.",
            "example": "81234567800012"
          },
          "taxId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant fiscal."
          },
          "address": {
            "type": "string",
            "nullable": true,
            "description": "Adresse postale."
          },
          "accountingCode": {
            "type": "string",
            "nullable": true,
            "description": "Code comptable du propriétaire."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de création du profil."
          }
        }
      },
      "OwnerMutation": {
        "type": "object",
        "required": [
          "first_name"
        ],
        "properties": {
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "email_address": {
            "type": "string",
            "format": "email"
          },
          "phone_number": {
            "type": "string"
          },
          "owner_type": {
            "type": "string",
            "enum": [
              "individual",
              "company",
              "sci"
            ],
            "default": "individual"
          },
          "company_name": {
            "type": "string"
          },
          "siret": {
            "type": "string"
          },
          "address": {
            "type": "string"
          }
        }
      },
      "OwnerDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'acteur propriétaire (Actor)."
          },
          "profileId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du profil propriétaire (OwnerProfile)."
          },
          "name": {
            "type": "string",
            "description": "Nom complet (prénom + nom).",
            "example": "Jean Dupont"
          },
          "firstName": {
            "type": "string",
            "nullable": true,
            "description": "Prénom."
          },
          "lastName": {
            "type": "string",
            "nullable": true,
            "description": "Nom de famille."
          },
          "contactType": {
            "type": "string",
            "description": "Type de contact (toujours \"owner\").",
            "example": "owner"
          },
          "subType": {
            "type": "string",
            "description": "Sous-type (type de propriétaire)."
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Adresse e-mail.",
            "example": "jean.dupont@example.fr"
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de téléphone."
          },
          "companyName": {
            "type": "string",
            "nullable": true,
            "description": "Raison sociale."
          },
          "siret": {
            "type": "string",
            "nullable": true,
            "description": "Numéro SIRET.",
            "example": "81234567800012"
          },
          "address": {
            "type": "string",
            "nullable": true,
            "description": "Adresse postale."
          },
          "taxId": {
            "type": "string",
            "nullable": true,
            "description": "Identifiant fiscal."
          },
          "bankAccounts": {
            "type": "array",
            "description": "Comptes bancaires du propriétaire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "label": {
                  "type": "string",
                  "nullable": true,
                  "description": "Libellé du compte."
                },
                "iban": {
                  "type": "string",
                  "nullable": true
                },
                "bic": {
                  "type": "string",
                  "nullable": true
                },
                "bankName": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom de la banque."
                },
                "isDefault": {
                  "type": "boolean",
                  "description": "Compte par défaut."
                }
              }
            }
          },
          "ownerships": {
            "type": "array",
            "description": "Détentions de tantièmes par lot.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "propertyName": {
                  "type": "string",
                  "description": "Nom de l'immeuble."
                },
                "propertyId": {
                  "type": "string",
                  "format": "uuid"
                },
                "unitIdentifier": {
                  "type": "string",
                  "description": "Identifiant du lot."
                },
                "unitId": {
                  "type": "string",
                  "format": "uuid"
                },
                "shareNumerator": {
                  "type": "integer",
                  "description": "Numérateur de la quote-part."
                },
                "shareDenominator": {
                  "type": "integer",
                  "description": "Dénominateur de la quote-part."
                }
              }
            }
          },
          "properties": {
            "type": "array",
            "description": "Immeubles détenus par le propriétaire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string",
                  "description": "Nom de l'immeuble."
                },
                "propertyType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type d'immeuble."
                },
                "address": {
                  "type": "string",
                  "nullable": true
                },
                "city": {
                  "type": "string",
                  "nullable": true
                },
                "unitsOwned": {
                  "type": "integer",
                  "description": "Nombre de lots détenus dans cet immeuble."
                }
              }
            }
          },
          "ownerPayments": {
            "type": "array",
            "description": "10 derniers reversements au propriétaire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "reference": {
                  "type": "string",
                  "nullable": true
                },
                "billingRunReference": {
                  "type": "string",
                  "nullable": true,
                  "description": "Référence de la campagne de facturation associée."
                },
                "periodStart": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "periodEnd": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "grossAmountCents": {
                  "type": "integer",
                  "description": "Montant brut en centimes."
                },
                "netAmountCents": {
                  "type": "integer",
                  "description": "Montant net en centimes."
                },
                "status": {
                  "type": "string"
                },
                "executedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                }
              }
            }
          },
          "mandates": {
            "type": "array",
            "description": "10 derniers mandats de gestion.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "reference": {
                  "type": "string",
                  "nullable": true
                },
                "mandateType": {
                  "type": "string",
                  "description": "Type de mandat."
                },
                "status": {
                  "type": "string"
                },
                "startDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "endDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                }
              }
            }
          },
          "currentCgp": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "CGP (conseiller en gestion de patrimoine) actuellement affecté au propriétaire.",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "displayName": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "nullable": true
              },
              "phoneNumber": {
                "type": "string",
                "nullable": true
              },
              "companyName": {
                "type": "string",
                "nullable": true
              },
              "activity": {
                "type": "string",
                "nullable": true
              },
              "validFrom": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Début de validité de l'affectation."
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création du profil."
          }
        }
      },
      "Tenant": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'acteur locataire (Actor)."
          },
          "tenantProfileId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du profil locataire (TenantProfile) par mandataire."
          },
          "displayName": {
            "type": "string",
            "description": "Nom d'affichage du locataire.",
            "example": "Dupont Jean"
          },
          "firstName": {
            "type": "string",
            "nullable": true,
            "description": "Prénom."
          },
          "lastName": {
            "type": "string",
            "nullable": true,
            "description": "Nom de famille."
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Adresse e-mail effective de facturation (billing_email, sinon profil, sinon acteur).",
            "example": "jean.dupont@example.fr"
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de téléphone."
          },
          "tenantType": {
            "type": "string",
            "description": "Type de locataire (personne physique / morale)."
          },
          "tradeName": {
            "type": "string",
            "nullable": true,
            "description": "Nom commercial (locataire professionnel)."
          },
          "siret": {
            "type": "string",
            "nullable": true,
            "description": "Numéro SIRET.",
            "example": "81234567800012"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de création du profil."
          }
        }
      },
      "TenantDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'acteur locataire (Actor)."
          },
          "profileId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant du profil locataire (TenantProfile)."
          },
          "name": {
            "type": "string",
            "description": "Nom d'affichage du locataire.",
            "example": "Jean Dupont"
          },
          "firstName": {
            "type": "string",
            "nullable": true,
            "description": "Prénom."
          },
          "lastName": {
            "type": "string",
            "nullable": true,
            "description": "Nom de famille."
          },
          "contactType": {
            "type": "string",
            "description": "Type de contact (toujours \"tenant\").",
            "example": "tenant"
          },
          "subType": {
            "type": "string",
            "description": "Sous-type (type de locataire)."
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "Adresse e-mail effective de facturation (billing_email, sinon profil, sinon acteur).",
            "example": "jean.dupont@example.fr"
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de téléphone."
          },
          "tradeName": {
            "type": "string",
            "nullable": true,
            "description": "Nom commercial (locataire professionnel)."
          },
          "siret": {
            "type": "string",
            "nullable": true,
            "description": "Numéro SIRET.",
            "example": "81234567800012"
          },
          "vatNumber": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de TVA intracommunautaire."
          },
          "billingEmail": {
            "type": "string",
            "nullable": true,
            "description": "E-mail dédié à la facturation."
          },
          "billingAddress": {
            "type": "string",
            "nullable": true,
            "description": "Adresse de facturation."
          },
          "bankAccounts": {
            "type": "array",
            "description": "Comptes bancaires du locataire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "label": {
                  "type": "string",
                  "nullable": true,
                  "description": "Libellé du compte."
                },
                "iban": {
                  "type": "string",
                  "nullable": true
                },
                "bic": {
                  "type": "string",
                  "nullable": true
                },
                "bankName": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom de la banque."
                },
                "isDefault": {
                  "type": "boolean",
                  "description": "Compte par défaut."
                }
              }
            }
          },
          "leases": {
            "type": "array",
            "description": "Baux du locataire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "reference": {
                  "type": "string",
                  "nullable": true
                },
                "status": {
                  "type": "string"
                },
                "leaseType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type de bail."
                },
                "startDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "endDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "unitIdentifier": {
                  "type": "string",
                  "nullable": true,
                  "description": "Identifiant du premier lot (fallback legacy)."
                },
                "propertyName": {
                  "type": "string",
                  "nullable": true,
                  "description": "Nom de l'immeuble du premier lot (fallback legacy)."
                },
                "units": {
                  "type": "array",
                  "description": "Lots rattachés au bail.",
                  "items": {
                    "type": "object",
                    "additionalProperties": true,
                    "properties": {
                      "identifier": {
                        "type": "string",
                        "nullable": true,
                        "description": "Identifiant du lot."
                      },
                      "propertyName": {
                        "type": "string",
                        "nullable": true,
                        "description": "Nom de l'immeuble."
                      }
                    }
                  }
                },
                "securityDepositId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Identifiant du dépôt de garantie associé."
                }
              }
            }
          },
          "rentCalls": {
            "type": "array",
            "description": "Appels de loyer du locataire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "reference": {
                  "type": "string",
                  "nullable": true
                },
                "periodStart": {
                  "type": "string",
                  "format": "date"
                },
                "periodEnd": {
                  "type": "string",
                  "format": "date"
                },
                "totalAmountCents": {
                  "type": "integer",
                  "description": "Montant total appelé en centimes."
                },
                "rentAmountCents": {
                  "type": "integer",
                  "description": "Part loyer en centimes."
                },
                "chargesAmountCents": {
                  "type": "integer",
                  "description": "Part charges en centimes."
                },
                "paidAmountCents": {
                  "type": "integer",
                  "description": "Montant réglé en centimes (projeté du grand livre)."
                },
                "remainingAmountCents": {
                  "type": "integer",
                  "description": "Reste à payer en centimes."
                },
                "fullyPaid": {
                  "type": "boolean",
                  "description": "Appel intégralement soldé."
                },
                "overdue": {
                  "type": "boolean",
                  "description": "Appel en retard."
                },
                "dueDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "status": {
                  "type": "string"
                },
                "sentAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                }
              }
            }
          },
          "payments": {
            "type": "array",
            "description": "10 derniers paiements du locataire.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "amountCents": {
                  "type": "integer",
                  "description": "Montant en centimes."
                },
                "currency": {
                  "type": "string",
                  "description": "Devise."
                },
                "date": {
                  "type": "string",
                  "format": "date",
                  "description": "Date du paiement."
                },
                "reference": {
                  "type": "string",
                  "nullable": true
                },
                "status": {
                  "type": "string"
                },
                "leaseReference": {
                  "type": "string",
                  "nullable": true,
                  "description": "Référence du bail associé."
                },
                "leaseId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                }
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création du profil."
          }
        }
      },
      "Payment": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string",
            "nullable": true,
            "description": "Référence du paiement",
            "example": "PAY-2026-00042"
          },
          "amountCents": {
            "type": "integer",
            "description": "Montant en centimes",
            "example": 120000
          },
          "currency": {
            "type": "string",
            "description": "Devise ISO 4217",
            "example": "EUR"
          },
          "amount": {
            "$ref": "#/components/schemas/Money"
          },
          "paymentDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date du paiement"
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true,
            "description": "Moyen de paiement (ex. virement, prélèvement, chèque)",
            "example": "virement"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inflow",
              "outflow"
            ],
            "description": "Sens du flux : entrant (encaissement) ou sortant (reversement)",
            "example": "inflow"
          },
          "status": {
            "type": "string",
            "description": "Statut du paiement",
            "example": "allocated"
          },
          "leaseReference": {
            "type": "string",
            "nullable": true,
            "description": "Référence du bail rattaché"
          },
          "leaseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du bail rattaché"
          },
          "bankAccountId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Compte bancaire associé"
          },
          "tenantName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du locataire"
          }
        }
      },
      "PaymentDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string",
            "nullable": true,
            "description": "Référence du paiement",
            "example": "PAY-2026-00042"
          },
          "amountCents": {
            "type": "integer",
            "description": "Montant en centimes",
            "example": 120000
          },
          "currency": {
            "type": "string",
            "description": "Devise ISO 4217",
            "example": "EUR"
          },
          "amount": {
            "$ref": "#/components/schemas/Money"
          },
          "paymentDate": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date du paiement"
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true,
            "description": "Moyen de paiement (ex. virement, prélèvement, chèque)",
            "example": "virement"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inflow",
              "outflow"
            ],
            "description": "Sens du flux : entrant (encaissement) ou sortant (reversement)",
            "example": "inflow"
          },
          "status": {
            "type": "string",
            "description": "Statut du paiement",
            "example": "allocated"
          },
          "leaseReference": {
            "type": "string",
            "nullable": true,
            "description": "Référence du bail rattaché"
          },
          "leaseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du bail rattaché"
          },
          "bankAccountId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Compte bancaire associé"
          },
          "tenantName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du locataire"
          },
          "bankTransaction": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Transaction bancaire rapprochée, le cas échéant",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "label": {
                "type": "string",
                "nullable": true,
                "description": "Libellé de la transaction"
              },
              "amount": {
                "$ref": "#/components/schemas/Money"
              },
              "transactionDate": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Date de la transaction"
              }
            }
          },
          "allocations": {
            "type": "array",
            "description": "Imputations du paiement sur les lignes de facturation",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "allocatedAmountCents": {
                  "type": "integer",
                  "description": "Montant imputé en centimes",
                  "example": 60000
                },
                "allocatedAmount": {
                  "$ref": "#/components/schemas/Money"
                },
                "billingLineId": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Ligne de facturation imputée"
                },
                "billingLinePeriod": {
                  "type": "string",
                  "nullable": true,
                  "description": "Période de la ligne de facturation",
                  "example": "2026-01-01 - 2026-01-31"
                },
                "billingLineComputedAmount": {
                  "type": "number",
                  "nullable": true,
                  "description": "Montant calculé de la ligne de facturation, en euros"
                },
                "allocationDate": {
                  "type": "string",
                  "format": "date",
                  "nullable": true,
                  "description": "Date de l'imputation"
                },
                "status": {
                  "type": "string",
                  "description": "Statut de l'imputation",
                  "example": "confirmed"
                }
              }
            }
          },
          "allocatedAmount": {
            "$ref": "#/components/schemas/Money"
          },
          "remainingAmount": {
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "Incident": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique de l'incident."
          },
          "reference": {
            "type": "string",
            "description": "Référence interne de l'incident (unique par mandataire).",
            "example": "INC-2026-000421"
          },
          "flexicielReference": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de réclamation Flexiciel/PREM (Aareon) quand la déclaration a été matérialisée.",
            "example": "1015299"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Titre court de l'incident.",
            "example": "Écoulement continu"
          },
          "status": {
            "type": "string",
            "description": "Statut courant dans le cycle de vie de l'incident.",
            "enum": [
              "submitted",
              "acknowledged",
              "in_progress",
              "pending_info",
              "pending_intervention",
              "intervention_in_progress",
              "resolved",
              "closed",
              "reopened"
            ],
            "example": "in_progress"
          },
          "priority": {
            "type": "string",
            "description": "Priorité de traitement de l'incident.",
            "enum": [
              "low",
              "normal",
              "high",
              "urgent"
            ],
            "example": "high"
          },
          "scopeType": {
            "type": "string",
            "description": "Type d'entité localisant l'incident (logement, immeuble ou montée).",
            "enum": [
              "Loctavia::Unit",
              "Loctavia::Property",
              "Loctavia::Entrance"
            ],
            "example": "Loctavia::Unit"
          },
          "scopeId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'entité de localisation (selon scopeType)."
          },
          "categoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de la catégorie d'incident."
          },
          "categoryName": {
            "type": "string",
            "nullable": true,
            "description": "Libellé de la catégorie d'incident.",
            "example": "Plomberie"
          },
          "assignedToId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du gestionnaire assigné."
          },
          "assignedToName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du gestionnaire assigné."
          },
          "reporterId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'acteur déclarant."
          },
          "reporterName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du déclarant."
          },
          "reporterType": {
            "type": "string",
            "nullable": true,
            "description": "Type de l'acteur déclarant (classe de l'acteur)."
          },
          "leaseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du bail associé à l'incident."
          },
          "room": {
            "type": "string",
            "nullable": true,
            "description": "Pièce ou localisation précise dans le logement.",
            "example": "Salle de bain"
          },
          "contactEmail": {
            "type": "string",
            "nullable": true,
            "description": "Email de contact rattaché à l'incident."
          },
          "contactPhone": {
            "type": "string",
            "nullable": true,
            "description": "Téléphone de contact rattaché à l'incident."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création de l'incident."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Date de dernière modification de l'incident."
          },
          "acknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'accusé de réception (passage en acknowledged)."
          },
          "resolvedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de résolution de l'incident."
          },
          "closedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de clôture de l'incident."
          },
          "escalatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'escalade de l'incident."
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'échéance cible de traitement."
          },
          "flexicielType": {
            "type": "string",
            "nullable": true,
            "description": "Type de la typologie Flexiciel (référentiel TNM Aareon)."
          },
          "flexicielNature": {
            "type": "string",
            "nullable": true,
            "description": "Nature de la typologie Flexiciel (référentiel TNM Aareon)."
          },
          "flexicielMotif": {
            "type": "string",
            "nullable": true,
            "description": "Motif de la typologie Flexiciel (référentiel TNM Aareon)."
          },
          "aareonSyncState": {
            "type": "string",
            "description": "État de transmission de la déclaration vers Aareon/PREM.",
            "enum": [
              "pending",
              "sent",
              "materialized",
              "skipped",
              "failed"
            ],
            "example": "sent"
          },
          "aareonSkipReason": {
            "type": "string",
            "nullable": true,
            "description": "Raison du skip de transmission Aareon, le cas échéant.",
            "example": "category_unmapped"
          },
          "aareonSyncRepairable": {
            "type": "boolean",
            "description": "Indique si le skip de transmission Aareon est réparable après enrichissement."
          },
          "aareonSyncStale": {
            "type": "boolean",
            "description": "Indique une déclaration poussée mais jamais matérialisée depuis trop longtemps."
          },
          "aareonPulled": {
            "type": "boolean",
            "description": "Indique si l'incident provient d'un pull Aareon (matérialisé côté PREM)."
          },
          "aareonFollowupsSummary": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Résumé d'état des étapes de follow-up Aareon (verify-after-emission).",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Nombre total d'étapes de follow-up."
              },
              "verified": {
                "type": "integer",
                "description": "Nombre d'étapes confirmées attachées côté Aareon."
              },
              "pending": {
                "type": "integer",
                "description": "Nombre d'étapes en attente de vérification."
              },
              "failed": {
                "type": "integer",
                "description": "Nombre d'étapes en échec."
              },
              "status": {
                "type": "string",
                "description": "Statut global des follow-ups.",
                "enum": [
                  "none",
                  "all_verified",
                  "failed",
                  "partial",
                  "pending"
                ],
                "example": "partial"
              }
            }
          }
        }
      },
      "IncidentDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant unique de l'incident."
          },
          "reference": {
            "type": "string",
            "description": "Référence interne de l'incident (unique par mandataire).",
            "example": "INC-2026-000421"
          },
          "flexicielReference": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de réclamation Flexiciel/PREM (Aareon) quand la déclaration a été matérialisée.",
            "example": "1015299"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Titre court de l'incident.",
            "example": "Écoulement continu"
          },
          "status": {
            "type": "string",
            "description": "Statut courant dans le cycle de vie de l'incident.",
            "enum": [
              "submitted",
              "acknowledged",
              "in_progress",
              "pending_info",
              "pending_intervention",
              "intervention_in_progress",
              "resolved",
              "closed",
              "reopened"
            ],
            "example": "in_progress"
          },
          "priority": {
            "type": "string",
            "description": "Priorité de traitement de l'incident.",
            "enum": [
              "low",
              "normal",
              "high",
              "urgent"
            ],
            "example": "high"
          },
          "scopeType": {
            "type": "string",
            "description": "Type d'entité localisant l'incident (logement, immeuble ou montée).",
            "enum": [
              "Loctavia::Unit",
              "Loctavia::Property",
              "Loctavia::Entrance"
            ],
            "example": "Loctavia::Unit"
          },
          "scopeId": {
            "type": "string",
            "format": "uuid",
            "description": "Identifiant de l'entité de localisation (selon scopeType)."
          },
          "categoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de la catégorie d'incident."
          },
          "categoryName": {
            "type": "string",
            "nullable": true,
            "description": "Libellé de la catégorie d'incident.",
            "example": "Plomberie"
          },
          "assignedToId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du gestionnaire assigné."
          },
          "assignedToName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du gestionnaire assigné."
          },
          "reporterId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant de l'acteur déclarant."
          },
          "reporterName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du déclarant."
          },
          "reporterType": {
            "type": "string",
            "nullable": true,
            "description": "Type de l'acteur déclarant (classe de l'acteur)."
          },
          "leaseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du bail associé à l'incident."
          },
          "room": {
            "type": "string",
            "nullable": true,
            "description": "Pièce ou localisation précise dans le logement.",
            "example": "Salle de bain"
          },
          "contactEmail": {
            "type": "string",
            "nullable": true,
            "description": "Email de contact rattaché à l'incident."
          },
          "contactPhone": {
            "type": "string",
            "nullable": true,
            "description": "Téléphone de contact rattaché à l'incident."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Date de création de l'incident."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Date de dernière modification de l'incident."
          },
          "acknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'accusé de réception (passage en acknowledged)."
          },
          "resolvedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de résolution de l'incident."
          },
          "closedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de clôture de l'incident."
          },
          "escalatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'escalade de l'incident."
          },
          "dueAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date d'échéance cible de traitement."
          },
          "flexicielType": {
            "type": "string",
            "nullable": true,
            "description": "Type de la typologie Flexiciel (référentiel TNM Aareon)."
          },
          "flexicielNature": {
            "type": "string",
            "nullable": true,
            "description": "Nature de la typologie Flexiciel (référentiel TNM Aareon)."
          },
          "flexicielMotif": {
            "type": "string",
            "nullable": true,
            "description": "Motif de la typologie Flexiciel (référentiel TNM Aareon)."
          },
          "aareonSyncState": {
            "type": "string",
            "description": "État de transmission de la déclaration vers Aareon/PREM.",
            "enum": [
              "pending",
              "sent",
              "materialized",
              "skipped",
              "failed"
            ],
            "example": "sent"
          },
          "aareonSkipReason": {
            "type": "string",
            "nullable": true,
            "description": "Raison du skip de transmission Aareon, le cas échéant.",
            "example": "category_unmapped"
          },
          "aareonSyncRepairable": {
            "type": "boolean",
            "description": "Indique si le skip de transmission Aareon est réparable après enrichissement."
          },
          "aareonSyncStale": {
            "type": "boolean",
            "description": "Indique une déclaration poussée mais jamais matérialisée depuis trop longtemps."
          },
          "aareonPulled": {
            "type": "boolean",
            "description": "Indique si l'incident provient d'un pull Aareon (matérialisé côté PREM)."
          },
          "aareonFollowupsSummary": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Résumé d'état des étapes de follow-up Aareon (verify-after-emission).",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Nombre total d'étapes de follow-up."
              },
              "verified": {
                "type": "integer",
                "description": "Nombre d'étapes confirmées attachées côté Aareon."
              },
              "pending": {
                "type": "integer",
                "description": "Nombre d'étapes en attente de vérification."
              },
              "failed": {
                "type": "integer",
                "description": "Nombre d'étapes en échec."
              },
              "status": {
                "type": "string",
                "description": "Statut global des follow-ups.",
                "enum": [
                  "none",
                  "all_verified",
                  "failed",
                  "partial",
                  "pending"
                ],
                "example": "partial"
              }
            }
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Description détaillée de l'incident."
          },
          "outcomeMessage": {
            "type": "string",
            "nullable": true,
            "description": "Message de résolution/issue communiqué."
          },
          "categoryPath": {
            "type": "string",
            "nullable": true,
            "description": "Arborescence complète de la catégorie (racine vers feuille).",
            "example": "Plomberie · Fuite · Fenêtre PVC"
          },
          "scope": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Résumé de l'entité de localisation de l'incident (logement, immeuble ou montée).",
            "properties": {
              "type": {
                "type": "string",
                "description": "Classe de l'entité de localisation.",
                "enum": [
                  "Loctavia::Property",
                  "Loctavia::Unit",
                  "Loctavia::Entrance"
                ]
              },
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Identifiant de l'entité de localisation."
              },
              "name": {
                "type": "string",
                "nullable": true,
                "description": "Nom de l'entité (immeuble ou montée)."
              },
              "identifier": {
                "type": "string",
                "nullable": true,
                "description": "Identifiant du logement (pour un Loctavia::Unit)."
              },
              "address": {
                "type": "string",
                "nullable": true,
                "description": "Adresse de l'entité."
              },
              "propertyId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Identifiant de l'immeuble parent (logement/montée)."
              },
              "propertyName": {
                "type": "string",
                "nullable": true,
                "description": "Nom de l'immeuble parent."
              },
              "propertyAddress": {
                "type": "string",
                "nullable": true,
                "description": "Adresse de l'immeuble parent."
              },
              "patrimonyCode": {
                "type": "string",
                "nullable": true,
                "description": "Code long patrimoine PRH (code_long, code_bati ou code_montee)."
              }
            }
          },
          "leaseReference": {
            "type": "string",
            "nullable": true,
            "description": "Référence du bail associé à l'incident."
          },
          "routedMaintenanceContractId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identifiant du contrat de maintenance vers lequel l'incident a été routé."
          },
          "routedMaintenanceContractName": {
            "type": "string",
            "nullable": true,
            "description": "Nom du contrat de maintenance de routage."
          },
          "llmAnalysis": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Analyse LLM structurée de l'incident (enrichissement automatique)."
          },
          "funnelData": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Données de parcours de déclaration (funnel) collectées côté portail."
          },
          "allowedTransitions": {
            "type": "array",
            "description": "Transitions de statut autorisées depuis le statut courant.",
            "items": {
              "type": "string"
            },
            "example": [
              "pending_info",
              "pending_intervention",
              "intervention_in_progress",
              "resolved"
            ]
          },
          "aareonFollowups": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Résumé du dernier envoi de follow-ups Aareon (null tant qu'aucun envoi).",
            "properties": {
              "event": {
                "type": "string",
                "nullable": true,
                "description": "Type d'événement de follow-up émis."
              },
              "posted": {
                "type": "integer",
                "description": "Nombre de follow-ups postés avec succès."
              },
              "errored": {
                "type": "integer",
                "description": "Nombre de follow-ups refusés par Aareon."
              },
              "errors": {
                "type": "array",
                "description": "Détail des erreurs rencontrées.",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Horodatage du dernier envoi de follow-ups."
              }
            }
          },
          "aareonFollowupsFailed": {
            "type": "boolean",
            "description": "Indique qu'au moins un follow-up du dernier envoi a été refusé par Aareon."
          },
          "aareonFollowupSteps": {
            "type": "array",
            "description": "Détail granulaire verify-after-emission, une entrée par (code, source) émis vers Aareon.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "code": {
                  "type": "string",
                  "nullable": true,
                  "description": "Code du follow-up émis."
                },
                "source": {
                  "type": "string",
                  "nullable": true,
                  "description": "Source de l'émission du follow-up."
                },
                "status": {
                  "type": "string",
                  "nullable": true,
                  "description": "État de l'étape (emitted, verified, failed)."
                },
                "emittedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true,
                  "description": "Horodatage d'émission de l'étape."
                },
                "verifiedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true,
                  "description": "Horodatage de confirmation d'attachement côté Aareon."
                },
                "attempts": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Nombre de tentatives de vérification."
                },
                "error": {
                  "type": "string",
                  "nullable": true,
                  "description": "Message d'erreur de l'étape le cas échéant."
                }
              }
            }
          },
          "attachments": {
            "type": "array",
            "description": "Pièces jointes de l'incident (photos, documents).",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Identifiant de la pièce jointe."
                },
                "filename": {
                  "type": "string",
                  "description": "Nom du fichier.",
                  "example": "fuite-cuisine.jpg"
                },
                "contentType": {
                  "type": "string",
                  "nullable": true,
                  "description": "Type MIME de la pièce jointe.",
                  "example": "image/jpeg"
                },
                "byteSize": {
                  "type": "integer",
                  "description": "Taille du fichier en octets."
                },
                "image": {
                  "type": "boolean",
                  "description": "Indique si la pièce jointe est une image."
                },
                "url": {
                  "type": "string",
                  "description": "Chemin d'accès au blob de la pièce jointe."
                }
              }
            }
          },
          "messagesCount": {
            "type": "integer",
            "description": "Nombre de messages échangés sur l'incident."
          },
          "activeAssignmentsCount": {
            "type": "integer",
            "description": "Nombre d'assignations actives sur l'incident."
          }
        }
      },
      "JournalEntry": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string",
            "description": "Numérotation `<journal>-<société>-AAAA-NNN` (ex. LOYERS-CABMAR-2026-042)"
          },
          "journal": {
            "type": "string",
            "enum": [
              "LOYERS",
              "CHARGES",
              "BANQUE",
              "PROPRIO",
              "OD"
            ]
          },
          "societyCode": {
            "type": "string",
            "nullable": true,
            "description": "Code de la société comptable émettrice",
            "example": "CABMAR"
          },
          "entryDate": {
            "type": "string",
            "format": "date-time"
          },
          "accountingDate": {
            "type": "string",
            "format": "date"
          },
          "periodStart": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "periodEnd": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "validated",
              "posted",
              "reversed"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "totalDebit": {
            "type": "number",
            "description": "Somme des débits des lignes, en euros"
          },
          "totalCredit": {
            "type": "number",
            "description": "Somme des crédits des lignes, en euros"
          },
          "linesCount": {
            "type": "integer"
          },
          "sourceType": {
            "type": "string",
            "nullable": true,
            "description": "Pièce d'origine (ex. Loctavia::BillingLine, Loctavia::Payment)"
          },
          "sourceId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "JournalEntryLine": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lineNumber": {
            "type": "integer"
          },
          "accountId": {
            "type": "string",
            "format": "uuid"
          },
          "accountCode": {
            "type": "string",
            "nullable": true,
            "description": "Numéro de compte PCG (ex. 411000, 706000)"
          },
          "accountName": {
            "type": "string",
            "nullable": true
          },
          "debit": {
            "type": "number",
            "description": "Montant débité, en euros"
          },
          "credit": {
            "type": "number",
            "description": "Montant crédité, en euros"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "entityType": {
            "type": "string",
            "nullable": true,
            "description": "Entité analytique rattachée (ex. Loctavia::TenantProfile)"
          },
          "entityId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "letteringCode": {
            "type": "string",
            "nullable": true
          },
          "letteringStatus": {
            "type": "string",
            "nullable": true,
            "enum": [
              "unlettered",
              "partially_lettered",
              "lettered",
              null
            ]
          }
        }
      },
      "JournalEntryDetails": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string",
            "description": "Numérotation `<journal>-<société>-AAAA-NNN` (ex. LOYERS-CABMAR-2026-042)"
          },
          "journal": {
            "type": "string",
            "enum": [
              "LOYERS",
              "CHARGES",
              "BANQUE",
              "PROPRIO",
              "OD"
            ]
          },
          "societyCode": {
            "type": "string",
            "nullable": true,
            "description": "Code de la société comptable émettrice",
            "example": "CABMAR"
          },
          "entryDate": {
            "type": "string",
            "format": "date-time"
          },
          "accountingDate": {
            "type": "string",
            "format": "date"
          },
          "periodStart": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "periodEnd": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "validated",
              "posted",
              "reversed"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "totalDebit": {
            "type": "number",
            "description": "Somme des débits des lignes, en euros"
          },
          "totalCredit": {
            "type": "number",
            "description": "Somme des crédits des lignes, en euros"
          },
          "linesCount": {
            "type": "integer"
          },
          "sourceType": {
            "type": "string",
            "nullable": true,
            "description": "Pièce d'origine (ex. Loctavia::BillingLine, Loctavia::Payment)"
          },
          "sourceId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "validatedBy": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Utilisateur ayant validé l'écriture"
          },
          "validatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Date de validation de l'écriture"
          },
          "reversalOfId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Écriture extournée par celle-ci"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Métadonnées libres de l'écriture"
          },
          "lines": {
            "type": "array",
            "description": "Lignes en partie double",
            "items": {
              "$ref": "#/components/schemas/JournalEntryLine"
            }
          }
        }
      },
      "FecExportMutation": {
        "type": "object",
        "required": [
          "fiscal_year"
        ],
        "properties": {
          "fiscal_year": {
            "type": "integer",
            "description": "Exercice civil à exporter (ex. 2026)"
          },
          "siren": {
            "type": "string",
            "description": "SIREN émetteur (9 chiffres). Défaut : 000000000"
          }
        }
      },
      "FecExport": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fiscalYear": {
            "type": "integer",
            "description": "Exercice fiscal concerné",
            "example": 2026
          },
          "siren": {
            "type": "string",
            "description": "SIREN émetteur (9 chiffres), normalisé",
            "example": "123456789"
          },
          "filename": {
            "type": "string",
            "description": "Nom réglementaire `<siren>FEC<AAAA1231>.txt`",
            "example": "123456789FEC20261231.txt"
          },
          "entriesCount": {
            "type": "integer",
            "description": "Nombre d'écritures incluses",
            "example": 342
          },
          "linesCount": {
            "type": "integer",
            "description": "Nombre de lignes incluses",
            "example": 918
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OctopiaIngestionRequest": {
        "type": "object",
        "description": "Données à ingérer dans le Knowledge Graph Octopia",
        "required": [
          "entities"
        ],
        "properties": {
          "entities": {
            "type": "array",
            "description": "Liste des entités à créer ou mettre à jour (upsert par nom)",
            "items": {
              "type": "object",
              "required": [
                "type",
                "name"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Type de l'entité",
                  "enum": [
                    "person",
                    "company",
                    "team",
                    "skill",
                    "tag",
                    "project",
                    "contract",
                    "product",
                    "service",
                    "location",
                    "event",
                    "role"
                  ]
                },
                "name": {
                  "type": "string",
                  "description": "Nom de l'entité (utilisé comme clé d'upsert)"
                },
                "attributes": {
                  "type": "object",
                  "description": "Attributs supplémentaires de l'entité (dépendent du type)",
                  "additionalProperties": true
                }
              }
            }
          },
          "relations": {
            "type": "array",
            "description": "Liste des relations à créer entre entités",
            "items": {
              "type": "object",
              "required": [
                "from_type",
                "from_name",
                "to_type",
                "to_name",
                "edge_type"
              ],
              "properties": {
                "from_type": {
                  "type": "string",
                  "description": "Type de l'entité source",
                  "enum": [
                    "person",
                    "company",
                    "team",
                    "skill",
                    "tag",
                    "project",
                    "contract",
                    "product",
                    "service",
                    "location",
                    "event",
                    "role"
                  ]
                },
                "from_name": {
                  "type": "string",
                  "description": "Nom de l'entité source"
                },
                "to_type": {
                  "type": "string",
                  "description": "Type de l'entité cible",
                  "enum": [
                    "person",
                    "company",
                    "team",
                    "skill",
                    "tag",
                    "project",
                    "contract",
                    "product",
                    "service",
                    "location",
                    "event",
                    "role"
                  ]
                },
                "to_name": {
                  "type": "string",
                  "description": "Nom de l'entité cible"
                },
                "edge_type": {
                  "type": "string",
                  "description": "Type de la relation (ex: works_in, member_of, has_skill, related_to, etc.)\n"
                }
              }
            }
          }
        }
      },
      "OctopiaIngestionResponse": {
        "type": "object",
        "description": "Résultat de l'ingestion dans le Knowledge Graph",
        "properties": {
          "imported": {
            "type": "integer",
            "description": "Nombre d'entités créées"
          },
          "updated": {
            "type": "integer",
            "description": "Nombre d'entités mises à jour"
          },
          "errors": {
            "type": "array",
            "description": "Liste des erreurs rencontrées pendant l'ingestion",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Memory": {
        "type": "object",
        "description": "Mémoire sémantique du cerveau d'entreprise (partagée entre devs et agents).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string",
            "description": "Fait atomique en langage naturel."
          },
          "memory_type": {
            "type": "string",
            "enum": [
              "fact",
              "preference",
              "context",
              "insight"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "professional",
              "personal",
              "project",
              "domain",
              "technical"
            ],
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "description": "0.0–1.0."
          },
          "salience": {
            "type": "number",
            "format": "float"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "origin": {
            "type": "string",
            "description": "Provenance de la mémoire (ex. `octopia_api`, `openmemory`)."
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "Auteur de la mémoire (utilisateur du token)."
          },
          "similarity": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "description": "Score de similarité 0..1 (présent uniquement dans les résultats de recherche)."
          },
          "related_entities": {
            "type": "array",
            "nullable": true,
            "description": "Entités du graphe liées (lecture unitaire et recherche).",
            "items": {
              "$ref": "#/components/schemas/RelatedEntity"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryMutation": {
        "type": "object",
        "description": "Payload de stockage d'une mémoire.",
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "string",
            "minLength": 30,
            "description": "Fait atomique en langage naturel (≥ 30 caractères ; un contenu trop court ou bruité est rejeté par le quality gate en `422`).",
            "example": "Le client DSH utilise l'ERP Aareon Prem'Habitat pour ses signalements."
          },
          "memory_type": {
            "type": "string",
            "enum": [
              "fact",
              "preference",
              "context",
              "insight"
            ],
            "default": "context"
          },
          "category": {
            "type": "string",
            "enum": [
              "professional",
              "personal",
              "project",
              "domain",
              "technical"
            ]
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 1,
            "default": 0.8
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "array",
            "description": "Entités du graphe auxquelles rattacher la mémoire (client, projet…).",
            "items": {
              "$ref": "#/components/schemas/EntityLink"
            }
          }
        }
      },
      "EntityLink": {
        "type": "object",
        "description": "Lien vers une entité du graphe de connaissances (client, projet…). C'est le\n\"scope\" de la mémoire : le namespace n'est pas une chaîne libre mais une\nentité à laquelle la mémoire se rattache (arête `about`). Entité créée à la\nvolée si absente.\n",
        "required": [
          "type",
          "name"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Type d'entité : `client`/`company`, `project`, `person`, `team`, `skill`, `tag`, `contract`, `product`, `service`, `location`, `event`, `role`.",
            "example": "client"
          },
          "name": {
            "type": "string",
            "description": "Nom de l'entité.",
            "example": "DSH"
          }
        }
      },
      "RelatedEntity": {
        "type": "object",
        "description": "Entité du graphe liée à une mémoire (renvoyée à la lecture/recherche).",
        "properties": {
          "type": {
            "type": "string",
            "description": "Type d'entité (collection du graphe, au singulier).",
            "example": "company"
          },
          "label": {
            "type": "string",
            "description": "Libellé de l'entité.",
            "example": "DSH"
          }
        }
      },
      "MemorySearchRequest": {
        "type": "object",
        "description": "Requête de recherche sémantique.",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "Texte de recherche en langage naturel.",
            "example": "quel ERP utilise DSH ?"
          },
          "memory_type": {
            "type": "string",
            "enum": [
              "fact",
              "preference",
              "context",
              "insight"
            ]
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 5
          },
          "scope": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EntityLink"
              }
            ],
            "description": "Restreint la recherche aux mémoires liées à cette entité."
          }
        }
      },
      "MemorySearchResponse": {
        "type": "object",
        "description": "Résultats de recherche, triés par similarité décroissante.",
        "properties": {
          "query": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Memory"
            }
          }
        }
      },
      "TokenExchangeRequest": {
        "type": "object",
        "description": "Requête d'échange de token JWT Edifice",
        "required": [
          "grant_type",
          "assertion",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "urn:ietf:params:oauth:grant-type:jwt-bearer"
            ],
            "description": "Type de grant OAuth2 pour l'échange JWT (RFC 7523)",
            "example": "urn:ietf:params:oauth:grant-type:jwt-bearer"
          },
          "assertion": {
            "type": "string",
            "description": "JWT signé par Edifice contenant les informations utilisateur",
            "example": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImVkaWZpY2Uta2V5LTEifQ.eyJzdWIiOiJ1c2VyLTEyMyIsImVtYWlsIjoidXNlckBlZGlmaWNlLmNvbSIsImZpcnN0X25hbWUiOiJKb2huIiwibGFzdF9uYW1lIjoiRG9lIiwiaXNzIjoiZWRpZmljZSIsImF1ZCI6ImNvcmV4IiwiZXhwIjoxNzA1NDEzNjAwfQ.signature"
          },
          "client_id": {
            "type": "string",
            "description": "Identifiant de l'application OAuth2",
            "example": "Q0OEEVE5kLDll1uAek5B-rJcXJCtkIoEdPwLvDiQ888"
          },
          "client_secret": {
            "type": "string",
            "description": "Secret de l'application OAuth2",
            "example": "jnZuQF8PFv42WmA5vLLBdPrhBAwAqLuBTERGMUERUPE"
          }
        }
      },
      "TokenExchangeResponse": {
        "type": "object",
        "description": "Réponse d'échange de token JWT Edifice",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "created_at"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Token d'accès Bearer pour authentifier les requêtes API",
            "example": "abc123def456ghi789..."
          },
          "token_type": {
            "type": "string",
            "description": "Type de token (toujours \"Bearer\")",
            "enum": [
              "Bearer"
            ],
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Durée de validité du token en secondes",
            "example": 7200
          },
          "created_at": {
            "type": "integer",
            "description": "Timestamp Unix de création du token",
            "example": 1705410000
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "description": "Réponse d'erreur OAuth2 (RFC 6749)",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Code d'erreur OAuth2",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unauthorized_client",
              "unsupported_grant_type",
              "server_error"
            ],
            "example": "invalid_grant"
          },
          "error_description": {
            "type": "string",
            "description": "Description détaillée de l'erreur",
            "example": "JWT verification failed: signature invalid"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "description": "OAuth2 Token Response",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Token d'accès Bearer",
            "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
          },
          "token_type": {
            "type": "string",
            "description": "Type de token (toujours \"Bearer\" pour Doorkeeper)",
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Durée de validité du token en secondes",
            "example": 7200
          },
          "refresh_token": {
            "type": "string",
            "description": "Jeton de rafraîchissement (si applicable, selon la configuration Doorkeeper)",
            "example": "def50200cde4321cfa9..."
          },
          "scope": {
            "type": "string",
            "description": "Portée accordée (peut être différente de celle demandée)",
            "example": "read write"
          },
          "created_at": {
            "type": "integer",
            "description": "Timestamp Unix de création du token",
            "example": 1640995200
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Requête incorrecte - paramètres invalides",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "bad_request",
              "message": "Paramètres de requête invalides"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentification requise",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "unauthorized",
              "message": "Authentification requise"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Accès refusé - permissions insuffisantes",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "forbidden",
              "message": "Accès refusé"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ressource introuvable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "not_found",
              "message": "Ressource introuvable"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Erreurs de validation",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "validation_error",
              "message": "La validation a échoué",
              "details": [
                "Le nom est requis",
                "L'identifiant d'espace de travail doit être valide"
              ]
            }
          }
        }
      },
      "InternalError": {
        "description": "Erreur interne du serveur",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "internal_error",
              "message": "Une erreur inattendue s'est produite"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "OAuth2Password": {
        "type": "oauth2",
        "description": "Flow password pour applications de confiance (mobile, CLI, tests)",
        "flows": {
          "password": {
            "tokenUrl": "/oauth/token",
            "scopes": {
              "read": "Lecture des ressources",
              "write": "Modification des ressources",
              "admin": "Accès administratif"
            }
          }
        }
      },
      "OAuth2AuthCode": {
        "type": "oauth2",
        "description": "Flow authorization code pour applications frontend/tierce",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/oauth/authorize",
            "tokenUrl": "/oauth/token",
            "scopes": {
              "read": "Lecture des ressources",
              "write": "Modification des ressources",
              "admin": "Accès administratif"
            }
          }
        }
      },
      "ConnectorApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Connector-Key",
        "description": "Clé API d'un connecteur Octopia (générée lors de la création du connecteur)"
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "Hubdoc API",
      "tags": [
        "Workspaces",
        "Folders",
        "Documents",
        "Composed Documents",
        "Document Templates",
        "BulkUploads",
        "ChunkedUploads",
        "Contacts",
        "Groups",
        "Group Members",
        "Permissions",
        "Public Access",
        "Users",
        "Mass Communications"
      ]
    },
    {
      "name": "Loctavia API",
      "tags": [
        "Loctavia — Mandataires",
        "Loctavia — Baux",
        "Loctavia — Quittancements",
        "Loctavia — Mandats",
        "Loctavia — Patrimoine",
        "Loctavia — Tiers",
        "Loctavia — Paiements",
        "Loctavia — Incidents",
        "Loctavia — Comptabilité"
      ]
    },
    {
      "name": "Octopia Knowledge Graph API",
      "tags": [
        "Octopia",
        "Octopia — Mémoire"
      ]
    },
    {
      "name": "Authentification",
      "tags": [
        "Authentication",
        "CLI Auth"
      ]
    }
  ]
}