
# Mettre à jour les paramètres de l'environnement

`PATCH /v1/environment-settings` · autorisation `environment-settings:write`

Met à jour les paramètres de l'environnement avec un correctif de fusion JSON (`application/merge-patch+json`, RFC 7396). `If-Match` est obligatoire : son absence renvoie `428`, et une version obsolète renvoie `412`. Si les paramètres n'ont jamais été écrits, la version est `"0"` et la première écriture crée la ligne. Un correctif qui ne modifie rien n'écrit rien et conserve la version.

## Requête

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `If-Match` | en-tête | oui | L'`ETag` des paramètres tel que lu en dernier (`"0"` s'ils n'ont jamais été écrits). Absent : `428` ; obsolète : `412`. |
| `locales.enabled` | string[] | non | Codes de langues activées (langues enregistrées), 1 à 64 entrées, sans doublon. Ne peut pas être `null`. |
| `locales.default` | string | non | Langue par défaut ; doit figurer dans `locales.enabled`. Ne peut pas être `null`. |
| `default_zoneinfo` | string | non | Nom de fuseau horaire IANA (par défaut `UTC`). Ne peut pas être `null`. |
| `session.idle_timeout_s` | integer | non | Délai d'inactivité en secondes, de 60 à 8640000 (par défaut 259200) ; non supérieur à `session.absolute_timeout_s`. Ne peut pas être `null`. |
| `session.absolute_timeout_s` | integer | non | Durée de vie absolue en secondes, de 60 à 31536000 (par défaut 2592000). Ne peut pas être `null`. |
| `session.max_concurrent` | integer | non | Sessions simultanées par utilisateur, de 0 à 500 ; `0` signifie la limite du système (par défaut). Ne peut pas être `null`. |
| `session.concurrent_mode` | string | non | `evict_oldest` (par défaut) ou `deny_new`. Ne peut pas être `null`. |
| `session.anomaly_action` | string | non | `enforce` (par défaut) ou `log`. Ne peut pas être `null`. |
| `signup.mode` | string | non | `open` (par défaut), `invite_only` ou `closed`. Ne peut pas être `null`. |
| `signup.email_domain_mode` | string | non | `off` (par défaut), `allowlist` ou `denylist`. Ne peut pas être `null`. |
| `signup.email_domains` | string[] \| null | non | 1000 domaines au maximum ; remplace la liste entière (RFC 7396 ne fusionne pas les tableaux). `null` ou `[]` la vide. Les entrées sont normalisées (minuscules, étiquette A IDNA), les doublons sont supprimés et la liste est stockée triée. Conservée tant que le mode est `off`. |
| `security_notifications.enabled` | boolean | non | Interrupteur général (par défaut `true`). Ne peut pas être `null`. |
| `security_notifications.events` | string[] \| null | non | Sous-ensemble des 12 événements de sécurité (`password_changed`, `password_reset`, `email_changed`, `phone_changed`, `authenticator_added`, `authenticator_removed`, `recovery_code_used`, `recovery_codes_regenerated`, `account_locked`, `breached_password_detected`, `new_device_sign_in`, `account_recovery_started`) ; remplace la liste entière et est renvoyé dans l'ordre canonique. `null` ou `[]` la vide (aucun événement). |
| `account_recovery.mode` | string | non | `self_service_waiting` (par défaut), `admin_only` ou `disabled`. Ne peut pas être `null`. |
| `account_recovery.waiting_hours` | integer | non | De 24 à 168 heures (par défaut 72) ; enregistré dans tous les modes. Ne peut pas être `null`. |
| `account_recovery.support_url` | string | non | Vide ou URL `https://` de 2048 octets au maximum, sans informations utilisateur ni fragment. Affichée lorsque la récupération est `disabled`. Ne peut pas être `null`. |
| `mfa.email_second_factor_enabled` | boolean | non | Second facteur par e-mail activé ou désactivé (par défaut `false`). Ne peut pas être `null`. |
| `mfa.email_second_factor_acr` | string | non | Niveau d'assurance accordé par le facteur e-mail : `1fa` (par défaut) ou `mfa`. Ne peut pas être `null`. |
| `mfa.totp_issuer_label` | string | non | Libellé d'émetteur TOTP, 64 caractères au maximum ; vide signifie le nom d'affichage de l'environnement. Aucun caractère de contrôle, `:` ni espace en début ou en fin. Ne peut pas être `null`. |
| `mfa.recovery_codes_enabled` | boolean | non | Codes de récupération activés ou désactivés (par défaut `true`). Ne peut pas être `null`. |
| `mfa.recovery_code_count` | integer | non | Nombre de codes de récupération, de 8 à 16 (par défaut 10). Ne peut pas être `null`. |
| `mfa.required` | boolean | non | Exiger un second facteur à chaque connexion dans l'environnement (par défaut `false`). Combiné par OU avec les exigences de l'application, de l'organisation et de la requête ; une session SSO existante inférieure à ce niveau est soumise à une élévation (`interaction_required` avec `prompt=none`). Ne peut pas être `null`. |
| `mfa.min_acr` | string | non | Niveau d'assurance minimal que chaque connexion de l'environnement doit atteindre : `1fa` (par défaut, sans effet), `mfa` ou `phr`. Le seuil le plus élevé parmi ceux de l'environnement, de l'application, de l'organisation et de la requête s'applique, aux nouvelles connexions comme à la réutilisation des sessions SSO. Ne peut pas être `null`. |
| `mfa.enrollment_grace_days` | integer | non | Nombre de jours pendant lesquels un utilisateur sans second facteur peut continuer à se connecter sans l'enregistrer, compté à partir de la première fois que l'enregistrement lui est demandé, de 0 à 14 (par défaut `0`, pas de délai de grâce). Le délai ne reporte que ce qu'exigent les axes de l'environnement et de l'organisation ; le `min_acr` d'une application et les `acr_values` demandés ne sont jamais reportés. Une valeur hors plage renvoie `422 validation_failed` avec `out_of_range`. Une réduction prend effet immédiatement. Ne peut pas être `null`. |
| `frontchannel_logout_enabled` | boolean | non | Front-Channel Logout activé ou désactivé (par défaut `false`) ; le désactiver conserve les URI enregistrées. Ne peut pas être `null`. |
| `allowed_first_factors` | string[] | non | Premiers facteurs autorisés : au moins un parmi `password`, `passkey`, `email_otp`, `magic_link`, `sms_otp`, `social`, `enterprise`, sans doublon. Remplace la liste entière et est renvoyé dans cet ordre. Par défaut `password`, `passkey`, `social`, `enterprise` ; `email_otp`, `magic_link` et `sms_otp` ne sont activés que s'ils sont ajoutés explicitement. Une liste vide renvoie `422 required` (un environnement dans lequel personne ne pourrait se connecter). Ne peut pas être `null`. |

