Environment Management APIs

API endpoints for managing environments, targets, agents, freeze windows, and inventory.

Status: Planned (not yet implemented) Source: Architecture Advisory Section 6.3.2 Related Modules: Environment Manager, Agents

Runtime note: environment APIs are documented here; controller implementation sequencing is tracked in ../promotion-runtime-gap-closure-plan.md.

Overview

The Environment Management API provides CRUD operations for environments, target groups, deployment targets, agents, freeze windows, and inventory synchronization. All endpoints require authentication and respect tenant isolation via Row-Level Security.


Environment Endpoints

Create Environment

Endpoint: POST /api/v1/environments

Request:

{
  "name": "production",
  "displayName": "Production",
  "orderIndex": 3,
  "config": {
    "deploymentTimeout": 600,
    "healthCheckInterval": 30
  },
  "requiredApprovals": 2,
  "requireSod": true,
  "promotionPolicy": "default"
}

Response: 201 Created

{
  "id": "uuid",
  "name": "production",
  "displayName": "Production",
  "orderIndex": 3,
  "isProduction": true,
  "requiredApprovals": 2,
  "requireSeparationOfDuties": true,
  "createdAt": "2026-01-10T14:23:45Z"
}

List Environments

Endpoint: GET /api/v1/environments

Query Parameters:

Response: 200 OK

[
  {
    "id": "uuid",
    "name": "development",
    "displayName": "Development",
    "orderIndex": 1,
    "currentRelease": {
      "id": "release-uuid",
      "name": "myapp-v2.3.1",
      "deployedAt": "2026-01-09T10:00:00Z"
    }
  }
]

Get Environment

Endpoint: GET /api/v1/environments/{id}

Response: 200 OK - Full environment details

Update Environment

Endpoint: PUT /api/v1/environments/{id}

Request: Partial environment object

Response: 200 OK - Updated environment

Delete Environment

Endpoint: DELETE /api/v1/environments/{id}

Response: 200 OK

{ "deleted": true }

Set Environment Lifecycle (archive / dispose / restore)

Sprint 20260717 (G9); reviewed/hardened in SPRINT_20260720_006. Explicit, owner-confirmed lifecycle transition. This is the only way an environment is demoted from the estate matrix, readiness table, and release/verify/custody pickers — the state is never inferred from the environment name (a name heuristic was rejected as dishonest).

Endpoint: PUT /api/v1/release-orchestrator/environments/{id}/lifecycle

Request:

{ "state": "archived" }

state is one of:

Response: 200 OK — the updated environment (includes the lifecycleState field). Emits the set_environment_lifecycle platform audit event.

Every environment read now carries lifecycleState (Active | Archived | Disposable). The list endpoint accepts an optional ?lifecycle= filter (all [default], active, demoted, archived, disposable) so pickers can request the active estate directly instead of demoting client-side.

Endpoint (target): PUT /api/v1/release-orchestrator/targets/{id}/lifecycle — same body/semantics for a single deployment target; every target read carries lifecycleState and the transition emits the set_target_lifecycle platform audit event. Backed by two idempotent, forward-only embedded-SQL migrations: 006_environment_lifecycle_state.sql adds lifecycle_state to release.environments and release.targets, backfills every existing row to active (no row is auto-demoted), and adds partial active-only indexes; 007_environment_lifecycle_state_constraints.sql adds a fail-closed CHECK (lifecycle_state IN ('active', 'archived', 'disposable')) on both tables so a hand-edited/unknown token can never widen an estate view. Both endpoints require the Operate scope and validate the token (400 invalid_lifecycle_state otherwise); a missing target returns 404 target_not_found (tenant-scoped, so a cross-tenant id is indistinguishable from a missing one).

Console consumption (2026-07-21, iteration-2 estate-cleanup)

The console is wired to this lifecycle end-to-end:


Freeze Window Endpoints

Create Freeze Window

Endpoint: POST /api/v1/environments/{envId}/freeze-windows

Request:

{
  "start": "2026-01-15T00:00:00Z",
  "end": "2026-01-20T00:00:00Z",
  "reason": "Holiday freeze",
  "exceptions": ["user-uuid-1", "user-uuid-2"]
}

Response: 201 Created

{
  "id": "uuid",
  "environmentId": "env-uuid",
  "start": "2026-01-15T00:00:00Z",
  "end": "2026-01-20T00:00:00Z",
  "reason": "Holiday freeze",
  "createdBy": "user-uuid"
}

List Freeze Windows

Endpoint: GET /api/v1/environments/{envId}/freeze-windows

Query Parameters:

Response: 200 OK - Array of freeze windows

Delete Freeze Window

Endpoint: DELETE /api/v1/environments/{envId}/freeze-windows/{windowId}

Response: 200 OK

{ "deleted": true }

Target Group Endpoints

Create Target Group

Endpoint: POST /api/v1/environments/{envId}/target-groups

List Target Groups

Endpoint: GET /api/v1/environments/{envId}/target-groups

Get Target Group

Endpoint: GET /api/v1/target-groups/{id}

Update Target Group

Endpoint: PUT /api/v1/target-groups/{id}

Delete Target Group

Endpoint: DELETE /api/v1/target-groups/{id}


Target Endpoints

Create Target

Endpoint: POST /api/v1/targets

Request:

{
  "environmentId": "env-uuid",
  "targetGroupId": "group-uuid",
  "name": "prod-web-01",
  "targetType": "docker_host",
  "connection": {
    "host": "192.168.1.100",
    "port": 2375,
    "tlsEnabled": true
  },
  "labels": {
    "role": "web",
    "datacenter": "us-east-1"
  },
  "deploymentDirectory": "/opt/deployments"
}

Response: 201 Created

{
  "id": "uuid",
  "name": "prod-web-01",
  "targetType": "docker_host",
  "healthStatus": "unknown",
  "createdAt": "2026-01-10T14:23:45Z"
}

List Targets

Endpoint: GET /api/v1/targets

Query Parameters:

Response: 200 OK - Array of targets

Get Target

Endpoint: GET /api/v1/targets/{id}

Update Target

Endpoint: PUT /api/v1/targets/{id}

Delete Target

Endpoint: DELETE /api/v1/targets/{id}

Trigger Health Check

Endpoint: POST /api/v1/targets/{id}/health-check

Response: 200 OK

{
  "status": "healthy",
  "message": "Docker daemon responding",
  "checkedAt": "2026-01-10T14:23:45Z"
}

Get Version Sticker

Endpoint: GET /api/v1/targets/{id}/sticker

Response: 200 OK

{
  "releaseId": "uuid",
  "releaseName": "myapp-v2.3.1",
  "components": [
    {
      "componentId": "uuid",
      "componentName": "api",
      "digest": "sha256:abc123..."
    }
  ],
  "deployedAt": "2026-01-09T10:00:00Z",
  "deployedBy": "user-uuid"
}

Check Drift

Endpoint: GET /api/v1/targets/{id}/drift

Response: 200 OK

{
  "hasDrift": true,
  "expected": { "releaseId": "uuid", "digest": "sha256:abc..." },
  "actual": { "digest": "sha256:def..." },
  "differences": [
    { "component": "api", "expected": "sha256:abc...", "actual": "sha256:def..." }
  ]
}

See Also