
# Impostare il CSS personalizzato

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

Scrive il CSS personalizzato della bozza dell'ambiente (piano Startup o superiore, funzionalità `branding_custom_css`). Il CSS passa per un sanificatore a lista consentita: solo le classi `ui_catalog` (`idp-*`) e le proprietà e i valori consentiti; il livello protetto (testo legale, consenso ed elementi di accessibilità) non può essere nascosto né coperto. Tutto o niente: con una sola violazione non viene scritto nulla. La bozza memorizza l'output canonico del sanificatore, non il testo inviato; `css` nella risposta è quell'output. Una stringa vuota lo cancella e non è soggetta al piano.

## Richiesta

| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| `If-Match` | intestazione | sì | L'`ETag` della bozza letto per ultimo (`"0"` se mai scritta). Assente: `428`; obsoleto: `412`. |
| `css` | stringa | sì | Al massimo 51200 byte, UTF-8 valido; `""` cancella. |

## Risposta

`200 OK` con la bozza dell'ambiente e il suo `ETag`. `theme` contiene solo i token impostati; gli altri ereditano il valore predefinito della piattaforma.

```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"
}
```

## Errori

| HTTP | code | Quando |
|---|---|---|
| 400 | `unknown_field` | Il corpo contiene un campo sconosciuto o non è un oggetto JSON. |
| 403 | `plan_feature_unavailable` | Il piano non include la funzionalità (`plan_feature_unavailable`, estensioni `feature` e `required_plan`). Gli ambienti di sviluppo non sono limitati. |
| 412 | `precondition_failed` | `If-Match` non corrisponde alla versione corrente della bozza. |
| 422 | `validation_failed` | `css` è assente o `null` (`required`), troppo lungo (`too_long`) oppure non è UTF-8 valido (`invalid_utf8`). |
| 422 | `branding_validation_failed` | Il CSS non ha superato il sanificatore. L'estensione `violations` elenca ogni violazione: `line`, `column` (a partire da 1), `rule` (ad esempio `class_not_in_catalog`, `protected_property`, `property_not_allowed`, `syntax_error`) e, se presente, `name` (nome della proprietà, della classe o della funzione). I valori CSS non vengono mai restituiti. Dopo 100 violazioni il sanificatore si ferma; l'ultimo elemento ha la regola `too_many_issues`. L'output canonico è più lungo dell'input (ogni regola riceve un prefisso di ambito); se supera i 51200 byte, l'unica violazione è la regola `too_large` alla riga 1, colonna 1. |
| 428 | `precondition_required` | L'intestazione `If-Match` è assente. |
| 401 | `unauthenticated` | La credenziale manca, non è valida o è stata revocata. |
| 403 | `insufficient_permissions` | La credenziale non ha il permesso richiesto. |
| 429 | `rate_limited` | Il limite di frequenza è stato superato; riprovare dopo il periodo `Retry-After`. |
| 503 | `service_unavailable` | Una dipendenza è temporaneamente non disponibile; riprovare dopo il periodo `Retry-After`. |

Esempio di rifiuto del sanificatore:

```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"
}
```

## Esempio

```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 }"}'
```
