
# Définir le CSS personnalisé

`PUT /v1/branding/css` · permission `branding:write`

Écrit le CSS personnalisé du brouillon de l'environnement (plan Startup ou supérieur, fonctionnalité `branding_custom_css`). Le CSS passe par un assainisseur à liste blanche : seules les classes `ui_catalog` (`idp-*`) et les propriétés et valeurs autorisées sont acceptées ; la couche protégée (texte légal, consentement et éléments d'accessibilité) ne peut être ni masquée ni recouverte. Tout ou rien : à la moindre violation, rien n'est écrit. Le brouillon enregistre la sortie canonique de l'assainisseur, et non le texte envoyé ; `css` dans la réponse est cette sortie. Une chaîne vide l'efface et n'est pas soumise au plan.

## Requête

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `If-Match` | en-tête | oui | L'`ETag` du brouillon lu en dernier (`"0"` s'il n'a jamais été écrit). Absent : `428` ; périmé : `412`. |
| `css` | chaîne | oui | 51200 octets au plus, UTF-8 valide ; `""` efface. |

## Réponse

`200 OK` avec le brouillon de l'environnement et son `ETag`. `theme` ne contient que les jetons définis ; les autres héritent de la valeur par défaut de la plateforme.

```json
{
  "theme": {"mode": "light", "light": {"color": {"primary": "#0a3d91"}}, "logo_height": 40},
  "css": ".idp-c .idp-title{color:#0a3d91}\n",
  "page_template": null,
  "updated_at": "2026-10-05T09:30:00.123456Z"
}
```

## Erreurs

| HTTP | code | Quand |
|---|---|---|
| 400 | `unknown_field` | Le corps contient un champ inconnu ou n'est pas un objet JSON. |
| 403 | `plan_feature_unavailable` | Le plan n'inclut pas la fonctionnalité (`plan_feature_unavailable`, extensions `feature` et `required_plan`). Les environnements de développement ne sont pas restreints. |
| 412 | `precondition_failed` | `If-Match` ne correspond pas à la version actuelle du brouillon. |
| 422 | `validation_failed` | `css` est absent ou `null` (`required`), trop long (`too_long`) ou n'est pas de l'UTF-8 valide (`invalid_utf8`). |
| 422 | `branding_validation_failed` | Le CSS n'a pas passé l'assainisseur. L'extension `violations` liste toutes les violations : `line`, `column` (à partir de 1), `rule` (par exemple `class_not_in_catalog`, `protected_property`, `property_not_allowed`, `syntax_error`) et, le cas échéant, `name` (nom de la propriété, de la classe ou de la fonction). Les valeurs CSS ne sont jamais renvoyées. Après 100 violations, l'assainisseur s'arrête ; le dernier élément a la règle `too_many_issues`. Le résultat canonique est plus long que l'entrée (chaque règle reçoit un préfixe de portée) ; s'il dépasse 51200 octets, la seule violation est la règle `too_large` à la ligne 1, colonne 1. |
| 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 n'a pas la permission requise. |
| 429 | `rate_limited` | La limite de débit est dépassée ; réessayez après le délai `Retry-After`. |
| 503 | `service_unavailable` | Une dépendance est temporairement indisponible ; réessayez après le délai `Retry-After`. |

Exemple de rejet par l'assainisseur :

```css
.idp-button{position:fixed}
.my-banner{color:red}
```

```json
{
  "type": "https://docs.keenauth.com/errors/branding_validation_failed",
  "title": "Branding validation failed",
  "status": 422,
  "code": "branding_validation_failed",
  "violations": [
    {"line": 1, "column": 13, "rule": "property_not_allowed", "name": "position"},
    {"line": 2, "column": 1, "rule": "class_not_in_catalog", "name": "my-banner"}
  ],
  "request_id": "req_01J9Z3N5W8Q2F6V4K7T1M0XB3C"
}
```

## Exemple

```bash
curl -X PUT https://api.eu.keenauth.com/v1/branding/css \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "3"' \
  -d '{"css":".idp-title { color: #0a3d91 }"}'
```
