
# 構成プランを適用

`POST /v1/config-plans/{plan_id}:apply` · 権限 `config:write`、プランの読み取り権限と、変更される各種類の書き込み権限

保存済みの[プラン](create.md)を環境に適用します。すべての変更は1つのトランザクションで書き込まれ、1つでも失敗した場合は何も保持されません。プランは一度だけ適用できます。

## リクエスト

| パラメーター | 説明 |
|---|---|
| `plan_id` | プランの ID、`cfgp_…`。 |

本文は省略可能です。

| フィールド | 説明 |
|---|---|
| `resolved_secrets` | `requires_secret: true` を持つ変更ごとに、`secret_ref` の name → value のオブジェクトを指定します（例: `KEENAUTH_CONNECTION_GITHUB_CLIENT_SECRET`）。値は最大 8 KiB です。プランによって保存、ログ記録、返却されることはありません。 |
| `confirm_deletions` | 確認する削除の `<kind>/<key>` のリスト（例: `connection/legacy`）。プランの `deletion_requires_confirmation` 警告に、それが必要な削除が一覧されます。 |
| `sp_host` | 環境のサービスプロバイダーのホスト（例: `login.example.com`）。プランがエンタープライズ接続の OIDC 素材を作成する場合にのみ必要です（コールバック `https://<sp_host>/sso/<connection id>/callback` はこのホストから固定されます）。必要のないリクエストで送信してはなりません。 |

- 書き込み権限は `environment-settings:write`、`identity-settings:write`、`attack-protection:write`、`branding:write`（`branding` および `application_branding` 用）、`connections:write`、`apis:write`、`roles:write`、`applications:write` です。変更（`no_op` 以外）がある種類についてのみ必要です。
- 書き込み前に対象が再読み取りされます。計画された任意のリソースのバージョン、またはプランの種類の内容が、プラン作成後に変わっていた場合は、何も書き込まれず、プランは `conflicted` になり、リクエストは `409 config_conflict` で失敗します。新しいプランを作成してください。`prune` を指定すると、計画されたリソースを参照する種類も比較され、リソース間の参照が再確認されます。参照が壊れている場合も、プランは `conflicted` になります。
- 接続の削除、または過去 30 日間にトークンを受け取ったアプリケーションの削除は、`confirm_deletions` で繰り返す必要があります。そうしないと、リクエストは `409 deletion_not_confirmed` で失敗し、プランは `planned` のままです。
- エンタープライズ接続の OIDC 素材（`spec.oidc`）とそのクライアントシークレットは、接続レコードの後にフェデレーションサービスへ書き込まれます。この2回目の書き込みが拒否された場合、プランは `planned` のままで、接続レコードは既に存在している可能性があります。新しいプランでは不足している素材のみが書き込まれます。`spec.oidc`（`null`）は素材を変更しません。ソーシャル接続のクライアントシークレットはまだ書き込めません。そのような `requires_secret` の変更を含むプランは、シークレットの確認後に `501 not_implemented` を返します。何も書き込まれません。
- `Idempotency-Key` がサポートされています。可能な場合は、本文にシークレットを含めずに送信してください。キーのレコードには本文のハッシュが保持されます。

## レスポンス

`200 OK` と `Cache-Control: no-store` を返します。[構成プランの取得](get.md) と同じ形式のプランで、`status: applied` と `applied_at` を含みます。

変更前の影響を受けるリソースの構成は、リビジョンとして保持されます（環境ごとに最新 50 件、最長 90 日）。適用時には、環境の管理ログに `config.applied` のエントリが 1 件書き込まれ、作成、更新、削除、変更なしのリソース数が記録されます。変更された各リソースも、それぞれ独自のエントリを書き込みます。

## エラー

