---
title: "Plan a formation deployment"
method: POST
path: "/v1/projects/{project_id}/formations/plan"
tags: ["Formations"]
---

# Plan a formation deployment

`POST /v1/projects/{project_id}/formations/plan`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/formations/plan`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Computes a diff between the desired template and the current stack state without making any changes. Returns the list of planned actions.

## Request body

- object
  - `formation_id` string: Existing formation ID to compare against. Omit for new formation planning.
  - `template` union, required: A formation template supplied as either a JSON object or a YAML/JSON string. When a string is provided the server parses it with a YAML parser (JSON is valid YAML) before processing.
    - FormationTemplate
      - `parameters` object, nullable: Declared parameters for this template. Each parameter may have a default value and an optional description. Parameters without a default must be supplied in the `parameters` field of the deploy request.
      - `resources` object, required: Map of logical resource IDs to resource declarations
      - `outputs` object, nullable: Map of output names to values. Values may use `{ "ref": "logicalId" }` to reference physical IDs of created resources, or `{ "param": "ParamName" }` and `{ "sub": "text ${ParamName}" }` to embed parameter values. `{ "ref_attr": "LogicalId.attribute" }` resolves a named attribute of a created resource, except one carrying credential material — a trigger's or webhook's `secret` is refused with 400 VALIDATION_FAILED, since a formation is readable by anyone holding `formations:GetFormation`. Read those from the resource's own secret route instead.
      - `metadata` NullableMetadataBag, nullable: A `MetadataBag` on a request field where `null` is meaningful: a full-replacement update that clears the bag. A record whose bag is unset leaves the field out.
    - string: YAML or JSON string representation of a FormationTemplate
  - `parameters` object, nullable: Runtime parameter values that override or supply template parameter defaults. Keys must match parameter names declared in `template.parameters`. A parameter declared with `use_previous_value: true` may be omitted to reuse its stored value.

## Response `200`

Plan result

- PlanResult
  - `changes` PlanChange[]
    - `logical_id` string
    - `resource_type` string
    - `action` 'create' | 'update' | 'delete' | 'no-op'
    - `physical_resource_id` string: The existing resource's physical ID. Present for update / no-op / delete actions, absent for create.
    - `diff` object: Resolved desired-state properties (post parameter/ref substitution) and, when available, the current live or last-applied properties they were compared against. Omitted when neither side could be computed (e.g. an unregistered resource type).
      - `desired` object
      - `current` object, nullable
  - `unauthorized_actions` UnauthorizedFormationAction[]: The per-resource actions the caller may not perform. A formation may only do what the caller could do directly, so applying this template would be refused while any of these remain. Absent when the caller may perform every action the plan implies. A plan itself changes nothing, so it reports them rather than failing.
    - `logical_id` string, required: The template's own name for the resource.
    - `resource_type` string, required: The declared resource type.
    - `action` string, required: The action the caller lacks.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
