---
title: "Projects and environments"
description: "Create and read projects, their environments, previews and the events that happened in them, and promote one environment onto another."
order: 2
---

Create and read projects, their environments, previews and the events that happened in them, and promote one environment onto another.

## `GET /api/v1/catalog`

The registered resource types: kind, scope and the spec schema each form is rendered from. Sorted by kind.

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [CatalogEntry](#catalogentry) |

## `GET /api/v1/projects`

The caller's projects, live ones first, archived after; each by slug.

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [ProjectResponse](#projectresponse) |

## `POST /api/v1/projects`

Create a project; its production environment comes with it (following `branch`, deploy mode auto).

**Body:** [CreateProjectRequest](#createprojectrequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [ProjectResponse](#projectresponse) |
| 409 | Slug exists |  |
| 422 | Bad or reserved slug, empty display name, bad edge mode or retention |  |

## `GET /api/v1/projects/{slug}`

One project's settings, archived or not.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ProjectResponse](#projectresponse) |
| 404 | No such project |  |

## `PUT /api/v1/projects/{slug}`

Full replace of the editable fields; `archived: true` archives, `false` restores. An archived project refuses every write but a restore, which applies the fields and lifts the archive in one call (the audit reads update, then restore).

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

**Body:** [UpdateProjectRequest](#updateprojectrequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ProjectResponse](#projectresponse) |
| 404 | No such project |  |
| 409 | Project is archived and the request keeps it so |  |
| 422 | Bad field |  |

## `DELETE /api/v1/projects/{slug}`

Already archived is still 204.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | No such project |  |

## `GET /api/v1/projects/{slug}/environments`

Production first, then by name, each with its figures.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [EnvironmentResponse](#environmentresponse) |
| 404 | No such project |  |

## `POST /api/v1/projects/{slug}/environments`

Add an environment. Kind `production` is refused (a project has exactly one, created with it) and so is `preview` (it comes from its pull request).

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

**Body:** [CreateEnvironmentRequest](#createenvironmentrequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [EnvironmentResponse](#environmentresponse) |
| 404 | No such project |  |
| 409 | Name exists, kind production or preview, or project archived |  |
| 422 | Reserved or bad name, bad kind, deploy mode or quota |  |

## `PATCH /api/v1/projects/{slug}/environments/{environment}`

Replace an environment's branch, deploy mode and quota. A quota below what the environment's services already spend is refused with both figures: the operator scales the services down first.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` | path | string |  |

**Body:** [UpdateEnvironmentRequest](#updateenvironmentrequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [EnvironmentResponse](#environmentresponse) |
| 404 | No such project or environment |  |
| 409 | Project archived, or the quota is below current usage |  |
| 422 | Bad deploy mode or quota |  |

## `DELETE /api/v1/projects/{slug}/environments/{environment}`

Delete an environment and everything in it. The production environment stays with the project, and the environment's name has to be typed back.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` | path | string |  |
| `confirm` (optional) | query | string or null |  |

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | No such project or environment |  |
| 409 | Production, archived, unconfirmed, or a resource refused |  |

## `GET /api/v1/projects/{slug}/environments/{environment}/promote`

What promoting `from` into this environment would change.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` | path | string |  |
| `from` (optional) | query | string or null |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [PromotePlan](#promoteplan) |
| 404 | No such project, environment or source environment |  |
| 422 | from must name a different environment |  |

## `POST /api/v1/projects/{slug}/environments/{environment}/promote`

Deploy the source's digests into this environment. Always 200 once the guards pass: a service the deploy path refuses is one refused outcome, never the batch's status code.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` | path | string |  |

**Body:** [PromoteRequest](#promoterequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [PromoteResult](#promoteresult) |
| 404 | No such project, environment or source environment |  |
| 409 | Project archived |  |
| 422 | from must name a different environment |  |

## `GET /api/v1/projects/{slug}/events`

Everything that changed in the project and its resources, newest first. Narrow it by resource type and name.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `limit` (optional) | query | integer or null | 1 to 500, default 100. |
| `type` (optional) | query | string or null | Narrow to one resource type (`project` and `environment` for the registry). |
| `name` (optional) | query | string or null |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [EventResponse](#eventresponse) |
| 404 | No such project |  |

## `GET /api/v1/projects/{slug}/previews`

The project's live previews, by pull request number.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [PreviewResponse](#previewresponse) |
| 404 | No such project |  |

## `PATCH /api/v1/projects/{slug}/previews`

Replace both preview figures.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |

**Body:** [UpdatePreviewSettingsRequest](#updatepreviewsettingsrequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [PreviewSettings](#previewsettings) |
| 404 | No such project |  |
| 409 | Project archived |  |
| 422 | Limit or TTL out of range |  |

## `DELETE /api/v1/projects/{slug}/previews/{number}`

Tear one preview down by hand, before its pull request closes and before its TTL runs out.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `number` | path | integer |  |

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | No such project or preview |  |
| 409 | A resource stopped the cascade |  |

## Types

### CatalogEntry

One catalog line: the kind, where its instances live, and the spec schema the console renders the form from.

| Field | Type | About |
| --- | --- | --- |
| `kind` | string |  |
| `schema` | any |  |
| `scope` | [Scope](#scope) |  |

### ProjectResponse

| Field | Type | About |
| --- | --- | --- |
| `archived_at` (optional) | string or null |  |
| `created_at` | string |  |
| `display_name` | string |  |
| `obs_retention_days` | integer |  |
| `preview_limit` | integer | How many previews this project may hold open at once (0 switches them off). Read only here: `PATCH /previews` writes it. |
| `preview_ttl_days` | integer | How many days a preview lives after its last push. Read only here. |
| `signing_identity` (optional) | string or null | The cosign certificate identity regexp deploys verify against. |
| `slug` | string |  |
| `source_repo` (optional) | string or null |  |
| `updated_at` | string |  |

### CreateProjectRequest

| Field | Type | About |
| --- | --- | --- |
| `branch` (optional) | string | The branch the production environment follows (default `main`). |
| `display_name` | string |  |
| `obs_retention_days` (optional) | integer | 1 to 400, default 14. |
| `signing_identity` (optional) | string or null |  |
| `slug` | string | `^[a-z0-9][a-z0-9-]{0,62}$`. |
| `source_repo` (optional) | string or null |  |

### UpdateProjectRequest

Full replace of the editable fields; `archived` flips the archive state.

| Field | Type | About |
| --- | --- | --- |
| `archived` (optional) | boolean |  |
| `display_name` | string |  |
| `obs_retention_days` | integer |  |
| `signing_identity` (optional) | string or null |  |
| `source_repo` (optional) | string or null |  |

### EnvironmentResponse

| Field | Type | About |
| --- | --- | --- |
| `branch` (optional) | string or null | The branch this environment follows; null = promote only. |
| `created_at` | string |  |
| `deploy_mode` | string | `auto` or `manual`. |
| `kind` | string | `production`, `staging`, `development` or `preview`. |
| `latest_build_id` (optional) | integer or null | The newest build of the environment; null before the first. |
| `name` | string |  |
| `overlay` | string | The encrypted overlay this environment's services join. |
| `project` | string |  |
| `quota` | [Quota](#quota) |  |
| `services` | integer |  |
| `updated_at` | string |  |
| `usage` | [Usage](#usage) |  |
| `worst_state` (optional) | null or [StatusState](#statusstate) |  |

### CreateEnvironmentRequest

| Field | Type | About |
| --- | --- | --- |
| `branch` (optional) | string or null |  |
| `deploy_mode` (optional) | string | `auto` or `manual` (default). |
| `kind` | string | `staging`, `development` or `preview` (production comes with the project). |
| `name` | string | `^[a-z0-9][a-z0-9-]{0,62}$`. |
| `quota` (optional) | null or [Quota](#quota) |  |

### UpdateEnvironmentRequest

Full replace of the editable fields, as `UpdateProjectRequest` is for a project. `name` and `kind` are immutable; `branch: null` means promote only.

| Field | Type | About |
| --- | --- | --- |
| `branch` (optional) | string or null |  |
| `deploy_mode` | string |  |
| `quota` | [Quota](#quota) |  |

### PromotePlan

| Field | Type | About |
| --- | --- | --- |
| `changing` | integer | How many of `services` have `will_change`. |
| `from` | string |  |
| `services` | array of [PromoteService](#promoteservice) |  |
| `to` | string |  |

### PromoteRequest

| Field | Type | About |
| --- | --- | --- |
| `from` | string | The environment the digests come from. |

### PromoteResult

| Field | Type | About |
| --- | --- | --- |
| `deployed` | integer |  |
| `from` | string |  |
| `refused` | integer |  |
| `services` | array of [PromoteOutcome](#promoteoutcome) |  |
| `skipped` | integer |  |
| `to` | string |  |

### EventResponse

| Field | Type | About |
| --- | --- | --- |
| `action` | string | `create`, `update`, `delete`, `archive` or `restore`. |
| `actor` | string | Operator email, `api-token` for the static bearer, `migration` for seeds. |
| `at` | string |  |
| `diff` | any | `{"before": ..., "after": ...}`. |
| `environment` (optional) | string or null |  |
| `id` | integer |  |
| `name` | string |  |
| `type` | string |  |

### PreviewResponse

| Field | Type | About |
| --- | --- | --- |
| `base_ref` | string | The branch the pull request targets: the specs are cloned from the environment that follows it. |
| `created_at` | string |  |
| `database` (optional) | string or null | `sample_pr48`; null when the base environment kept no template. |
| `environment` | string | `preview-48`, always `preview-` + number. |
| `expires_at` (optional) | string or null |  |
| `head_ref` | string |  |
| `head_sha` | string |  |
| `hostname` (optional) | string or null |  |
| `last_push_at` | string |  |
| `latest_build_id` (optional) | integer or null | The newest build of the preview environment; null before the first. |
| `number` | integer |  |
| `project` | string |  |
| `reason` (optional) | string or null |  |
| `repo` | string |  |
| `state` | string | `creating`, `ready`, `failed`, `refused`, `tearing_down` or `torn_down`. |
| `title` | string |  |
| `updated_at` | string |  |
| `worst_state` (optional) | null or [StatusState](#statusstate) |  |

### UpdatePreviewSettingsRequest

A full replace of both figures, as every other write in this area is.

| Field | Type | About |
| --- | --- | --- |
| `preview_limit` | integer |  |
| `preview_ttl_days` | integer |  |

### PreviewSettings

What the Quota section seeds its two fields from, and what the PATCH answers with. `ProjectResponse` carries the same pair read only, so the page needs no second GET.

| Field | Type | About |
| --- | --- | --- |
| `preview_limit` | integer | How many previews a project may hold at once; 0 switches previews off. |
| `preview_ttl_days` | integer | Days after the LAST PUSH, never after creation, so an actively reviewed pull request does not lose its preview mid review. |

### Scope

One of: `"environment"` or `"project"`.

### Quota

What an environment may spend.

| Field | Type | About |
| --- | --- | --- |
| `cpu` | number |  |
| `memory_mb` | integer |  |

### StatusState

One of: `"ready"` or `"pending"` or `"error"`.

### Usage

What the environment's services already spend, and how many there are.

| Field | Type | About |
| --- | --- | --- |
| `cpu` | number |  |
| `memory_mb` | integer |  |
| `services` | integer |  |

### PromoteService

| Field | Type | About |
| --- | --- | --- |
| `build_id` (optional) | integer or null |  |
| `commit_sha` (optional) | string or null | The source deployment's commit, for the console's line. |
| `service` | string |  |
| `skip_reason` (optional) | string or null | Why it will not change; null when it will. |
| `source_digest` (optional) | string or null | The source's current digest; null when the source never deployed it. |
| `source_rollout` (optional) | string or null | The source deployment's rollout: `rolling`, `healthy`, `unhealthy`, `rolled_back` or `failed`. Null when the source never deployed it. |
| `target_digest` (optional) | string or null | What the target runs today; null before its first deployment. |
| `will_change` | boolean |  |

### PromoteOutcome

| Field | Type | About |
| --- | --- | --- |
| `deployment_id` (optional) | integer or null |  |
| `digest` (optional) | string or null | The digest that was deployed, or would have been. |
| `outcome` | string | `deployed`, `skipped` or `refused`. |
| `reason` (optional) | string or null | Why it was skipped or refused; null for `deployed`. |
| `service` | string |  |
