---
title: "API REST de while.sh : chaque opération, droits et limites"
url: https://while.sh/fr/docs/reference/api/
updated: 2026-11-09
---

# L'API

L'API REST de while sert aux CI, aux scripts et aux outils qui ne parlent pas MCP. Cette page en donne les 265 opérations.

```bash titre="Lister vos projets"
curl -H "Authorization: Bearer $WHILE_TOKEN" https://app.while.sh/v1/projects
```

## L'adresse et le format

L'API répond à `https://app.while.sh`, en JSON. Sa description complète, au format OpenAPI 3.1, est à `https://app.while.sh/openapi.json` : un client se génère depuis elle.

## S'authentifier

Chaque appel porte un jeton dans l'en-tête `Authorization: Bearer`. Un jeton de projet sert à une CI ou à un script ; un jeton d'organisation voit les projets de l'organisation que voit sa personne ; la clé d'un compte de service (`whcs_…`) agit avec exactement ses droits. Un jeton de projet ou d'organisation ne dépasse jamais les droits de sa personne. Les rubriques [Jetons](#jetons) et [Droits et validations](#droits-et-validations) disent comment les créer.

Trois exceptions : le dépôt git prend le jeton comme mot de passe (Basic, utilisateur au choix), les SDK Sentry s'authentifient par la clé de leur DSN, et la télémétrie OTLP par la clé dans son adresse. Le serveur MCP, lui, est à `https://app.while.sh/mcp`.

## Les erreurs

Une erreur suit la RFC 9457 (`application/problem+json`) : `type`, `title`, `status`, `detail`. Un refus (403, 409, 428, 429) dit en plus ce qui bloque (`why`), la suite (`what_to_do`) et, quand il existe, l'appel tout prêt (`action`) : l'outil MCP, la requête de l'API ou le lien à ouvrir, et qui doit le faire, l'agent, une personne ou l'équipe while.

## Les validations

Un geste sensible, comme détruire la production ou rendre une appli publique, peut demander l'accord d'une personne. La réponse est alors 428, avec la demande (`validation`) et son lien. Une personne l'accorde dans while ; renvoyez ensuite le même appel avec `approval_id`. Un jeton ne valide jamais une demande.

## Sans jeton

Ce qui se lit sans s'authentifier : les tarifs, le catalogue des briques, les outils MCP, le schéma de while.yaml, les adresses réseau de while, sa version et la description de l'API.

### Lire la version de while

`GET /v1/info`

Rend la version de while et ses adresses publiques. Avec un jeton, la réponse ajoute le moteur et les briques, sans leurs adresses internes.

### Lire le catalogue des briques

`GET /v1/catalog`

Rend le contrat d'une appli et les briques qu'un while.yaml peut déclarer, chacune avec son état : disponible, partiel ou prévu.

### Lire les adresses réseau de while

`GET /v1/network`

Rend l'entrée de while (son nom, pour le CNAME d'un sous-domaine ou l'ALIAS d'un domaine nu, et ses adresses IPv4 et IPv6, pour les A et AAAA d'un domaine nu en dernier recours seulement : elles peuvent changer) et les adresses IPv4 par où sortent les applis (à déclarer chez un tiers qui filtre par adresse). Chacune avec le jour depuis lequel elle vaut, s'il est dit, et un changement annoncé : le jour et les nouvelles adresses. Sans jeton. La réponse est gardée cinq minutes, et un même réseau peut la lire 60 fois par minute.

### Lire les tarifs publics

`GET /v1/pricing`

Rend la grille de prix en vigueur (sa version et sa date), ses offres et leurs prix, et le prix de chaque ressource tel que la page Facture le montre. Sans jeton. La réponse est gardée cinq minutes, et un même réseau peut la lire 60 fois par minute.

### Lister les outils MCP

`GET /v1/mcp/tools`

Rend exactement ce que `tools/list` sert à l'agent d'une organisation sur `/mcp`, ou, avec `serveur=essai`, sur le serveur d'essai `/essai`. Sans jeton. La réponse est gardée cinq minutes, et un même réseau peut la lire 60 fois par minute.

Paramètres : `server`.

### Lire le schéma de while.yaml

`GET /schema/while.v1.json`

Rend le JSON Schema de while.yaml, le fichier qui décrit une appli. Votre agent s'en sert pour vérifier son fichier avant de mettre en ligne.

### Lire la description OpenAPI

`GET /openapi.json`

Rend la description de l'API, au format OpenAPI 3.1.

## Projets

Créer, lire et détruire un projet, lister les applis d'une organisation avec leurs mesures, et changer le propriétaire ou le budget d'un projet.

### Lister les applis d'une organisation, avec leurs mesures

`GET /v1/teams/{slug}/apps`

Les applis que votre identité voit dans cette organisation. Pour chacune, en production : la version en ligne (`version.number`, `version.since`), la dernière construction si rien n'est en ligne (build), les incidents ouverts, à qui ses services sont ouverts (open), ses requêtes des deux dernières heures par tranche de 5 minutes, la plus ancienne d'abord (traffic), le temps sous lequel 95 % des réponses sont parties (p95_ms), ses tâches et leur dernier passage, et la pause de l'organisation (stop) ; ce qu'elle a coûté ce mois-ci, en euros (null sans mesure).

### Lister les projets

`GET /v1/projects`

Rend les projets que votre identité voit.

Paramètres : `organization`.

### Créer un projet

`POST /v1/projects`

Crée un projet dans votre organisation, avec un budget mensuel de 20 € par défaut. Un budget sans plafond ou au-delà de 20 €, et le bac à sable, demandent un admin de l'organisation et la validation d'une autre personne : le projet est alors créé au budget par défaut, et la réponse 202 porte la demande. Un admin le fait sans attendre personne, au journal (son agent, au plancher, le lui fait confirmer), sauf si l'organisation a réglé deux approbateurs.

Corps : `slug` (obligatoire), `name`, `budget_eur`, `account`, `sandbox`, `team`.

### Lire un projet

`GET /v1/projects/{slug}`

Rend le projet s'il vous est visible, sinon 404.

### Détruire un projet

`DELETE /v1/projects/{slug}`

Détruit le projet avec ses environnements, son dépôt git et ses sources, en tâche de fond (202). Il faut le droit de détruire le projet. Un jeton d'agent le demande toujours à une personne (428, avec la demande) ; une production qui a des données se fait valider par un admin, qui la détruit lui-même dans while.

Paramètres : `approval_id`.

### Lire l'historique d'un projet

`GET /v1/projects/{slug}/history`

Rend les versions, les plans et les événements du projet, mêlés, du plus récent au plus ancien.

Paramètres : `limit`.

### Changer le propriétaire d'un projet

`PUT /v1/projects/{slug}/owner`

Réservé aux admins et au propriétaire actuel.

Corps : `email` (obligatoire).

### Changer le budget d'un projet

`PUT /v1/projects/{slug}/budget`

Réservé aux admins. Baisser le budget mensuel s'applique tout de suite ; le relever se fait confirmer ou valider (202, avec le lien).

Corps : `budget_eur` (obligatoire).

## Jetons

Les jetons d'un projet ou d'une organisation, pour une CI ou un script, et l'identifiant git d'un agent branché par OAuth.

### Lister les jetons d'un projet

`GET /v1/projects/{slug}/tokens`

Rend les jetons du projet, sans leur valeur. Réservé à l'administration du projet : un jeton de projet ne les liste pas.

### Créer un jeton de déploiement

`POST /v1/projects/{slug}/tokens`

Crée un jeton pour une CI externe ou un agent. Il faut la permission `access.grant` sur le projet. Le jeton agit pour vous : ses droits ne dépassent jamais les vôtres sur ce projet, et se recalculent à chaque requête.

Corps : `name`, `days`, `scopes`, `environments`.

### Révoquer un jeton

`DELETE /v1/tokens/{id}`

