Plan a configuration change

POST /v1/config-plans · permission config:read and the read permission of each kind in the document

Compares a configuration document with the environment's current configuration and stores the result as a plan. Nothing is changed: a plan only shows what applying the document would do. Use it to review a promotion from development to staging or production before you apply it.

Request

Field Description
document Required. A configuration document in the format of GET /v1/config-export. It must match the JSON Schema https://docs.keenauth.com/config/schema/v1.json.
prune Optional, default false. When true, resources of the document's kinds that are missing from the document are planned for deletion.

Response

201 Created with Location: /v1/config-plans/{plan_id} and Cache-Control: no-store.

Field Description
id Plan id, cfgp_….
status planned; expired after expires_at.
expires_at The plan is valid for 1 hour. It is deleted 7 days after it expires.
summary Number of create, update, delete and no_op actions.
changes One entry per resource, in kind order and then by key.
warnings secret_required: the connection needs a client secret that the target does not have yet.

Each change has kind, key, action (create, update, delete or no_op), object_version (the target resource's version at plan time; null for create), requires_secret, fields and, for create and update, the desired spec. Each entry in fields is a field difference: pointer (RFC 6901, for example /spec/name), op (add, remove or replace, as in RFC 6902), before and after. Lists are compared as whole values.

An exported document applied unchanged plans no_op for every resource, whatever the order of resources and set-like lists.

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

Every plan writes a config_plan.created entry to the environment's management log.

Limits

Errors

HTTP code When
400 malformed_request, unknown_field The body is not JSON or has a field other than document and prune.
401 unauthenticated The credential is missing, invalid or revoked.
403 insufficient_permissions The credential lacks a required permission; required_permissions lists all of them.
413 config_document_too_large, payload_too_large The document has more than 5,000 resources or 10 MiB; plan fewer kinds.
422 validation_failed See below; errors[].pointer points into the request (/document/...).
429 rate_limited The plan rate limit or the environment's plan quota was reached, or the environment is computing too many plans; retry after the Retry-After period (for the quota, when the oldest plan expires or is deleted).
503 service_unavailable Plan computation is at capacity or a dependency is temporarily unavailable; retry after the Retry-After period.
errors[].code Meaning
required document is missing.
invalid The document does not match the schema at this pointer.
duplicate Two resources have the same kind and key.
kind_not_listed The resource's kind is not in kinds.
secret_value A secret value was found; use a secret_ref placeholder.
unresolved_reference The key it refers to would not exist after the change.
key_in_use The key belongs to an application that is not managed by configuration (organization-owned or self-registered), or to the system API.
still_referenced /prune: a resource that stays refers to a resource the plan would delete.
too_large A resource, a warning or a top-level field has more than 200,000 JSON values.
too_long Connection settings exceeds 32 KiB, or application jwks or a permission's consent_text_i18n exceeds 16 KiB.
too_deep Connection settings is nested deeper than 8 levels.
reserved The API identifier is in the platform's urn:idp: namespace.

Example

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