
# Appliquer un plan de configuration

`POST /v1/config-plans/{plan_id}:apply` · autorisation `config:write`, les autorisations de lecture du plan et l'autorisation d'écriture de chaque type qu'il modifie

Applique un [plan](create.md) enregistré à l'environnement. Chaque modification est écrite dans une seule transaction : si l'une échoue, aucune n'est conservée. Un plan ne peut être appliqué qu'une seule fois.

## Requête

| Paramètre | Description |
|---|---|
| `plan_id` | L'identifiant du plan, `cfgp_…`. |

Le corps est facultatif.

| Champ | Description |
|---|---|
| `resolved_secrets` | Objet de nom → valeur `secret_ref` pour chaque modification avec `requires_secret: true` (par exemple `KEENAUTH_CONNECTION_GITHUB_CLIENT_SECRET`). Les valeurs ne dépassent pas 8 Kio. Elles ne sont jamais stockées par le plan, journalisées ni renvoyées. |
| `confirm_deletions` | Liste des suppressions `<kind>/<key>` que vous confirmez, par exemple `connection/legacy`. Les avertissements `deletion_requires_confirmation` du plan énumèrent les suppressions qui l'exigent. |
| `sp_host` | L'hôte du fournisseur de services de l'environnement, par exemple `login.example.com`. Requis uniquement lorsque le plan crée le matériel OIDC d'une connexion d'entreprise (le rappel `https://<sp_host>/sso/<connection id>/callback` est fixé à partir de celui-ci) ; une requête qui n'en a pas besoin ne doit pas l'envoyer. |

- Les autorisations d'écriture sont `environment-settings:write`, `identity-settings:write`, `attack-protection:write`, `branding:write` (pour `branding` et `application_branding`), `connections:write`, `apis:write`, `roles:write` et `applications:write`, uniquement pour les types ayant une modification autre que `no_op`.
- Avant l'écriture, la cible est relue. Si la version d'une ressource planifiée ou le contenu des types du plan a changé après sa création, rien n'est écrit, le plan passe à `conflicted` et la requête échoue avec `409 config_conflict`. Créez un nouveau plan. Avec `prune`, les types qui font référence aux ressources planifiées sont aussi comparés, et les références entre ressources sont vérifiées à nouveau ; une référence rompue fait également passer le plan à `conflicted`.
- La suppression d'une connexion, ou d'une application ayant reçu un jeton au cours des 30 derniers jours, doit être répétée dans `confirm_deletions`. Sinon, la requête échoue avec `409 deletion_not_confirmed` et le plan reste `planned`.
- Le matériel OIDC d'une connexion d'entreprise (`spec.oidc`) et son secret client sont écrits dans le service de fédération après l'enregistrement de la connexion. Si cette seconde écriture est refusée, le plan reste `planned`, l'enregistrement de la connexion peut déjà exister, et un nouveau plan n'écrit que le matériel manquant. Un `spec.oidc` de `null` laisse le matériel inchangé. Le secret client d'une connexion sociale ne peut pas encore être écrit : un plan comportant une telle modification `requires_secret` renvoie `501 not_implemented` après la vérification des secrets. Rien n'est écrit.
- `Idempotency-Key` est pris en charge. Envoyez-le sans secrets dans le corps lorsque c'est possible : l'enregistrement de la clé conserve un hachage du corps.

## Réponse

`200 OK` avec `Cache-Control: no-store` : le plan sous la forme de [Obtenir un plan de configuration](get.md) avec `status: applied` et `applied_at`.

La configuration des ressources concernées avant la modification est conservée sous forme de révision (les 50 plus récentes par environnement, pendant 90 jours au maximum). L'application écrit une seule entrée `config.applied` dans le journal de gestion de l'environnement, avec le nombre de ressources créées, mises à jour, supprimées et inchangées ; chaque ressource modifiée écrit également sa propre entrée.

## Erreurs

