# Manifest

> Validate, plan, apply and export guv.yaml.

Validate, plan, apply and export `guv.yaml`.

| Operation | Method and path |
| --- | --- |
| [Export live state as guv.yaml (pull / eject)](#export-live-state-as-guvyaml-pull--eject) | `GET /v1/projects/{project}/manifest` |
| [Manifest management and drift status](#manifest-management-and-drift-status) | `GET /v1/projects/{project}/manifest/status` |
| [Validate guv.yaml (schema, references, policy)](#validate-guvyaml-schema-references-policy) | `POST /v1/projects/{project}/manifest/validate` |
| [Plan guv.yaml against live state](#plan-guvyaml-against-live-state) | `POST /v1/projects/{project}/manifest/plan` |
| [Apply a plan](#apply-a-plan) | `POST /v1/projects/{project}/manifest/apply` |

## Export live state as guv.yaml (pull / eject)

```http
GET /v1/projects/{project}/manifest
```

`Accept: application/yaml` returns the file itself.

- **Operation:** `exportManifest`
- **Scopes:** `projects:read`
- **CLI:** `guv pull`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |
| `environment` | query | string |  |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [ManifestExport](/docs/api/objects/manifest-export/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/projects/shopfront/manifest \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "manifest": "string",
  "generated_at": "2026-10-22T13:05:00Z"
}
```

## Manifest management and drift status

```http
GET /v1/projects/{project}/manifest/status
```

- **Operation:** `getManifestStatus`
- **Scopes:** `projects:read`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [ManifestStatus](/docs/api/objects/manifest-status/) |

**Errors:** `400`, `401`, `403`, `404`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl https://api.guv.dev/v1/projects/shopfront/manifest/status \
  -H "Authorization: Bearer $GUV_TOKEN"
```

**Example response**

```json
{
  "managed_by_manifest": false,
  "source": {
    "repo": "string",
    "path": "string",
    "commit": "string"
  },
  "last_applied": {
    "plan_hash": "string",
    "at": "2026-10-22T13:05:00Z",
    "by": {
      "type": "agent",
      "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
      "display": "Stewart Webb via agent claude-code-on-stewart-laptop",
      "via_agent": {
        "id": "tok_01jbx2m9k7q4r8t5v6w3y0z1tk",
        "name": "claude-code-on-stewart-laptop"
      },
      "on_behalf_of": {
        "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
        "display": "Stewart Webb"
      }
    },
    "operation_id": "string"
  },
  "drift": {
    "detected": false,
    "checked_at": "2026-10-22T13:05:00Z",
    "changes": [
      {
        "action": "create",
        "resource": {
          "type": "service",
          "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
          "name": "web",
          "url": "/v1/projects/primecarers/services/web"
        },
        "environment": "production",
        "summary": "Create Postgres 18 database main (HA, 50 GB, 30 days point-in-time recovery)",
        "diff": [
          {}
        ],
        "destructive": false,
        "requires_approval": false,
        "reason": "string"
      }
    ]
  }
}
```

## Validate guv.yaml (schema, references, policy)

```http
POST /v1/projects/{project}/manifest/validate
```

- **Operation:** `validateManifest`
- **Scopes:** `projects:read`
- **CLI:** `guv validate`
- **Idempotency-Key:** optional

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |

**Request body** (`application/json`, required): [ManifestInput](/docs/api/objects/manifest-input/)

| Field | Type | Description |
| --- | --- | --- |
| `manifest` * | string | The guv.yaml text (YAML or JSON). |
| `files` | map of string to string | Extra files referenced by `include` (path to content). |
| `environment` | string | Limit the plan to one environment. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [ManifestValidation](/docs/api/objects/manifest-validation/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/manifest/validate \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "manifest": "string",
  "files": {
    "key": "string"
  },
  "environment": "string"
}'
```

**Example response**

```json
{
  "valid": false,
  "errors": [
    {
      "path": "/services/web/port",
      "line": 1,
      "column": 1,
      "code": "secret_missing",
      "message": "string",
      "fix": "string",
      "severity": "error"
    }
  ],
  "warnings": [
    {
      "path": "/services/web/port",
      "line": 1,
      "column": 1,
      "code": "secret_missing",
      "message": "string",
      "fix": "string",
      "severity": "error"
    }
  ],
  "schema_version": "1"
}
```

## Plan guv.yaml against live state

```http
POST /v1/projects/{project}/manifest/plan
```

Never changes anything. Returns the diff, cost delta, policy decision and the `plan_hash` to apply.

- **Operation:** `planManifest`
- **Scopes:** `projects:read`
- **CLI:** `guv plan`
- **Idempotency-Key:** optional

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |

**Request body** (`application/json`, required): [ManifestInput](/docs/api/objects/manifest-input/)

| Field | Type | Description |
| --- | --- | --- |
| `manifest` * | string | The guv.yaml text (YAML or JSON). |
| `files` | map of string to string | Extra files referenced by `include` (path to content). |
| `environment` | string | Limit the plan to one environment. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | OK. | [Plan](/docs/api/objects/plan/) |

**Errors:** `400`, `401`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/manifest/plan \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "manifest": "string",
  "files": {
    "key": "string"
  },
  "environment": "string"
}'
```

**Example response**

```json
{
  "plan_hash": "sha256:9f2c1e0b7a4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0f9e8d7c6b5a49",
  "dry_run": true,
  "summary": "1 to update. +£5.00/month at current usage.",
  "changes": [
    {
      "action": "update",
      "resource": {
        "type": "service",
        "id": "svc_01jbx2m9k7q4r8t5v6w3y0z1sv",
        "name": "web"
      },
      "environment": "production",
      "summary": "Raise web memory limit from 1 GB to 2 GB",
      "diff": [
        {
          "path": "/resources/memory_mb",
          "before": 1024,
          "after": 2048
        }
      ],
      "destructive": false,
      "requires_approval": true,
      "reason": "production requires approval for changes made by agents"
    }
  ],
  "cost": {
    "current_monthly": {
      "amount": "18.20",
      "currency": "GBP",
      "display": "£18.20"
    },
    "projected_monthly": {
      "amount": "23.20",
      "currency": "GBP",
      "display": "£23.20"
    },
    "delta_monthly": {
      "amount": "5.00",
      "currency": "GBP",
      "display": "£5.00"
    },
    "confidence": "medium"
  },
  "policy": {
    "decision": "approval_required",
    "reasons": [
      {
        "code": "environment_protected",
        "message": "production requires approval for changes made by agents",
        "policy": "guv.environment.protection"
      }
    ],
    "approver_roles": [
      "owner",
      "admin"
    ]
  },
  "requires_approval": true,
  "expires_at": "2026-10-23T15:00:00Z"
}
```

## Apply a plan

```http
POST /v1/projects/{project}/manifest/apply
```

The `plan_hash` must match a fresh plan of the same manifest; otherwise `409 plan_hash_mismatch`. The plan hash doubles as the idempotency key of the apply operation.

- **Operation:** `applyManifest`
- **Scopes:** `manifest:apply`
- **CLI:** `guv apply`
- **Approval:** In environments with protection turned on (usually production), changes by agents and by roles outside `approver_roles` return `approval_required`.
- **Idempotency-Key:** required; supports `?dry_run=true`

**Parameters**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `project` * | path | string | Project id (`prj_…`) or slug (resolved in the `Guv-Workspace` workspace, or the caller's default). |

**Request body** (`application/json`, required): [ManifestApplyRequest](/docs/api/objects/manifest-apply-request/)

| Field | Type | Description |
| --- | --- | --- |
| `manifest` * | string | The guv.yaml text (YAML or JSON). |
| `files` | map of string to string | Extra files referenced by `include` (path to content). |
| `environment` | string | Limit the plan to one environment. |
| `plan_hash` * | string | Must match a plan made from the same manifest and the current live state, or the call fails with `plan_hash_mismatch`. |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` OK | Dry run. Nothing was changed; this is what would happen. | [Plan](/docs/api/objects/plan/) |
| `202` Accepted | Accepted. The work continues in the background; follow the operation. | [Operation](/docs/api/objects/operation/) |

**Errors:** `400`, `401`, `402`, `403`, `404`, `409`, `422`, `429`, `500`, as [problem+json](/errors/) with a stable `code`.

**Example**

```bash
curl -X POST https://api.guv.dev/v1/projects/shopfront/manifest/apply \
  -H "Authorization: Bearer $GUV_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "manifest": "string",
  "files": {
    "key": "string"
  },
  "environment": "string",
  "plan_hash": "string"
}'
```

**Example response**

```json
{
  "id": "op_01jbx2m9k7q4r8t5v6w3y0z1op",
  "kind": "deployment.create",
  "status": "running",
  "summary": "Deploying web to production",
  "workspace_id": "ws_01jbx2m9k7q4r8t5v6w3y0z1ws",
  "project_id": "prj_01jbx2m9k7q4r8t5v6w3y0z1pr",
  "environment_id": "env_01jbx2m9k7q4r8t5v6w3y0z1en",
  "resource": {
    "type": "deployment",
    "id": "dep_01jbx2m9k7q4r8t5v6w3y0z1dp",
    "url": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  },
  "progress": {
    "current_step": "build",
    "completed_steps": 1,
    "total_steps": 6,
    "percent": 20
  },
  "steps": [
    {
      "name": "resolve",
      "title": "Reading your settings",
      "status": "succeeded",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:00Z",
      "finished_at": "2026-10-23T14:02:01Z"
    },
    {
      "name": "build",
      "title": "Building your app",
      "status": "running",
      "attempt": 1,
      "started_at": "2026-10-23T14:02:01Z",
      "finished_at": null
    }
  ],
  "created_by": {
    "type": "user",
    "id": "usr_01jbx2m9k7q4r8t5v6w3y0z1us",
    "display": "Stewart Webb"
  },
  "created_at": "2026-10-23T14:02:00Z",
  "links": {
    "self": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op",
    "events": "/v1/operations/op_01jbx2m9k7q4r8t5v6w3y0z1op/events",
    "resource": "/v1/deployments/dep_01jbx2m9k7q4r8t5v6w3y0z1dp"
  }
}
```
