
# Pianificare una modifica della configurazione

`POST /v1/config-plans` · permesso `config:read` e il permesso di lettura di ogni tipo nel documento

Confronta un documento di configurazione con la configurazione attuale dell'ambiente e salva il risultato come piano. Non viene modificato nulla: un piano mostra solo cosa farebbe l'applicazione del documento. Usalo per verificare una promozione da `development` a `staging` o `production` prima di applicarla.

## Richiesta

| Campo | Descrizione |
|---|---|
| `document` | Obbligatorio. Un documento di configurazione nel formato di [`GET /v1/config-export`](../config-export/get.md). Deve rispettare il JSON Schema `https://docs.keenauth.com/config/schema/v1.json`. |
| `prune` | Facoltativo, predefinito `false`. Se `true`, le risorse dei tipi del documento assenti dal documento vengono pianificate per l'eliminazione. |

- I tipi del documento sono `kinds` se indicato (ogni risorsa deve allora essere di un tipo elencato), altrimenti i tipi delle sue risorse. Vengono confrontati solo questi tipi; gli altri restano invariati.
- Una risorsa assente dal documento **non** viene eliminata a meno che `prune` sia `true`. I tipi singleton (`environment_settings`, `identity_settings`, `branding`) non vengono mai eliminati.
- Nei tipi singleton un campo assente dal documento resta invariato: viene mantenuto il valore attuale della destinazione. Un documento esportato prima che un campo esistesse quindi non reimposta quel campo. Un valore indicato esplicitamente, `null` compreso, viene comunque scritto. Negli altri tipi un campo assente assume il valore predefinito.
- Il documento non deve contenere valori segreti. Un campo segreto contiene solo un segnaposto `secret_ref` o `null`; un valore viene rifiutato e non viene mai restituito.
- I `warnings` del documento vengono ignorati; ne sono accettati al massimo 15.000 (un'esportazione non ne contiene mai di più).
- I permessi sono quelli di un'esportazione degli stessi tipi. Con `prune` vengono letti anche i tipi che possono fare riferimento a una risorsa eliminata, quindi servono anche i loro permessi.

## Risposta

`201 Created` con `Location: /v1/config-plans/{plan_id}` e `Cache-Control: no-store`.

| Campo | Descrizione |
|---|---|
| `id` | ID del piano, `cfgp_…`. |
| `status` | `planned`; `expired` dopo `expires_at`. |
| `expires_at` | Il piano è valido per 1 ora. Viene eliminato 7 giorni dopo la scadenza. |
| `summary` | Numero di azioni `create`, `update`, `delete` e `no_op`. |
| `changes` | Una voce per risorsa, in ordine di tipo e poi per `key`. |
| `warnings` | `secret_required`: la connessione richiede un client secret che la destinazione non ha ancora. |

Ogni modifica ha `kind`, `key`, `action` (`create`, `update`, `delete` o `no_op`), `object_version` (la versione della risorsa di destinazione al momento del piano; `null` per `create`), `requires_secret`, `fields` e, per `create` e `update`, lo `spec` desiderato. Ogni voce di `fields` è una differenza di campo: `pointer` (RFC 6901, ad esempio `/spec/name`), `op` (`add`, `remove` o `replace`, come in RFC 6902), `before` e `after`. Gli elenchi vengono confrontati come valori interi.

Un documento esportato inviato senza modifiche pianifica `no_op` per ogni risorsa, indipendentemente dall'ordine delle risorse e degli elenchi di tipo insieme.

```json
{
  "id": "cfgp_01m4qpqtywfs7930tb6kqn5rc5",
  "status": "planned",
  "prune": false,
  "kinds": ["connection"],
  "expires_at": "2026-10-12T11:00:00Z",
  "summary": {"create": 1, "update": 1, "delete": 0, "no_op": 1},
  "changes": [
    {"kind": "connection", "key": "db", "action": "no_op", "object_version": 5, "requires_secret": false, "fields": []},
    {"kind": "connection", "key": "github", "action": "create", "object_version": null, "requires_secret": true, "fields": [], "spec": {"type": "social", "strategy": "github", "name": "GitHub", "display_name": null, "status": "active", "settings": {}, "client_secret": {"secret_ref": {"source": "env", "name": "KEENAUTH_CONNECTION_GITHUB_CLIENT_SECRET"}}}},
    {"kind": "connection", "key": "google", "action": "update", "object_version": 4, "requires_secret": false, "fields": [{"pointer": "/spec/name", "op": "replace", "before": "Google", "after": "Google Workspace"}], "spec": {"type": "social", "strategy": "google", "name": "Google Workspace", "display_name": null, "status": "active", "settings": {}, "client_secret": {"secret_ref": {"source": "env", "name": "KEENAUTH_CONNECTION_GOOGLE_CLIENT_SECRET"}}}}
  ],
  "warnings": [{"code": "secret_required", "kind": "connection", "key": "github", "pointer": "/spec/client_secret"}]
}
```

Ogni piano scrive una voce `config_plan.created` nel registro di gestione dell'ambiente.

## Limiti

- **Frequenza:** i piani hanno un proprio limite al minuto per ambiente: in `production` 20 su Free, 60 su Startup e Business, 120 su Enterprise; in `development` e `staging` un decimo (almeno 2).
- **Quota per ambiente:** al massimo 100 piani né applicati né scaduti, e al massimo 2.000 piani e 1 GiB di piani archiviati fino alla loro eliminazione (7 giorni dopo la scadenza). I piani di altri ambienti non contano mai.
- **Capacità:** il server calcola un numero limitato di piani grandi contemporaneamente; un ambiente può usare al massimo un quarto di questa capacità.
- **Limiti dei campi:** sono quelli che il servizio di configurazione applica in scrittura: lo schema limita liste e testi (ad esempio `redirect_uris` 50, `allowed_web_origins` 20, `permissions` 1.000), i `settings` di una connessione sono al massimo 32 KiB e 8 livelli di profondità, i `jwks` di un'applicazione e il `consent_text_i18n` di ogni permesso al massimo 16 KiB.

## Errori

| HTTP | code | Quando |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | Il corpo non è JSON o contiene un campo diverso da `document` e `prune`. |
| 401 | `unauthenticated` | Le credenziali mancano, non sono valide o sono state revocate. |
| 403 | `insufficient_permissions` | Alle credenziali manca un permesso richiesto; `required_permissions` li elenca tutti. |
| 413 | `config_document_too_large`, `payload_too_large` | Il documento supera 5.000 risorse o 10 MiB; pianifica meno tipi. |
| 422 | `validation_failed` | Vedi sotto; `errors[].pointer` punta all'interno della richiesta (`/document/...`). |
| 429 | `rate_limited` | È stato raggiunto il limite di frequenza dei piani o la quota di piani dell'ambiente, oppure l'ambiente sta calcolando troppi piani; riprova dopo il periodo `Retry-After` (per la quota, quando il piano più vecchio scade o viene eliminato). |
| 503 | `service_unavailable` | Il calcolo dei piani è al limite di capacità o una dipendenza è temporaneamente non disponibile; riprova dopo il periodo `Retry-After`. |

| `errors[].code` | Significato |
|---|---|
| `required` | `document` manca. |
| `invalid` | Il documento non rispetta lo schema in questo puntatore. |
| `duplicate` | Due risorse hanno lo stesso `kind` e la stessa `key`. |
| `kind_not_listed` | Il tipo della risorsa non è in `kinds`. |
| `secret_value` | È stato trovato un valore segreto; usa un segnaposto `secret_ref`. |
| `unresolved_reference` | La `key` a cui si fa riferimento non esisterebbe dopo la modifica. |
| `key_in_use` | La `key` appartiene a un'applicazione non gestita dalla configurazione (di proprietà di un'organizzazione o auto-registrata) o all'API di sistema. |
| `still_referenced` | `/prune`: una risorsa che resta fa riferimento a una risorsa che il piano eliminerebbe. |
| `too_large` | Una risorsa, un avviso o un campo di primo livello ha più di 200.000 valori JSON. |
| `too_long` | I `settings` di una connessione superano 32 KiB, oppure i `jwks` di un'applicazione o il `consent_text_i18n` di un permesso superano 16 KiB. |
| `too_deep` | I `settings` di una connessione sono annidati oltre 8 livelli. |
| `reserved` | L'`identifier` dell'API è nello spazio dei nomi `urn:idp:` della piattaforma. |

## Esempio

```bash
curl -X POST "https://api.eu.keenauth.com/v1/config-plans" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"document\": $(cat keenauth.json), \"prune\": false}"

keenauth config plan -f keenauth.yaml
```
