{"openapi":"3.1.0","info":{"title":"ILYGO — service de composition documentaire","summary":"Compose des documents administratifs suisses en PDF, sans état.","description":"Ce service transforme un **profil de société** et des **données** en un document PDF prêt à envoyer : facture avec QR-facture suisse, devis, fiche de paie, décompte TVA, dossier fiscal, contrats et actes de gouvernance.\n\nIl est **totalement stateless**. Il ne conserve rien : ni les données reçues, ni les documents produits. Deux appels identiques rendent le même document ; aucun appel n'en influence un autre.\n\nLa mise en page est garantie mécaniquement : en-tête de tableau répété, aucun titre orphelin, blocs de totaux et de signature jamais coupés, numérotation « page X sur Y » exacte, et aucun texte saisi n'est interprété comme du balisage.\n\nUne page de démonstration est servie à la racine.","contact":{"name":"ILYGO","url":"https://ilygo.ch/"},"version":"30ef49bd"},"servers":[{"url":"https://documents.ilygo.ch","description":"Production"},{"url":"https://documents.uat.ilygo.ch","description":"UAT — données de test, aucune valeur légale"}],"paths":{"/api/health":{"get":{"tags":["service"],"summary":"Santé du service","description":"Sonde de vivacité. C'est cette route que la CI interroge au déploiement.","operationId":"sante_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sante"}}}}}}},"/v1/documents":{"get":{"tags":["catalogue"],"summary":"Types de documents","description":"Les documents que ce service sait composer, groupés par famille.\n\nUn client peut s'en servir pour construire une interface sans coder en dur\nla liste des types.","operationId":"lister_types_v1_documents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/documents/{type_document}":{"post":{"tags":["documents"],"summary":"Composer un document","description":"Compose un document et le rend en `application/pdf`.\n\nLe profil et les données ne sont ni conservés ni journalisés : ils vivent\nle temps de la requête. Le document n'est écrit nulle part.","operationId":"composer_v1_documents__type_document__post","parameters":[{"name":"type_document","in":"path","required":true,"schema":{"type":"string","title":"Type Document"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Clé d'API signée (`ilg.…`) ou clé statique. Requise dès que le service en reconnaît.","title":"X-Api-Key"},"description":"Clé d'API signée (`ilg.…`) ou clé statique. Requise dès que le service en reconnaît."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemandeDocument"}}}},"responses":{"200":{"description":"Le document composé.","content":{"application/pdf":{}}},"401":{"description":"Clé d'API absente ou invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"404":{"description":"Type de document inconnu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"413":{"description":"Corps de requête trop volumineux.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Données incomplètes ou incohérentes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}}}}},"/v1/exemples":{"get":{"tags":["démonstration"],"summary":"Cas de démonstration","description":"Les cas d'exemple servis par la page d'accueil.\n\nToutes les données sont **fictives** : société, personnes, montants et\nidentifiants sont inventés. Aucune donnée réelle n'est exposée.","operationId":"lister_exemples_v1_exemples_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/exemples/{code}.json":{"get":{"tags":["démonstration"],"summary":"Données d'un cas de démonstration","description":"Le corps de requête exact qui produit le PDF correspondant.","operationId":"exemple_json_v1_exemples__code__json_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/exemples/{code}.pdf":{"get":{"tags":["démonstration"],"summary":"PDF d'un cas de démonstration","description":"Compose le cas de démonstration et rend le PDF.\n\nOuvert sans clé d'API : c'est la vitrine du service, et rien de réel n'y\ntransite.","operationId":"exemple_pdf_v1_exemples__code__pdf_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/cles":{"post":{"tags":["administration"],"summary":"Émettre une clé","description":"Émet une clé d'API signée pour un usage nommé.\n\nLa clé est rendue **une seule fois** : le service ne la stocke pas — il la\nreconnaîtra à sa signature, sans jamais l'avoir gardée. Notez-la à sa\ncréation, ou réémettez-en une.\n\n`duree_jours: 0` émet une clé **sans échéance**, pour une intégration\npermanente. Elle vivra tant que le secret d'exploitation ne changera pas :\nnotez son numéro de série, c'est le seul moyen de la couper ensuite.","operationId":"emettre_cle_v1_admin_cles_post","parameters":[{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Secret d'exploitation. Protège l'émission de clés.","title":"X-Admin-Secret"},"description":"Secret d'exploitation. Protège l'émission de clés."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemandeCle"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CleEmise"}}}},"401":{"description":"Secret d'administration invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"503":{"description":"ADMIN_SECRET non configuré.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/cles/{nom}/rotation":{"post":{"tags":["administration"],"summary":"Faire tourner une clé","description":"Émet une clé de remplacement pour le même usage.\n\n**Rien n'est coupé.** L'ancienne clé reste valable jusqu'à sa propre\néchéance : c'est ce recouvrement qui permet de déployer la nouvelle sans\ninterruption de service, puis de laisser l'ancienne mourir.\n\nPour couper l'ancienne, composez un bordereau de révocation\n(`POST /v1/admin/revocations`) et déployez-le. La coupe par nom d'usage\nest faite pour ce cas : elle n'exige pas de connaître le numéro de série\nde la clé fuitée, qui n'est conservé nulle part.","operationId":"rotation_cle_v1_admin_cles__nom__rotation_post","parameters":[{"name":"nom","in":"path","required":true,"schema":{"type":"string","title":"Nom"}},{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Secret d'exploitation. Protège l'émission de clés.","title":"X-Admin-Secret"},"description":"Secret d'exploitation. Protège l'émission de clés."}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DemandeCle"},{"type":"null"}],"title":"Demande"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CleEmise"}}}},"401":{"description":"Secret d'administration invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"503":{"description":"ADMIN_SECRET non configuré.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/revocations":{"get":{"tags":["administration"],"summary":"Bordereau en service","description":"Ce que le service refuse **réellement**, en ce moment.\n\nÀ lire avant de composer : c'est l'état de départ du prochain bordereau,\net la seule source qui dise ce qui est appliqué par opposition à ce qui a\nété composé.","operationId":"revocations_en_service_v1_admin_revocations_get","parameters":[{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Secret d'exploitation. Protège l'émission de clés.","title":"X-Admin-Secret"},"description":"Secret d'exploitation. Protège l'émission de clés."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Secret d'administration invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"503":{"description":"ADMIN_SECRET non configuré.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["administration"],"summary":"Composer un bordereau","description":"Compose et signe le bordereau **suivant**. N'applique rien.\n\nLe service est sans état : il n'a aucun registre où marquer une révocation.\nLa mémoire vit dans le déploiement. Ce point d'entrée produit donc le\ndocument signé à déposer dans la configuration — et la réponse porte\n`applique: false` tant que ce dépôt n'a pas eu lieu.\n\n**Couper un nom d'usage** refuse toutes les clés de ce nom émises jusqu'à\nl'instant de la coupe. Réémettre ensuite pour le même nom fonctionne : la\nclé neuve porte une date d'émission postérieure au pivot. C'est l'ordre à\nsuivre — couper d'abord, réémettre ensuite.","operationId":"composer_revocation_v1_admin_revocations_post","parameters":[{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Secret d'exploitation. Protège l'émission de clés.","title":"X-Admin-Secret"},"description":"Secret d'exploitation. Protège l'émission de clés."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemandeRevocation"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Secret d'administration invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"503":{"description":"ADMIN_SECRET non configuré.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/cles/inspection":{"post":{"tags":["administration"],"summary":"Inspecter une clé","description":"Décrit une clé : usage, numéro de série, échéance, validité.\n\nNe lève pas sur une clé invalide — elle est décrite comme telle, avec son\nmotif. C'est l'outil pour répondre à « cette clé marche-t-elle encore ? ».","operationId":"inspecter_cle_v1_admin_cles_inspection_post","parameters":[{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Secret d'exploitation. Protège l'émission de clés.","title":"X-Admin-Secret"},"description":"Secret d'exploitation. Protège l'émission de clés."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemandeInspection"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Secret d'administration invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"503":{"description":"ADMIN_SECRET non configuré.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/etat":{"get":{"tags":["administration"],"summary":"État de l'administration","description":"Ce que le service sait de son propre contrôle d'accès.\n\nAucun registre de clés n'existe : le service est sans état. Il ne peut donc\npas lister ce qu'il a émis — seulement dire s'il exige une clé, combien de\nnuméros de série il refuse, et comment révoquer.","operationId":"etat_admin_v1_admin_etat_get","parameters":[{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Secret d'exploitation. Protège l'émission de clés.","title":"X-Admin-Secret"},"description":"Secret d'exploitation. Protège l'émission de clés."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Secret d'administration invalide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"503":{"description":"ADMIN_SECRET non configuré.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erreur"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CleEmise":{"properties":{"cle":{"type":"string","title":"Cle","description":"La clé, à transmettre à l'appelant. Le service ne la conserve pas : elle n'est pas récupérable ensuite."},"nom":{"type":"string","title":"Nom"},"serie":{"type":"string","title":"Serie","description":"Numéro de série — l'identifiant à porter dans `CLES_REVOQUEES` pour révoquer cette clé précise."},"environnement":{"type":"string","title":"Environnement"},"emise_le":{"type":"string","title":"Emise Le"},"expire_le":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expire Le","description":"Échéance, ou `null` pour une clé sans échéance."},"duree_jours":{"type":"integer","title":"Duree Jours","description":"`0` pour une clé sans échéance."},"perpetuelle":{"type":"boolean","title":"Perpetuelle","description":"Vrai si la clé n'a pas d'échéance.","default":false},"avertissement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avertissement","description":"Présent uniquement pour une clé sans échéance."}},"type":"object","required":["cle","nom","serie","environnement","emise_le","duree_jours"],"title":"CleEmise"},"DemandeCle":{"properties":{"nom":{"type":"string","maxLength":50,"minLength":3,"title":"Nom","description":"Usage de la clé — c'est par ce nom qu'on saura, dans les journaux, quelle application appelle. Ex. `facturation-erp`.","examples":["facturation-erp"]},"pas_avant":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pas Avant","description":"Horodatage Unix après lequel dater l'émission. À renseigner avec le `reemettre_apres` d'un bordereau composé mais pas encore déployé : sans lui, une clé réémise dans la seconde de la coupe naîtrait morte.","examples":[null]},"duree_jours":{"type":"integer","maximum":730.0,"minimum":0.0,"title":"Duree Jours","description":"Durée de validité, en jours. **`0` émet une clé sans échéance**, valable tant que le secret d'exploitation ne change pas : rien ne la retirera de lui-même, il faudra révoquer son numéro de série.","default":90,"examples":[90,0]}},"additionalProperties":false,"type":"object","required":["nom"],"title":"DemandeCle","description":"Émission ou rotation d'une clé d'API."},"DemandeDocument":{"properties":{"profil":{"additionalProperties":true,"type":"object","title":"Profil","description":"Profil de la société émettrice."},"donnees":{"additionalProperties":true,"type":"object","title":"Donnees","description":"Données propres au document demandé."},"options":{"additionalProperties":true,"type":"object","title":"Options","description":"Options de rendu (ex. `version` pour une fiche de paie)."},"nom_fichier":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Nom Fichier","description":"Nom proposé au téléchargement. À défaut, dérivé du type."}},"additionalProperties":false,"type":"object","required":["profil"],"title":"DemandeDocument","description":"Corps d'une demande de composition."},"DemandeInspection":{"properties":{"cle":{"type":"string","title":"Cle","description":"Clé à inspecter."}},"additionalProperties":false,"type":"object","required":["cle"],"title":"DemandeInspection"},"DemandeRevocation":{"properties":{"usages":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Usages","description":"Noms d'usage à COUPER. Toutes les clés portant ce nom, émises jusqu'à maintenant, seront refusées — y compris celles sans échéance. C'est la voie normale : le numéro de série d'une clé fuitée n'est presque jamais connu.","examples":[["facturation-erp"]]},"series":{"items":{"type":"string"},"type":"array","maxItems":500,"title":"Series","description":"Numéros de série à refuser nommément, si on les connaît."},"retirer_usages":{"items":{"type":"string"},"type":"array","title":"Retirer Usages","description":"Lever la coupe d'un usage. Sans effet sur les clés déjà refusées par `CLES_REVOQUEES`, qui est un plancher."},"retirer_series":{"items":{"type":"string"},"type":"array","title":"Retirer Series","description":"Retirer un numéro de série du bordereau."},"depuis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Depuis","description":"Bordereau composé précédemment et **pas encore déployé**, sur lequel enchaîner. Indispensable pour composer deux coupes d'affilée : sans lui, la seconde repart de l'état déployé et efface la première. Le service est sans état, il ne peut pas se souvenir de ce qu'il a composé.","examples":["ilgr.…"]}},"additionalProperties":false,"type":"object","title":"DemandeRevocation","description":"Ce qu'il faut ajouter (ou retirer) au bordereau de révocation."},"Erreur":{"properties":{"erreur":{"type":"string","title":"Erreur","description":"Message lisible, en français."},"type":{"type":"string","title":"Type","description":"Catégorie : `donnees` | `interne`."}},"type":"object","required":["erreur","type"],"title":"Erreur"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"Sante":{"properties":{"statut":{"type":"string","title":"Statut"},"version":{"type":"string","title":"Version"},"environnement":{"type":"string","title":"Environnement"},"documents":{"type":"integer","title":"Documents"},"revocations":{"type":"string","title":"Revocations","description":"Empreinte courte de l'état des révocations en service. Change dès que le bordereau change, alors que `version` reste identique quand seule la configuration bouge — c'est ce qui permet à un déploiement de vérifier que la révocation est réellement appliquée, et non servie par l'ancienne tâche. Ne révèle rien : c'est un condensat.","default":"aucune"}},"type":"object","required":["statut","version","environnement","documents"],"title":"Sante"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"documents","description":"Composition de documents."},{"name":"catalogue","description":"Types disponibles et leurs champs."},{"name":"démonstration","description":"Cas d'exemple anonymes."},{"name":"service","description":"Santé et métadonnées."},{"name":"administration","description":"Émission et rotation des clés d'API. Protégée par `X-Admin-Secret`, distinct des clés d'API."}]}