apply is the bulk-upsert primitive. It takes a list of catalog
kinds — schema and data — and reconciles them
against the project graph in one request, the same way terraform apply
reconciles HCL against real resources. It supports plan-only diffs (dryRun),
breaking-change gating (force), and post-apply deletions.
Backed by POST /projects/:projectId/apply (RBAC: apply:write).
The CLI normalizes one or more YAML/JSON documents (or a directory of them) into this envelope:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
items | AnyKind[] | yes | — | Schema + data declarations. Any of the seven kinds. |
force | boolean | no | false | Allow breaking schema changes (removing a state entities are in; removing a property with values). |
deletions | Deletion[] | no | [] | Schema-level kinds to delete after a successful apply, in dependency order. |
dryRun | boolean | no | false | Plan-only — no writes. Returns a diff instead. |
Only schema-level kinds may be deleted via apply. Entities and relationships
are data and are removed through their own routes (or by lifecycle Actions).
| Field | Type | Required | Description |
|---|---|---|---|
kind | EntityType | Cell | RelationshipType | Action | Secret | yes | The schema kind to delete. |
name | string | yes | The name of the item to delete. |
Items are processed in two phases, auto-sorted by dependency order within each — you never hand-order a blueprint:
EntityType, Secret, Cell, RelationshipType, Action.Entity, Relationship.Schema-then-data ordering is why a Cell referencing a freshly-declared
EntityType, an Action referencing a new Cell, or an Entity of a new type
all resolve correctly inside a single apply. deletions run after the apply, also
in dependency order, so the caller never sequences N+1 calls.
| Field | Type | Description |
|---|---|---|
succeeded | { kind, name }[] | Items applied successfully. |
failed | { kind, name, error }[] | Items that failed, each with its error. |
skipped | { kind, name, reason }[] | Items skipped, each with a reason. |
diff | Diff | Present only when dryRun: true. |
The endpoint returns 200 when every item succeeds, and 207 (Multi-Status)
when some items fail — inspect failed for the per-item errors.
When dryRun: true, no writes occur and each item is classified — mirroring
terraform plan:
| Field | Type | Description |
|---|---|---|
created | { kind, name }[] | Items that would be created. |
updated | { kind, name, changedFields }[] | Items that would change, with the fields that differ. |
unchanged | { kind, name }[] | Items already matching the desired state. |
deleted | { kind, name }[] | Items that would be deleted (the requested deletions). |
forceTwo schema edits are treated as destructive and rejected by default with 409:
Re-run with force: true (CLI --force) to allow them. A dry-run (dryRun: true)
also bypasses the destructive check, since it never writes.
apply is the single ingress for declarative catalog changes — the CLI's
import-terraform, the UI's bulk import, and terrantula applyall funnel into
the same envelope and the same two-phase reconciliation.
Deleting an EntityType cascades to its entities. In a dryRun, the deleted
list reflects only the requested deletions — the EntityType deletion cascade
is notexpanded in the diff. Verify cascades against the live graph before
applying.
dryRun still requires apply:write— a plan is gated the same as a real apply.
Keep your whole blueprint in version control and apply it as a directory:
terrantula apply --file ./terrantula/. Top-level YAML/JSON files are merged into
one atomic envelope; READMEs and other non-catalog files are skipped.
apply ingests.POST /apply route and its schema.terrantula apply flags.