{"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://while.k8s-lab-01.nextmap.io"}},"servers":[{"url":"https://while.k8s-lab-01.nextmap.io"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"projets"},{"name":"intention"},{"name":"versions"},{"name":"télémétrie"},{"name":"secrets"},{"name":"validations"},{"name":"oauth"},{"name":"équipes"},{"name":"git"}],"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"]}}}}}}}}}},"/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"}}}}}}},"/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)","responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"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"}}}}}},"get":{"tags":["projets"],"summary":"Lister les jetons du projet (sans leur valeur)","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"}},"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é","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 de validation humaine","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/approvals/{id}/approve":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["validations"],"summary":"Accorder une validation humaine","description":"Session de l'interface uniquement : un jeton d'agent ne peut pas se valider lui-même (403).","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"action":{"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/approvals/{id}/reject":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["validations"],"summary":"Refuser une validation humaine","description":"Session de l'interface uniquement : un jeton d'agent ne peut pas se valider lui-même (403).","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"action":{"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/approvals":{"get":{"tags":["validations"],"summary":"Demandes de validation en attente","description":"Un jeton de projet ne voit que celles de son projet ; une session les voit toutes.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"}}}}}}},"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/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}/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"},"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","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"]}}}}}},"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"}}}}}}},"/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, 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"}}}}}},"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/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}/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_…)"},"basicAuth":{"type":"http","scheme":"basic","description":"Jeton while comme mot de passe, utilisateur au choix (clients git)"},"sessionCookie":{"type":"apiKey","in":"cookie","name":"while_session","description":"Session de l'interface (humain)"},"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, domaines, environnements, budget. while le traduit en infrastructure (OpenTofu) chez un fournisseur.","type":"object","required":["apiVersion","project","services"],"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","minItems":1,"maxItems":10,"items":{"$ref":"#/$defs/service"}},"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."},"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])?$"},"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."},"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é."}}},"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"},"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"}}},"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), heure de Paris : « 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."},"timeout_seconds":{"type":"integer","minimum":10,"maximum":3600,"default":600}}}}},"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"}}}}}}