## Comportement

- Les groupes se fusionnent récursivement : n'envoyez que les champs modifiés dans `locales`, `session`, `signup`, `security_notifications`, `account_recovery` et `mfa`. Un groupe lui-même ne peut pas être `null`.
- La réduction de la durée d'une session, la baisse de la limite de sessions simultanées, le resserrement de l'inscription ou de la récupération de compte, le passage du second facteur e-mail de `mfa` à `1fa`, ainsi que la suppression d'un facteur de `allowed_first_factors`, s'appliquent immédiatement aux nouvelles sessions et connexions ; les sessions existantes conservent leur propre durée de vie.
- Les erreurs de validation portent le pointeur JSON du champ, par exemple `/session/idle_timeout_s` avec le code `out_of_range`. Une erreur dans une entrée de liste pointe vers l'entrée : `/allowed_first_factors/1` avec `unknown_value` (valeur inconnue) ou `duplicate` (valeur répétée).
- `allowed_first_factors` est intersecté avec les politiques de l'application et de l'organisation : elles ne peuvent que restreindre cet ensemble.
- Si le nouvel ensemble ne laisse aucune méthode commune avec une application qui possède son propre `allowed_first_factors`, la requête renvoie `422 policy_conflict` ; modifiez d'abord l'ensemble de l'application.
- L'enregistrement d'audit et l'événement de modification contiennent les noms des champs modifiés, jamais leurs valeurs. La protection contre les attaques est une ressource distincte et ne fait pas partie de celle-ci.

## Réponse

`200 OK` avec les paramètres mis à jour et le nouvel `ETag`.

```json
{
  "locales": {"enabled": ["en", "tr"], "default": "tr"},
  "default_zoneinfo": "Europe/Istanbul",
  "session": {"idle_timeout_s": 3600, "absolute_timeout_s": 2592000, "max_concurrent": 5, "concurrent_mode": "evict_oldest", "anomaly_action": "enforce"},
  "signup": {"mode": "invite_only", "email_domain_mode": "allowlist", "email_domains": ["example.com"]},
  "security_notifications": {"enabled": true, "events": ["password_changed", "password_reset"]},
  "account_recovery": {"mode": "self_service_waiting", "waiting_hours": 72, "support_url": ""},
  "mfa": {"email_second_factor_enabled": false, "email_second_factor_acr": "1fa", "totp_issuer_label": "Acme", "recovery_codes_enabled": true, "recovery_code_count": 10, "required": false, "min_acr": "1fa", "enrollment_grace_days": 0},
  "frontchannel_logout_enabled": false,
  "allowed_first_factors": ["password", "passkey", "magic_link", "enterprise"],
  "updated_at": "2026-10-05T09:30:00.123456Z"
}
```

## Erreurs

| HTTP | code | Quand |
|---|---|---|
| 400 | `unknown_field` | Le correctif contient un champ inconnu ou en lecture seule, ou n'est pas un objet JSON (`malformed_request`). |
| 412 | `precondition_failed` | `If-Match` ne correspond pas à la version actuelle. |
| 415 | `unsupported_media_type` | Le type de contenu n'est pas `application/merge-patch+json`. |
| 422 | `validation_failed` | Une valeur est invalide (le pointeur désigne le champ), ou un champ qui ne peut pas être `null` a été défini à `null` (`code: invalid`). |
| 422 | `policy_conflict` | Le nouveau `allowed_first_factors` ne laisse aucune méthode commune avec une application qui possède son propre ensemble. |
| 428 | `precondition_required` | L'en-tête `If-Match` est absent. |
| 401 | `unauthenticated` | L'identifiant est absent, invalide ou révoqué. |
| 403 | `insufficient_permissions` | L'identifiant ne possède pas l'autorisation requise. |
| 429 | `rate_limited` | La limite de débit a été dépassée ; réessayez après la période indiquée par `Retry-After`. |
| 503 | `service_unavailable` | Une dépendance est temporairement indisponible ; réessayez après la période indiquée par `Retry-After`. |

## Exemple

Activer les liens magiques par e-mail et désactiver les mots de passe :

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/environment-settings \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H 'If-Match: "0"' \
  -d '{"allowed_first_factors":["passkey","magic_link","social","enterprise"]}'
```

Paramètres de session et d'inscription :

```bash
curl -X PATCH https://api.eu.keenauth.com/v1/environment-settings \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H 'If-Match: "0"' \
  -d '{"session":{"idle_timeout_s":3600},"signup":{"mode":"invite_only","email_domain_mode":"allowlist","email_domains":["example.com"]}}'
```
