# Declarative configuration

`cademi config` manages part of your account as code: you describe the desired state in a manifest, check it, review the plan of changes, and apply it. The CLI accepts manifests in YAML or JSON and runs the validation, plan, and application flow of the API.

## The manifest

A manifest uses the `cademi/configuration-manifest/v1` format. In YAML:

```yaml
$schema: cademi/configuration-manifest/v1
resources:
  - kind: product
    ref: course-a
    external_id: erp-123
    attributes:
      name: Course A
      status: published
  - kind: module
    ref: mod-1
    attributes:
      name: Module 1
      product: { $ref: course-a }
  - kind: webhook
    ref: erp
    id: whk_01J8Z3...
    attributes:
      url: https://erp.example/hook
      secret: whsec_...
  - kind: banner
    ref: old
    id: ban_01J8Z2...
    $delete: true
```

Files ending in `.yaml` or `.yml` are read as YAML, and any other file as JSON. Pass `-` to read the manifest from standard input, in either format. The CLI converts YAML to JSON before sending it, so both formats follow the same rules.

The supported kinds, identity rules (`ref`, `id`, `external_id`), references (`$ref`), explicit deletion (`$delete`), ordered sets, and limits are described in [Declarative configuration](https://cademi.dev/api/declarative-configuration.md). Existing resources that the manifest omits are never deleted.

## Validate

```sh
cademi config validate catalog.yaml
```

Validation changes nothing. The command prints each error and warning with its path, message, and code, confirms when the manifest is valid, and lists the permissions that applying it would require. It exits with code `6` when the manifest is invalid. The credentials need `configuration.validate`.

## Plan

```sh
cademi config plan catalog.yaml
cademi config plan catalog.yaml --out plan.json
```

The plan compares the manifest with the account and changes nothing. The CLI prints the plan ID and its expiration, then one line per step, with the kind, the `ref` followed by the resource ID or `external_id` when there is one, and the action: `+` create, `~` update, `-` delete, `=` no change, and `!` skip with its reason. Below each step, it lists the fields that change, from the current value to the new one. A summary line counts the steps to create, update, delete, leave unchanged, and skip.

`--out` saves the plan as JSON, to review it or apply it later. A plan is valid for 24 hours, can be applied once, and only with the credentials that created it. The credentials need `configuration.plan`.

If the manifest is invalid, the API creates no plan, and the command exits with code `6`.

## Apply

```sh
cademi config apply catalog.yaml
```

`apply` computes the plan, prints it, asks for confirmation, and applies it. The API applies the plan as an asynchronous operation, one item per step, and the command waits for it to finish, printing its progress. The credentials need `configuration.apply`.

- When the account already matches the manifest, the command says so and applies nothing.
- `--yes` (`-y`) skips the confirmation. Without a terminal, `apply` fails with exit code `2` unless you pass `--yes`. When the profile is a production profile, the question starts with `[production]`.
- `--no-wait` returns as soon as the operation is created. Follow it with `cademi operations wait <operation_id>`.
- If the operation does not end as `succeeded`, the command exits with code `1`. Steps that were already applied stay applied: run `cademi config apply` again to compute a new plan and converge.

To apply a plan you saved, pass it with `--plan`:

```sh
cademi config plan catalog.yaml --out plan.json
cademi config apply --plan plan.json catalog.yaml
```

The API rejects a saved plan that expired (`409 plan_expired`), that was already applied (`409 state_conflict`), or that no longer matches the account because a resource changed after planning (`409 plan_stale`), and the command exits with code `6`. In each case, compute a new plan.

## Secrets

Secrets in the manifest, such as `webhook.secret` and write-only settings fields, are never printed: the plan and the saved plan file show them as `***`. When you apply, the CLI replaces each `***` with the real value from the manifest, matching the step by its `ref`. For that reason, `cademi config apply --plan` still needs the manifest when the plan contains secrets. If a secret has no value in the manifest, the command stops before applying anything and exits with code `2`.

Keep manifests that contain secrets out of version control, or protect them like any other secret file.

## The underlying operations

`cademi config` combines three API operations, which are also available as regular commands: `cademi configuration validations create`, `cademi configuration plans create`, and `cademi configuration applications create`. Use them when you need the raw responses.
