# projects (/docs/reference/api/projects)

> Projects group a tenant's work without creating more tenants. A project is app-owned, lives in one tenant, and its creator becomes its owner in the authorization projection; membership is a Better IAM relationship on `compute-project/{id}`. See [Tenancy](/docs/guides/iam/tenancy).



Projects group a tenant's work without creating more tenants. A project is app-owned, lives in one tenant, and its creator becomes its owner in the authorization projection; membership is a Better IAM relationship on `compute-project/{id}`. See [Tenancy](/docs/guides/iam/tenancy).

All 4 methods are served by this release.

| Method                | Kind     | Authorization                                      | Phase | Status     |
| --------------------- | -------- | -------------------------------------------------- | ----- | ---------- |
| [`create`](#create)   | mutation | `compute:projects:manage` @ `compute-tenant`       | v0.1  | registered |
| [`get`](#get)         | read     | `compute:projects:read` @ `compute-project`        | v0.1  | registered |
| [`list`](#list)       | read     | `compute:projects:read` @ `compute-tenant` + items | v0.1  | registered |
| [`archive`](#archive) | mutation | `compute:projects:manage` @ `compute-project`      | v0.2  | registered |

## create [#create]

`POST /api/compute/projects/create`

Creates an `active` project with a DNS-label name that is unique in the tenant; returns the project.

* Decided with `compute:projects:manage` on `compute-tenant/{tenantId}`.

* Idempotent per tenant, caller identity and `idempotencyKey`: the project id derives from them, and the same request again returns the existing project without a second IAM decision. The same key with a different name is `CONFLICT`.

* A second project with the same name in the tenant is `CONFLICT`.

* Emits `project.created`.

* **Kind:** mutation

* **Authorization:** `compute:projects:manage` @ `compute-tenant`

* **Module:** `server`

* **Phase:** v0.1 (registered)

**Input**

| Field            | Type   | Required | Constraints                                                             |
| ---------------- | ------ | -------- | ----------------------------------------------------------------------- |
| `tenantId`       | string | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$`              |
| `name`           | string | yes      | at most 63 characters; matches `^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$` |
| `idempotencyKey` | string | yes      | at most 128 characters; matches `^[A-Za-z0-9._~:/+=-]{1,128}$`          |

## get [#get]

`POST /api/compute/projects/get`

Returns one project of the tenant by id.

Decided with `compute:projects:read` on `compute-project/{projectId}`. `NOT_FOUND` when the project is not in the tenant.

* **Kind:** read
* **Authorization:** `compute:projects:read` @ `compute-project`
* **Module:** `server`
* **Phase:** v0.1 (registered)

**Input**

| Field       | Type   | Required | Constraints                                                      |
| ----------- | ------ | -------- | ---------------------------------------------------------------- |
| `tenantId`  | string | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$`       |
| `projectId` | string | yes      | at most 64 characters; matches `^prj_[0-9a-hjkmnp-tv-z]{26,52}$` |

## list [#list]

`POST /api/compute/projects/list`

Lists the tenant's projects the caller may read, ordered by id, optionally filtered by `state` (`active` or `archived`); returns a page with a cursor.

Decided with `compute:projects:read` on the tenant, then per project. Items the caller may not read are dropped from the page, so a page can hold fewer than `limit` items (at most 50, the default) while a cursor is still returned.

* **Kind:** read
* **Authorization:** `compute:projects:read` @ `compute-tenant` + items
* **Module:** `server`
* **Phase:** v0.1 (registered)

**Input**

| Field      | Type                        | Required | Constraints                                                |
| ---------- | --------------------------- | -------- | ---------------------------------------------------------- |
| `tenantId` | string                      | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$` |
| `state`    | one of `active`, `archived` |          |                                                            |
| `limit`    | integer                     |          | 1-50; default `50`                                         |
| `cursor`   | string                      |          | at most 256 characters; matches `^[A-Za-z0-9._:-]{1,256}$` |

## archive [#archive]

`POST /api/compute/projects/archive`

Marks a project `archived` and increments its version; returns the project.

* Decided with `compute:projects:manage` on `compute-project/{projectId}`.

* Archiving an archived project returns it unchanged and emits nothing.

* Emits `project.archived`. `NOT_FOUND` when the project is not in the tenant.

* **Kind:** mutation

* **Authorization:** `compute:projects:manage` @ `compute-project`

* **Module:** `server`

* **Phase:** v0.2 (registered)

**Input**

| Field       | Type   | Required | Constraints                                                      |
| ----------- | ------ | -------- | ---------------------------------------------------------------- |
| `tenantId`  | string | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$`       |
| `projectId` | string | yes      | at most 64 characters; matches `^prj_[0-9a-hjkmnp-tv-z]{26,52}$` |
