---
title: "Resources"
description: "Services, databases, buckets, jobs, domains and secret groups: list, create, change and remove them, and run the actions each kind offers."
order: 3
---

Services, databases, buckets, jobs, domains and secret groups: list, create, change and remove them, and run the actions each kind offers.

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

Every resource of one environment (production unless you name another), with the project wide ones, by type and then name.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [ResourceResponse](#resourceresponse) |
| 404 | No such project or environment |  |

## `POST /api/v1/projects/{slug}/resources/bucket/{name}/check`

Count the bucket now and answer it with the fresh status. A bucket that does not answer shows as pending, not as an error.

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

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ResourceResponse](#resourceresponse) |
| 404 | No such project or bucket |  |

## `GET /api/v1/projects/{slug}/resources/database/{name}/restore-runbook`

The steps that bring this database back to a point in time, written out for this database.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [RestoreRunbookResponse](#restorerunbookresponse) |
| 404 | No such project, environment or database |  |

## `POST /api/v1/projects/{slug}/resources/database/{name}/rotate-password`

Rotate the project's Postgres role password and rewrite every DSN it appears in.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ResourceResponse](#resourceresponse) |
| 404 | No such project, environment or database |  |
| 409 | The plane refused the rotation, or the database is adopted |  |
| 503 | Secrets store sealed |  |

## `POST /api/v1/projects/{slug}/resources/job/{name}/run`

Queue a manual run. One live run per job, so a second press is a 409 rather than a second container.

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

| Status | Means | Body |
| --- | --- | --- |
| 202 | Accepted, and carried on in the background | [JobRunResponse](#jobrunresponse) |
| 404 | No such project, environment or job |  |
| 409 | A run is already queued or running |  |

## `GET /api/v1/projects/{slug}/resources/job/{name}/runs`

A job's runs, newest first.

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

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [JobRunResponse](#jobrunresponse) |
| 404 | No such project, environment or job |  |

## `GET /api/v1/projects/{slug}/resources/job/{name}/runs/{id}/log`

One run's log lines after `after`, and whether to poll again.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `id` | path | integer |  |
| `environment` (optional) | query | string or null |  |
| `after` (optional) | query | integer or null |  |
| `limit` (optional) | query | integer or null |  |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [JobLogResponse](#joblogresponse) |
| 404 | No such project, environment, job or run |  |

## `POST /api/v1/projects/{slug}/resources/secret-group/{name}/rotate`

Issue the group's values again and restart every service of the environment that uses the group, so they pick the new values up.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [RotateGroupResponse](#rotategroupresponse) |
| 404 | No such project, environment or group |  |
| 409 | No variables to render |  |
| 503 | Secrets store sealed |  |

## `GET /api/v1/projects/{slug}/resources/secret-group/{name}/variables`

Text values come back in the clear (one audited export); secrets never do.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [VariableResponse](#variableresponse) |
| 404 | No such project, environment or group |  |
| 503 | Secrets store sealed |  |

## `PUT /api/v1/projects/{slug}/resources/secret-group/{name}/variables/{key}`

Set or rotate one value. A name the group does not declare is appended to the spec (names and kinds only) once the value is stored.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `key` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

**Body:** [SetVariableRequest](#setvariablerequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [VariableResponse](#variableresponse) |
| 404 | No such project, environment or group |  |
| 422 | Name not env var shaped, or kind not secret or text |  |
| 503 | Secrets store sealed |  |

## `DELETE /api/v1/projects/{slug}/resources/secret-group/{name}/variables/{key}`

Remove one variable from the store and from the group's spec.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `key` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | No such group, or no such variable |  |
| 503 | Secrets store sealed |  |

## `GET /api/v1/projects/{slug}/resources/secret-group/{name}/versions`

The group's rendered history, newest first.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [RenderedVersion](#renderedversion) |
| 404 | No such project, environment or group |  |

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

The resources of one type in one environment, by name.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `type` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [ResourceResponse](#resourceresponse) |
| 404 | No such project or environment |  |
| 422 | Unknown resource type |  |

## `POST /api/v1/projects/{slug}/resources/{type}`

A new resource of one type; the spec is checked against the type's schema.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `type` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

**Body:** [CreateResourceRequest](#createresourcerequest)

| Status | Means | Body |
| --- | --- | --- |
| 201 | Created | [ResourceResponse](#resourceresponse) |
| 404 | No such project or environment |  |
| 409 | Name exists, or project archived |  |
| 422 | Unknown type, bad name, or spec fails the schema |  |

## `GET /api/v1/projects/{slug}/resources/{type}/{name}`

One resource with its spec and stored status.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `type` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ResourceResponse](#resourceresponse) |
| 404 | No such project, environment or resource |  |
| 422 | Unknown resource type |  |

## `PUT /api/v1/projects/{slug}/resources/{type}/{name}`

Create or replace one resource: 201 when it did not exist, 200 when its spec was replaced (the version bumps either way it is written).

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `type` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

**Body:** [SetResourceRequest](#setresourcerequest)

| Status | Means | Body |
| --- | --- | --- |
| 200 | Replaced | [ResourceResponse](#resourceresponse) |
| 201 | Created | [ResourceResponse](#resourceresponse) |
| 404 | No such project or environment |  |
| 409 | It conflicts with what is there |  |
| 422 | Unknown type, bad name, or spec fails the schema |  |

## `DELETE /api/v1/projects/{slug}/resources/{type}/{name}`

Remove one resource. Ops refuses when removing it would leave something running that nothing manages.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `type` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | No such project, environment or resource |  |
| 409 | Project archived, or the type refused the destroy |  |
| 422 | Unknown type or bad name |  |

## `POST /api/v1/projects/{slug}/resources/{type}/{name}/observe`

Look at the resource now and answer it with its fresh status.

| Parameter | In | Type | About |
| --- | --- | --- | --- |
| `slug` | path | string |  |
| `type` | path | string |  |
| `name` | path | string |  |
| `environment` (optional) | query | string or null |  |
| `kind` (optional) | query | string or null |  |
| `confirm` (optional) | query | string or null | Typed confirmation on a delete, read by the types that refuse to destroy without one (the Database wants its own name). |

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | [ResourceResponse](#resourceresponse) |
| 404 | No such project, environment or resource |  |
| 409 | Project archived |  |
| 422 | Unknown type or bad name |  |
| 503 | Not available right now |  |

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

The project's managed secrets' metadata, never the values.

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

| Status | Means | Body |
| --- | --- | --- |
| 200 | Done | array of [SecretMetaResponse](#secretmetaresponse) |

## `GET /api/v1/projects/{slug}/secrets/{service}/env`

The service's secrets rendered as a dotenv document (text/plain). This is the only value egress; the export is audited. Deploy usage: `curl … > sample-backend.env && docker secret create sample-backend.env -`.

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

| Status | Means | Body |
| --- | --- | --- |
| 200 | dotenv document | `text/plain` bytes |
| 404 | Service has no secrets |  |

## `PUT /api/v1/projects/{slug}/secrets/{service}/{name}`

Create or rotate one secret for a deploy unit (e.g. `sample-backend` / `S3_SECRET_ACCESS_KEY`).

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

**Body:** [SetSecretRequest](#setsecretrequest)

| Status | Means | Body |
| --- | --- | --- |
| 204 | Done, with no body |  |
| 404 | The unit is another project's |  |
| 422 | Name/service not env-var shaped |  |

## `DELETE /api/v1/projects/{slug}/secrets/{service}/{name}`

Remove one secret.

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

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

## Types

### ResourceResponse

| Field | Type | About |
| --- | --- | --- |
| `created_at` | string |  |
| `environment` (optional) | string or null | Null for a project level type (Bucket). |
| `id` | string (uuid) |  |
| `name` | string |  |
| `project` | string |  |
| `spec` | any |  |
| `status` | [ResourceStatus](#resourcestatus) | The stored status; `Pending` with summary "null" until first observed. |
| `type` | string |  |
| `updated_at` | string |  |
| `updated_by` | string |  |
| `version` | integer | Bumps on every put, an unchanged spec included. |

### RestoreRunbookResponse

The steps that bring one database back, rendered for it.

| Field | Type | About |
| --- | --- | --- |
| `backup` | string | `PITR via the plane stanza` or `null` (`OPS_DB_PLANE_PITR`). |
| `database` | string |  |
| `stanza` | string |  |
| `steps` | array of string | One shell line per step. |
| `text` | string | The same lines as one block, for the copy button. |

### JobRunResponse

| Field | Type | About |
| --- | --- | --- |
| `duration_secs` (optional) | integer or null | Whole seconds between started_at and finished_at; null while running. |
| `environment` | string |  |
| `error` (optional) | string or null |  |
| `exit_code` (optional) | integer or null |  |
| `finished_at` (optional) | string or null |  |
| `id` | integer |  |
| `job` | string |  |
| `lines` | integer |  |
| `queued_at` | string |  |
| `requested_by` | string | `scheduler` or the operator email. |
| `started_at` (optional) | string or null |  |
| `status` | string | `queued`, `running`, `succeeded` or `failed`. |
| `trigger` | string | `schedule` or `manual`. |

### JobLogResponse

| Field | Type | About |
| --- | --- | --- |
| `lines` | array of [JobLogLine](#joblogline) |  |
| `live` | boolean | The run is queued or in flight: poll again. |
| `next` | integer | Pass as `after` for the next page: the last sequence returned, or the `after` given when there was nothing new. |

### RotateGroupResponse

| Field | Type | About |
| --- | --- | --- |
| `reason` (optional) | string or null |  |
| `rolled` | array of string |  |
| `secret` (optional) | string or null | `sample-api.env.v4`; null when pending. |
| `skipped` | array of [SkippedService](#skippedservice) | Services left alone and why. |
| `state` | string |  |
| `version` (optional) | integer or null |  |

### VariableResponse

| Field | Type | About |
| --- | --- | --- |
| `has_value` | boolean | True when the store holds a value. |
| `kind` | string | `secret` or `text`. |
| `name` | string |  |
| `pending` | boolean | The value moved after the newest render. |
| `updated_at` (optional) | string or null |  |
| `value` (optional) | string or null | Null for a secret, the value for a text variable, null when no value is stored yet. |
| `version` | integer | Store version; 0 when no value yet. |

### SetVariableRequest

| Field | Type | About |
| --- | --- | --- |
| `kind` (optional) | string or null | `secret` (default) or `text`; only read when the variable is new to the group, since changing a stored secret into a readable text must be a deliberate spec edit. |
| `value` | string | SecretString (#18); wire shape a JSON string. |

### RenderedVersion

| Field | Type | About |
| --- | --- | --- |
| `actor` | string |  |
| `current` | boolean | The version the services of the group are mounting now. |
| `rendered_at` | string |  |
| `secret` | string |  |
| `version` | integer |  |

### CreateResourceRequest

| Field | Type | About |
| --- | --- | --- |
| `name` | string | `^[a-z0-9][a-z0-9-]{0,62}$`. |
| `spec` | any | Checked against the type's schema (see `/catalog`). |

### SetResourceRequest

| Field | Type | About |
| --- | --- | --- |
| `spec` | any |  |
| `version` (optional) | integer or null |  |

### SecretMetaResponse

| Field | Type | About |
| --- | --- | --- |
| `name` | string |  |
| `service` | string |  |
| `updated_at` | string |  |
| `version` | integer | Bumps on every rotation (PUT over an existing name). |

### SetSecretRequest

| Field | Type | About |
| --- | --- | --- |
| `value` | string |  |

### ResourceStatus

| Field | Type | About |
| --- | --- | --- |
| `detail` (optional) | any | Type specific figures for the detail component; `{}` when there are none. |
| `observed_at` | string |  |
| `state` | [StatusState](#statusstate) |  |
| `summary` | string |  |

### JobLogLine

| Field | Type | About |
| --- | --- | --- |
| `at` | string |  |
| `level` | string | `info`, `warn` or `error`; a run's stderr arrives as `warn`. |
| `line` | string |  |
| `seq` | integer |  |

### SkippedService

| Field | Type | About |
| --- | --- | --- |
| `reason` | string |  |
| `service` | string |  |

### StatusState

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