La personne du jeton le révoque toujours, dans sa session ; sinon, il faut pouvoir donner des accès sur sa portée (son projet ou l'organisation). Un refus répond 404, comme un jeton qui n'existe pas.

### Créer un jeton d'organisation

`POST /v1/teams/{slug}/tokens`

Le jeton voit les projets de l'organisation que vous voyez, et eux seuls. Sa valeur n'est montrée qu'une fois.

Corps : `name`, `scopes`, `days`, `environments`.

### Fixer l'expiration d'un jeton

`POST /v1/tokens/{id}/expiry`

30 jours par défaut. L'appel ne peut que rapprocher l'expiration.

Corps : `days`.

### Obtenir un identifiant git pour un agent OAuth

`POST /v1/projects/{slug}/git`

Un agent branché par OAuth ne crée pas de jeton : cet appel lui donne un identifiant git valable une heure, pour ce projet seul, avec les droits que sa personne a consentis. Il sert à `git push` et `git fetch`, sur les branches seulement, sans tag, sans réécrire ni supprimer de branche ; l'API et le MCP le refusent. Il se coupe quand on déconnecte l'agent, ou quand sa personne ne peut plus mettre en ligne, et cinq au plus sont en cours à la fois. Passez-le dans l'en-tête `Authorization: Bearer`, jamais dans l'adresse.

## while.yaml et plans

Le fichier qui décrit l'appli, et le plan qui dit ce qui va changer avant de l'appliquer.

### Lire le while.yaml en cours

`GET /v1/projects/{slug}/intent`

Rend le fichier qui décrit l'appli, tel que while l'a reçu en dernier.

Paramètres : `environment`.

### Déposer un while.yaml

`PUT /v1/projects/{slug}/intent`

Enregistre le fichier qui décrit l'appli, une fois validé par le schéma de while.yaml.

Paramètres : `environment`.

### Calculer un plan

`POST /v1/projects/{slug}/environments/{env}/plan`

Rend ce qui va changer dans l'environnement, sans rien appliquer. Sans intent ni `from_state` : le dernier while.yaml déposé.

Corps : `intent`, `from_state`.

### L'offre de l'organisation

`GET /v1/teams/{slug}/plan`

Réservé à ses admins. Son offre (ce qu'elle comprend, l'usage face à ses limites, ses options), les offres à comparer, les prix et ce qui est compris ; le moyen de paiement quand il est obligatoire (son état, la carte en quatre chiffres) et la cause d'une pause.

### Lire un plan

`GET /v1/plans/{id}`

Rend l'état d'un plan.

### Appliquer un plan

`POST /v1/plans/{id}/apply`

Applique les changements du plan. Une suppression en production, ou d'une base, demande une validation humaine : la réponse est 428, avec le lien de la demande.

Corps : `approval_id`.

### Écarter un plan

`POST /v1/plans/{id}/discard`

Écarte un plan que vous n'appliquerez pas. Il faut pouvoir mettre en ligne sur l'environnement du plan. Un plan déjà appliqué ou écarté rend 409.

### Lire le schéma d'une appli

`GET /v1/projects/{slug}/environments/{env}/schema`

Ce qui compose l'appli et comment c'est relié, tiré de son while.yaml : ses entrées (internet, l'équipe), ses services, ses tâches, ses bases, ses caches, ses stockages, les bases d'autres applis qu'elle lit, ses sources de données et ses sauvegardes (briques), et leurs liens (links : de, vers, genre). Avec ce que while en mesure et que status ne dit pas : les requêtes de chaque service sur deux heures par tranche de 5 minutes (`serie_2h`) et leur temps de réponse, les passages du jour de chaque tâche, les appels des sources sur 24 h, la dernière sauvegarde et son essai de restauration. L'état de chaque brique se lit dans status.

## Versions et mises en ligne

Les sources, les versions, les retours arrière et l'état d'un environnement.

### Mettre en ligne

`POST /v1/projects/{slug}/environments/{env}/deploy`

Met en ligne une archive de sources (`tar.gz`), une image déjà construite, ou, avec un corps JSON vide, le dernier commit des sources du projet chez while, écrites par `POST …/files` ou poussées par git. Si ce commit est déjà en ligne, rien n'est reconstruit (200) ; si une version de ce commit est en cours, elle est rendue telle quelle. Sinon, une nouvelle version démarre (202).

Corps : `image`, `digest`, `intent`.

### Lister les fichiers des sources

`GET /v1/projects/{slug}/files`

Rend les fichiers des sources du projet chez while, celles de son dépôt git : chemins, tailles et dernier commit.

Paramètres : `path`.

### Écrire des fichiers des sources

`POST /v1/projects/{slug}/files`

Écrit les fichiers nommés dans un commit sur la branche du projet ; les autres ne changent pas, et `delete` retire des fichiers ou des dossiers. Du texte UTF-8, 2 Mio et 200 fichiers au plus par appel. Si rien ne change, aucun commit n'est créé (200) ; si un autre commit arrive pendant l'écriture, la réponse est 409. Ensuite, `POST …/environments/{env}/deploy` sans archive met ce commit en ligne.

Corps : `files`, `delete`, `message`.

### Lire un fichier des sources

`GET /v1/projects/{slug}/files/{path}`

Rend le contenu d'un fichier texte, 256 Kio au plus. Un fichier binaire est signalé, sans son contenu.

### Télécharger les sources d'une version

`GET /v1/projects/{slug}/environments/{env}/releases/{number}/sources`

Rend l'archive `tar.gz` que la construction de la version a lue. Elle est gardée 7 jours, et tant que la version est en ligne.

### Lister les versions

`GET /v1/projects/{slug}/environments/{env}/releases`

Rend l'historique des versions de l'environnement.

Paramètres : `limit`.

### Remettre en ligne une version construite

`POST /v1/projects/{slug}/environments/{env}/releases/{number}/redeploy`

Crée une nouvelle version avec la même image et les variables d'aujourd'hui, sans rien reconstruire. Quand la politique de l'organisation demande une validation, la réponse est 428 avec le lien ; une mise en production durcie s'arrête avant d'être appliquée et reprend d'elle-même une fois validée.

### Lire une version

`GET /v1/releases/{id}`

Rend la version, avec sa construction et son plan.

### Retirer une version de ses liaisons

`POST /v1/projects/{slug}/environments/{env}/releases/{number}/remove-for-binding`

Pour la fédération OIDC, avec le corps `{ reason }`. Une personne approbatrice du projet, par sa session. La version ne signera plus jamais, même revenue par un retour arrière.

Corps : `reason` (obligatoire).

### Revenir à une version précédente

`POST /v1/projects/{slug}/environments/{env}/rollback`

Remet en ligne une version précédente. Quand une validation est attendue, la réponse est 428 avec le lien de la demande, comme pour `POST …/deploy`, et une CI s'arrête là ; renvoyez l'appel avec `approval_id` une fois la demande accordée. Par un lien d'envoi d'agent (`POST /v1/upload/{token}`), la réponse est 202 avec la suite ; par l'outil MCP, « validation humaine requise ».

Corps : `to`, `approval_id`.

### Redémarrer un service

`POST /v1/projects/{slug}/environments/{env}/services/{service}/restart`

Redémarre un service de la version en ligne, sans créer de version, et coupe les requêtes en cours. Les instances d'un service web sont recréées ; un worker redémarre en roulement. 409 si rien ne tourne.

### Lire l'état d'un environnement

`GET /v1/projects/{slug}/environments/{env}/status`

Rend la version en ligne, son adresse, sa santé et le trafic des 15 dernières minutes (p50, p95, p99). `services[].titre` et `taches[].titre` donnent le titre du while.yaml, `null` sans titre. Pour chaque brique, la réponse donne ce qu'elle réserve et ce qu'elle prend, mesuré (`null` tant que ce n'est pas mesuré).

Paramètres : `probe`.

### Lire ce qui a été appliqué

`GET /v1/projects/{slug}/environments/{env}/state`

Rend les 20 dernières versions d'état de l'environnement : ce qui a réellement été appliqué.

### Créer une prévisualisation

`POST /v1/projects/{slug}/previews`

Crée un environnement de prévisualisation. Il faut le droit de mettre en ligne.

Corps : `name`, `branch`, `from`, `copy_database`, `ttl_hours`.

### Détruire un environnement

`DELETE /v1/projects/{slug}/environments/{env}`

Il faut le droit de détruire le projet (un admin ; pas une développeuse). Détruire la production demande une validation humaine : la réponse est 428 avec le lien, et vous renvoyez ensuite l'appel avec `approval_id`.

Corps : `approval_id`.

### Lire une construction

`GET /v1/builds/{id}`

Rend l'état d'une construction : ses temps, son cache, chaud ou froid, et ses tests.

### Lire le journal d'une construction

`GET /v1/builds/{id}/logs`

Rend le journal en JSON, ou en flux SSE avec l'en-tête `Accept: text/event-stream`.

Paramètres : `stream`, `since`.

### Envoyer des sources par un lien signé

`POST /v1/upload/{token}`

Reçoit l'archive des sources d'une mise en ligne. Le lien vient de l'outil MCP `upload_source` et vaut 15 minutes.

Sans jeton.

### Télécharger des sources par un lien signé

`GET /v1/sources/{token}`

Rend l'archive `tar.gz` des sources d'une version. Le lien vient de l'outil MCP `read_source` et vaut 10 minutes.

Sans jeton.

## Git et forges

Le dépôt git de chaque projet, et la liaison avec GitHub ou GitLab.

### Lire l'aide du dépôt git

`GET /git/{repo}`

Rend les commandes pour ajouter le dépôt comme remote et y pousser.

Sans jeton.

### Annoncer les références git

`GET /git/{repo}/info/refs`

Le protocole git intelligent. `git-receive-pack` demande le droit de mettre en ligne. Un projet déjà à sa limite de stockage (sources et git) reçoit 409, avec le refus du quota en texte, que git affiche.

Paramètres : `service` (obligatoire).

### Récupérer un dépôt git

`POST /git/{repo}/git-upload-pack`

`git fetch` et `git clone` (stateless-rpc). Corps brut ou en gzip (`Content-Encoding`) ; 413 au-delà de la limite d'une archive de sources, 64 Mo, décompressé compris.

### Pousser sur un dépôt git

`POST /git/{repo}/git-receive-pack`

`git push` : il faut le droit de mettre en ligne. Un push sur main met en production ; sur une autre branche, il ouvre une prévisualisation. Corps brut ou en gzip (`Content-Encoding`) ; 413 au-delà de la limite d'une archive de sources, 64 Mo, décompressé compris.

### Lire la liaison GitHub ou GitLab

`GET /v1/projects/{slug}/forge`

Rend le dépôt lié au projet, sans son jeton.

### Lier un dépôt GitHub ou GitLab

`POST /v1/projects/{slug}/forge`

Un push sur la branche par défaut met en production ; une demande de fusion ouvre une prévisualisation, commentée sur la demande ; chaque commit reçoit son statut. Le jeton de la forge est vérifié chez elle, rangé au coffre, et sert à créer le webhook.

Corps : `forge` (obligatoire), `repository` (obligatoire), `token` (obligatoire), `base_url`.

### Délier le dépôt

`DELETE /v1/projects/{slug}/forge`

Retire la liaison, et le webhook chez la forge.

## Domaines et liens d'accès

Les domaines d'un environnement, l'adresse principale d'un projet, et les liens qui montrent une appli à quelqu'un sans compte.

### Lister les domaines

`GET /v1/projects/{slug}/environments/{env}/domains`

Rend les domaines de l'environnement : statut, enregistrements DNS à créer, constat de while et certificat.

### Ajouter un domaine

`POST /v1/projects/{slug}/environments/{env}/domains`

Ajoute un domaine à un service (le seul qui a une adresse quand service est absent) : le while.yaml change et une nouvelle version part (statut inchangé, sans version, quand le service a déjà ce domaine). Le domaine attend ensuite son enregistrement DNS (GET domains le dit).

Corps : `domain` (obligatoire), `service`.

### Vérifier un domaine

`POST /v1/projects/{slug}/environments/{env}/domains/{host}/verify`

Vérifie tout de suite un domaine personnalisé : sa preuve TXT et son pointage.

### Donner l'adresse du projet à un autre service

`PUT /v1/projects/{slug}/principal-address`

Sous un domaine des applis sans région : l'adresse du projet seul passe à ce service web public, dans chaque environnement en ligne (la version en ligne est remise telle quelle). Une personne seulement, par sa session : le propriétaire de l'appli ou un admin ; un jeton reçoit 403. Sans confirmer, 409 et l'aperçu : les adresses qui partent et celles qui arrivent.

Corps : `service` (obligatoire), `confirm`.

### Lister les liens d'accès

`GET /v1/projects/{slug}/environments/{env}/links`

Rend les liens d'accès de l'environnement : nom, échéance, mot de passe ou non, ouvertures. Le lien lui-même n'est jamais rendu.

### Créer un lien d'accès

`POST /v1/projects/{slug}/environments/{env}/links`

Un lien d'accès montre l'appli à quelqu'un qui n'a pas de compte while. Corps : `{ name, days, password }`, avec `days` de 1 à 90 et un `password` facultatif, de 8 caractères au moins. Le lien n'est rendu qu'une fois : `url`, et `urls`, une par adresse de l'environnement. En production, il naît inactif et s'active quand une autre personne le valide (`approval` : la demande). Il faut être une personne, avec `project.configure` : un jeton d'agent reçoit 403, et l'outil MCP `request_access_link` rend la page de while à ouvrir. Un admin le fait sans attendre personne, au journal (son agent, au plancher, le lui fait confirmer), sauf si l'organisation a réglé deux approbateurs.

Corps : `name` (obligatoire), `days` (obligatoire), `password`.

### Révoquer un lien d'accès

`DELETE /v1/projects/{slug}/environments/{env}/links/{id}`

Le lien cesse de marcher tout de suite. Il faut être une personne, avec `project.configure`.

### Créer un lien présigné vers un objet

`POST /v1/projects/{slug}/environments/{env}/buckets/{name}/links`

Rend un lien présigné vers un objet, valable 15 minutes par défaut et 24 heures au plus. Un lien d'envoi sert à un PUT et demande `data.write`.

Corps : `key` (obligatoire), `upload`, `minutes`, `upstream`.

## Variables et secrets

Les variables d'un environnement, et le coffre, compatible avec Vault.

### Lister les variables

`GET /v1/projects/{slug}/environments/{env}/variables`

Les noms posés dans l'environnement (secrets, variables en clair, secrets en fichier et de construction), jamais leurs valeurs.

### Poser des variables

`PUT /v1/projects/{slug}/environments/{env}/variables`

Pose plusieurs variables d'un coup, sans toucher aux autres : au coffre par défaut, en clair avec `secret: false`. Elles arrivent dans l'appli à la prochaine mise en ligne.

Corps : `values` (obligatoire), `secret`, `file`, `build`.

### Supprimer une variable

`DELETE /v1/projects/{slug}/environments/{env}/variables/{name}`

Supprime une variable ou un secret de l'environnement, et le note au journal ; sur un projet lié à une connexion fédérée, la production passe par ses approbateurs (428). Avec `?build=1`, un secret de construction. Le changement vaut à la prochaine mise en ligne.

Paramètres : `build`.

### Vérifier le coffre

`GET /vault/v1/sys/health`

Compatible avec l'API de Vault.

Sans jeton.

### Lire un secret

`GET /vault/v1/secret/data/{path}`

Compatible Vault KV v2.

Paramètres : `project`, `environment`.

### Écrire un secret

`POST /vault/v1/secret/data/{path}`

Compatible Vault KV v2. Il faut le droit de mettre en ligne.

Paramètres : `project`, `environment`.

Corps : `data` (obligatoire).

### Supprimer un secret

`DELETE /vault/v1/secret/data/{path}`

Compatible Vault KV v2. Il faut le droit de mettre en ligne.

Paramètres : `project`, `environment`.

### Lister les secrets sous un préfixe

`GET /vault/v1/secret/metadata/{path}`

Compatible Vault KV v2.

Paramètres : `project`, `environment`.

## Bases et buckets

Lire une base en SQL, ses sauvegardes et ses requêtes, et les fichiers des buckets.

### Lister les sauvegardes

`GET /v1/projects/{slug}/environments/{env}/backups`

Rend les sauvegardes des bases de l'environnement, avec le résultat de leur test de restauration. status : running, succeeded, failed, ou expired (ses objets sont effacés depuis `expired_at` : elle ne se restaure plus).

### Restaurer une sauvegarde

`POST /v1/projects/{slug}/environments/{env}/restore`

Restaure une sauvegarde d'une base, en une seule transaction, après une sauvegarde de sûreté. Demande une validation humaine.

Corps : `database`, `backup_id`, `instant`, `approval_id`.

### Rétablir une connexion coupée

`POST /v1/teams/{slug}/connections/{connection}/restore`

Il faut le même droit que pour la couper. La source reprend telle qu'elle était : phase, règles, droits. Sans effet sur une source ouverte. Pas d'outil MCP.

### Lire une base en SQL

`POST /v1/projects/{slug}/environments/{env}/query`

Exécute une seule requête SQL de lecture (SELECT, WITH, VALUES, TABLE), plafonnée, notée au journal sans ses valeurs. Elle tourne au nom de la personne, sous le rôle PostgreSQL de son identité (`while_id_…`), qui n'a que ses droits sur les données : `data.read` sur la base, un schéma, une table ou des colonnes. Transaction READ ONLY ; 10 secondes 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, une écriture, du DDL, COPY, DO, les fonctions qui agissent, et ce qui fabrique une grosse valeur en mémoire (repeat, lpad, rpad ou format au-delà de 10 000 caractères, `array_fill`, `generate_series` au-delà de 100 000 valeurs, une agrégation sur une série plus grande). Faute de droit, PostgreSQL refuse : 403, avec la table, les colonnes lisibles et la permission (`missing_permission`, `object`, `readable_columns`). Une base qui n'a pas encore le rôle `while_acces`, avant sa prochaine mise en ligne, rend 409 ; un autre refus de la base, 422. Une base qui déclare `replica: true` est encore lue sur la base principale, en lecture seule, et la réponse le dit.

Corps : `sql` (obligatoire), `database`, `limit`, `max_bytes`, `timeout_seconds`.

### Exporter une requête en CSV

`POST /v1/projects/{slug}/environments/{env}/export.csv`

Exporte le résultat d'une seule requête SQL de lecture, avec le même rôle, les mêmes refus et le même journal que `POST …/query`. 10 000 lignes et 1 000 000 octets par défaut (50 000 et 4 000 000 au plus), 10 secondes (30 au plus). Un en-tête puis les lignes, en CRLF ; NULL est vide, une chaîne vide s'écrit `""`. Une cellule qu'un tableur lirait comme une formule (`=`, `+`, `-` ou `@` en tête, hors nombres) est précédée d'une apostrophe. Les en-têtes `x-while-lignes` et `x-while-tronque` (`non`, `rows` ou `bytes`) disent ce qui a été rendu. 429 quand l'organisation a déjà 3 lectures en cours.

Corps : `sql` (obligatoire), `database`, `limit`, `max_bytes`, `timeout_seconds`.

### Lire un bucket

`GET /v1/projects/{slug}/environments/{env}/buckets/{name}/objects`

Lit un bucket par while : la liste de ses objets, 200 par page, ou, avec `?cle=`, le début d'un objet texte (64 Kio). Il faut `data.read`.

Paramètres : `key`, `prefix`, `next`.

### Renouveler la clé S3

`POST /v1/projects/{slug}/environments/{env}/buckets/rotate-key`

Crée une nouvelle clé S3 pour l'environnement. Elle part à la prochaine mise en ligne, et l'ancienne vaut jusque-là ; avec `immediate`, l'ancienne est supprimée tout de suite.

Corps : `immediate`.

### Lire les requêtes SQL d'une base

`GET /v1/projects/{slug}/environments/{env}/databases/{database}/queries`

Rend les requêtes que l'appli envoie à sa base, d'après `pg_stat_statements` : les plus fréquentes et les plus lentes. L'outil MCP `observe_app` (vue `database`) rend la même chose.

Paramètres : `limit`, `window_minutes`.

## Tâches planifiées

Les tâches de chaque environnement et leurs passages.

### Lister les tâches planifiées

`GET /v1/projects/{slug}/environments/{env}/tasks`

Pour chaque tâche : son titre (celui du while.yaml, `null` sans titre), son planning, son dernier passage (état, durée, erreur, quoi faire), le passage en cours, le prochain et les 14 derniers.

### Lister les passages d'une tâche

`GET /v1/projects/{slug}/environments/{env}/tasks/{task}/runs`

Rend l'historique des passages : déclenchement, auteur, arguments, début, fin, état, code de sortie, extrait du journal et Job.

Paramètres : `limit`.

### Lancer une tâche maintenant

`POST /v1/projects/{slug}/environments/{env}/tasks/{task}/runs`

Lance un passage manuel, ou le rattrapage d'une période (`WHILE_CATCH_UP_START` et `WHILE_CATCH_UP_END`). Refusé si un passage tourne déjà.

Corps : `args`, `env`, `catch_up`.

## Observer

Les journaux, les métriques, les traces, les erreurs et les événements de l'appli, et ce que les agents y ont fait.

### Lire les traces

`GET /v1/projects/{slug}/environments/{env}/traces`

Rend les traces OpenTelemetry les plus récentes de l'appli, ou une trace par son `trace_id`. Elles sont gardées 30 jours. L'outil MCP `observe_app` (vue `traces`) rend la même chose.

Paramètres : `trace_id`, `service`, `operation`, `since_minutes`, `min_duration`, `errors`, `limit`.

### Lire les menaces d'un environnement

`GET /v1/projects/{slug}/environments/{env}/security`

Rend ce qui menace l'environnement : les failles de ses dépendances (UpdateFOMO), son exposition (Probr), les robots qui l'appellent (Polygraphe) et les constats de sa construction (Gitleaks, Opengrep). L'outil MCP `get_app_status` en rend les failles et les robots (`include: ["vulnerabilities"]`).

Paramètres : `since`.

### Télécharger la nomenclature de la version en ligne

`GET /v1/projects/{slug}/environments/{env}/sbom`

Rend la nomenclature CycloneDX de la version en ligne de l'environnement, tirée de ses fichiers de verrouillage, en fichier à télécharger ; 404 quand cette version n'en a pas.

### Lire les journaux de l'appli

`GET /v1/projects/{slug}/logs`

Interroge les journaux de l'appli en LogsQL.

Paramètres : `environment`, `query`, `limit`, `minutes`.

### Lire les métriques

`GET /v1/projects/{slug}/metrics`

Interroge les métriques de l'appli en PromQL, par l'API HTTP de Prometheus.

Paramètres : `query`, `environment`, `start`, `end`, `step`, `range`.

### Lire les événements

`GET /v1/projects/{slug}/events`

Rend les événements du projet au format CloudEvents. Avec `stream=1`, ou l'en-tête `Accept: text/event-stream`, la réponse est un flux SSE. Le flux revérifie votre identité (le jeton ou la session, avec `project.view`) à chaque événement et toutes les 60 secondes, et se ferme dès qu'elle ne passe plus : membre retiré, jeton révoqué, session fermée.

Paramètres : `limit`, `stream`.

### Lire les erreurs de l'appli

`GET /v1/projects/{slug}/errors`

Rend les erreurs de l'appli regroupées par empreinte, 50 groupes au plus : le nombre d'occurrences, la première et la dernière fois, la version déclarée.

Paramètres : `environment`, `since`.

### Lire la consommation en CloudEvents

`GET /v1/usage/events`

Rend la consommation des projets que vous voyez, en CloudEvents au format OpenMeter.

### Lire ce que consomme chaque service

`GET /v1/projects/{slug}/environments/{env}/resources`

Le processeur (vCPU), la mémoire (octets) et le nombre de copies de chaque service sur les dernières heures (1, 6, 24 ou 168), une centaine de points par courbe ([instant en ms, valeur]). Un service endormi n'a pas de point. mesure : false quand les mesures n'ont pas répondu. Ce que chaque service réserve se lit dans status (sizes).

Paramètres : `hours`.

### Lire le trafic dans le temps

`GET /v1/projects/{slug}/environments/{env}/traffic`

Par tranche (1 min sur 1 heure, 5 min sur 6 heures, 15 min sur 24 heures, 2 heures sur 7 jours) : les requêtes, celles en erreur, la médiane et le 95e centile de la réponse en ms, la version qui a servi le plus. Avec les mises en ligne et les incidents de la plage, et le seuil d'erreurs de l'alerte par défaut en % (null en sourdine).

Paramètres : `hours`.

### Lire la ligne de temps

`GET /v1/projects/{slug}/timeline`

Rend sur une seule ligne les événements de while, les erreurs nouvelles et les actions des agents par MCP.

Paramètres : `environment`, `since`, `until`, `families`, `limit`.

### Lire l'impact d'une version

`GET /v1/projects/{slug}/environments/{env}/impact`

Compare le trafic, les erreurs et la latence avant et après la version, et rend un verdict et des propositions.

Paramètres : `version`, `window_minutes`.

### Poser une question sur la production

`POST /v1/projects/{slug}/environments/{env}/ask`

Une question comme « pourquoi c'est lent depuis 14 h ? ». Rend les faits, les suspects et des propositions.

Corps : `question` (obligatoire).

### Lister les actions des agents par MCP

`GET /v1/projects/{slug}/mcp-actions`

Rend ce que les agents ont fait par MCP : l'outil, ses paramètres (valeurs sensibles masquées) et le résultat.

Paramètres : `since`, `limit`.

### Lire les points d'entrée de la télémétrie

`GET /v1/projects/{slug}/telemetry`

Rend le DSN Sentry et les points d'entrée OpenTelemetry du projet. La clé du DSN se crée à la première lecture.

### Lire une erreur et sa pile

`GET /v1/projects/{slug}/errors/{fingerprint}`

Une erreur regroupée par son empreinte : ses occurrences, la première et la dernière fois, et la pile de son dernier événement (fonction, fichier, ligne), avec l'adresse demandée.

### Lister les webhooks

`GET /v1/projects/{slug}/webhooks`

Rend les webhooks du projet, sans leur secret.

### Abonner un webhook

`POST /v1/projects/{slug}/webhooks`

Abonne une adresse aux événements du projet : il faut le droit de configurer le projet, et une URL https:// qui se résout vers une adresse publique. Les envois sont signés, au format CloudEvents 1.0, selon Standard Webhooks.

Corps : `url` (obligatoire), `types`, `secret`.

### Retirer un webhook

`DELETE /v1/projects/{slug}/webhooks/{id}`

Il faut la permission `project.configure`. Les livraisons en attente partent avec lui.

### Envoyer des erreurs au format Sentry

`POST /api/{projectRef}/envelope/`

Reçoit les enveloppes des SDK Sentry, tels quels. Authentifié par la clé publique du DSN (`X-Sentry-Auth`, `Authorization` ou ?`sentry_key`), pas par un jeton. 50 événements et 512 Ko au plus par enveloppe.

### Envoyer de la télémétrie OTLP

`POST /otlp/{key}/{environment}/v1/{signal}`

Relaie les journaux et les métriques OTLP/HTTP de l'appli. Le projet et l'environnement sont imposés aux données relayées ; 502 si le collecteur refuse.

Sans jeton.

## Alertes et incidents

Les règles, les incidents, les canaux et la page de statut.

### Lire les alertes d'un projet

`GET /v1/projects/{slug}/alerts`

Rend les alertes par défaut (état, sourdine), les règles écrites (condition en clair, dernier état), les incidents ouverts et ceux des 30 derniers jours, les canaux, et l'origine de la mesure de disponibilité.

### Créer ou modifier une règle d'alerte

`POST /v1/projects/{slug}/alerts/rules`

Par son nom : une source, une condition structurée (ni PromQL ni LogsQL), une fenêtre, une gravité, quoi faire et les canaux. Noté au journal.

Corps : `name` (obligatoire), `source` (obligatoire), `condition`, `window_minutes`, `severity`, `message` (obligatoire), `channels`, `environment`, `active`.

### Supprimer une règle d'alerte

`DELETE /v1/projects/{slug}/alerts/rules/{rule}`

Par son identifiant ou son nom. Ses incidents ouverts sont clos.

### Mettre une alerte par défaut en sourdine

`PUT /v1/projects/{slug}/alerts/defaults/{default}`

Met en sourdine, ou réactive, une alerte par défaut pour ce projet, parmi : `task`, `unreachable`, `restarts`, `errors`, `latency`, `budget`, `vulnerabilities`, `disk`.

Corps : `mute` (obligatoire).

### Lister les incidents

`GET /v1/projects/{slug}/incidents`

Rend les incidents ouverts ou acquittés, et ceux clos dans les 30 derniers jours : quoi, depuis quand, quoi faire, et les envois.

### Acquitter un incident

`POST /v1/projects/{slug}/incidents/{id}/acknowledge`

Dit que quelqu'un s'en occupe. L'incident se clôt de lui-même au retour à la normale. Noté au journal.

Corps : `reason`.

### Lire la page de statut

`GET /v1/projects/{slug}/status-page`

Rend les données de la page de statut (`/statut/<organisation>/<projet>`) : ouverte au public ou non, état, disponibilité sur 30 jours, temps de réponse et incidents récents.

### Ouvrir ou fermer la page de statut

`PUT /v1/projects/{slug}/status-page`

`public: true` l'ouvre, `false` la ferme ; elle est fermée par défaut. Ouverte, tout le monde y lit le nom affiché, l'état et les incidents en cours, sans détail technique. Noté au journal.

Corps : `public` (obligatoire).

### Lister les canaux d'alerte

`GET /v1/teams/{slug}/alert-channels`

Rend les canaux de l'organisation, sans leurs adresses, qui sont des secrets, et le réveil des agents, toujours actif.

### Ajouter un canal d'alerte

`POST /v1/teams/{slug}/alert-channels`

Réservé aux admins. Un courriel (l'adresse d'un membre de l'organisation, sinon 403), Slack (l'adresse du webhook entrant) ou un webhook générique (CloudEvents signés ; la clé de signature n'est rendue qu'une fois). 10 canaux par organisation ; au-delà, 409 quota-exceeded.

Corps : `type` (obligatoire), `name`, `address`, `url`, `min_severity`.

### Retirer un canal d'alerte

`DELETE /v1/teams/{slug}/alert-channels/{channel}`

Réservé aux admins. Son adresse est détruite dans le coffre.

### Tester un canal d'alerte

`POST /v1/teams/{slug}/alert-channels/{channel}/test`

Envoie un message d'essai par ce canal. Réservé aux admins ; un par minute et par canal, sinon 429 avec `Retry-After`.

### Lire la page de statut d'une appli

`GET /v1/status/{organization}/{project}`

La page de statut publique d'une appli, sans compte quand son projet l'a ouverte : le nom affiché, l'état, les incidents en cours, sans détail technique. Qui voit le projet la lit entière (chaque adresse, ses 30 jours, la page ouverte ou non). Sinon 404, la même réponse qu'une appli inconnue.

Sans jeton.

## Applis internes

Qui entre dans une appli réservée à l'organisation, l'accès de son agent, et la clé qui signe son identité.

### Lister qui entre dans les applis internes

`GET /v1/projects/{slug}/sign-ins`

Rend, pour chaque appli interne du projet, la règle qui dit qui peut entrer, et qui est entré, avec la date. Il faut pouvoir lire le journal du projet.

### Lister les accès des agents aux applis internes

`GET /v1/projects/{slug}/app-access`

Rend les identifiants en cours que vos agents ont ouverts pour entrer dans une appli du projet. L'identifiant lui-même n'est jamais rendu, seulement son id, pour le couper.

### Ouvrir une appli interne à l'agent d'une personne

`POST /v1/projects/{slug}/app-access`

Donne à l'agent d'une personne un identifiant d'une heure pour entrer dans une appli interne (`access: internal`). L'agent passe par son branchement OAuth ou par un jeton de sa personne ; un compte de service ou une session est refusé. L'appli le reçoit comme la personne : mêmes droits, sa règle relue à chaque requête, et `while_agent` dans l'en-tête `X-While-Identity`. L'identifiant vaut pour cette adresse seule, cinq au plus en cours par personne, et passe dans l'en-tête Cookie (`__Host-while-agent=…`), jamais dans l'adresse. Avec `read_only`, seuls GET et HEAD passent. Il se coupe quand on déconnecte l'agent, ou par DELETE. Quand le rôle de la personne demande une validation pour « Ouvrir une appli interne par son agent », la réponse est 428 avec la demande ; renvoyez ensuite l'appel avec `approval_id`.

Corps : `service`, `environment`, `address`, `read_only`, `approval_id`.

### Couper l'accès d'un agent à une appli

`DELETE /v1/projects/{slug}/app-access/{id}`

Coupe l'identifiant tout de suite. Sa personne ou un admin de l'organisation peut le faire.

### Lire la clé des jetons d'identité

`GET /.well-known/jwks.json`

Rend la clé publique (JWKS, ES256) qui signe le jeton d'identité `X-While-Identity` transmis aux applis internes.

Sans jeton.

## Connexions

Les API externes que les applis appellent par while : leurs règles, leurs secrets, leurs plafonds et leur journal.

### Lister les connexions

`GET /v1/teams/{slug}/connections`

Rend les connexions de l'organisation : phase (déclaration à valider, déclarée, apprentissage, verrouillée), authentification (sans secret), appels et refus des dernières 24 heures, et la variable `<NOM>_URL`.

### Déclarer une connexion

`POST /v1/teams/{slug}/connections`

La déclaration est validée par un approbateur, ou passe à votre nom quand les règles de l'organisation vous laissent autonome (201). Elle se fait sans secret, saisi ensuite par `PUT …/secret`. La connexion démarre en apprentissage pendant 7 jours : les écritures y sont bloquées, sauf les lectures en POST (`post_read`).

Corps : `name` (obligatoire), `label`, `domain`, `model`, `secondary_domains`, `authentication`, `post_read`, `rules`, `learning_days`, `rate_per_minute`, `owner`, `project`, `apps`, `direction`.

### Lire une connexion

`GET /v1/teams/{slug}/connections/{connection}`

Rend ses règles (sa surface : méthode, motif, validation à chaque appel) ; qui peut l'appeler (attributions `connection.read` et `connection.write`, en phrases) ; qui l'a appelée cette semaine, et par quelle attribution ; les refus de la semaine ; les changements de règles et d'accès ; les appels observés et la proposition de verrouillage ; les derniers appels (métadonnées) et les appels retenus ; les approbateurs, et comment l'appeler.

### Saisir le secret d'une connexion

`PUT /v1/teams/{slug}/connections/{connection}/secret`

Qui peut configurer la connexion (un admin, ou une attribution qui le donne) saisit ou change le secret. Il est rangé au coffre, jamais rendu ni noté au journal ; une connexion déclarée entre alors en apprentissage. Les champs suivent la recette : `value` ; `user` et `password` (`basic`) ; `client_id` et `client_secret` (`oauth2`) ; `identifier` et `password` (`session`).

### Lire la proposition de verrouillage

`GET /v1/teams/{slug}/connections/{connection}/lock`

Rend, sans rien demander, les règles que while calcule depuis les appels observés (identifiants généralisés en *) et le diff. Qui peut appeler reste l'affaire des attributions.

### Verrouiller une connexion sur ce qui a servi

`POST /v1/teams/{slug}/connections/{connection}/lock`

while calcule les règles depuis les appels observés et crée la demande de validation, avec le diff ; un admin n'attend personne : verrouillée tout de suite (200). Une fois validée, tout appel hors des règles reçoit 403 (règle manquante), et l'appelant est prévenu.

### Couper une connexion

`POST /v1/teams/{slug}/connections/{connection}/revoke`

Le geste d'urgence d'une personne qui en répond (`connection.configure`), sans validation. Le relais refuse ensuite tout appel vers la source (code « coupee »), avant même les droits. Sans effet sur une source déjà coupée. Pas d'outil MCP.

### Lister les plafonds d'appels

`GET /v1/teams/{slug}/call-limits`

Rend les plafonds d'appels par appelant : ceux d'une source (`?connexion=`) et ceux de toute l'organisation, qui valent aussi pour elle ; sans connexion, tous. Pour chacun : l'appelant, la fenêtre, le nombre, les appels de la fenêtre en cours et quand elle repart. Il faut `org.view`.

Paramètres : `connection`.

### Poser un plafond d'appels

`POST /v1/teams/{slug}/call-limits`

Pose un plafond, ou change son nombre : un appelant, une fenêtre (minute, heure, jour, mois) et un nombre d'appels, sur une source (connexion) ou sur toute l'organisation. L'appelant est `appli:<projet>/<service>`, `task:<projet>/<tâche>`, `personne:<e-mail>`, `agent:<identifiant>`, `compte_service:<nom>` ou `groupe:<nom>`, qui compte ses membres et leurs agents ensemble. Au-delà, le relais refuse l'appel (429, code « plafond », `Retry-After`) ; une alerte part à 80 %, une autre au premier refus, une fois par fenêtre. Il faut `connection.configure`, et l'offre Entreprise pour une organisation qui a une offre.

Corps : `connection`, `caller` (obligatoire), `window` (obligatoire), `calls` (obligatoire).

### Retirer un plafond d'appels

`DELETE /v1/teams/{slug}/call-limits/{limit}`

Il faut le même droit que pour le poser (`connection.configure`), quelle que soit l'offre. Noté au journal.

### Lister les liaisons d'une connexion fédérée

`GET /v1/teams/{slug}/connections/{connection}/bindings`

Pour la fédération OIDC : chaque liaison, avec son `sub` (`b:<uuid>`), son état (`demandee`, `validee`, `suspendue` ou `retiree`), ce qu'elle fige (audience, hôte d'échange, hôtes d'API) et ses validations.

### Demander une liaison à une connexion fédérée

`POST /v1/teams/{slug}/connections/{connection}/bindings`

Lie une appli de production, un service ou `task:<nom>`, à une connexion fédérée, en figeant ses paramètres et l'empreinte de ses règles. La liaison demande une validation stricte des approbateurs de la connexion, jamais par la plateforme ni en force, et, quand la case est cochée, celle de chaque version par les approbateurs du projet, sur une mise en production. 409 si la fédération n'est pas en service, si la connexion n'est pas fédérée et verrouillée, si l'appelant a déjà une liaison vivante, ou si la version en ligne a une image sans digest. Une liaison tombe d'elle-même (version de production sans son appelant, environnement détruit, projet supprimé, connexion refusée ou redéclarée, retrait) et ne revient jamais.

Corps : `project` (obligatoire), `caller` (obligatoire), `approve_production_deploys`.

### Reprendre une liaison suspendue

`POST /v1/teams/{slug}/connections/{connection}/bindings/{binding}/resume`

Un retour arrière automatique suspend la liaison. Cet appel crée une demande stricte aux approbateurs du projet ; validée, la liaison reprend si la version en ligne est validée pour elle et n'en a pas été retirée.

### Faire valider chaque mise en production d'une liaison

`POST /v1/teams/{slug}/connections/{connection}/bindings/{binding}/case`

La case « Faire valider chaque mise en production », avec le corps `{ approve: boolean, reason }`. La cocher prend effet tout de suite (200) ; la décocher est une demande stricte aux approbateurs du projet (202).

Corps : `approve` (obligatoire), `reason` (obligatoire).

### Retirer une liaison

`DELETE /v1/teams/{slug}/connections/{connection}/bindings/{binding}`

Pour de bon, avec le corps `{ reason }`. Il faut pouvoir configurer le projet ou la connexion. Noté au journal de l'organisation.

Corps : `reason` (obligatoire).

### Changer les lectures en POST d'une connexion

`PUT /v1/teams/{slug}/connections/{connection}/post-read`

Envoie la liste voulue des chemins où un POST est une lecture. Un chemin retiré vaut tout de suite : un POST y redevient une écriture, qui demande `connection.write` et une règle. Un chemin ajouté se fait valider, comme une règle ; un admin autonome passe sans attendre.

Corps : `post_read`, `reason`.

### Demander une règle de plus

`POST /v1/teams/{slug}/connections/{connection}/rules`

Une règle donne une méthode, un motif (* pour un segment, ** en dernier pour le reste) et `approve_each_call` ; elle passe par une validation, sauf pour un admin, qui n'attend personne : appliquée tout de suite (200). Qui peut appeler se donne par une attribution (appel-connexion, appel-connexion-ecriture), pas par la règle.

Corps : `method` (obligatoire), `pattern` (obligatoire), `approve_each_call`, `read`, `reason`.

### Lire le journal des appels d'une connexion

`GET /v1/teams/{slug}/connections/{connection}/calls`

Rend chaque appel et chaque refus : quand, qui et par où, méthode, chemin normalisé, statut, durée, taille du corps envoyé en octets, raison du refus, et l'attribution qui l'a permis (rôle, donnée par qui, quand) ; jamais le contenu. Rend aussi les appels retenus. 7 jours par défaut ; au-delà de 30 jours, il faut `log.read` sur la connexion. Gardé selon la conservation de l'organisation.

Paramètres : `since`, `until`, `refusal`, `limit`.

### Lire un appel retenu

`GET /v1/teams/{slug}/connections/{connection}/calls/{call}`

Un appel d'une règle « valider chaque appel » : en attente, exécuté (statut et réponse, bornée), refusé ou expiré. Refusé par le relais à son exécution, il porte le refus de while : `refusal`, `refused_by`, `message`, `what_to_do`, `detail`.

### Appeler une connexion depuis un poste

`GET /sorties/{organization}/{connection}/{path}`

Depuis un poste de développement ou un script, avec votre 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 hébergées par while, elles, appellent `<NOM>_URL`.

### Appeler une connexion depuis un poste (POST)

`POST /sorties/{organization}/{connection}/{path}`

Mêmes règles qu'en GET. Les écritures sont bloquées pendant l'apprentissage, et une règle « valider chaque appel » rend 202.

### Appeler une connexion depuis un poste (PUT)

`PUT /sorties/{organization}/{connection}/{path}`

Mêmes règles qu'en GET. Les écritures sont bloquées pendant l'apprentissage, et une règle « valider chaque appel » rend 202.

### Appeler une connexion depuis un poste (PATCH)

`PATCH /sorties/{organization}/{connection}/{path}`

Mêmes règles qu'en GET. Les écritures sont bloquées pendant l'apprentissage, et une règle « valider chaque appel » rend 202.

### Appeler une connexion depuis un poste (DELETE)

`DELETE /sorties/{organization}/{connection}/{path}`

Mêmes règles qu'en GET. Les écritures sont bloquées pendant l'apprentissage, et une règle « valider chaque appel » rend 202.

### Découvrir l'émetteur OIDC d'une organisation

`GET /oidc/o/{organization}/.well-known/openid-configuration`

Pour la fédération OIDC : `iss` est l'adresse de while suivie de `/oidc/o/<uuid>`, avec son JWKS, en RS256. Servi dès qu'une connexion fédérée est déclarée, avant même la première clé ; 404 sans émetteur.

Sans jeton.

### Lire les clés de l'émetteur OIDC d'une organisation

`GET /oidc/o/{organization}/.well-known/jwks.json`

Rend les clés publiques RS256 de l'organisation, jamais celles d'une autre ; `kid` est la version de la clé. `{ keys: [] }` avant la première clé, 404 sans émetteur, 503 si les clés sont illisibles.

Sans jeton.

## Votre compte et vos agents

Votre compte et sa langue, vos agents branchés et leurs droits, vos annonces, et la déconnexion des agents d'une personne.

### Lire mes notifications : chaque sorte de courriel ou d'alerte, et si je la reçois

`GET /v1/me/notifications`

### Régler mes notifications

`PUT /v1/me/notifications`

Corps `{ notifications: { <kind>: true | false } }` (alerts, approvals, `access_requests`, `audit_streams`) ; les sortes absentes ne changent pas. Depuis la session de la personne seulement, jamais par un agent.

Corps : `notifications` (obligatoire).

### Lire mon compte

`GET /v1/me`

Rend la personne connectée : son adresse, son nom s'il en a donné un, la langue de while pour elle (null : celle de son organisation), platform (true : la session du back-office d'un administrateur nommé de la plateforme) et `platform_administrator` (true : un administrateur de la plateforme, qui peut entrer au back-office par le SSO). Par sa session ou un jeton de son compte.

### Changer la langue de mon compte

`PUT /v1/me`

Corps `{ language }` : l'une des langues de while, ou vide pour celle de son organisation (400 sinon).

Corps : `language`.

### Lire mes applis dans une organisation

`GET /v1/me/apps/{organization}`

Les applis internes que la personne de la session peut ouvrir dans cette organisation (la règle de chacune, comme à l'ouverture), et les invitations de cette organisation qui attendent son accord. Une organisation où rien ne lui est ouvert, ou qui n'est pas la sienne, répond comme une adresse inconnue : `organization` vaut null, sans son nom ; `home` : ses applis dans l'organisation de sa connexion d'entreprise. Depuis une session seulement.

### Lister mes agents

`GET /v1/me/agents`

Rend les agents de la personne, de tous genres : ses agents branchés par OAuth, les jetons de son compte qui ont servi, les clients vus au journal par sa session. Chacun avec où il travaille, ce qu'il peut faire (permissions null : comme elle), quand il a été vu, ses actions des 14 derniers jours (lire, agir), sa journée heure par heure et les sources qu'il appelle. Paramètre organization (son adresse) : ceux de cette organisation, et le nombre d'agents des autres membres (`other_agents`). Depuis la session de la personne seulement.

Paramètres : `organization`.

### Lire mes annonces

`GET /v1/me/announcements`

Les annonces en cours de l'équipe while pour la personne. Par sa session : son bandeau, moins celles qu'elle a fermées ou accusées ; ?organization= : le bandeau de cette organisation seule (404 si elle n'en est pas membre). Par son jeton : toutes celles en cours de son organisation, avec acknowledged (son accusé). `read_receipt` : l'annonce demande un accusé de lecture (POST `/v1/announcements/`{id}`/acknowledge`).

Paramètres : `organization`.

### Lire un de mes agents

`GET /v1/me/agents/{id}`

Rend l'agent (son id : o-\<famille>, j-\<jeton>, s-\<empreinte>), ses 25 dernières actions et ses appels aux sources, les applis où il agit, ses demandes dans son organisation (validations, droits), et les choix pour changer ses droits (`change_permissions`, null pour une session ou un agent qui crée son projet). Depuis la session de la personne seulement.

### Déconnecter un de mes agents

`DELETE /v1/me/agents/{id}`

Un agent branché par OAuth (o-\<famille>, ou sa famille) : ses jetons tombent, ses droits restent, il se rebranche par OAuth. Un jeton se révoque par DELETE `/v1/tokens/`{id}. Depuis la session de la personne seulement : un jeton ou un agent reçoit 403.

### Changer les droits d'un de mes agents

`PUT /v1/me/agents/{id}/permissions`

Corps `{ level, scope, project }` : level read, act ou admin ; scope app (avec project, une appli de son organisation) ou organization (pour qui y donne des accès ; admin seulement là). Jamais plus que ce que la personne peut là où il travaille ; vaut tout de suite, et c'est noté au journal. Une famille OAuth ou un jeton de son compte, depuis sa session.

Corps : `level` (obligatoire), `scope` (obligatoire), `project`.

### Régler les groupes d'outils d'un de mes agents

`PUT /v1/me/agents/{id}/tool-groups`

Corps `{ tool_groups }` : la liste entière des groupes d'outils MCP que l'agent garde (alerts, `feedback_and_docs`, `environments_and_storage`, administer), ceux qu'il a chargés par `load_tools`. Un groupe ne donne aucun droit : il ne fait que lister ses outils. Noté au journal ; une famille OAuth ou un jeton de son compte, depuis sa session.

Corps : `tool_groups` (obligatoire).

### Déconnecter les agents d'une personne

`POST /v1/teams/{slug}/agents/disconnect`

Révoque tout de suite les familles d'accès données (agents[].familles de la liste des membres) ; la personne garde ses droits. Elle-même, ou qui administre l'organisation ; chacune au journal.

Corps : `families` (obligatoire).

## Organisation et membres

Les organisations, leurs membres, les invitations, les groupes et la connexion d'entreprise.

### Lister les organisations

`GET /v1/teams`

Rend vos organisations avec votre rôle, ou celle du jeton. Chacune vient avec son offre, le niveau de son abonnement du mois (`{ key, label, since }`), ou `null` sans abonnement.

### Créer une organisation

`POST /v1/teams`

Crée une organisation depuis une session. La personne qui la crée en est propriétaire. `moyen_de_paiement: "a_enregistrer"` est réservé à la plateforme, pour une organisation jetable des tests (`e2e-…`, mode test de Mollie) : elle naît sans dérogation, son moyen de paiement à enregistrer.

Corps : `slug` (obligatoire), `name`, `payment_method`, `language`.

### Lire une organisation

`GET /v1/teams/{slug}`

Rend la même fiche que la liste, avec votre rôle et l'offre. Une organisation qui ne vous est pas visible rend 404.

### Changer la langue d'une organisation

`PATCH /v1/teams/{slug}`

Sa langue (langue) : celle de while pour ses membres qui n'ont pas choisi la leur, pour ses agents et ses courriels ; ses propriétaires et ses admins la changent. Une organisation ne se renomme pas : un corps qui porte slug ou nom rend 400, sans rien changer. Une organisation renommée avant garde chaque ancienne adresse 90 jours : l'API y répond sans rediriger, l'interface y redirige (302), personne d'autre ne la prend.

Corps : `language`, `slug`, `name`.

### Lire la carte de l'organisation

`GET /v1/teams/{slug}/card`

Rend, en faits, les projets, leurs services (publics ou internes), leurs bases, leurs tâches et leurs connexions, et qui lit quelle base ou appelle quelle connexion. Seulement ce que vous voyez : rien d'un projet invisible, ni de ce qui le lie aux autres. L'outil MCP `get_organization { view: "map" }` rend la même chose.

### Lister les personnes entrées par la connexion d'entreprise

`GET /v1/teams/{slug}/enterprise-people`

Rend les personnes entrées par la connexion d'entreprise sans être membres : adresse, nom de l'émetteur, groupes reçus, applis ouvertes et la règle qui les y laisse, dates et session. Elles n'ont aucun droit dans while.

### Fermer les sessions d'une personne d'entreprise

`POST /v1/teams/{slug}/enterprise-people/{id}/close-sessions`

Ferme ses sessions dans while, dans le realm et dans les applis ouvertes. Elle revient tant que la règle d'une appli la laisse passer : la réponse dit lesquelles, et comment l'en empêcher.

### Lister les membres

`GET /v1/teams/{slug}/members`

Rend les membres de l'organisation : leur rôle, depuis quand, leur nom, leur dernière connexion, s'ils entrent par le SSO, leurs groupes et leurs agents branchés. Pour qui administre, en plus : les clés de comptes de service que chacun a créées (révoquées s'il part) et l'échéance de son invitation.

### Inviter une personne

`POST /v1/teams/{slug}/members`

Réservé aux propriétaires et aux admins. La réponse est la même, que la personne ait déjà un compte while ou non : 202 `{ status, email, role, invitation: null, note }`. `status` vaut « invitation envoyée », ou « invitation enregistrée » quand aucun courriel n'est parti : envoi impossible, ou adresse de test ou d'exemple, à laquelle while n'écrit jamais. Le lien part par courriel à l'adresse invitée, jamais à qui invite. Un rôle admin ou propriétaire s'applique après la validation d'une autre personne. 10 invitations d'adresses non membres par heure et par organisation (429, `retry_after_s`). Un admin le fait sans attendre personne, au journal (son agent, au plancher, le lui fait confirmer), sauf si l'organisation a réglé deux approbateurs.

Corps : `email` (obligatoire), `role`, `reason`.

### Renvoyer une invitation

`POST /v1/teams/{slug}/members/{email}/resend`

Envoie un nouveau lien par courriel à l'adresse invitée ; l'ancien ne sert plus. Une fois par heure au plus.

### Lire un lien de rattachement à la connexion d'entreprise

`GET /v1/sso/link`

Le lien reçu à l'adresse d'un compte while après sa première connexion par la connexion d'entreprise de son organisation (le SSO des membres, 30 minutes) : l'adresse du compte, l'organisation et l'émetteur. Un lien qui ne sert plus : 410.

Sans jeton.

Paramètres : `j` (obligatoire).

### Relier son compte à la connexion d'entreprise

`POST /v1/sso/link`

Le titulaire confirme, une fois : son compte entre ensuite par la connexion d'entreprise. Aucune session ne s'ouvre ici ; né d'une session par mot de passe, le lien demande cette même session. `to` : la page où aller. Depuis une page de while (contrôle d'origine) ; un lien qui ne sert plus : 410.

Sans jeton.

Corps : `j` (obligatoire).

### Lister les groupes

`GET /v1/teams/{slug}/groups`

Rend les groupes de l'organisation, faits dans while ou venus de la connexion d'entreprise, et leurs membres.

### Créer un groupe

`POST /v1/teams/{slug}/groups`

Réservé aux admins.

Corps : `name` (obligatoire), `sso`, `enterprise`.

### Relier un groupe à un groupe d'entreprise

`PUT /v1/teams/{slug}/groups/{group}/enterprise`

Relie un groupe while à un groupe de l'émetteur de la connexion d'entreprise, par l'identifiant stable du groupe chez l'émetteur, jamais par son nom ; `entreprise: null` le détache. Réservé à une personne admin, par sa session : un jeton reçoit 403. Relier fait entrer dans le groupe ceux que l'émetteur y annonce : il faut donc avoir soi-même ce que le groupe donne. Un groupe qui administre, ou désigné approbateur, attend une validation (428). Noté au journal.

Corps : `enterprise`.

### Lier un groupe à un groupe du SSO

`PUT /v1/teams/{slug}/groups/{group}/sso`

Réservé aux admins ; `sso: null` le détache. À chaque connexion par le SSO, si le jeton d'identité porte la revendication `groups`, la personne entre dans les groupes while dont le `sso` y figure, et sort de ceux dont il n'y figure plus, sans tenir compte de la casse ni d'un « / » initial. Noté au journal.

Corps : `sso`.

### Changer le rôle d'un membre

`PUT /v1/teams/{slug}/members/{email}/role`

Donne le nouveau rôle sur l'organisation et retire l'ancien, comme les outils MCP `grant_role` et `revoke_role`, et le note au journal. `access.grant` suffit, puisque vous ne donnez que ce que vous avez. Nommer un admin ou un propriétaire attend la validation d'une autre personne (202) : d'ici là, la personne garde son rôle, et l'ancien part une fois validé. Le rôle externe retire tout rôle sur l'organisation entière. Un admin le fait sans attendre personne, au journal (son agent, au plancher, le lui fait confirmer), sauf si l'organisation a réglé deux approbateurs.

Corps : `role` (obligatoire), `reason`.

### Supprimer un groupe

`DELETE /v1/teams/{slug}/groups/{group}`

Réservé aux admins. Ses désignations d'approbateur partent avec lui ; un groupe désigné approbateur demande d'abord une validation (428).

### Ajouter un membre à un groupe

`POST /v1/teams/{slug}/groups/{group}/members`

Réservé aux admins, pour un membre de l'organisation, et pas pour un groupe de la connexion d'entreprise. Un groupe qui administre, ou désigné approbateur, demande d'abord une validation (428).

Corps : `email` (obligatoire).

### Retirer quelqu'un d'un groupe

`DELETE /v1/teams/{slug}/groups/{group}/members/{email}`

Réservé aux admins. Un groupe désigné approbateur demande d'abord une validation (428).

### Retirer une personne de l'organisation

`DELETE /v1/teams/{slug}/members/{email}`

Réservé aux admins. Ses jetons et ses agents branchés sont révoqués. Seul un propriétaire retire un propriétaire, et le dernier propriétaire ne peut pas être retiré (409).

### Lire la connexion d'entreprise

`GET /v1/teams/{slug}/sso`

Réservé aux admins. Rend l'émetteur OpenID, le client, les domaines et la revendication des groupes. Le secret du client est au coffre : seule sa présence est dite (`secret: true`). `domain_proofs` dit, 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.

### Régler la connexion d'entreprise

`PUT /v1/teams/{slug}/sso`

Une connexion OpenID Connect générique : Keycloak, Google Workspace, Microsoft Entra… while lit tout de suite sa découverte (`/.well-known/openid-configuration`, en https, à une adresse publique). Seules les adresses des domaines donnés entrent par cette connexion, une fois chaque domaine prouvé par un enregistrement DNS TXT (`domain_proofs`). L'adresse de retour à déclarer chez l'émetteur est `https://app.while.sh/access/sso/callback`. Noté au journal.

Corps : `name`, `issuer`, `client_id`, `secret`, `domains`, `groups_claim`, `active`, `member_sso`, `member_passwords`, `provider`, `tenant_id`.

### Retirer la connexion d'entreprise

`DELETE /v1/teams/{slug}/sso`

Son secret est détruit, et les sessions ouvertes par elle sont révoquées.

### Lire où en est la connexion d'entreprise

`GET /v1/teams/{slug}/sso/setup`

Réservé aux admins, même sans connexion (connection null) : le réglage et son fournisseur, l'adresse de retour à déclarer chez lui, la preuve de chaque domaine et son enregistrement TXT, la validation en cours et qui peut la donner, les groupes reliés, les applis déjà ouvertes, le dernier essai de la personne, et les membres qui perdraient l'organisation sans mot de passe.

### Vérifier les domaines maintenant

`POST /v1/teams/{slug}/sso/verify`

Relit tout de suite l'enregistrement TXT de chaque domaine (une fois toutes les 15 secondes au plus) et rend leurs preuves. Réservé aux admins.

### Faire entrer les membres par la connexion d'entreprise

`PUT /v1/teams/{slug}/sso/members`

Corps `{ member_sso, member_passwords, confirm }` : les membres entrent avec leur compte d'entreprise, et le mot de passe leur reste permis ou non (seuls les propriétaires le gardent alors). confirm est exigé quand des membres perdraient l'organisation. Une personne admin, par sa session ; noté au journal.

Corps : `member_sso`, `member_passwords`, `confirm`.

### Essayer la connexion d'entreprise

`POST /v1/teams/{slug}/sso/trial`

Rend l'adresse du fournisseur où la personne admin se connecte avec son propre compte ; rien n'est ouvert ni créé. Le navigateur garde la demande (un cookie) ; au retour, while revient à la page de la connexion d'entreprise avec ce que l'essai a vu. Depuis sa session seulement.

### Lister mes invitations

`GET /v1/invitations`

Rend les organisations où votre compte est invité. Depuis la session de la personne invitée seulement, ni jeton ni agent. Une adresse pas encore prouvée, ni par le SSO ni par un lien reçu par courriel, n'en voit aucune : `address_verified`: `false`.

### Accepter une invitation

`POST /v1/invitations/{organization}/accept`

La personne entre dans l'organisation avec le rôle proposé, donné au nom de qui l'a invitée, dans la limite de ses droits d'aujourd'hui (sinon `granted_role`: `false`). Un rôle admin ou propriétaire attend une validation. Depuis une session, avec une adresse prouvée : un jeton, un agent ou une adresse non prouvée reçoit 403 (`adresse_a_prouver` : ouvrir le lien reçu par courriel, ou se connecter par le SSO).

### Décliner une invitation

`POST /v1/invitations/{organization}/decline`

L'invitation disparaît, et le journal de l'organisation le note. Depuis une session, avec une adresse prouvée.

### Accuser réception d'une annonce

`POST /v1/announcements/{id}/acknowledge`

La personne dit avoir pris connaissance d'une annonce de l'équipe while ; c'est daté, et son bandeau se ferme. Une personne, par sa session ou son jeton ; une clé de compte de service reçoit 403. 404 si ce n'est pas une annonce en cours de ses organisations ; 409 si elle ne demande pas d'accusé. La deuxième fois, la première date reste.

### Fermer une annonce pour soi

`POST /v1/announcements/{id}/close`

Le bandeau ne montre plus cette annonce à la personne. Par sa session seulement (403 par un jeton). Une urgente ne se ferme pas avant sa fin (403) ; une qui demande un accusé reste jusqu'à l'accusé (POST `/v1/announcements/`{id}`/acknowledge`). 404 si ce n'est pas une annonce en cours de ses organisations.

## Droits et validations

Qui peut faire quoi, les demandes d'accès, les comptes de service, les approbateurs et les règles de validation.

### Lister les identités

`GET /v1/teams/{slug}/identities`

Les personnes, les groupes, les comptes de service et les applis qui ont une attribution, chacune avec ses accès directs en cours. Qui voit l'organisation les voit toutes ; sinon, soi seulement.

### La grille des accès aux sources

`GET /v1/teams/{slug}/access/grid`

Ce que peut chacun sur chaque source de données, d'après ses droits effectifs : les agents (avec les droits de leur personne), les applis, les tâches et les comptes de service en lignes, les sources en colonnes ; pour chaque case, lire et écrire (sur toute la source, ou une partie de ses chemins) et la régler. Toutes les sources pour qui donne des accès, sinon celles qu'on voit.

### Changer qui ouvre un service

`PUT /v1/projects/{slug}/environments/{env}/services/{service}/access`

ouvert : public (tout internet), organization (ses membres) ou people (qui : des adresses, des @domaines, des groupes ; membres : true pour garder aussi les membres). Le while.yaml change et une nouvelle version part (statut inchangé, sans version, quand l'accès est déjà celui demandé) ; ouvrir à tout internet ou au-delà de l'organisation demande une validation par défaut (428), que les règles de l'organisation peuvent assouplir.

Corps : `open` (obligatoire), `who`, `members`.

### Lire les règles de validation et le budget

`GET /v1/teams/{slug}/rules`

`rules` est la liste ordonnée des règles, chacune en clair : la première qui correspond l'emporte. `budget_eur` est le budget mensuel (`null` : sans plafond). `defaults` dit ce qui vaut sans règle : autonome dans le budget, même en production ; validation au-delà ; validation pour rendre une appli publique. `floor` liste ce qui demande toujours une validation. `pending` montre un assouplissement qui attend sa validation.

### Changer les règles et le budget

`PUT /v1/teams/{slug}/rules`

Réservé aux admins. Envoie la liste entière des règles, le budget, ou les deux. Ce qui durcit s'applique tout de suite (200, `status` « appliquées »). Ce qui assouplit, même un seul cas, ou relève le budget (`null` : plus de plafond) passe en entier par une validation (202, `status` « validation humaine requise », avec la demande). Une règle sur un geste du plancher, comme détruire des données de production, changer les accès ou la politique, ou un appel « valider chaque appel », est refusée (400, avec la raison). Le journal note ce qui assouplit et ce qui durcit.

Corps : `rules`, `budget_eur`, `required_approvals`.

### Lire qui a accès à un projet

`GET /v1/projects/{slug}/access`

Rend chaque attribution du projet, ou d'une de ses bases ou tables, en une phrase : « Léa Martin lit la table `ventes.commandes` (entrepot, pilotage) jusqu'au 30 novembre. Donné par Oscar le 21 octobre : audit du T4. » Avec `?qui=`, tout ce que cette identité peut faire ; avec `?qui=` et `?permission=`, oui ou non, et la chaîne qui le permet : attribution, groupe, rôle, qui l'a donnée, quand et pourquoi. Les droits des autres se lisent là où vous avez `access.grant`.

Paramètres : `activity`, `project`, `environment`, `database`, `schema`, `table`, `columns`, `connection`, `paths`, `who`, `permission`.

### Lire qui a accès à une ressource de l'organisation

`GET /v1/teams/{slug}/access`

Comme pour un projet, sur une ressource de l'organisation, ou sur l'organisation elle-même sans ressource : qui a accès, ce que peut faire une identité, et si elle peut faire une chose.

Paramètres : `activity`, `project`, `environment`, `database`, `schema`, `table`, `columns`, `connection`, `paths`, `who`, `permission`.

### Donner un accès

`POST /v1/teams/{slug}/attributions`

Donne à une identité un rôle sur une portée, jusqu'à une date, pour une raison, comme l'outil MCP `grant_role`. Vous ne donnez que ce que vous avez (`access.grant`, et chaque permission du rôle sur la portée) : jamais à vous-même, ni par votre agent, ni à un groupe dont vous êtes membre, ni à un compte de service que vous utilisez. Sans échéance : seulement pour un admin de la portée, sinon 90 jours au plus. Donner le droit d'administrer des accès attend une validation (202). Un refus (403) dit ce qui manque. Tout va au journal (famille `access`).

Corps : `who` (obligatoire), `role` (obligatoire), `scope`, `until`, `days`, `reason` (obligatoire).

### Retirer une attribution

`DELETE /v1/teams/{slug}/attributions/{id}`

L'attribution ne compte plus, tout de suite. La raison est obligatoire, et va au journal.

Paramètres : `reason`.

Corps : `reason`.

### Lister les attributions inutilisées

`GET /v1/teams/{slug}/attributions/unused`

Rend les attributions que vous pouvez retirer, sans usage depuis 60 jours : ni action permise, ni appel. Le travail périodique les signale une fois, au journal. Pour en retirer une : `DELETE …/attributions/{id}` ; pour la garder : `POST …/attributions/{id}/garder`.

### Garder une attribution inutilisée

`POST /v1/teams/{slug}/attributions/{id}/keep`

Son compte de 60 jours repart, sans inventer d'usage, et la raison va au journal. Qui peut retirer l'attribution peut la garder, jamais la sienne.

Corps : `reason` (obligatoire).

### Exporter les attributions pour un auditeur

`GET /v1/teams/{slug}/attributions/export`

Il faut `log.read` sur l'organisation. En JSON : chaque attribution (en cours, échue ou retirée), ses entrées du journal (numéro, empreinte, précédente, contenu haché), la tête de la chaîne et sa vérification. En CSV : une ligne par attribution, puis une ligne « verification ». L'export est noté au journal (famille security).

Paramètres : `format`.

### Lister les rôles

`GET /v1/teams/{slug}/roles`

Rend les rôles fournis par while et ceux de l'organisation, leurs permissions en clair, où ils se donnent et qui les tient, et ce que vous pouvez composer.

### Lire un rôle

`GET /v1/teams/{slug}/roles/{role}`

Rend un rôle, fourni ou de l'organisation, par sa clé.

### Créer ou modifier un rôle

`PUT /v1/teams/{slug}/roles/{role}`

Compose un rôle de l'organisation à partir du catalogue des permissions. Il faut `roles.define`, et vous ne composez qu'avec ce que vous avez : chacune des permissions du rôle sur l'organisation. Modifier un rôle tenu revient à le redonner à ses titulaires : jamais pour un rôle que vous tenez vous-même (403), et un rôle tenu ne gagne jamais `access.grant`, `org.admin` ni `roles.define` (409). Tout va au journal (famille `access`), refus compris.

Corps : `name`, `description`, `permissions` (obligatoire), `reason` (obligatoire).

### Supprimer un rôle

`DELETE /v1/teams/{slug}/roles/{role}`

Il faut `roles.define`. Seulement quand plus aucune attribution en cours ne le donne.

Paramètres : `reason`.

Corps : `reason`.

### Lister les demandes d'accès

`GET /v1/teams/{slug}/access-requests`

Rend les demandes que vous pouvez trancher, les vôtres, et vos attributions qui arrivent à échéance, avec l'appel qui les renouvelle.

### Demander un accès

`POST /v1/teams/{slug}/access-requests`

Demande un droit pour soi : une personne, son agent pour elle, ou un compte de service par sa clé. Sert aussi à renouveler une de ses attributions (`renew`), ou à demander l'appel d'une connexion pour une appli (`for`). Se fait après un 403 qui porte `missing` : le plus petit rôle, la plus petite portée, l'appel tout prêt. Champs : `role`, ou `permission` (while prend le plus petit rôle qui la donne), `scope`, `reason` (obligatoire), `days` (7 par défaut, 90 au plus). Ceux qui peuvent l'accorder sont prévenus (À traiter, courriel, et leurs agents sur `while://access/events`) ; sans réponse en 72 heures, elle expire. Une demande identique encore ouverte est rendue telle quelle (200), et 10 demandes au plus restent ouvertes (429). Notée au journal (famille `access`).

Corps : `role`, `permission`, `scope`, `days`, `reason` (obligatoire), `renew`, `for`.

### Lire une demande d'accès

`GET /v1/teams/{slug}/access-requests/{id}`

Rend sa phrase, qui peut l'accorder, sa décision et ce que vous pouvez en faire. Visible par qui l'a faite et par qui donne des accès sur sa portée (`access.grant`) ; sinon 404.

### Annuler sa demande d'accès

`DELETE /v1/teams/{slug}/access-requests/{id}`

Seulement tant qu'elle est ouverte.

Paramètres : `reason`.

### Accorder une demande d'accès

`POST /v1/teams/{slug}/access-requests/{id}/grant`

Une personne qui peut donner ce droit l'accorde, au plus ce qui est demandé, et la demande devient une attribution bornée. Jamais sa propre demande, jamais par un agent ni un compte de service (403). Champs : `role` (ses permissions parmi celles demandées), `scope` (contenue dans celle demandée), `until` ou `days` (au plus la durée demandée, 90 jours, toujours une échéance), `reason` (obligatoire). Une demande de niveau admin va aux propriétaires : un admin de la portée qui n'est pas propriétaire la refuse, ou donne le rôle lui-même, et une autre personne le valide. Accorder un renouvellement prolonge l'attribution. Noté au journal.

Corps : `role`, `scope`, `until`, `days`, `reason` (obligatoire).

### Refuser une demande d'accès

`POST /v1/teams/{slug}/access-requests/{id}/deny`

Une personne qui pourrait l'accorder la refuse, avec une raison, qui part à qui a demandé.

Corps : `reason` (obligatoire).

### Lister les comptes de service

`GET /v1/teams/{slug}/service-accounts`

Rend chaque compte de service de l'organisation : sa phrase, ses droits, ses clés (préfixe, échéance, dernier usage) et qui l'utilise. Qui voit les membres (`org.view`) les voit tous ; sinon, ceux qu'il gère (`access.grant` sur leur portée). Un compte lit le sien par sa clé.

### Créer un compte de service

`POST /v1/teams/{slug}/service-accounts`

Crée une identité non humaine (tableau de bord, script, agent autonome), sans droit ni clé. Il faut être une personne, ou son agent, avec `access.grant` sur sa portée : l'organisation, ou le projet donné. Ses droits se donnent ensuite par une attribution (qui : `compte_service:<nom>`), par une autre personne que celles qui l'utilisent, puis une clé. Noté au journal.

Corps : `name` (obligatoire), `description`, `reason` (obligatoire), `project`.

### Lire un compte de service

`GET /v1/teams/{slug}/service-accounts/{account}`

Rend sa phrase, ses droits en phrases et ses clés.

### Supprimer un compte de service

`DELETE /v1/teams/{slug}/service-accounts/{account}`

Ses attributions sont retirées (au journal), ses clés partent et son nom se libère.

Paramètres : `reason`.

### Désactiver un compte de service

`POST /v1/teams/{slug}/service-accounts/{account}/disable`

Ses clés ne passent plus, ses droits ne comptent plus, et ses sessions SQL sont coupées.

Corps : `reason` (obligatoire).

### Réactiver un compte de service

`POST /v1/teams/{slug}/service-accounts/{account}/reactivate`

Ses clés repartent. Il faut avoir déjà chacun de ses droits.

Corps : `reason` (obligatoire).

### Créer une clé de compte de service

`POST /v1/teams/{slug}/service-accounts/{account}/keys`

Crée une clé `whcs_…`, montrée une seule fois, toujours à échéance : 90 jours au plus, un an pour un admin de la portée du compte. La clé donne à qui la tient tout ce que le compte peut faire : il faut gérer le compte (`access.grant` sur sa portée) et avoir déjà chacun de ses droits. Elle agit avec exactement ses attributions, sur l'API et le MCP, dans l'en-tête `Authorization: Bearer`. Le journal note la création, jamais le secret.

Corps : `name`, `until`, `days`, `reason`.

### Tourner une clé

`POST /v1/teams/{slug}/service-accounts/{account}/keys/{key}/rotation`

Crée une nouvelle clé, aux mêmes règles qu'une création. L'ancienne vaut encore le temps du chevauchement : 24 heures par défaut, de 0 à 168.

Corps : `name`, `until`, `days`, `overlap_hours`.

### Révoquer une clé

`DELETE /v1/teams/{slug}/service-accounts/{account}/keys/{key}`

Elle est refusée dès la requête suivante. La raison va au journal.

Paramètres : `reason`.

### Lister qui valide quoi

`GET /v1/teams/{slug}/approvers`

Rend, pour chaque ressource (organisation, projets, bases, connexions), son propriétaire et ses approbateurs. Sans désignation, valident le propriétaire de la ressource et les admins de l'organisation ; une base hérite de son projet.

### Désigner un approbateur

`POST /v1/teams/{slug}/approvers`

Désigne une personne ou un groupe comme approbateur d'une ressource. Réservé aux admins.

Corps : `resource_type` (obligatoire), `resource`, `subject_type` (obligatoire), `subject` (obligatoire).

### Retirer un approbateur

`DELETE /v1/teams/{slug}/approvers/{id}`

Retire une désignation d'approbateur. Réservé aux admins.

### Revoir les accès

`GET /v1/teams/{slug}/access/review`

Réservé aux admins. Rend qui lit, met en ligne et valide chaque ressource, les jetons à revoir et les agents branchés. Un agent hérite des droits de sa personne. Un jeton est « à revoir » quand il n'expire pas, qu'il n'a pas servi depuis 30 jours, ou qu'il a plus de droits que sa personne (ils sont alors plafonnés).

### Lire une demande de validation

`GET /v1/approvals/{id}`

Rend la demande : son niveau, son empreinte, son expiration, ses approbateurs, et ce que vous pouvez en faire.

### Lire le corps d'un appel retenu

`GET /v1/approvals/{id}/body`

Le corps d'un appel retenu (« valider chaque appel »), déchiffré pour une personne qui peut décider cette demande, connectée à while : jamais l'agent qui l'a faite, jamais un jeton. Son texte en UTF-8 (null s'il est binaire), borné à 32 000 caractères (truncated au-delà), son type, sa taille et son empreinte (la même que celle de la demande). Chaque lecture va au journal de l'organisation, sans le contenu. 404 pour qui ne peut pas décider, et une fois la demande tranchée ou expirée.

### Valider une demande

`POST /v1/approvals/{id}/approve`

Une demande se tranche par une autre personne, approbatrice de la ressource, connectée à while : jamais sa propre demande, ni celle de son agent (même compte, 403). Un jeton (d'agent, OAuth ou de compte de service) ne tranche ni ne force jamais : 403, raison « agent », avec le lien de la demande à transmettre à sa personne (`action.lien`). Refuser et passer en force demandent un motif (400 sans motif) ; passer en force est réservé aux admins de l'organisation et émet l'événement `sh.while.approval.forced`. Une demande déjà tranchée ou expirée rend 409.

Corps : `reason`.

### Refuser une demande

`POST /v1/approvals/{id}/reject`

Une demande se tranche par une autre personne, approbatrice de la ressource, connectée à while : jamais sa propre demande, ni celle de son agent (même compte, 403). Un jeton (d'agent, OAuth ou de compte de service) ne tranche ni ne force jamais : 403, raison « agent », avec le lien de la demande à transmettre à sa personne (`action.lien`). Refuser et passer en force demandent un motif (400 sans motif) ; passer en force est réservé aux admins de l'organisation et émet l'événement `sh.while.approval.forced`. Une demande déjà tranchée ou expirée rend 409.

Corps : `reason`.

### Passer une demande en force

`POST /v1/approvals/{id}/force`

Une demande se tranche par une autre personne, approbatrice de la ressource, connectée à while : jamais sa propre demande, ni celle de son agent (même compte, 403). Un jeton (d'agent, OAuth ou de compte de service) ne tranche ni ne force jamais : 403, raison « agent », avec le lien de la demande à transmettre à sa personne (`action.lien`). Refuser et passer en force demandent un motif (400 sans motif) ; passer en force est réservé aux admins de l'organisation et émet l'événement `sh.while.approval.forced`. Une demande déjà tranchée ou expirée rend 409.

Corps : `reason`.

### Lister les demandes en attente

`GET /v1/approvals`

Rend les demandes des projets que vous voyez, et celles de vos organisations selon votre rôle. Un jeton de projet ne voit que celles de son projet.

Paramètres : `organization`.

### Lister les demandes de validation d'une organisation

`GET /v1/teams/{slug}/approvals`

Les demandes de validation de cette organisation que votre identité voit, en attente et tranchées, les plus récentes d'abord (50 au plus) ; chacune comme GET `/v1/approvals/`{id} la rend : qui l'a tranchée (`decided_by`), quand (`decided_at`), son motif (`decision_reason`). Une demande passée sans réponse est rendue expirée.

## Journal

Le journal de l'organisation : le lire, l'exporter, le vérifier et l'envoyer à un SIEM.

### Lire le journal de l'organisation

`GET /v1/teams/{slug}/activity-log`

Qui a fait quoi, par quoi, sur quoi, du plus récent au plus ancien. Le journal s'écrit en ajout seul, chaîné par empreinte, et se garde 13 mois par défaut. Un admin de l'organisation 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 SQL n'y laissent que des métadonnées : la requête normalisée sans ses valeurs, jamais de résultat.

Paramètres : `tab`, `person`, `via`, `family`, `project`, `resource`, `since`, `until`, `cursor`, `limit`.

### Exporter le journal en JSON Lines

`GET /v1/teams/{slug}/activity-log.jsonl`

Une entrée par ligne, avec les mêmes filtres que la lecture ; il faut le droit d'exporter le journal, plus large que celui de le lire. L'export est noté au journal.

Paramètres : `tab`, `person`, `via`, `family`, `project`, `resource`, `since`, `until`, `cursor`.

### Exporter le journal vérifiable

`GET /v1/teams/{slug}/activity-log/verifiable.jsonl`

Toute la chaîne de l'organisation, revérifiable hors de while. En JSONL : un en-tête (l'ancre de départ, les caviardages), chaque entrée avec son rang (`seq`), son empreinte, la précédente et le contenu haché, puis une fin, la tête et sa signature Ed25519. `?depuis=<rang>` donne la suite d'un export précédent. Il faut `log.export` sur l'organisation entière.

Paramètres : `since`.

### Lire les clés publiques du journal

`GET /journal/o/{organization}/cles.json`

Rend chaque version des clés publiques du journal d'une organisation, l'ancienne comprise, par l'identifiant stable de l'organisation.

### Lister les flux vers un SIEM

`GET /v1/teams/{slug}/activity-log/streams`

Réservé aux admins. Rend chaque flux du journal vers un SIEM et son état : curseur, dernier envoi, retard, erreurs.

### Ajouter un flux vers un SIEM

`POST /v1/teams/{slug}/activity-log/streams`

Une personne admin, par sa session, avec l'offre Entreprise. Corps : `{ name, url, since, delay_threshold_minutes }`, avec une `url` en https, publique, et `since` à `maintenant` ou à `start`. while envoie des lots JSONL signés comme les alertes (Standard Webhooks) ; le rang fait foi. La clé de signature n'est rendue qu'une fois.

Corps : `name`, `url`, `since`, `delay_threshold_minutes`.

### Retirer un flux vers un SIEM

`DELETE /v1/teams/{slug}/activity-log/streams/{stream}`

Réservé aux admins, dans leur session : un jeton ne retire pas un flux.

### Vérifier la chaîne du journal

`GET /v1/teams/{slug}/activity-log/verification`

Toute personne qui lit le journal. Rend ok, ou la première entrée qui ne colle pas. Après une purge de conservation, la chaîne repart de l'empreinte de la dernière entrée purgée (`since_purge`).

### Régler la conservation du journal

`PUT /v1/teams/{slug}/activity-log/retention`

Réservé aux admins : de 1 à 120 mois, 13 par défaut. La purge passe chaque jour.

Corps : `month` (obligatoire).

### Lire la conservation des appels des connexions

`GET /v1/teams/{slug}/activity-log/call-retention`

Rend la durée, en jours, pendant laquelle le journal des appels des connexions est gardé.

### Régler la conservation des appels des connexions

`PUT /v1/teams/{slug}/activity-log/call-retention`

Il faut `org.admin`. De 90 jours à 5 ans, 400 jours par défaut ; la durée vaut aussi pour les appels déjà gardés.

Corps : `days` (obligatoire).

## Consommation et quotas

Ce que consomment les applis, ce que coûte un mois, et les limites.

### Lire la consommation d'un projet

`GET /v1/projects/{slug}/usage`

Rend la consommation du mois (CPU, mémoire, requêtes, constructions, bases, caches), son coût, la projection des alertes et le budget. `per_month_euros` est ce que coûterait un mois normal à ce rythme : les bases et les caches à leur réservation d'aujourd'hui, l'appli à sa consommation moyenne des 7 derniers jours (ou depuis sa mise en ligne), sur un mois entier, sans le forfait de l'offre, qui est celui de l'organisation. Le coût suit les prix de l'organisation, ceux de son abonnement (sa grille, son niveau, ses remises) ou, sans abonnement, la grille de référence à son niveau par défaut ; il est donné à ce jour et projeté, et `detail` le donne par ligne de la grille. `pricing` donne ces prix : `price_list`, `subscription`, `level`, `levels[]`, `resources[]` `{ key, label, unit, price_cents, by, text, negocie?, prix_grille_centimes? }`, `options[]`, `discounts[]` `{ target, text }`, `included[]` et `examples`.

### Lire le relevé mensuel

`GET /v1/teams/{slug}/statement`

Rend les quantités du mois qu'un modèle de prix facturerait, sans les prix : le calcul consommé (vCPU·h, Gio·h) des applis, des caches et des bases ; ce que les bases réservent (taille, haute disponibilité, disque) ; le stockage objet ; les requêtes ; les constructions ; les connexions avec et sans clé, et leurs appels ; les développeurs, lecteurs, membres et comptes de service ; les réglages (SSO, droits fins, conservation). Le relevé est provisoire pendant le mois, recalculé chaque heure, et clos une heure après sa fin. Réservé aux admins de l'organisation.

Paramètres : `month`, `recompute`.

### Lire la consommation de l'organisation

`GET /v1/teams/{slug}/usage`

Réservé aux admins de l'organisation. Le montant principal est `euros_per_month`, en euros HT : ce que coûterait un mois normal à ce rythme, le même que la page Consommation et l'outil MCP `get_organization`. `per_month` le détaille : le forfait de l'offre, les bases et les caches à leur réservation d'aujourd'hui, les applis à leur consommation moyenne des 7 derniers jours, sur un mois entier ; `calculation` dit comment. `since_start` est ce qui est dû depuis le 1er du mois, ou depuis la création (`since`), c'est-à-dire ce qui sera facturé pour ce mois. `estimated_month_end` est la projection des alertes de budget, et `per_app` détaille chaque appli, brique par brique. Les lignes de `per_month` et de `since_start` sont en centimes HT, avec leur devise.

### Lire les quotas

`GET /v1/teams/{slug}/quotas`

Réservé aux admins de l'organisation. Pour chaque ressource : la limite, par défaut ou relevée, l'usage, et si elle est atteinte. Une limite se relève sur demande à l'équipe while. Au-delà d'une limite, la création est refusée : 409 pour un stock, 429 pour un débit (avec `retry_after_s`), avec un Problem de type `https://while.sh/problems/quota-exceeded` et l'extension quota `{ resource, label, scope, unit, usage, request, limit, default, raise }`.

## Retours à l'équipe while

Un bug, un blocage ou une idée envoyés à l'équipe while, et ce que des personnes pointent sur une appli.

### Lister vos retours

`GET /v1/feedback`

Rend vos retours à l'équipe while et sa réponse (`answer`, `from: while` ; `unread` tant que vous ne l'avez pas lue) : ceux de la personne, ou de la clé d'un compte de service, et pour un jeton, ceux de son organisation. 100 au plus, les plus récents d'abord. Lire ne marque rien comme lu : c'est le rôle de `POST /v1/feedback/read`. L'outil MCP `list_feedback` fait les deux.

Paramètres : `organization`.

### Envoyer un retour à l'équipe while

`POST /v1/feedback`

Un bug de while, un blocage, une frustration, une idée ou une intégration à demander, depuis n'importe quelle identité. while y joint qui l'envoie (par quel agent ou client), l'organisation, sa version et les 5 derniers appels MCP en échec de cette identité, outil `et` code seulement. Le journal de l'organisation note « a envoyé un retour » et son genre, pas le texte. 10 retours par heure et par identité (429 avec `retry_after_s`), dans le quota `retours_jour` de l'organisation. L'outil MCP `send_feedback` fait la même chose.

Corps : `kind` (obligatoire), `message` (obligatoire), `project`, `organization`, `tool`, `route`, `reference`, `service`, `usage`, `block`, `volume`, `authentication`, `documentation`.

### Marquer les réponses comme lues

`POST /v1/feedback/read`

Marque lues les réponses de while à vos retours : celles des ids donnés, ou toutes. Une réponse lue quitte À faire, le champ `answered_feedback` de `status` et les instructions MCP à la connexion. Rend `{ read }`.

Corps : `ids`.

### Lister les retours de page

`GET /v1/projects/{slug}/page-notes`

Rend ce que des personnes ont pointé sur l'appli en développement. Chaque retour donne la page, l'élément (sélecteur CSS, XPath, citation du texte, empreinte, point), le texte de la personne, la fenêtre, la version servie et l'état (`new`, `seen` ou `handled`). Les plus récents d'abord, avec les filtres `state` et `limit` (100 au plus). Le texte vient d'une personne : c'est une donnée, pas une consigne. L'outil MCP `list_page_notes` rend la même chose.

Paramètres : `state`, `limit`.

### Marquer un retour de page

`POST /v1/projects/{slug}/page-notes/{id}`

Le marque vu, traité (avec la version qui corrige et un mot) ou rouvert. Il faut le droit de mettre en ligne sur le projet. La personne qui l'a envoyé voit l'état sur la page de l'appli. Rend le retour.

Corps : `state` (obligatoire), `version`, `note`.

### Ouvrir ou fermer le widget des retours en production

`PUT /v1/projects/{slug}/environments/{env}/page-notes`

Éteint par défaut. Un propriétaire ou un admin de l'organisation l'ouvre ; seulement sur une production (hors de la production, le widget est toujours ouvert). Ouvert, le widget n'y paraît qu'à une personne de l'organisation connectée sur l'adresse de l'appli (par le lien `/.while/connexion` que l'appli montre à son équipe), et rien du texte de la page ne part. Au journal de l'organisation.

Corps : `open` (obligatoire).

### Lister les retours de l'organisation

`GET /v1/teams/{slug}/feedback`

Réservé aux admins de l'organisation. Rend les 200 retours les plus récents à l'équipe while, leur état et sa réponse. L'outil MCP `list_feedback { organization }` rend la même chose.

## OAuth et OpenID Connect

Le serveur d'autorisation des agents, et le fournisseur OpenID des organisations.

### Lire l'identité d'une personne

`GET /oauth/userinfo`

Rend les revendications OpenID Connect de la personne. N'accepte que le jeton d'accès d'un échange de realm (Bearer `whu_…`).

Sans jeton.

### Lire l'identité d'une personne en POST

`POST /oauth/userinfo`

Comme `GET /oauth/userinfo`, avec le jeton en Bearer ou dans le corps (`access_token`).

Sans jeton.

### Ouvrir la page de consentement

`GET /oauth/authorize`

L'adresse d'autorisation des clients OAuth (RFC 6749) : la page de consentement de l'interface, à une session (sans elle, la connexion, qui y ramène). Un membre qui ouvre une appli interne de son organisation repart aussitôt vers son realm, avec son code.

Sans jeton.

### Enregistrer un client OAuth

`POST /oauth/register`

Enregistre dynamiquement un client public (RFC 7591). PKCE est obligatoire.

Sans jeton.

Corps : `client_name`, `redirect_uris` (obligatoire), `token_endpoint_auth_method`.

### Obtenir un jeton OAuth

`POST /oauth/token`

Échange un code d'autorisation, avec son `code_verifier` PKCE, ou fait tourner un jeton de rafraîchissement. Un jeton de rafraîchissement déjà utilisé qui revient révoque toute la session. Le client confidentiel du realm d'une organisation (`realm-org-<n>`, ouvert par while) s'authentifie en `client_secret_basic` ou `client_secret_post`, et reçoit un `id_token` ES256 sans jeton de rafraîchissement.

### Révoquer un jeton OAuth

`POST /oauth/revoke`

Révoque un jeton (RFC 7009). Révoquer un jeton de rafraîchissement révoque toute sa session.

Sans jeton.

### Lire les métadonnées du serveur OAuth

`GET /.well-known/oauth-authorization-server`

Rend les métadonnées du serveur d'autorisation OAuth 2.1 de while (RFC 8414), aussi servies sous `/.well-known/openid-configuration`.

Sans jeton.

### Découvrir la configuration OpenID Connect

`GET /.well-known/openid-configuration`

Rend les métadonnées OAuth des agents, et celles du fournisseur OpenID des realms des organisations : userinfo, jetons d'identité ES256, portées openid, email et profile.

Sans jeton.

### Lire les métadonnées de la ressource MCP

`GET /.well-known/oauth-protected-resource`

Rend les métadonnées de la ressource protégée MCP (RFC 9728) : ses serveurs d'autorisation et ses droits.

Sans jeton.

## Prochaines étapes

- [while.yaml, champ par champ](/fr/docs/reference/while-yaml/)
- [Limites et quotas](/fr/docs/reference/limites/)
- [Votre première mise en ligne](/fr/docs/demarrer/premiere-mise-en-ligne/)
