
# Eine Konfigurationsänderung planen

`POST /v1/config-plans` · Berechtigung `config:read` und die Leseberechtigung jedes Typs im Dokument

Vergleicht ein Konfigurationsdokument mit der aktuellen Konfiguration der Umgebung und speichert das Ergebnis als Plan. Es wird nichts geändert: Ein Plan zeigt nur, was das Anwenden des Dokuments bewirken würde. Prüfen Sie damit eine Übernahme von `development` nach `staging` oder `production`, bevor Sie sie anwenden.

## Anfrage

| Feld | Beschreibung |
|---|---|
| `document` | Erforderlich. Ein Konfigurationsdokument im Format von [`GET /v1/config-export`](../config-export/get.md). Es muss dem JSON Schema `https://docs.keenauth.com/config/schema/v1.json` entsprechen. |
| `prune` | Optional, Standard `false`. Bei `true` wird das Löschen von Ressourcen der Dokumenttypen geplant, die im Dokument fehlen. |

- Die Typen des Dokuments sind `kinds`, falls angegeben (dann muss jede Ressource einen aufgeführten Typ haben), sonst die Typen seiner Ressourcen. Nur diese Typen werden verglichen; andere bleiben unverändert.
- Eine im Dokument fehlende Ressource wird **nicht** gelöscht, außer `prune` ist `true`. Die Singleton-Typen (`environment_settings`, `identity_settings`, `branding`) werden nie gelöscht.
- Bei den Singleton-Arten bleibt ein im Dokument fehlendes Feld unverändert: Der aktuelle Wert des Ziels wird beibehalten. Ein Dokument, das exportiert wurde, bevor es ein Feld gab, setzt dieses Feld daher nicht zurück. Ein explizit angegebener Wert, auch `null`, wird weiterhin geschrieben. Bei den anderen Arten erhält ein fehlendes Feld seinen Standardwert.
- Das Dokument darf keine geheimen Werte enthalten. Ein geheimes Feld enthält nur einen `secret_ref`-Platzhalter oder `null`; ein Wert wird abgelehnt und nie zurückgegeben.
- `warnings` im Dokument werden ignoriert; höchstens 15.000 werden angenommen (ein Export enthält nie mehr).
- Die Berechtigungen sind die eines Exports derselben Typen. Mit `prune` werden auch die Typen gelesen, die auf eine gelöschte Ressource verweisen können, daher sind auch deren Berechtigungen erforderlich.

## Antwort

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

| Feld | Beschreibung |
|---|---|
| `id` | Plan-ID, `cfgp_…`. |
| `status` | `planned`; nach `expires_at` `expired`. |
| `expires_at` | Der Plan ist 1 Stunde gültig. Er wird 7 Tage nach Ablauf gelöscht. |
| `summary` | Anzahl der Aktionen `create`, `update`, `delete` und `no_op`. |
| `changes` | Ein Eintrag pro Ressource, in Typreihenfolge und dann nach `key`. |
| `warnings` | `secret_required`: Die Verbindung benötigt ein Client-Secret, das das Ziel noch nicht hat. |

Jede Änderung hat `kind`, `key`, `action` (`create`, `update`, `delete` oder `no_op`), `object_version` (die Version der Zielressource zum Planzeitpunkt; `null` bei `create`), `requires_secret`, `fields` und bei `create` und `update` den gewünschten `spec`. Jeder Eintrag in `fields` ist ein Feldunterschied: `pointer` (RFC 6901, zum Beispiel `/spec/name`), `op` (`add`, `remove` oder `replace` wie in RFC 6902), `before` und `after`. Listen werden als ganze Werte verglichen.

Ein unverändert übergebenes exportiertes Dokument plant für jede Ressource `no_op`, unabhängig von der Reihenfolge der Ressourcen und mengenartigen Listen.

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

Jeder Plan schreibt einen Eintrag `config_plan.created` in das Verwaltungsprotokoll der Umgebung.

