{"openapi":"3.1.0","info":{"title":"while — plan de contrôle","version":"2.0.0","summary":"Déploiement piloté par agents : intention, plan, application, état versionné.","description":"Toutes les erreurs sont au format RFC 9457 (application/problem+json). L'authentification se fait par jeton d'API (Bearer, ou Basic avec le jeton comme mot de passe) ou par session de l'interface ; le serveur MCP est exposé sur /mcp. Exceptions : dépôt git (Basic), enveloppes Sentry (clé du DSN), OTLP (clé dans le chemin).","contact":{"name":"while","url":"https://app.while.sh"}},"servers":[{"url":"https://app.while.sh"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"projets"},{"name":"intention"},{"name":"versions"},{"name":"télémétrie"},{"name":"secrets"},{"name":"validations"},{"name":"oauth"},{"name":"équipes"},{"name":"organisation","description":"Groupes, approbateurs, propriétaire, revue des accès"},{"name":"git"},{"name":"tâches"},{"name":"acces","description":"Accès interne : appli derrière la connexion de while ou d'entreprise, identité transmise (X-While-*)"},{"name":"alertes","description":"Défauts sans réglage, règles écrites par les agents, incidents, canaux de l'organisation, sondes externes, page de statut"},{"name":"journal","description":"Journal de l'organisation : lecture filtrée, export, vérification de la chaîne, conservation"},{"name":"données","description":"Lecture SQL des bases (query), en lecture seule et journalisée"},{"name":"connexions","description":"Sorties : les API externes par le proxy de while (clé hors de l'appli, apprentissage, verrouillage sur ce qui a servi, validation par appel, chaque appel tracé)"},{"name":"plateforme","description":"Administration de la plateforme (jeton d'amorçage) : sauvegardes de la base de while, realms Keycloak des organisations"},{"name":"interne","description":"Plan de données (rôles entree, acces et sorties sur while-prod) : routes de machine, jeton whd_… de la portée plateforme-donnees seulement (docs/plan-de-donnees.md)"}],"paths":{"/healthz":{"get":{"summary":"Sonde de vie","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/readyz":{"get":{"summary":"Sonde de disponibilité : la base répond","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"ok"}}}}}},"503":{"description":"Base injoignable","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"degraded"},"detail":{"type":"string"}}}}}}}}},"/openapi.json":{"get":{"summary":"Cette description OpenAPI 3.1","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/info":{"get":{"summary":"Version et briques actives","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/catalogue":{"get":{"summary":"Contrat d'une application et briques déclarables, avec leur état (disponible, partiel, prévu)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/pilotes":{"get":{"summary":"Pilote en service derrière chaque brique d'infrastructure : standard, implémentation, mode (réel, minimal, simulacre)","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["brique","standard","implementation","mode"],"properties":{"brique":{"type":"string"},"standard":{"type":"string"},"implementation":{"type":"string"},"mode":{"type":"string","enum":["réel","minimal","simulacre"]},"cible":{"type":["string","null"]},"point":{"type":["string","null"]},"etat":{"type":"object","description":"Brique « sauvegarde de while » seulement : la dernière sauvegarde réussie de la base de while","properties":{"etat":{"type":"string","enum":["ok","en_retard","en_echec","jamais","inconnu"]},"derniere_reussie":{"type":["string","null"],"format":"date-time"}}}}}}}}}}}},"/v1/envoi/{jeton}":{"parameters":[{"name":"jeton","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Envoi des sources par lien signé (donné par l'outil MCP envoi, valable 15 min)","security":[],"requestBody":{"required":true,"content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/sources/{jeton}":{"parameters":[{"name":"jeton","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Sources d'une version par lien signé (donné par l'outil MCP sources, valable 10 min) : l'archive tar.gz","security":[],"responses":{"200":{"description":"Archive tar.gz des sources","content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/constructions/{jeton}/source":{"parameters":[{"name":"jeton","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Contexte d'une construction sur while-build, par lien signé (Secret build-<id>, 15 min), servi une seule fois à l'image d'exécution : l'archive tar.gz","security":[],"responses":{"200":{"description":"Archive tar.gz du contexte, Dockerfile.while compris","content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/releases/{number}/sources":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}},{"name":"number","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["versions"],"summary":"Sources d'une version déployée : l'archive tar.gz que la construction a lue (gardée 7 jours, et tant que la version est en ligne)","responses":{"200":{"description":"Archive tar.gz des sources","content":{"application/gzip":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/schema/while.v1.json":{"get":{"summary":"JSON Schema du fichier d'intention","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/projects":{"get":{"tags":["projets"],"summary":"Lister les projets","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["projets"],"summary":"Créer un projet","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9]([a-z0-9-]{0,28}[a-z0-9])?$"},"name":{"type":"string"},"budget_eur":{"type":"number"}}}}}},"responses":{"201":{"description":"Projet créé","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["projets"],"summary":"Détail d'un projet","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["projets"],"summary":"Détruire le projet : environnements, dépôt git, sources","description":"Réservé à une session de l'interface (droit admin) ; un jeton d'agent ne supprime pas son projet.","responses":{"202":{"description":"Destruction lancée","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"job_id":{"type":"string"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/tokens":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["projets"],"summary":"Créer un jeton de déploiement (CI externe, agent)","description":"Il faut être admin du projet (par l'organisation, ou par un accès de projet). Ses droits ne dépassent jamais ceux de qui le crée sur ce projet, recalculés à chaque requête.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"days":{"type":"integer"},"scopes":{"type":"array","items":{"type":"string","enum":["deploy","read"]}},"environments":{"type":"array","items":{"type":"string"},"description":"Environnements où le jeton peut agir : production, previsualisations (tout sauf la production), ou des noms. Sans liste, tous. Ailleurs, mise en ligne, retour arrière, variable, tâche, restauration et destruction répondent 403."}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"environments":{"type":["array","null"],"items":{"type":"string"}}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["projets"],"summary":"Lister les jetons du projet (sans leur valeur) : administration du projet seulement, jamais un jeton de projet","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"prefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"environnements":{"type":["array","null"],"items":{"type":"string"},"description":"Environnements permis ; null : tous."},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tokens/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["projets"],"summary":"Révoquer un jeton (droit admin)","description":"Un jeton d'administration lié à un projet ne révoque que les jetons de ce projet.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"const":true}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/intent":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["intention"],"summary":"Fichier d'intention courant","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["intention"],"summary":"Déposer un fichier d'intention (validé par le schéma)","requestBody":{"required":true,"content":{"application/yaml":{"schema":{"type":"string"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/plan":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["intention"],"summary":"Calculer un plan : ce qui va changer","responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/plans/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["intention"],"summary":"État d'un plan","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/plans/{id}/apply":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["intention"],"summary":"Appliquer un plan","description":"Une suppression en production ou sur une base demande une validation humaine (réponse 428 avec le lien).","responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"428":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/deploy":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["versions"],"summary":"Déployer : archive de sources (tar.gz) ou image déjà construite","requestBody":{"content":{"application/gzip":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"type":"object","properties":{"image":{"type":"string"},"digest":{"type":"string"},"intent":{"type":"string"}},"required":["image"]}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Release"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"428":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/releases":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["versions"],"summary":"Historique des versions","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array"}}}}}}},"/v1/projects/{slug}/environments/{env}/rollback":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["versions"],"summary":"Retour arrière vers une version précédente","responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/status":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["versions"],"summary":"État courant : version active, URL, santé, trafic des 15 dernières minutes (p50, p95, p99)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/builds/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["versions"],"summary":"État d'une construction (temps, cache chaud ou froid, tests)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/builds/{id}/logs":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["versions"],"summary":"Journal de construction (JSON, ou flux SSE avec Accept: text/event-stream)","responses":{"200":{"description":"Journal"}}}},"/v1/projects/{slug}/logs":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Journaux applicatifs (LogsQL sur VictoriaLogs)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/projects/{slug}/metrics":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Métriques (PromQL, API HTTP Prometheus)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/projects/{slug}/events":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Événements CloudEvents (flux SSE possible)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array"}}}}}}},"/v1/projects/{slug}/webhooks":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["télémétrie"],"summary":"Abonner un webhook signé (CloudEvents 1.0, Standard Webhooks)","responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["télémétrie"],"summary":"Lister les webhooks du projet (sans leur secret)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/approvals/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["validations"],"summary":"État d'une demande : niveau, empreinte, expiration, approbateurs, ce que vous pouvez en faire","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"action":{"type":"string"},"reason":{"type":"string"},"status":{"type":"string","enum":["pending","approved","rejected","expired","executed"]},"niveau":{"type":"string","enum":["libre","validation"]},"empreinte":{"type":["string","null"],"description":"sha256 du contenu décidé : la décision ne vaut que pour lui"},"expires_at":{"type":"string","format":"date-time","description":"72 h ; 24 h pour un appel sensible"},"ressource_type":{"type":"string","enum":["organisation","projet","connexion","base"]},"ressource_id":{"type":["string","null"]},"demandeur":{"type":["string","null"]},"forced":{"type":"boolean"},"motif":{"type":["string","null"]},"approbateurs":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"nom":{"type":"string"},"pourquoi":{"type":"string"}}}},"vous":{"type":"object","properties":{"peut_decider":{"type":"boolean"},"peut_forcer":{"type":"boolean"},"raison":{"type":"string"},"message":{"type":"string"}}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/approvals/{id}/approve":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["validations"],"summary":"Valider une demande","description":"Une autre personne, approbatrice de la ressource, décide : jamais sa propre demande, ni celle de son agent (même compte, 403). Un jeton décide pour sa personne s'il n'est pas en lecture seule, toujours avec un motif. Passer en force : admin de l'organisation (jeton d'administration), motif obligatoire (400 sinon), événement sh.while.approval.forced. Une demande tranchée ou expirée : 409.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"motif":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"action":{"type":"string"},"forced":{"type":"boolean"},"motif":{"type":["string","null"]}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/approvals/{id}/reject":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["validations"],"summary":"Refuser une demande (motif obligatoire)","description":"Une autre personne, approbatrice de la ressource, décide : jamais sa propre demande, ni celle de son agent (même compte, 403). Un jeton décide pour sa personne s'il n'est pas en lecture seule, toujours avec un motif. Passer en force : admin de l'organisation (jeton d'administration), motif obligatoire (400 sinon), événement sh.while.approval.forced. Une demande tranchée ou expirée : 409.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"motif":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"action":{"type":"string"},"forced":{"type":"boolean"},"motif":{"type":["string","null"]}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/approvals/{id}/force":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["validations"],"summary":"Passer en force (admin, motif obligatoire)","description":"Une autre personne, approbatrice de la ressource, décide : jamais sa propre demande, ni celle de son agent (même compte, 403). Un jeton décide pour sa personne s'il n'est pas en lecture seule, toujours avec un motif. Passer en force : admin de l'organisation (jeton d'administration), motif obligatoire (400 sinon), événement sh.while.approval.forced. Une demande tranchée ou expirée : 409.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"motif":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"action":{"type":"string"},"forced":{"type":"boolean"},"motif":{"type":["string","null"]}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/approvals":{"get":{"tags":["validations"],"summary":"Demandes de validation en attente","description":"Celles des projets visibles, et de ses organisations (rôle d'un membre) ; un jeton de projet ne voit que celles de son projet.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"action":{"type":"string"},"reason":{"type":"string"},"status":{"type":"string","enum":["pending","approved","rejected","expired","executed"]},"niveau":{"type":"string","enum":["libre","validation"]},"empreinte":{"type":["string","null"],"description":"sha256 du contenu décidé : la décision ne vaut que pour lui"},"expires_at":{"type":"string","format":"date-time","description":"72 h ; 24 h pour un appel sensible"},"ressource_type":{"type":"string","enum":["organisation","projet","connexion","base"]},"ressource_id":{"type":["string","null"]},"demandeur":{"type":["string","null"]},"forced":{"type":"boolean"},"motif":{"type":["string","null"]},"approbateurs":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"nom":{"type":"string"},"pourquoi":{"type":"string"}}}},"vous":{"type":"object","properties":{"peut_decider":{"type":"boolean"},"peut_forcer":{"type":"boolean"},"raison":{"type":"string"},"message":{"type":"string"}}}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/releases/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["versions"],"summary":"Détail d'une version, avec sa construction et son plan","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Release"},{"type":"object","properties":{"build":{"type":["object","null"]},"plan":{"type":["object","null"]}}}]}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/history":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["versions"],"summary":"Historique du projet : versions, plans et événements mêlés, du plus récent au plus ancien","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"kind":{"enum":["release","plan","event"]},"id":{"type":"string","format":"uuid"},"at":{"type":"string","format":"date-time"},"environment":{"type":"string"},"data":{"type":"object"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["versions"],"summary":"Détruire un environnement (droit deploy)","description":"Détruire la production demande une validation humaine (428 avec le lien) ; renvoyer ensuite approval_id.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"approval_id":{"type":"string","format":"uuid"}}}}}},"responses":{"202":{"description":"Destruction lancée","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"destroying"},"job_id":{"type":"string"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"428":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/state":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["versions"],"summary":"Versions d'état : ce qui a réellement été appliqué (20 dernières)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"serial":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"plan_id":{"type":["string","null"],"format":"uuid"},"release_id":{"type":["string","null"],"format":"uuid"},"outputs":{"type":["object","null"]}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/previews":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["versions"],"summary":"Créer un environnement de prévisualisation (droit deploy)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"name ou branch est obligatoire (le nom se déduit de la branche).","properties":{"name":{"type":"string"},"branch":{"type":"string"},"from":{"type":"string","description":"Environnement de référence"},"copy_database":{"type":"boolean"},"ttl_hours":{"type":"number"}}}}}},"responses":{"202":{"description":"Création lancée","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"creating"},"environment":{"type":"string"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/errors":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Erreurs applicatives regroupées par empreinte (50 groupes) : occurrences, première et dernière vue, version déclarée","parameters":[{"name":"environment","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"tags":["oauth"],"security":[],"summary":"Métadonnées du serveur d'autorisation OAuth 2.1 (RFC 8414), aussi servies sous /.well-known/openid-configuration","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"tags":["oauth"],"security":[],"summary":"Métadonnées de la ressource protégée MCP (RFC 9728) : serveurs d'autorisation, droits","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/oauth/register":{"post":{"tags":["oauth"],"security":[],"summary":"Enregistrement dynamique d'un client public (RFC 7591) ; PKCE obligatoire","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["redirect_uris"],"properties":{"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":10},"token_endpoint_auth_method":{"type":"string","enum":["none"]}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur OAuth (RFC 6749)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/oauth/token":{"post":{"tags":["oauth"],"security":[],"summary":"Échange d'un code (avec code_verifier PKCE) ou rotation d'un jeton de rafraîchissement","description":"Un jeton de rafraîchissement déjà utilisé qui revient révoque toute la session.","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["grant_type","client_id"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string"},"redirect_uri":{"type":"string"},"refresh_token":{"type":"string"},"resource":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string"},"token_type":{"type":"string"},"expires_in":{"type":"integer"},"refresh_token":{"type":"string"},"scope":{"type":"string"}}}}}},"400":{"description":"Erreur OAuth (RFC 6749)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/oauth/revoke":{"post":{"tags":["oauth"],"security":[],"summary":"Révocation (RFC 7009) : un jeton de rafraîchissement révoque toute sa session","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/projects/{slug}/forge":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["projets"],"summary":"Liaison GitHub / GitLab du projet (sans jeton)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["projets"],"summary":"Lier un dépôt : push sur la branche par défaut → production ; demande de fusion → prévisualisation commentée ; statut au commit","description":"Le jeton est vérifié sur la forge, rangé au coffre, et sert à créer le webhook.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["forge","depot","jeton"],"properties":{"forge":{"type":"string","enum":["github","gitlab"]},"depot":{"type":"string"},"jeton":{"type":"string"},"base":{"type":"string","format":"uri"}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["projets"],"summary":"Délier le dépôt (le webhook est retiré)","responses":{"204":{"description":"Déliée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/forges/{id}/webhook":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["projets"],"security":[],"summary":"Webhook GitHub (signature X-Hub-Signature-256) ou GitLab (X-Gitlab-Token)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Reçu ou ignoré","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Signature invalide","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Liaison inconnue","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/updatefomo/webhook":{"post":{"tags":["projets"],"security":[],"summary":"Webhook d'UpdateFOMO : faille nouvelle, correctif publié, faille exploitée (KEV) sur une dépendance suivie","description":"Signé : X-UpdateFOMO-Signature « t=<horodatage>,v1=<HMAC-SHA256 hex de « t.corps brut »> », 5 minutes au plus. Dédoublonné sur X-UpdateFOMO-Delivery. Jamais 410 (le webhook serait désactivé).","responses":{"200":{"description":"ping, ou envoi déjà reçu","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Reçu (traité en arrière-plan) ou ignoré","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Corps illisible","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Signature invalide ou trop ancienne","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"Secret du webhook non configuré","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/projects/{slug}/environments/{env}/domains":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["projets"],"summary":"Domaines de l'environnement : statut, enregistrements DNS à créer, constat, certificat","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/domains/{host}/verify":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}},{"name":"host","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["projets"],"summary":"Vérifier un domaine personnalisé maintenant (preuve TXT et pointage)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/backups":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["projets"],"summary":"Sauvegardes des bases de l'environnement, avec le résultat de leur test de restauration","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/buckets/rotate-key":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["projets"],"summary":"Renouveler la clé S3 de l'environnement : la nouvelle part à la prochaine mise en ligne, l'ancienne vaut jusque-là (immediat : supprimée tout de suite)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"immediat":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/restore":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["projets"],"summary":"Restaurer une sauvegarde (validation humaine, sauvegarde de sûreté avant, une seule transaction)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"database":{"type":"string"},"backup_id":{"type":"string"},"instant":{"type":"string","format":"date-time","description":"While-prod (CloudNativePG) : restaurer à cet instant, depuis la dernière sauvegarde terminée avant lui"},"approval_id":{"type":"string"}}}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"428":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/usage":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["équipes"],"summary":"Consommation du mois (CPU, mémoire, requêtes, constructions, stockage), coût estimé, projection et budget","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/usage/events":{"get":{"tags":["équipes"],"summary":"Consommation des projets visibles en CloudEvents (format OpenMeter)","responses":{"200":{"description":"Lot de CloudEvents","content":{"application/cloudevents-batch+json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams":{"get":{"tags":["équipes"],"summary":"Équipes visibles : les siennes (avec son rôle), celle du jeton, ou toutes pour la plateforme","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["équipes"],"summary":"Créer une équipe (session) ; la personne qui la crée en est propriétaire","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string"},"nom":{"type":"string"}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["équipes"],"summary":"Supprimer une équipe sans projet vivant (administration de la plateforme)","responses":{"204":{"description":"Supprimée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/members":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["équipes"],"summary":"Membres de l'équipe et leur rôle","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["équipes"],"summary":"Inviter une personne (propriétaire ou admin) : rend un lien à usage unique de 48 h, à transmettre","description":"Nommer un admin ou un propriétaire donne de nouveaux droits : la réponse est 202 avec une validation, et le rôle s'applique quand une autre personne, approbatrice de l'organisation, l'a validée (lecteur en attendant). Seul un propriétaire nomme un propriétaire.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["proprietaire","admin","developpeur","lecteur","externe"],"description":"externe : aucun projet ni page d'organisation ; on lui ouvre un projet par PUT /v1/projects/{slug}/acces."}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Ajoutée en lectrice ; le rôle attend une validation","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/tokens":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["équipes"],"summary":"Jeton d'équipe : voit les projets de l'équipe que sa personne voit, et eux seuls (montré une fois)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["admin","deploy","read"]}},"days":{"type":"integer"},"environments":{"type":"array","items":{"type":"string"},"description":"Environnements où le jeton peut agir : production, previsualisations (tout sauf la production), ou des noms. Sans liste, tous. Ailleurs, mise en ligne, retour arrière, variable, tâche, restauration et destruction répondent 403."}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/groups":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["organisation"],"summary":"Groupes de l'organisation (faits dans while ou venus de la connexion d'entreprise) et leurs membres","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["organisation"],"summary":"Créer un groupe (admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom"],"properties":{"nom":{"type":"string","maxLength":60},"sso":{"type":"string","description":"Le groupe du SSO (Keycloak) que ce groupe suit."}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/groups/{groupe}/sso":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"groupe","in":"path","required":true,"schema":{"type":"string"}}],"put":{"tags":["organisation"],"summary":"Lier un groupe à un groupe du SSO (admin), ou le détacher (sso: null)","description":"À chaque connexion par le SSO, si le jeton d'identité porte le claim groups, la personne entre dans les groupes while dont le sso y figure, et sort de ceux dont le sso n'y figure plus (sans casse ni « / » initial). Journalisé.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sso":{"type":["string","null"],"maxLength":200}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"groupe":{"type":"string"},"sso":{"type":["string","null"]}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/politique":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["organisation"],"summary":"La politique de validation : le socle, et les règles que l'organisation a durcies (en production)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["organisation"],"summary":"Durcir une règle (libre → validation, en production), ou la rendre à son socle (admin) : passe par une validation","description":"Seules mettre-en-ligne, revenir-en-arriere et relancer-tache se règlent ; rien de ce qui est en validation dans le socle ne devient libre (400). La réponse est 202 avec la demande de validation : la règle s'applique quand un approbateur de l'organisation l'a validée. Journalisé.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action","niveau"],"properties":{"action":{"type":"string","enum":["mettre-en-ligne","revenir-en-arriere","relancer-tache"]},"niveau":{"type":"string","enum":["validation","libre"]}}}}}},"responses":{"200":{"description":"Déjà à ce niveau","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Demande de validation créée","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/acces":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["projets"],"summary":"Qui a accès au projet (admins du projet) : personnes, groupes, jetons, agents, avec leur rôle effectif et la raison","description":"Le rôle effectif est le plus fort entre le rôle d'organisation (sauf externe), les accès directs et ceux des groupes. Avec ?qui=…&action=…&environnement=… : la réponse à « X peut-il … ? » (oui, non, oui après validation) et sa raison. action : lire, mettre_en_ligne, revenir_en_arriere, lancer_tache, set_env, detruire, administrer.","parameters":[{"name":"qui","in":"query","schema":{"type":"string"},"description":"Une adresse, un nom de groupe, ou l'identifiant d'un jeton."},{"name":"action","in":"query","schema":{"type":"string"}},{"name":"environnement","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["projets"],"summary":"Donner ou changer un accès au projet (admins du projet) : une personne ou un groupe, lecteur, developpeur ou admin","description":"Une personne qui n'est pas membre entre comme externe (admins de l'organisation seulement ; lien d'invitation rendu s'il le faut). Donner admin passe par une validation (202) : une autre personne, approbatrice du projet, valide, et l'accès s'applique. Journalisé.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"email":{"type":"string","format":"email"},"groupe":{"type":"string"},"role":{"type":"string","enum":["lecteur","developpeur","admin"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Admin : validation demandée","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["projets"],"summary":"Retirer l'accès d'une personne ou d'un groupe au projet (admins du projet) ; le rôle d'organisation ne se retire pas ici","parameters":[{"name":"email","in":"query","schema":{"type":"string"}},{"name":"groupe","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/groups/{groupe}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"groupe","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["organisation"],"summary":"Supprimer un groupe (admin) ; ses désignations d'approbateur partent avec","responses":{"204":{"description":"Supprimé"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/groups/{groupe}/members":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"groupe","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["organisation"],"summary":"Ajouter un membre de l'organisation au groupe (admin ; pas pour un groupe de la connexion d'entreprise)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/groups/{groupe}/members/{email}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"groupe","in":"path","required":true,"schema":{"type":"string"}},{"name":"email","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["organisation"],"summary":"Retirer quelqu'un du groupe (admin)","responses":{"204":{"description":"Retiré"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/members/{email}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"email","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["organisation"],"summary":"Retirer une personne de l'organisation (admin) : ses jetons et ses agents branchés sont révoqués","description":"Seul un propriétaire retire un propriétaire ; le dernier propriétaire ne se retire pas (409).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"retire":{"const":true},"jetons_revoques":{"type":"integer"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/approvers":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["organisation"],"summary":"Qui valide quoi : par ressource (organisation, projets, bases, connexions), le propriétaire et les approbateurs","description":"Sans désignation, valident le propriétaire de la ressource et les admins de l'organisation ; une base hérite de son projet.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["organisation"],"summary":"Désigner un approbateur (personne ou groupe) pour une ressource (admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ressource_type","sujet_type","sujet"],"properties":{"ressource_type":{"type":"string","enum":["organisation","projet","connexion","base"]},"ressource":{"type":"string","description":"Projet : son nom ; base : projet/environnement/base ou son identifiant ; organisation : vide"},"sujet_type":{"type":"string","enum":["compte","groupe"]},"sujet":{"type":"string","description":"Adresse d'un membre, ou nom d'un groupe"}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/approvers/{id}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["organisation"],"summary":"Retirer une désignation d'approbateur (admin)","responses":{"204":{"description":"Retirée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/access":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["organisation"],"summary":"Revue des accès (admin) : qui lit, met en ligne et valide chaque ressource ; jetons à revoir ; agents branchés","description":"Un agent hérite des droits de sa personne. Un jeton est « à revoir » sans expiration, inutilisé depuis 30 jours, ou avec plus de droits que sa personne (plafonnés).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/sso":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["organisation"],"summary":"Connexion d'entreprise de l'organisation (admin) : émetteur OpenID, client, domaines, revendication des groupes","description":"Le secret du client est au coffre : seule sa présence est dite (secret: true). preuves_domaines : pour chaque domaine, s'il est prouvé, et l'enregistrement DNS TXT à créer (_while-challenge.<domaine>, while-verification=<jeton>). Sans preuve, aucune adresse de ce domaine n'entre.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["organisation"],"summary":"Régler la connexion d'entreprise (OIDC générique : Keycloak, Google Workspace, Microsoft Entra)","description":"La découverte (/.well-known/openid-configuration) est lue tout de suite (https, adresse publique). Seules les adresses des domaines donnés, une fois prouvés par un enregistrement DNS TXT (preuves_domaines), entrent par cette connexion. Adresse de retour à déclarer chez l'émetteur : https://app.while.sh/acces/entreprise/retour. Journalisé.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nom":{"type":"string","maxLength":60,"description":"Ce que voient les employés : « Google Workspace »."},"issuer":{"type":"string","format":"uri"},"client_id":{"type":"string"},"secret":{"type":"string","description":"Secret du client ; facultatif pour une modification."},"domaines":{"type":"array","items":{"type":"string"},"minItems":1},"revendication_groupes":{"type":"string","default":"groups"},"actif":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["organisation"],"summary":"Retirer la connexion d'entreprise : secret détruit, sessions qui en viennent révoquées","responses":{"204":{"description":"Retirée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/acces/echange":{"post":{"tags":["acces"],"security":[],"summary":"Échange du code d'accès à une appli interne (appel interne du routeur de while)","description":"Code à usage unique, valable 60 s, pour l'hôte qui l'a demandé et le navigateur qui a lancé la connexion (vérificateur). L'en-tête X-While-Routeur prouve l'appel du routeur ; toute autre origine reçoit 403.","parameters":[{"name":"X-While-Routeur","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","hote"],"properties":{"code":{"type":"string"},"hote":{"type":"string"},"verificateur":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ouverture":{"type":"string","format":"uuid"},"expire":{"type":"integer"}}}}}},"400":{"description":"Code refusé","content":{"application/json":{"schema":{"type":"object","properties":{"erreur":{"type":"string","enum":["inconnu","utilise","expire","autre-hote","defi"]}}}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/.well-known/jwks.json":{"get":{"tags":["acces"],"security":[],"summary":"Clé publique (JWKS, ES256) des jetons d'identité X-While-Identite transmis aux applis internes","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object"}}}}}}}}}},"/v1/teams/{slug}/connexions":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["connexions"],"summary":"Connexions de l'organisation : phase (déclaration à valider, déclarée, apprentissage, verrouillée), authentification (sans secret), appels et refus des dernières 24 h, variable <NOM>_URL","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Connexion"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["connexions"],"summary":"Déclarer une connexion (validation par un approbateur, jamais soi-même). Sans secret : il se saisit ensuite (PUT …/secret). Elle démarre en apprentissage : 7 jours, écritures bloquées sauf post_lecture","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom","domaine"],"properties":{"nom":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,38}[a-z0-9]$","examples":["fever"]},"libelle":{"type":"string"},"domaine":{"type":"string","description":"Nom d'hôte public, sans chemin (data-reporting.feverup.com) ; ni IP ni nom interne"},"domaines_secondaires":{"type":"array","items":{"type":"string"},"description":"Relayés sans clé (/~<domaine>/…) : fichiers sur un CDN"},"authentification":{"$ref":"#/components/schemas/RecetteAuthentification"},"post_lecture":{"type":"array","items":{"type":"string"},"description":"Motifs de chemin lus en POST (/api/v1/searches) : permis pendant l'apprentissage"},"apprentissage_jours":{"type":"integer","minimum":1,"maximum":30,"default":7},"debit_par_minute":{"type":"integer","minimum":1,"maximum":60000,"default":600},"proprietaire":{"type":"string","format":"email","description":"Membre propriétaire ; par défaut, la personne qui déclare"}}}}}},"responses":{"202":{"description":"Déclaration à valider","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"validation requise"},"connexion":{"$ref":"#/components/schemas/Connexion"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/connexions/{connexion}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["connexions"],"summary":"Détail d'une connexion : règles (méthode, motif, appelants, validation à chaque appel), appels observés, proposition de verrouillage, derniers appels et refus (métadonnées), appels retenus, approbateurs, comment l'appeler","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Connexion"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/connexions/{connexion}/secret":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}}],"put":{"tags":["connexions"],"summary":"Saisir ou changer le secret (propriétaire, approbateur ou admin) : rangé au coffre, jamais rendu ni journalisé ; une connexion déclarée entre alors en apprentissage. Champs selon la recette : valeur ; utilisateur et mot_de_passe (basic) ; client_id et client_secret (oauth2) ; identifiant et mot_de_passe (session)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"connexion":{"type":"string"},"phase":{"type":"string"},"champs":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/connexions/{connexion}/lock":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["connexions"],"summary":"La proposition de verrouillage, sans la demander : les règles calculées depuis les appels observés (identifiants généralisés en *), avec leurs appelants, et le diff","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["connexions"],"summary":"« Verrouiller sur ce qui a servi » : while calcule les règles et crée la demande de validation, avec le diff. Une fois validée, tout ce qui sort des règles reçoit un 403 (règle manquante) et l'appelant est prévenu","responses":{"202":{"description":"Verrouillage à valider","content":{"application/json":{"schema":{"type":"object","properties":{"validation":{"type":"object"},"regles":{"type":"array","items":{"type":"string"}},"diff":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/connexions/{connexion}/rules":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["connexions"],"summary":"Demander une règle de plus (validation) : méthode, motif (* pour un segment, ** en dernier pour le reste), appelants, valider_chaque_appel","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["methode","motif"],"properties":{"methode":{"enum":["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS","*"]},"motif":{"type":"string","examples":["/api/v1/slots/*/close"]},"appelants":{"type":"array","items":{"type":"string"},"description":"* (défaut), service:<projet>/<service>, tache:<projet>/<tâche>, personne:<e-mail>, groupe:<nom>, projet:<projet>"},"valider_chaque_appel":{"type":"boolean","default":false},"lecture":{"type":"boolean","description":"Écriture HTTP qui ne fait que lire"},"raison":{"type":"string"}}}}}},"responses":{"202":{"description":"Règle à valider","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/connexions/{connexion}/calls":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["connexions"],"summary":"Derniers appels (métadonnées : quand, appelant, via, méthode, chemin normalisé, statut, durée, octets, refus et raison ; jamais le contenu), refus récents, appels retenus","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/connexions/{connexion}/calls/{call}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}},{"name":"call","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["connexions"],"summary":"Un appel retenu (« valider chaque appel ») : en attente, exécuté (statut et réponse, bornée), refusé ou expiré","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/sorties/{organisation}/{connexion}/{path}":{"parameters":[{"name":"organisation","in":"path","required":true,"schema":{"type":"string"}},{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","required":true,"schema":{"type":"string"},"description":"Chemin chez le fournisseur ; /~<domaine secondaire>/… pour un domaine secondaire"}],"get":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (développement, scripts), avec son jeton personnel : while ajoute la clé, applique les règles, relaie en flux (SSE et gros envois compris) et réécrit les adresses du fournisseur vers cette adresse. Les applis du cluster, elles, utilisent <NOM>_URL (rôle sorties)","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"head":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (HEAD) : mêmes règles ; écriture bloquée en apprentissage, 202 pour une règle « valider chaque appel »","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (POST) : mêmes règles ; écriture bloquée en apprentissage, 202 pour une règle « valider chaque appel »","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (PUT) : mêmes règles ; écriture bloquée en apprentissage, 202 pour une règle « valider chaque appel »","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"patch":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (PATCH) : mêmes règles ; écriture bloquée en apprentissage, 202 pour une règle « valider chaque appel »","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (DELETE) : mêmes règles ; écriture bloquée en apprentissage, 202 pour une règle « valider chaque appel »","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"options":{"tags":["connexions"],"summary":"Appeler une connexion depuis un poste (OPTIONS) : mêmes règles ; écriture bloquée en apprentissage, 202 pour une règle « valider chaque appel »","responses":{"200":{"description":"Réponse du fournisseur, relayée (adresses réécrites, Set-Cookie retiré)"},"202":{"description":"Appel retenu : en-tête X-While-Validation","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"const":"en attente de validation"},"appel":{"type":"string"},"validation":{"type":"object"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refusé (règle manquante, écriture en apprentissage, appelant hors règle) : en-tête X-While-Refus","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/journal":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["journal"],"summary":"Journal de l'organisation : qui a fait quoi, par quoi, sur quoi ; de la plus récente à la plus ancienne","description":"En ajout seul, chaîné par empreinte, gardé 13 mois par défaut. Droits : un admin de l'organisation (ou la plateforme) lit tout ; un membre lit les entrées des projets qu'il voit et ses propres actions ; un jeton de projet, son projet. Les appels des connexions et les lectures par query n'y laissent que des métadonnées (requête normalisée sans valeurs, jamais de résultat).","parameters":[{"name":"onglet","in":"query","schema":{"type":"string","enum":["tout","agents","securite","validations"]},"description":"Filtre prêt à l'emploi"},{"name":"personne","in":"query","schema":{"type":"string"},"description":"Adresse exacte, ou nom partiel ; « while » pour while"},{"name":"via","in":"query","schema":{"type":"string","enum":["agent","appli","tache","interface","navigateur","api","while"]}},{"name":"famille","in":"query","schema":{"type":"string"},"description":"deploiement, donnees, securite, acces, droits, validation, tache, alerte, connexion, appli, observation (séparées par des virgules)"},{"name":"projet","in":"query","schema":{"type":"string"}},{"name":"ressource","in":"query","schema":{"type":"string"},"description":"Texte cherché dans l'objet, ou identifiant ou type de ressource"},{"name":"depuis","in":"query","schema":{"type":"string"},"description":"30m, 24h, 7j, une date ou un instant ISO 8601"},{"name":"jusqua","in":"query","schema":{"type":"string"},"description":"Même format ; une date seule compte toute la journée"},{"name":"curseur","in":"query","schema":{"type":"string"},"description":"Le « suivant » de la page précédente"},{"name":"limite","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"organisation":{"type":"string"},"entrees":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"quand":{"type":"string","format":"date-time"},"qui":{"type":"string"},"via":{"type":"string"},"client":{"type":["string","null"]},"action":{"type":"string"},"objet":{"type":"string"},"famille":{"type":"string"},"projet":{"type":["string","null"]},"ressource":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":["string","null"]}}},"detail":{"type":"object"},"phrase":{"type":"string","description":"« qui via quoi action objet », prête à citer"}}}},"suivant":{"type":["string","null"],"description":"Curseur de la page suivante (paramètre curseur)"},"conservation_mois":{"type":"integer"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/journal.jsonl":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["journal"],"summary":"Export du journal en JSON lines (une entrée par ligne), avec les mêmes filtres et les mêmes droits ; noté au journal","parameters":[{"name":"onglet","in":"query","schema":{"type":"string","enum":["tout","agents","securite","validations"]},"description":"Filtre prêt à l'emploi"},{"name":"personne","in":"query","schema":{"type":"string"},"description":"Adresse exacte, ou nom partiel ; « while » pour while"},{"name":"via","in":"query","schema":{"type":"string","enum":["agent","appli","tache","interface","navigateur","api","while"]}},{"name":"famille","in":"query","schema":{"type":"string"},"description":"deploiement, donnees, securite, acces, droits, validation, tache, alerte, connexion, appli, observation (séparées par des virgules)"},{"name":"projet","in":"query","schema":{"type":"string"}},{"name":"ressource","in":"query","schema":{"type":"string"},"description":"Texte cherché dans l'objet, ou identifiant ou type de ressource"},{"name":"depuis","in":"query","schema":{"type":"string"},"description":"30m, 24h, 7j, une date ou un instant ISO 8601"},{"name":"jusqua","in":"query","schema":{"type":"string"},"description":"Même format ; une date seule compte toute la journée"},{"name":"curseur","in":"query","schema":{"type":"string"},"description":"Le « suivant » de la page précédente"}],"responses":{"200":{"description":"Une entrée JSON par ligne","content":{"application/jsonl":{"schema":{"type":"string"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/journal/verification":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["journal"],"summary":"Vérification de la chaîne d'empreintes : ok, ou la première entrée qui ne colle pas (donnée aux admins)","description":"Après une purge de conservation, la chaîne repart de l'empreinte de la dernière entrée purgée (depuis_purge). Les entrées d'avant la migration 020 (format 1) dont le contenu ne se recalcule pas sont comptées dans non_recalculables : seul leur chaînage est vérifié.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"organisation":{"type":"string"},"ok":{"type":"boolean"},"entrees":{"type":"integer"},"casse":{"type":"string"},"non_recalculables":{"type":"integer"},"depuis_purge":{"type":["object","null"]},"conservation_mois":{"type":"integer"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/journal/conservation":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"put":{"tags":["journal"],"summary":"Régler la conservation du journal (admin) : de 1 à 120 mois, 13 par défaut ; la purge passe chaque jour","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mois"],"properties":{"mois":{"type":"integer","minimum":1,"maximum":120}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"organisation":{"type":"string"},"conservation_mois":{"type":"integer"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/query":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["données"],"summary":"Lire une base : UNE requête SQL de lecture (SELECT, WITH, VALUES, TABLE), plafonnée, notée au journal sans ses valeurs","description":"Au nom de la personne (droit de lecture sur le projet). Rôle PostgreSQL while_lecture (SELECT seulement, pas de super-utilisateur), transaction READ ONLY, 10 s par défaut (30 au plus), 400 lignes par défaut (2 000 au plus), 512 000 octets par défaut. Refusé (400) : plusieurs instructions, écriture, DDL, COPY, DO, fonctions qui agissent. Refusé par la base : 422. Une base qui déclare replica: true est encore lue sur la base principale, en lecture seule ; la réponse le dit.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sql"],"properties":{"sql":{"type":"string","maxLength":20000},"database":{"type":"string","description":"La première déclarée par défaut"},"limit":{"type":"integer","minimum":1,"maximum":2000,"default":400},"max_bytes":{"type":"integer","minimum":1000,"maximum":2000000,"default":512000},"timeout_seconds":{"type":"number","minimum":0.5,"maximum":30,"default":10}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"colonnes":{"type":"array","items":{"type":"string"}},"lignes":{"type":"array","items":{"type":"array","items":{"type":["string","null"]}}},"nombre":{"type":"integer"},"tronque":{"type":"boolean"},"troncature":{"type":"string"},"duree_ms":{"type":"integer"},"source":{"type":"string"},"replique":{"type":"string"},"plafonds":{"type":"object"},"journal":{"type":"string"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/owner":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"put":{"tags":["organisation"],"summary":"Changer le propriétaire nommé du projet (admin, ou le propriétaire actuel)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tokens/{id}/expiration":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["organisation"],"summary":"Fixer l'expiration d'un jeton (30 jours par défaut ; ne fait que la rapprocher)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","minimum":1,"maximum":365}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"expires_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/timeline":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Ligne de temps unique : événements de while, erreurs nouvelles, actions des agents par MCP","parameters":[{"name":"environment","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"6 h par défaut"},{"name":"until","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"familles","in":"query","schema":{"type":"string"},"description":"Liste séparée par des virgules"},{"name":"limit","in":"query","schema":{"type":"integer","maximum":500}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"moments":{"type":"array","items":{"type":"object"}},"total":{"type":"integer"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/tasks":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["tâches"],"summary":"Tâches planifiées : planning, dernier passage (état, durée, erreur, quoi faire), passage en cours, prochain, 14 derniers","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"environnement":{"type":"string"},"taches":{"type":"array","items":{"type":"object"}},"resume":{"type":"object"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/tasks/{task}/runs":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}},{"name":"task","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["tâches"],"summary":"Historique des passages d'une tâche : déclenchement, auteur, arguments, début, fin, état, code de sortie, extrait du journal, Job","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","maximum":200}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["tâches"],"summary":"Lancer un passage maintenant : manuel, ou rattrapage d'une période (WHILE_RATTRAPAGE_DEBUT / FIN) ; refusé si un passage tourne déjà","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"},"maxItems":20},"env":{"type":"object","additionalProperties":{"type":"string"}},"rattrapage":{"type":"object","required":["debut"],"properties":{"debut":{"type":"string"},"fin":{"type":"string"}}}}}}}},"responses":{"202":{"description":"Passage lancé","content":{"application/json":{"schema":{"type":"object","properties":{"passage_id":{"type":"string","format":"uuid"},"tache":{"type":"string"},"job":{"type":"string"},"declenchement":{"enum":["manuel","rattrapage"]}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/alerts":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["alertes"],"summary":"Alertes du projet : défauts (état, sourdine), règles écrites (condition en clair, dernier état), incidents ouverts et des 30 derniers jours, canaux, origine de la disponibilité","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"defauts":{"type":"array"},"regles":{"type":"array"},"incidents":{"type":"object"},"canaux":{"type":"array"},"disponibilite":{"type":"object"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/alerts/rules":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["alertes"],"summary":"Créer ou modifier (par son nom) une règle d'alerte : source, condition structurée (ni PromQL ni LogsQL), fenêtre, gravité, quoi faire, canaux ; journalisé","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom","source","message"],"properties":{"nom":{"type":"string","maxLength":80},"source":{"enum":["tache","metrique","journaux","evenement","fraicheur","disponibilite","budget"]},"condition":{"type":"object","description":"tache { tache?, etats? } ; metrique { mesure: taux_erreur (%) | p95_ms | debit | cpu | memoire, operateur, seuil, service? } ; journaux { texte, operateur, seuil, service? } ; evenement { type, operateur, seuil } ; fraicheur { base, table, colonne, max_heures } ; disponibilite { mesure: disponibilite (%) | temps_ms | certificat_jours, operateur, seuil, hote? } ; budget { seuil_pct }"},"fenetre_minutes":{"type":"integer","minimum":1,"maximum":1440},"gravite":{"enum":["info","avertissement","grave"]},"message":{"type":"string","minLength":10,"maxLength":500,"description":"Quoi faire, en français"},"canaux":{"type":"array","items":{"type":"string"},"maxItems":10},"environnement":{"type":"string","default":"production"},"active":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Règle modifiée","content":{"application/json":{"schema":{"type":"object"}}}},"201":{"description":"Règle créée","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/alerts/rules/{rule}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"rule","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["alertes"],"summary":"Supprimer une règle (par son identifiant ou son nom) ; ses incidents ouverts sont clos","responses":{"204":{"description":"Supprimée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/alerts/defaults/{defaut}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"defaut","in":"path","required":true,"schema":{"type":"string"}}],"put":{"tags":["alertes"],"summary":"Mettre en sourdine (ou réactiver) une alerte par défaut pour ce projet : tache, injoignable, erreurs, latence, budget","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourdine"],"properties":{"sourdine":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/incidents":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["alertes"],"summary":"Incidents ouverts (ou acquittés) et clos des 30 derniers jours : quoi, depuis quand, quoi faire, envois","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ouverts":{"type":"array"},"recents":{"type":"array"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/incidents/{id}/acknowledge":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["alertes"],"summary":"Acquitter un incident ouvert (« quelqu'un s'en occupe ») ; il se clôt de lui-même au retour à la normale ; journalisé","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"motif":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/status-page":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["alertes"],"summary":"Données de la page de statut (/statut/<organisation>/<projet>) : état, disponibilité sur 30 jours, temps de réponse, incidents récents","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/alert-channels":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["alertes"],"summary":"Canaux d'alerte de l'organisation (sans leurs URL, qui sont des secrets), et le réveil des agents, toujours actif","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["alertes"],"summary":"Ajouter un canal (admin) : e-mail (adresse), Slack (URL du webhook entrant), webhook générique (CloudEvents signés ; la clé de signature est rendue une seule fois)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"enum":["email","slack","webhook"]},"nom":{"type":"string"},"adresse":{"type":"string","format":"email"},"url":{"type":"string","format":"uri","description":"https://, rangée par le pilote de secrets"},"gravite_min":{"enum":["info","avertissement","grave"],"default":"avertissement"}}}}}},"responses":{"201":{"description":"Canal ajouté","content":{"application/json":{"schema":{"type":"object","properties":{"canal":{"type":"object"},"cle_signature":{"type":"string"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/alert-channels/{channel}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["alertes"],"summary":"Retirer un canal (admin) ; son URL est détruite dans le coffre","responses":{"204":{"description":"Retiré"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/teams/{slug}/alert-channels/{channel}/test":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["alertes"],"summary":"Envoyer un message d'essai par ce canal (admin)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"resultat":{"type":"string"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/plateforme/realms":{"get":{"tags":["plateforme"],"summary":"Realms Keycloak des organisations (administration de la plateforme) : le pilote, l'état de chaque realm org-<accountID>, ceux qui attendent leur suppression. Aucun secret","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"pilote":{"type":"object","properties":{"en_service":{"type":"boolean"},"raison":{"type":["string","null"]},"administration":{"type":["string","null"]},"client":{"type":"string"}}},"organisations":{"type":"array","items":{"type":"object","properties":{"organisation":{"type":"string"},"realm":{"type":"string"},"etat":{"enum":["a_creer","pret","erreur","a_supprimer"]},"emetteur":{"type":"string"},"applis_internes":{"type":"integer"},"erreur":{"type":["string","null"]},"essais":{"type":"integer"},"cree_le":{"type":["string","null"]},"synchronise_le":{"type":["string","null"]},"secret_renouvele_le":{"type":["string","null"]}}}},"a_supprimer":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/plateforme/realms/rattrapage":{"post":{"tags":["plateforme"],"summary":"Rattrapage des realms tout de suite (administration de la plateforme) : chaque organisation vérifiée (realm, réglages, client while-acces, secret au coffre), les realms des organisations purgées supprimés ; 503 sans pilote Keycloak","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"en_service":{"type":"boolean"},"verifies":{"type":"integer"},"prets":{"type":"integer"},"erreurs":{"type":"array","items":{"type":"object","properties":{"organisation":{"type":"string"},"realm":{"type":"string"},"erreur":{"type":"string"}}}},"supprimes":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/plateforme/realms/{organisation}/secret":{"parameters":[{"name":"organisation","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["plateforme"],"summary":"Renouveler le secret du client while-acces du realm d'une organisation (administration de la plateforme) : Keycloak le tire, while le range au coffre de l'organisation ; il n'apparaît pas dans la réponse","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"organisation":{"type":"string"},"realm":{"type":"string"},"client":{"type":"string"},"renouvele_le":{"type":["string","null"]},"coffre":{"type":"string"},"note":{"type":"string"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/plateforme/sauvegardes":{"get":{"tags":["plateforme"],"summary":"Sauvegardes de la base de while (administration de la plateforme) : état, dernière réussie, derniers passages, étiquettes présentes à la destination","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"etat":{"type":"string","enum":["ok","en_retard","en_echec","jamais"]},"message":{"type":"string"},"en_cours":{"type":"boolean"},"derniere_reussie":{"type":["object","null"]},"destination":{"type":"object"},"planification":{"type":"string"},"a_la_destination":{"description":"Étiquettes lues à la destination à l'instant (ou { erreur })"},"passages":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["plateforme"],"summary":"Lancer une sauvegarde de la base de while tout de suite (administration de la plateforme) ; 409 si une autre tourne","responses":{"202":{"description":"Programmée","content":{"application/json":{"schema":{"type":"object","properties":{"statut":{"type":"string"},"travail":{"type":"string"},"suivi":{"type":"string"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/probes":{"get":{"tags":["alertes"],"summary":"Sondes externes enregistrées : lieu, dernière vue, en service (administration de la plateforme)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["alertes"],"summary":"Enregistrer une sonde externe (administration de la plateforme) : rend son jeton whs_… une seule fois, et la commande pour la lancer","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom","lieu"],"properties":{"nom":{"type":"string"},"lieu":{"type":"string"}}}}}},"responses":{"201":{"description":"Sonde enregistrée","content":{"application/json":{"schema":{"type":"object","properties":{"jeton":{"type":"string"},"lancement":{"type":"string"}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/probes/{probe}":{"parameters":[{"name":"probe","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["alertes"],"summary":"Retirer une sonde (son jeton cesse de valoir)","responses":{"204":{"description":"Retirée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/probe/targets":{"get":{"tags":["alertes"],"summary":"Pour une sonde (jeton whs_…) : les adresses à sonder, chacune avec son chemin de santé, les statuts attendus et l'en-tête X-While-Sonde déjà signé","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sonde":{"type":"object"},"intervalle_s":{"type":"integer"},"cibles":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/changements":{"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Flux SSE des changements qui touchent le cache du plan de données (événements « pret », puis « changement » : domaines du cache et clé)","responses":{"200":{"description":"Flux text/event-stream"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/acces/hotes/{hote}":{"parameters":[{"name":"hote","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Un hôte d'appli : projet, environnement, service, organisation (accountID, realm), règle d'accès, client OIDC du realm","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/acces/ouvertures":{"post":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Ouvrir l'accès d'une personne revenue du realm de l'organisation (identité vérifiée par acces) : la règle de l'appli décide, noté au journal","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["hote","realm","email"],"properties":{"hote":{"type":"string"},"realm":{"type":"string"},"sub":{"type":"string"},"email":{"type":"string"},"nom":{"type":"string"},"groupes":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Ouverture","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Refus (autre organisation, règle)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/interne/acces/ouvertures/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Une ouverture encore valable, la personne relue (appartenance, groupes)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Déconnexion depuis l'appli : l'ouverture tombe, notée au journal","responses":{"204":{"description":"Fermée"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/acces/refus":{"post":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Un refus vu après la connexion (la règle a changé) : noté au journal","responses":{"204":{"description":"Noté"},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Hôte ou ouverture inconnus"}}}},"/v1/interne/acces/cles/{kid}":{"parameters":[{"name":"kid","in":"path","required":true,"schema":{"type":"string"}}],"put":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Clé publique (EC P-256) des jetons d'identité signés par acces : publiée dans /.well-known/jwks.json","responses":{"204":{"description":"Enregistrée"},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/domaines/{domaine}":{"parameters":[{"name":"domaine","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"TLS à la demande (ask de Caddy) : un domaine rattaché à un service et vérifié","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"verifie":{"const":true}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/telemetrie/otlp/{empreinte}":{"parameters":[{"name":"empreinte","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Le projet d'une clé OTLP, par son empreinte sha256 : locataire (accountID), environnements et leurs namespaces","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/telemetrie/dsn/{projet}":{"parameters":[{"name":"projet","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Le projet d'un DSN Sentry, avec l'empreinte de sa clé publique","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/telemetrie/erreurs/{projet}":{"parameters":[{"name":"projet","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Groupes d'erreurs d'un lot reçu par entree (50 au plus) ; une erreur jamais vue réveille l'agent","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/sorties/jetons/{empreinte}":{"parameters":[{"name":"empreinte","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Le jeton de service d'une appli (FEVER_URL), par son empreinte : environnement, namespace, connexions déclarées","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/sorties/connexions/{equipe}/{nom}":{"parameters":[{"name":"equipe","in":"path","required":true,"schema":{"type":"string"}},{"name":"nom","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Une connexion (règles, phase, recette), sans son secret","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/sorties/secrets/{connexion}":{"parameters":[{"name":"connexion","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Le secret d'une connexion, que seul le relais ajoute aux appels (jamais journalisé)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/sorties/traces":{"post":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Un lot de traces du relais (1000 au plus) : consignées, journal et événements comme par le relais local","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/interne/sorties/retenus":{"post":{"tags":["interne"],"security":[{"jetonDonnees":[]}],"summary":"Un appel à valider : corps chiffré, validation demandée","responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/probe/results":{"post":{"tags":["alertes"],"summary":"Pour une sonde (jeton whs_…) : ses résultats (statut, temps de réponse, expiration du certificat), 500 au plus par envoi","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resultats"],"properties":{"resultats":{"type":"array","maxItems":500,"items":{"type":"object","required":["host","chemin","ok","checked_at"],"properties":{"host":{"type":"string"},"chemin":{"type":"string"},"ok":{"type":"boolean"},"statut":{"type":["integer","null"]},"latence_ms":{"type":["integer","null"]},"certificat_expire_le":{"type":["string","null"],"format":"date-time"},"erreur":{"type":["string","null"]},"checked_at":{"type":"string","format":"date-time"}}}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"recus":{"type":"integer"},"ignores":{"type":"integer"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/impact":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Impact d'une version : trafic, erreurs et latence avant / après, verdict, propositions","parameters":[{"name":"version","in":"query","schema":{"type":"integer"},"description":"La version active par défaut"},{"name":"fenetre_minutes","in":"query","schema":{"type":"integer","minimum":5,"maximum":1440}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/environments/{env}/ask":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"env","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["télémétrie"],"summary":"Question sur la production (« pourquoi c'est lent depuis 14 h ? ») : faits, suspects, propositions","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string","minLength":3}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/mcp-actions":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"Ce que les agents ont fait par MCP : outil, paramètres (valeurs sensibles masquées), résultat","parameters":[{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"7 jours par défaut"},{"name":"limit","in":"query","schema":{"type":"integer","maximum":500}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/projects/{slug}/telemetry":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["télémétrie"],"summary":"DSN Sentry et points d'entrée OpenTelemetry du projet","description":"Crée la clé du DSN à la première lecture.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sentry_dsn":{"type":"string"},"otlp_http":{"type":"string","format":"uri"},"otlp_note":{"type":"string"},"logs_query":{"type":"string","format":"uri"},"metrics_query":{"type":"string","format":"uri"},"note":{"type":"string"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/{projectRef}/envelope/":{"parameters":[{"name":"projectRef","in":"path","required":true,"schema":{"type":"string"},"description":"Slug ou identifiant du projet"}],"post":{"tags":["télémétrie"],"summary":"Ingestion d'enveloppes Sentry (SDK Sentry tels quels)","description":"Authentifiée par la clé publique du DSN (X-Sentry-Auth, Authorization ou ?sentry_key), pas par jeton. 50 événements au plus par enveloppe, 512 Ko au plus.","security":[{"sentryAuth":[]},{"sentryKey":[]}],"requestBody":{"required":true,"content":{"application/x-sentry-envelope":{"schema":{"type":"string"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"accepted":{"type":"integer"}}}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/otlp/{key}/v1/{signal}":{"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Clé du DSN du projet : elle authentifie"},{"name":"signal","in":"path","required":true,"schema":{"enum":["logs","metrics"]}}],"post":{"tags":["télémétrie"],"summary":"Relais OTLP/HTTP (ancien chemin : environnement production)","description":"Projet et environnement sont imposés aux données relayées. 502 si le collecteur refuse.","security":[],"requestBody":{"required":true,"content":{"application/x-protobuf":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"const":true}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Collecteur en erreur","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"const":false}}}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/otlp/{key}/{environment}/v1/{signal}":{"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Clé du DSN du projet : elle authentifie"},{"name":"environment","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9-]{1,40}$"}},{"name":"signal","in":"path","required":true,"schema":{"enum":["logs","metrics"]}}],"post":{"tags":["télémétrie"],"summary":"Relais OTLP/HTTP vers VictoriaLogs et VictoriaMetrics","description":"Projet et environnement sont imposés aux données relayées. 502 si le collecteur refuse.","security":[],"requestBody":{"required":true,"content":{"application/x-protobuf":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"const":true}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Collecteur en erreur","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"const":false}}}}}},"503":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/git/{repo}":{"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string"},"description":"Slug du projet, suffixe .git facultatif"}],"get":{"tags":["git"],"summary":"Aide du dépôt git (commandes remote et push)","security":[],"responses":{"200":{"description":"Aide","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/git/{repo}/info/refs":{"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["git"],"summary":"Annonce des références (protocole git intelligent)","description":"git-receive-pack demande le droit deploy.","security":[{"basicAuth":[]}],"parameters":[{"name":"service","in":"query","required":true,"schema":{"enum":["git-upload-pack","git-receive-pack"]}}],"responses":{"200":{"description":"Annonce","content":{"application/x-git-upload-pack-advertisement":{"schema":{"type":"string","format":"binary"}},"application/x-git-receive-pack-advertisement":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Authentification requise (défi Basic)","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/git/{repo}/git-upload-pack":{"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["git"],"summary":"git fetch / clone (stateless-rpc)","security":[{"basicAuth":[]}],"requestBody":{"required":true,"content":{"application/x-git-upload-pack-request":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"OK","content":{"application/x-git-upload-pack-result":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Authentification requise (défi Basic)","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/git/{repo}/git-receive-pack":{"parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["git"],"summary":"git push (droit deploy) : main déploie la production, une autre branche une prévisualisation","security":[{"basicAuth":[]}],"requestBody":{"required":true,"content":{"application/x-git-receive-pack-request":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"OK","content":{"application/x-git-receive-pack-result":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Authentification requise (défi Basic)","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/vault/v1/sys/health":{"get":{"tags":["secrets"],"summary":"Santé, compatible Vault","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"initialized":{"type":"boolean"},"sealed":{"type":"boolean"},"standby":{"type":"boolean"},"version":{"type":"string"},"implementation":{"type":"string"}}}}}}}}},"/vault/v1/secret/data/{path}":{"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string"},"description":"Chemin du secret ; peut contenir des « / » (joker, ex. db/primaire)."},{"name":"project","in":"query","schema":{"type":"string"},"description":"Slug du projet ; obligatoire hors jeton de projet."},{"name":"environment","in":"query","schema":{"type":"string"},"description":"Nom de l'environnement (sinon secret du projet)."}],"get":{"tags":["secrets"],"summary":"Lire un secret (KV v2)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"},"data":{"type":"object","properties":{"data":{"type":"object"},"metadata":{"type":"object"}}}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["secrets"],"summary":"Écrire un secret (KV v2, droit deploy)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","format":"uuid"},"data":{"type":"object","properties":{"version":{"type":"integer"},"created_time":{"type":"string","format":"date-time"},"destroyed":{"type":"boolean"}}}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["secrets"],"summary":"Supprimer un secret (KV v2, droit deploy)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"const":true}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/vault/v1/secret/metadata/{path}":{"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string"},"description":"Préfixe à lister ; peut contenir des « / » ou être vide."},{"name":"project","in":"query","schema":{"type":"string"},"description":"Slug du projet ; obligatoire hors jeton de projet."},{"name":"environment","in":"query","schema":{"type":"string"},"description":"Nom de l'environnement (sinon secret du projet)."}],"get":{"tags":["secrets"],"summary":"Lister les clés sous un préfixe (KV v2)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"}}}}}}}}},"400":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Erreur","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Jeton d'API while (whl_…)"},"jetonDonnees":{"type":"http","scheme":"bearer","description":"Jeton du plan de données (whd_…, portée plateforme-donnees) : /v1/interne seulement"},"basicAuth":{"type":"http","scheme":"basic","description":"Jeton while comme mot de passe, utilisateur au choix (clients git)"},"sessionCookie":{"type":"apiKey","in":"cookie","name":"__Host-while_session","description":"Session de l'interface (humain ; while_session en http). Une requête qui modifie doit porter Origin (ou Referer) de while : sinon 403."},"sentryAuth":{"type":"apiKey","in":"header","name":"X-Sentry-Auth","description":"sentry_key=<clé publique du DSN>"},"sentryKey":{"type":"apiKey","in":"query","name":"sentry_key","description":"Clé publique du DSN"}},"schemas":{"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"Problem":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"required":["type","title","status"]},"IntentFile":{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://while.sh/schema/while.v1.json","title":"Fichier d'intention while (while.yaml), version 1","description":"Décrit un projet : services, bases, caches, stockage, files, tâches planifiées, domaines, environnements, budget. while le traduit en infrastructure (OpenTofu) chez un fournisseur. Un projet sans service web (une base et des tâches, par exemple) est permis : il déclare au moins un service, une base ou une tâche planifiée.","type":"object","required":["apiVersion","project"],"additionalProperties":false,"properties":{"apiVersion":{"const":"while/v1"},"project":{"$ref":"#/$defs/slug","description":"Identifiant du projet, en minuscules."},"region":{"type":"string","default":"fr-lab01","description":"Région du fournisseur. Nextmap uniquement au lancement."},"services":{"type":"array","maxItems":10,"items":{"$ref":"#/$defs/service"},"description":"Services de l'application. Facultatif quand le projet déclare au moins une base ou une tâche planifiée : un projet de données n'a pas toujours de service web."},"databases":{"type":"array","maxItems":4,"items":{"$ref":"#/$defs/database"}},"caches":{"type":"array","maxItems":4,"items":{"$ref":"#/$defs/cache"}},"buckets":{"type":"array","maxItems":4,"items":{"$ref":"#/$defs/bucket"}},"queues":{"type":"array","maxItems":4,"items":{"$ref":"#/$defs/queue"}},"crons":{"type":"array","maxItems":10,"items":{"$ref":"#/$defs/cron"},"description":"Tâches planifiées : une commande lancée à heure fixe, dans l'image d'un service, ou construite pour elle quand le projet n'a pas de service. Chaque passage est suivi (historique, échecs, passages manqués), relançable et rattrapable."},"environments":{"type":"object","description":"Surcharges par environnement. La clé est le nom de l'environnement (production, preview, …).","propertyNames":{"$ref":"#/$defs/slug"},"additionalProperties":{"$ref":"#/$defs/environmentOverride"}},"budget":{"type":"object","additionalProperties":false,"properties":{"monthly_eur":{"type":"number","minimum":0,"description":"Plafond mensuel. Au-delà, while demande une validation humaine."},"alert_at_percent":{"type":"number","minimum":1,"maximum":100,"default":80}}},"telemetry":{"type":"object","additionalProperties":false,"properties":{"logs":{"type":"boolean","default":true},"metrics":{"type":"boolean","default":true},"traces":{"type":"boolean","default":false},"errors":{"type":"boolean","default":true,"description":"Point d'entrée compatible Sentry (DSN fourni par while)."},"uptime":{"type":"object","additionalProperties":false,"properties":{"path":{"type":"string","default":"/health"},"interval_seconds":{"type":"integer","minimum":10,"default":60}}}}},"network":{"type":"object","additionalProperties":false,"description":"Contrôle des sorties réseau des applications.","properties":{"egress":{"enum":["none","restricted","all"],"default":"restricted"},"allow":{"type":"array","items":{"type":"string"},"description":"Noms d'hôtes autorisés en sortie quand egress vaut restricted."}}}},"$defs":{"slug":{"type":"string","pattern":"^[a-z0-9]([a-z0-9-]{0,28}[a-z0-9])?$"},"connexions":{"type":"array","maxItems":20,"uniqueItems":true,"items":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,38}[a-z0-9]$"},"description":"Connexions de l'organisation (API externes déclarées dans while) appelées par ce service ou cette tâche. Pour « fever », while injecte FEVER_URL : l'adresse du proxy, jeton compris ; la clé reste dans while, chaque appel est tracé."},"quantityCpu":{"type":"string","pattern":"^([0-9]+m|[0-9]+(\\.[0-9]+)?)$","description":"Quantité CPU Kubernetes (500m, 1)."},"quantityMemory":{"type":"string","pattern":"^[0-9]+(Mi|Gi|M|G)$"},"service":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"$ref":"#/$defs/slug"},"type":{"enum":["web","worker","static"],"default":"web","description":"web : service HTTP avec une URL. worker : processus sans URL ni sonde HTTP (file de travaux, consommateur). static : site statique servi avec cache HTTP."},"root":{"type":"string","default":".","description":"Pour un site statique : le dossier à servir une fois construit (dist, build, public…)."},"source":{"type":"string","default":".","description":"Chemin des sources dans l'archive envoyée ou le dépôt git."},"image":{"type":"string","description":"Image déjà construite (registre externe ou registre while), avec son digest de préférence."},"dockerfile":{"type":"string","description":"Chemin du Dockerfile ; sinon while détecte le langage et en génère un."},"build":{"type":"object","additionalProperties":false,"properties":{"context":{"type":"string","default":"."},"target":{"type":"string"},"args":{"type":"object","additionalProperties":{"type":"string"}},"test":{"type":"string","description":"Commande de tests jouée dans le même bac à sable que la construction. Par défaut, celle du langage détecté."},"skip_tests":{"type":"boolean","default":false}}},"command":{"type":"array","items":{"type":"string"}},"port":{"type":"integer","minimum":1,"maximum":65535,"default":8080},"public":{"type":"boolean","default":true,"description":"Exposé sur un domaine public."},"access":{"enum":["public","interne"],"default":"public","description":"public : tout internet. interne : derrière la connexion de while (ou la connexion d'entreprise de l'organisation) ; l'appli reçoit l'identité dans X-While-Email, X-While-Nom, X-While-Groupes et X-While-Identite. Sans groupes, emails ni domaines_email : les membres de l'organisation. Rendre publique une appli interne, ou changer qui y accède, demande une validation."},"groupes":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"maxItems":50,"description":"Accès interne : groupes de l'organisation (faits dans while ou venus de la connexion d'entreprise) qui entrent."},"emails":{"type":"array","items":{"type":"string","format":"email"},"maxItems":200,"description":"Accès interne : adresses qui entrent, membres ou non de l'organisation."},"domaines_email":{"type":"array","items":{"type":"string","format":"hostname"},"maxItems":20,"description":"Accès interne : domaines d'adresse qui entrent (exacts : nextmap.io n'ouvre pas fr.nextmap.io)."},"domains":{"type":"array","items":{"type":"string","format":"hostname"},"description":"Domaines personnalisés. while attribue toujours un domaine par défaut."},"health":{"type":"object","additionalProperties":false,"properties":{"path":{"type":"string","default":"/health"},"initial_delay_seconds":{"type":"integer","minimum":0,"default":2},"timeout_seconds":{"type":"integer","minimum":1,"default":3}}},"env":{"type":"object","description":"Variables d'environnement. Une valeur { secret: chemin } est lue dans le coffre.","additionalProperties":{"oneOf":[{"type":["string","number","boolean"]},{"type":"object","required":["secret"],"additionalProperties":false,"properties":{"secret":{"type":"string"}}}]}},"resources":{"type":"object","additionalProperties":false,"properties":{"cpu":{"$ref":"#/$defs/quantityCpu","default":"200m"},"memory":{"$ref":"#/$defs/quantityMemory","default":"192Mi"}}},"scale":{"type":"object","additionalProperties":false,"properties":{"min":{"type":"integer","minimum":0,"maximum":5,"default":1,"description":"0 met l'environnement en veille quand il n'y a pas de trafic."},"max":{"type":"integer","minimum":1,"maximum":5,"default":1}}},"uses":{"type":"array","items":{"type":"string"},"description":"Bases, caches, stockages ou files utilisés par ce service. Par défaut, tout ce qui est déclaré."},"connexions":{"$ref":"#/$defs/connexions"}}},"database":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"$ref":"#/$defs/slug"},"engine":{"enum":["postgres"],"default":"postgres"},"version":{"type":"string","default":"17"},"size":{"$ref":"#/$defs/quantityMemory","default":"1Gi"},"replica":{"type":"boolean","default":false,"description":"Prévu : une réplique en lecture (réplication en continu), lue par l'outil query des agents. Accepté dès maintenant ; tant qu'elle n'est pas servie, query lit la base principale, en transaction en lecture seule, et le dit dans sa réponse."},"backup":{"type":"object","additionalProperties":false,"properties":{"schedule":{"type":"string","default":"0 3 * * *"},"retention_days":{"type":"integer","minimum":1,"default":7},"test_restore":{"type":"boolean","default":true,"description":"while rejoue la restauration et affiche le résultat."}}}}},"cache":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"$ref":"#/$defs/slug"},"engine":{"enum":["valkey"],"default":"valkey"},"memory":{"$ref":"#/$defs/quantityMemory","default":"64Mi"},"durable":{"type":"boolean","default":false,"description":"Données gardées sur disque (journal AOF) et jamais évincées : pour une file de tâches (BullMQ, Sidekiq, Celery). Sans lui, un redémarrage vide le cache."}}},"bucket":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"$ref":"#/$defs/slug"},"public":{"type":"boolean","default":false}}},"queue":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"$ref":"#/$defs/slug"},"engine":{"enum":["nats"],"default":"nats"}}},"environmentOverride":{"type":"object","additionalProperties":false,"properties":{"from":{"$ref":"#/$defs/slug","description":"Environnement de référence dont celui-ci hérite."},"services":{"type":"array","items":{"type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"$ref":"#/$defs/slug"}}}},"databases":{"type":"array","items":{"type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"$ref":"#/$defs/slug"},"copy_from":{"$ref":"#/$defs/slug","description":"Copie instantanée de la base de cet environnement au moment de la création."}}}},"ttl_hours":{"type":"integer","minimum":1,"maximum":720,"description":"Durée de vie avant destruction automatique (environnements de prévisualisation)."}}},"cron":{"type":"object","required":["name","schedule","command"],"additionalProperties":false,"properties":{"name":{"$ref":"#/$defs/slug"},"schedule":{"type":"string","pattern":"^(@(hourly|daily|weekly|monthly)|(\\S+\\s+){4}\\S+)$","description":"Rythme au format cron (5 champs), dans le fuseau de « timezone » (Europe/Paris par défaut) : « 0 3 * * * » chaque jour à 3 h."},"command":{"type":"array","minItems":1,"items":{"type":"string"},"description":"Commande à lancer, par exemple [\"node\", \"cron.js\"]."},"service":{"$ref":"#/$defs/slug","description":"Service dont l'image et les variables sont reprises. Par défaut, le premier service ; sans service déclaré (ou avec image, source, dockerfile ou build), la tâche est construite pour elle-même."},"image":{"type":"string","description":"Image déjà construite pour une tâche sans service, avec son digest de préférence."},"source":{"type":"string","default":".","description":"Pour une tâche sans service : chemin des sources dans l'archive envoyée ou le dépôt git."},"dockerfile":{"type":"string","description":"Pour une tâche sans service : chemin du Dockerfile ; sinon while détecte le langage."},"build":{"$ref":"#/$defs/service/properties/build"},"env":{"$ref":"#/$defs/service/properties/env"},"uses":{"type":"array","items":{"type":"string"},"description":"Pour une tâche sans service : bases, caches, stockages ou files qu'elle utilise. Par défaut, tout ce qui est déclaré."},"connexions":{"$ref":"#/$defs/connexions"},"resources":{"type":"object","additionalProperties":false,"description":"Par défaut, celles du service repris ; 100 mCPU et 128 Mio pour une tâche sans service.","properties":{"cpu":{"$ref":"#/$defs/quantityCpu"},"memory":{"$ref":"#/$defs/quantityMemory"}}},"timeout_seconds":{"type":"integer","minimum":10,"maximum":21600,"default":600,"description":"Durée maximale d'un passage : au-delà, il est arrêté et compté « délai dépassé »."},"retries":{"type":"integer","minimum":0,"maximum":5,"default":0,"description":"Nouvelles tentatives après un échec, dans le même passage."},"concurrency":{"enum":["forbid","allow","replace"],"default":"forbid","description":"forbid : jamais deux passages en même temps (un passage manuel est refusé tant qu'un autre tourne). allow : chevauchement permis. replace : le nouveau passage arrête celui en cours."},"timezone":{"type":"string","default":"Europe/Paris","pattern":"^[A-Za-z][A-Za-z0-9_+-]*(/[A-Za-z0-9_+-]+){0,2}$","description":"Fuseau horaire du rythme (nom IANA : Europe/Paris, UTC…)."}}}}},"RecetteAuthentification":{"description":"Comment while s'authentifie chez le fournisseur ; le secret n'en fait jamais partie","oneOf":[{"type":"object","required":["type"],"properties":{"type":{"const":"aucune"}}},{"type":"object","required":["type"],"properties":{"type":{"const":"entete"},"nom":{"type":"string","default":"X-Api-Key"},"prefixe":{"type":"string"}}},{"type":"object","required":["type","nom"],"properties":{"type":{"const":"parametre"},"nom":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"enum":["basic","bearer"]}}},{"type":"object","required":["type","url_jeton"],"properties":{"type":{"const":"oauth2"},"url_jeton":{"type":"string","format":"uri"},"portee":{"type":"string"},"audience":{"type":"string"},"client_dans":{"enum":["basic","corps"]}}},{"type":"object","required":["type","url_connexion","champ_jeton"],"properties":{"type":{"const":"session"},"url_connexion":{"type":"string"},"methode":{"enum":["POST","GET"]},"format":{"enum":["json","formulaire"]},"champ_identifiant":{"type":"string"},"champ_mot_de_passe":{"type":"string"},"champ_jeton":{"type":"string"},"duree_secondes":{"type":"integer"},"entete":{"type":"string"},"prefixe":{"type":"string"},"parametre":{"type":"string"}}}]},"Connexion":{"type":"object","properties":{"nom":{"type":"string"},"domaine":{"type":"string"},"domaines_secondaires":{"type":"array","items":{"type":"string"}},"phase":{"enum":["proposee","declaree","apprentissage","verrouillee","refusee"]},"authentification":{"type":"string"},"secret":{"type":"string","description":"aucun, à saisir, ou qui l'a saisi et quand — jamais la valeur"},"proprietaire":{"type":["string","null"]},"variable":{"type":"string","examples":["FEVER_URL"]},"apprentissage":{"type":["object","null"]},"regles":{"type":"integer"},"appels_24h":{"type":"integer"},"refus_24h":{"type":"integer"}}},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"Plan":{"type":"object","description":"Les trois temps : un plan, une application, un état versionné. « extensions » porte les informations futures (coût…) sans changer la forme.","properties":{"id":{"type":"string","format":"uuid"},"status":{"enum":["planning","planned","applying","applied","failed","cancelled"]},"summary":{"type":"object"},"changes":{"type":"array","items":{"type":"object"}},"extensions":{"type":"object"}}},"Release":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"number":{"type":"integer"},"status":{"enum":["pending","building","built","deploying","active","failed","rolled_back","superseded"]},"image_ref":{"type":"string"},"image_digest":{"type":"string"}}}}}}