
# 構成変更をプランする

`POST /v1/config-plans` · 権限 `config:read` と、ドキュメント内の各種類の読み取り権限

構成ドキュメントを環境の現在の構成と比較し、結果をプランとして保存します。何も変更されません。プランはドキュメントを適用すると何が起きるかを示すだけです。`development` から `staging` や `production` への昇格を適用前に確認するために使います。

## リクエスト

| フィールド | 説明 |
|---|---|
| `document` | 必須。[`GET /v1/config-export`](../config-export/get.md) 形式の構成ドキュメント。JSON Schema `https://docs.keenauth.com/config/schema/v1.json` に一致する必要があります。 |
| `prune` | 任意、既定値 `false`。`true` の場合、ドキュメントの種類のうちドキュメントにないリソースの削除がプランされます。 |

- ドキュメントの種類は、`kinds` があればそれ（その場合すべてのリソースは列挙された種類である必要があります）、なければリソースの種類です。比較するのはこれらの種類だけで、その他の種類はそのままです。
- ドキュメントにないリソースは、`prune` が `true` でない限り削除**されません**。単一の種類（`environment_settings`、`identity_settings`、`branding`）は削除されません。
- 単一の種類では、ドキュメントにないフィールドは変更されず、対象の現在の値が保持されます。そのため、フィールドが追加される前にエクスポートされたドキュメントはそのフィールドをリセットしません。`null` を含め明示的に指定した値は書き込まれます。その他の種類では、ないフィールドは既定値になります。
- ドキュメントにシークレットの値を含めてはいけません。シークレットのフィールドには `secret_ref` プレースホルダーか `null` だけを入れます。値は拒否され、レスポンスに返されることはありません。
- ドキュメントの `warnings` は無視されます。受け付けるのは最大 15,000 件です(エクスポートにそれより多く含まれることはありません)。
- 権限は同じ種類をエクスポートする場合と同じです。`prune` を使うと、削除されるリソースを参照しうる種類も読み取るため、それらの権限も必要です。

## レスポンス

`201 Created`。`Location: /v1/config-plans/{plan_id}` と `Cache-Control: no-store` を含みます。

| フィールド | 説明 |
|---|---|
| `id` | プラン ID、`cfgp_…`。 |
| `status` | `planned`。`expires_at` 以降は `expired`。 |
| `expires_at` | プランの有効期間は 1 時間です。期限切れの 7 日後に削除されます。 |
| `summary` | `create`、`update`、`delete`、`no_op` の各アクションの数。 |
| `changes` | リソースごとに 1 件。種類の順、次に `key` の順。 |
| `warnings` | `secret_required`: 接続には、ターゲットにまだないクライアントシークレットが必要です。 |

各変更には `kind`、`key`、`action`（`create`、`update`、`delete`、`no_op`）、`object_version`（プラン時点のターゲットリソースのバージョン。`create` では `null`）、`requires_secret`、`fields`、さらに `create` と `update` では希望する `spec` があります。`fields` の各項目はフィールドの差分です: `pointer`（RFC 6901、例: `/spec/name`）、`op`（RFC 6902 と同じ `add`、`remove`、`replace`）、`before`、`after`。リストは値全体として比較します。

エクスポートしたドキュメントを変更せずに渡すと、リソースや集合的なリストの順序に関係なく、すべてのリソースが `no_op` としてプランされます。

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

プランを作成するたびに、環境の管理ログに `config_plan.created` が記録されます。

## 制限

- **レート:** プランには環境ごとに独自の毎分の上限があります。`production` では Free 20、Startup と Business 60、Enterprise 120、`development` と `staging` ではその 10 分の 1（最低 2）です。
- **環境ごとのクォータ:** 適用も期限切れもしていないプランは最大 100 件、削除されるまで（期限切れの 7 日後）保存されるプランは最大 2,000 件かつ 1 GiB です。他の環境のプランは数えません。
- **容量:** サーバーが同時に計算できる大きなプランの数には限りがあり、1 つの環境が使えるのはその容量の最大 4 分の 1 です。
- **フィールドの制限** は構成サービスが書き込み時に適用するものです。スキーマがリストとテキストを制限し（例: `redirect_uris` 50、`allowed_web_origins` 20、`permissions` 1,000）、接続の `settings` は最大 32 KiB・深さ 8 階層、アプリケーションの `jwks` と各権限の `consent_text_i18n` は最大 16 KiB です。

## エラー

| HTTP | code | 発生条件 |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | 本文が JSON ではないか、`document` と `prune` 以外のフィールドがあります。 |
| 401 | `unauthenticated` | 認証情報がない、無効、または失効しています。 |
| 403 | `insufficient_permissions` | 必要な権限がありません。`required_permissions` にすべて列挙されます。 |
| 413 | `config_document_too_large`, `payload_too_large` | ドキュメントが 5,000 リソースまたは 10 MiB を超えています。種類を減らしてプランしてください。 |
| 422 | `validation_failed` | 下記を参照してください。`errors[].pointer` はリクエスト内（`/document/...`）を指します。 |
| 429 | `rate_limited` | プランのレート制限または環境のプランクォータに達したか、環境が同時に多くのプランを計算しています。`Retry-After` の時間が経過してから再試行してください（クォータの場合は最も古いプランが期限切れまたは削除されたとき）。 |
| 503 | `service_unavailable` | プランの計算が容量の上限に達しているか、依存サービスが一時的に利用できません。`Retry-After` の時間が経過してから再試行してください。 |

| `errors[].code` | 意味 |
|---|---|
| `required` | `document` がありません。 |
| `invalid` | このポインターでドキュメントがスキーマに一致しません。 |
| `duplicate` | 2 つのリソースの `kind` と `key` が同じです。 |
| `kind_not_listed` | リソースの種類が `kinds` にありません。 |
| `secret_value` | シークレットの値が見つかりました。`secret_ref` プレースホルダーを使ってください。 |
| `unresolved_reference` | 参照している `key` は変更後に存在しません。 |
| `key_in_use` | `key` は構成で管理されないアプリケーション（組織所有または自己登録）またはシステム API に属しています。 |
| `still_referenced` | `/prune`: 残るリソースが、プランで削除されるリソースを参照しています。 |
| `too_large` | リソース、警告、または最上位のフィールドに 200,000 を超える JSON 値があります。 |
| `too_long` | 接続の `settings` が 32 KiB を超えているか、アプリケーションの `jwks` または権限の `consent_text_i18n` が 16 KiB を超えています。 |
| `too_deep` | 接続の `settings` が 8 階層より深くネストされています。 |
| `reserved` | API の `identifier` がプラットフォームの `urn:idp:` 名前空間にあります。 |

## 例

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