| HTTP | Code | Quand |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | Le corps n'est pas du JSON ou comporte un champ autre que `resolved_secrets`, `confirm_deletions` et `sp_host`. |
| 401 | `unauthenticated` | L'identifiant est absent, invalide ou révoqué. |
| 403 | `insufficient_permissions` | L'identifiant ne dispose pas d'une autorisation requise ; `required_permissions` les énumère toutes. |
| 404 | `not_found` | Le plan n'existe pas, appartient à un autre environnement ou a été supprimé. |
| 409 | `config_conflict` | La cible a changé après la création du plan ; le plan est maintenant `conflicted`. |
| 409 | `config_plan_not_applicable` | Le plan a déjà été appliqué ou est `conflicted`, ou une autre requête est en train de l'appliquer (`plan_status: applying`) ; `plan_status` indique son statut. |
| 409 | `deletion_not_confirmed` | Une suppression protégée est absente de `confirm_deletions` ; `unconfirmed_deletions` les énumère. |
| 409 | `conflict` | Une autre ressource utilise déjà une valeur unique d'une modification (`errors[].pointer` est `/changes/<i>/spec/<field>`, code `duplicate`) ; le plan reste `planned`. |
| 409 | `connection_in_use` | La connexion à supprimer est encore utilisée ; le plan reste `planned`. |
| 409 | `connection_test_required` | Une modification crée une connexion `enterprise` `active` ou en active une sans test de connexion réussi (`errors[].pointer` est `/changes/<i>/spec`, code `test_required`) ; le plan reste `planned`. |
| 410 | `config_plan_expired` | La durée de validité du plan est dépassée ; créez un nouveau plan. |
| 413 | `config_document_too_large` | Les modifications, ou la révision conservée pour elles (10 Mio au maximum), sont trop volumineuses pour être appliquées en une seule requête ; planifiez moins de types. |
| 422 | `validation_failed` | Voir ci-dessous. |
| 422 | `policy_conflict` | La modification est en conflit avec une politique de l'environnement ; le plan reste `planned`. |
| 429 | `rate_limited` | La limite de débit `config_plan` (partagée avec la création de plans) a été dépassée, ou trop de plans sont en cours de calcul ou d'application pour cet environnement à la fois ; le plan reste `planned` ; réessayez après la période `Retry-After`. |
| 501 | `not_implemented` | Le plan nécessite le secret client d'une connexion sociale, qui ne peut pas encore être écrit. |
| 503 | `service_unavailable` | Une dépendance (le répertoire ou, pour le matériel OIDC d'entreprise, le service de fédération) est temporairement indisponible, ou la capacité de planification est temporairement saturée ; rien n'a été écrit ou, pour le service de fédération, seul l'enregistrement de la connexion l'a été. Réessayez après la période `Retry-After`. |

| `errors[].pointer` | `errors[].code` | Signification |
|---|---|---|
| `/resolved_secrets/<NAME>` | `secret_missing` | Le plan nécessite ce secret. |
| `/resolved_secrets/<NAME>` | `secret_unused` | Le plan ne nécessite pas ce secret. |
| `/resolved_secrets/<NAME>` | `secret_invalid` | La valeur est vide, dépasse 8 Kio ou n'est pas du texte. |
| `/resolved_secrets/<NAME>` | `invalid` | Le nom n'a pas la forme `KEENAUTH_…` (`A-Z`, `0-9`, `_`). |
| `/sp_host` | `sp_host_required`, `sp_host_unused`, `invalid` | Le plan crée du matériel OIDC et aucun hôte n'a été fourni, aucun hôte n'est nécessaire, ou l'hôte n'est pas un nom DNS en minuscules. |
| `/changes/<i>/spec/oidc/...` | comme dans l'API OIDC des connexions | Le service de fédération a rejeté le matériel à cette position du plan `changes`. |
| `/confirm_deletions/<i>` | `duplicate`, `not_a_deletion` | L'élément est répété ou n'est pas une suppression dans le plan. |
| `/confirm_deletions/<i>` | `invalid` | L'élément n'a pas la forme `<kind>/<key>`. |
| `/resolved_secrets`, `/confirm_deletions` | `too_many` | Plus de 5 000 entrées. |
| `/changes/<i>/spec/...` | comme dans l'API propre à la ressource | L'environnement a rejeté la modification à cette position du plan `changes`. |

## Exemple

```bash
curl -X POST "https://api.eu.keenauth.com/v1/config-plans/cfgp_01m4qpqtywfs7930tb6kqn5rc5:apply" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm_deletions": ["connection/legacy"]}'

keenauth config apply cfgp_01m4qpqtywfs7930tb6kqn5rc5 --confirm-deletion connection/legacy
```

`keenauth config apply --secrets-from-env` lit chaque secret nécessaire au plan dans la variable d'environnement de même nom et ne l'affiche jamais.