| HTTP | code | 発生条件 |
|---|---|---|
| 400 | `malformed_request`, `unknown_field` | 本文が JSON ではないか、`resolved_secrets`、`confirm_deletions`、`sp_host` 以外のフィールドがあります。 |
| 401 | `unauthenticated` | 認証情報がない、無効、または取り消されています。 |
| 403 | `insufficient_permissions` | 認証情報に必要な権限がありません。`required_permissions` にすべてが一覧されます。 |
| 404 | `not_found` | プランが存在しない、別の環境に属する、または削除されています。 |
| 409 | `config_conflict` | プラン作成後に対象が変更されました。プランは現在 `conflicted` です。 |
| 409 | `config_plan_not_applicable` | プランは既に適用されているか `conflicted` であるか、別のリクエストが現在適用中です（`plan_status: applying`）。`plan_status` にステータスが示されます。 |
| 409 | `deletion_not_confirmed` | 保護された削除が `confirm_deletions` にありません。`unconfirmed_deletions` に一覧されます。 |
| 409 | `conflict` | 別のリソースが変更の一意の値を既に使用しています（`errors[].pointer` は `/changes/<i>/spec/<field>`、コード `duplicate`）。プランは `planned` のままです。 |
| 409 | `connection_in_use` | 削除対象の接続がまだ使用中です。プランは `planned` のままです。 |
| 409 | `connection_test_required` | 変更で `enterprise` 接続 `active` を作成するか、成功したテストサインインなしで有効化しています（`errors[].pointer` は `/changes/<i>/spec`、コード `test_required`）。プランは `planned` のままです。 |
| 410 | `config_plan_expired` | プランの有効期限が過ぎました。新しいプランを作成してください。 |
| 413 | `config_document_too_large` | 変更、またはそのために保持されるリビジョン（最大 10 MiB）が大きすぎて、1 回のリクエストで適用できません。プランの種類を減らしてください。 |
| 422 | `validation_failed` | 下記を参照してください。 |
| 422 | `policy_conflict` | 変更が環境のポリシーと競合しています。プランは `planned` のままです。 |
| 429 | `rate_limited` | `config_plan` のレート制限（プラン作成と共有）を超えたか、この環境で同時に計算または適用中のプランが多すぎます。プランは `planned` のままです。`Retry-After` 期間の後に再試行してください。 |
| 501 | `not_implemented` | プランはソーシャル接続のクライアントシークレットを必要としますが、まだ書き込めません。 |
| 503 | `service_unavailable` | 依存先（ディレクトリ、またはエンタープライズ OIDC 素材の場合はフェデレーションサービス）が一時的に利用できないか、プランの処理能力が一時的に上限に達しています。何も書き込まれていないか、フェデレーションサービスの場合は接続レコードのみが書き込まれています。`Retry-After` 期間の後に再試行してください。 |

| `errors[].pointer` | `errors[].code` | 意味 |
|---|---|---|
| `/resolved_secrets/<NAME>` | `secret_missing` | プランはこのシークレットを必要とします。 |
| `/resolved_secrets/<NAME>` | `secret_unused` | プランはこのシークレットを必要としません。 |
| `/resolved_secrets/<NAME>` | `secret_invalid` | 値が空、8 KiB より長い、またはテキストではありません。 |
| `/resolved_secrets/<NAME>` | `invalid` | 名前が `KEENAUTH_…` 形式ではありません（`A-Z`、`0-9`、`_`）。 |
| `/sp_host` | `sp_host_required`、`sp_host_unused`、`invalid` | プランが OIDC 素材を作成するのにホストが指定されていないか、ホストが不要か、ホストが小文字の DNS 名ではありません。 |
| `/changes/<i>/spec/oidc/...` | 接続の OIDC API と同じ | フェデレーションサービスが、プランの `changes` のこの位置で素材を拒否しました。 |
| `/confirm_deletions/<i>` | `duplicate`、`not_a_deletion` | 項目が重複しているか、プランに含まれる削除ではありません。 |
| `/confirm_deletions/<i>` | `invalid` | 項目が `<kind>/<key>` 形式ではありません。 |
| `/resolved_secrets`、`/confirm_deletions` | `too_many` | 5,000 件を超えるエントリがあります。 |
| `/changes/<i>/spec/...` | リソース自身の API と同じ | 環境が、プランの `changes` のこの位置で変更を拒否しました。 |

## 例

```bash
curl -X POST "https://api.eu.keenauth.com/v1/config-plans/cfgp_01m4qpqtywfs7930tb6kqn5rc5:apply" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm_deletions": ["connection/legacy"]}'

keenauth config apply cfgp_01m4qpqtywfs7930tb6kqn5rc5 --confirm-deletion connection/legacy
```

`keenauth config apply --secrets-from-env` は、プランが必要とする各シークレットを同名の環境変数から読み取り、その値を出力することはありません。