## Grenzen

- **Rate:** Pläne haben ein eigenes Limit pro Minute und Umgebung: in `production` 20 bei Free, 60 bei Startup und Business, 120 bei Enterprise; in `development` und `staging` ein Zehntel (mindestens 2).
- **Kontingent pro Umgebung:** höchstens 100 Pläne, die weder angewendet noch abgelaufen sind, und höchstens 2.000 Pläne und 1 GiB gespeicherte Pläne, bis sie gelöscht werden (7 Tage nach Ablauf). Pläne anderer Umgebungen zählen nie.
- **Kapazität:** Der Server berechnet nur eine begrenzte Zahl großer Pläne gleichzeitig; eine Umgebung kann höchstens ein Viertel dieser Kapazität nutzen.
- **Feldgrenzen** sind die, die der Konfigurationsdienst beim Schreiben durchsetzt: Das Schema begrenzt Listen und Texte (zum Beispiel `redirect_uris` 50, `allowed_web_origins` 20, `permissions` 1.000), `settings` einer Verbindung ist höchstens 32 KiB groß und 8 Ebenen tief, `jwks` einer Anwendung und `consent_text_i18n` jeder Berechtigung höchstens 16 KiB.

## Fehler

| HTTP | code | Wann |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | Der Body ist kein JSON oder enthält ein anderes Feld als `document` und `prune`. |
| 401 | `unauthenticated` | Die Anmeldedaten fehlen, sind ungültig oder widerrufen. |
| 403 | `insufficient_permissions` | Den Anmeldedaten fehlt eine erforderliche Berechtigung; `required_permissions` listet alle auf. |
| 413 | `config_document_too_large`, `payload_too_large` | Das Dokument hat mehr als 5.000 Ressourcen oder 10 MiB; planen Sie weniger Typen. |
| 422 | `validation_failed` | Siehe unten; `errors[].pointer` zeigt in die Anfrage (`/document/...`). |
| 429 | `rate_limited` | Das Planlimit oder das Plankontingent der Umgebung ist erreicht, oder die Umgebung berechnet zu viele Pläne; wiederholen Sie nach der `Retry-After`-Zeit (beim Kontingent, wenn der älteste Plan abläuft oder gelöscht wird). |
| 503 | `service_unavailable` | Die Planberechnung ist ausgelastet oder eine Abhängigkeit ist vorübergehend nicht verfügbar; wiederholen Sie nach der `Retry-After`-Zeit. |

| `errors[].code` | Bedeutung |
|---|---|
| `required` | `document` fehlt. |
| `invalid` | Das Dokument entspricht an diesem Pointer nicht dem Schema. |
| `duplicate` | Zwei Ressourcen haben dieselbe `kind` und `key`. |
| `kind_not_listed` | Der Typ der Ressource steht nicht in `kinds`. |
| `secret_value` | Ein geheimer Wert wurde gefunden; verwenden Sie einen `secret_ref`-Platzhalter. |
| `unresolved_reference` | Der referenzierte `key` würde nach der Änderung nicht existieren. |
| `key_in_use` | Der `key` gehört zu einer Anwendung, die nicht über die Konfiguration verwaltet wird (organisationseigen oder selbst registriert), oder zur System-API. |
| `still_referenced` | `/prune`: Eine verbleibende Ressource verweist auf eine Ressource, die der Plan löschen würde. |
| `too_large` | Eine Ressource, eine Warnung oder ein Feld der obersten Ebene hat mehr als 200.000 JSON-Werte. |
| `too_long` | `settings` einer Verbindung überschreitet 32 KiB, oder `jwks` einer Anwendung bzw. `consent_text_i18n` einer Berechtigung überschreitet 16 KiB. |
| `too_deep` | `settings` einer Verbindung ist tiefer als 8 Ebenen verschachtelt. |
| `reserved` | Der `identifier` der API liegt im Namensraum `urn:idp:` der Plattform. |

## Beispiel

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