# Projects

Projects group resources and credentials inside an [organization](https://www.daytona.io/docs/en/v2/organizations.md). Organizations contain projects, and projects contain everything else: every sandbox, image, snapshot, secret, registry credential, and API key is scoped to exactly one project.

Projects are the unit of isolation. Nothing is shared between projects implicitly: a secret in one project is invisible to sandboxes in another, and a project's API key cannot see other projects' resources. Organizations are the unit of membership and billing.

| **Concept**      | **Description**                                                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Organization** | The unit of membership and billing. Members, invitations, and credits belong to the organization. Contains any number of projects.   |
| **Project**      | The unit of isolation. Holds sandboxes, images, snapshots, secrets, registry credentials, and API keys.                              |
| **API key**      | Minted per project. Confined to that project and to its permission scopes; revoking a key takes effect immediately for new requests. |

Every organization has a project named default that cannot be renamed or deleted. The name is reserved, so no other project can use it.

### Project access

Projects also decide which organization members can reach their resources:

- Organization admins can access every project.
- Every member can access the **`default`** project.
- Any other project is visible only to members who have been added to it. Members who are not on a project get a **`404`** for it, as if it did not exist.

Whoever creates a project is added to it automatically.

Projects are managed on the dashboard's **Projects** page and switched in the header's organization and project switcher. These routes use your dashboard session token (an OIDC bearer JWT), not a project API key, and need matching organization project permissions.

## Create projects

Create a project. Names are trimmed, unique per organization, and up to 100 characters.

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click <Button>Create Project</Button>
3. Enter a <Button>Name</Button> for the project
4. Click <Button>Create Project</Button>

You can also click the project switcher in the header and choose **Create Project**.


```bash
daytona project create my-project --org ORG_ID
```


```bash
curl 'https://api.daytona.com/orgs/ORG_ID/projects' \
  --request POST \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "name": "my-project" }'
```

```json
{
  "id": "PROJECT_ID",
  "org_id": "ORG_ID",
  "name": "my-project",
  "default": false,
  "created_by": "USER_ID",
  "created_at": "2026-09-10T12:00:00Z",
  "updated_at": "2026-09-10T12:00:00Z"
}
```


The creator field is null unless the caller holds the members read permission.

## Operations

### List projects

List the projects you can access in an organization. The default project is always listed first.


```bash
daytona project list --org ORG_ID
```


```bash
curl 'https://api.daytona.com/orgs/ORG_ID/projects' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

```json
{
  "projects": [
    {
      "id": "DEFAULT_PROJECT_ID",
      "org_id": "ORG_ID",
      "name": "default",
      "default": true,
      "created_by": null,
      "created_at": "2026-09-01T09:00:00Z",
      "updated_at": "2026-09-01T09:00:00Z"
    },
    {
      "id": "PROJECT_ID",
      "org_id": "ORG_ID",
      "name": "my-project",
      "default": false,
      "created_by": "USER_ID",
      "created_at": "2026-09-10T12:00:00Z",
      "updated_at": "2026-09-10T12:00:00Z"
    }
  ]
}
```


### Select project

Select the project that later CLI commands run against. The selection is per organization, so switching organizations restores your last project there. Pass an ID or an unambiguous name; with no argument, the CLI shows a picker at a terminal.


```bash
daytona project use PROJECT_ID
```


### Get by ID

Get a single project.


```bash
daytona project get PROJECT_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```


### Rename projects

Rename a project. The default project cannot be renamed.

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click the <Button>⋮</Button> menu of the project
3. Click <Button>Rename</Button>
4. Enter the new name and confirm


```bash
daytona project update PROJECT_ID --name my-renamed-project --org ORG_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID' \
  --request PATCH \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "name": "my-renamed-project" }'
```


### Delete projects

Delete a project, revoking its API keys and removing its secrets and registries.

The default project cannot be deleted.

:::caution[Warning]
A project that still owns sandboxes, volumes, or snapshots cannot be deleted. Delete the remaining resources first.
:::

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click the <Button>⋮</Button> menu of the project
3. Click <Button>Delete</Button>
4. Confirm the deletion


```bash
daytona project delete PROJECT_ID --org ORG_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

```json
{ "deleted": "PROJECT_ID" }
```


## Project budgets

A project budget caps the project's spend in each calendar month, in UTC, at list price and before credits. When the month-to-date spend reaches the budget, creating, forking, starting, or resuming a sandbox in the project, or restoring one from a snapshot, is refused. After the organization's grace period, running persistent sandboxes are stopped and running ephemeral sandboxes are deleted. macOS sandboxes, which run on a prepaid Mac reservation, keep running until the reservation's paid period ends; while the project is held, a renewal answers `402 PROJECT_BUDGET_EXCEEDED`, so the reservation ends at its `period_end`. The hold lifts at the start of the next month; when the grace period would end after that, no sandbox is ended that month.

Spend is evaluated periodically and can lag usage by several minutes. Reading a budget requires the budgets read permission; setting or removing it requires budgets write.

### Get a project budget

Get a project's monthly budget and this month's spend. The SDKs, and the API with an API key, read the budget of the API key's project.


```python
from daytona import Daytona

daytona = Daytona()

budget = daytona.v2.get_project_budget()
if budget is None:
    print("The project has no budget")
else:
    print(f"Spent {budget.spent_cents} of {budget.monthly_limit_cents} cents")
```

- The API key needs the **`read:spending`** permission.


```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const budget = await daytona.v2.getProjectBudget()
if (budget === null) {
  console.log('The project has no budget')
} else {
  console.log(`Spent ${budget.spent_cents} of ${budget.monthly_limit_cents} cents`)
}
```

- The API key needs the **`read:spending`** permission.


```bash
# The argument is optional; omit it to use the selected project
daytona project budget get PROJECT_ID
```


```bash
# Any project, with a signed-in user token
curl 'https://api.daytona.com/projects/PROJECT_ID/budget' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'

# The API key's project
curl 'https://api.daytona.com/usage/budget' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

```json
{
  "project_id": "PROJECT_ID",
  "monthly_limit_cents": 50000,
  "period_start": "2026-10-01",
  "spent_cents": 12345,
  "evaluated_at": "2026-10-09T01:20:00Z",
  "exceeded_at": null,
  "stop_at": null,
  "stop_grace_hours": 1
}
```

The response has the following fields:

| **Field**             | **Description**                                                                                                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`monthly_limit_cents`** | The monthly budget, in US cents.                                                                                                                                                    |
| **`spent_cents`**         | The spend this period, in US cents. The value is **`null`** until the first evaluation in the period.                                                                                   |
| **`evaluated_at`**        | The value is **`null`** until the first evaluation in the period.                                                                                                                       |
| **`exceeded_at`**         | The time the budget was reached this period. The value is **`null`** while the spend is under the budget.                                                                               |
| **`stop_at`**             | The time running sandboxes are ended, which is **`exceeded_at`** plus **`stop_grace_hours`**. The value is **`null`** when **`exceeded_at`** is **`null`**. The value is also **`null`** when **`exceeded_at`** plus **`stop_grace_hours`** falls in the next month. |

- **`/projects/{id}/budget`** accepts only a signed-in user token.
- **`/projects/{id}/budget`** rejects **`dtno_`** organization API keys.
- **`/usage/budget`** accepts an API key with the **`read:spending`** permission.
- **`/usage/budget`** returns the same fields as **`/projects/{id}/budget`**.


### Set a project budget

Set or replace a project's monthly budget. Project budgets require the Team plan or above.

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click the project
3. In the **Budget** section, click <Button>Set budget</Button>, or <Button>Edit budget</Button> to replace an existing one
4. Enter the **Monthly budget** in USD
5. Click <Button>Save</Button>


```bash
# The argument is optional; omit it to use the selected project
daytona project budget set PROJECT_ID --monthly-usd 500
```

| **Flag**        | **Type** | **Description**                                                                          |
| --------------- | -------- | ---------------------------------------------------------------------------------------- |
| **`--monthly-usd`** | **`string`** | Required. Monthly budget in US dollars, at least **`1.00`** and at most two decimals, such as **`500`** or **`499.99`**. |


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/budget' \
  --request PUT \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --data '{ "monthly_limit_cents": 50000 }'
```

| **Field**             | **Type**  | **Description**                                                                    |
| --------------------- | --------- | ---------------------------------------------------------------------------------- |
| **`monthly_limit_cents`** | **`integer`** | Required. Monthly budget in US cents, from **`100`** ($1.00) to **`100000000000`** ($1 billion). |

- The request returns the budget after the write, in the same shape as [get a project budget](#get-a-project-budget).


### Remove a project budget

Remove a project's monthly budget. Removing it releases a budget hold on the project; sandboxes that were already stopped stay stopped.

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click the project
3. In the **Budget** section, click <Button>Remove budget</Button>
4. Confirm with <Button>Remove</Button>


```bash
# The argument is optional; omit it to use the selected project
daytona project budget remove PROJECT_ID
```

- **`daytona project budget remove`** asks for confirmation.


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/budget' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```


## Lifecycle defaults

Lifecycle defaults set the [timeouts](https://www.daytona.io/docs/en/v2/linux-sandboxes.md#timeouts) that new Linux sandboxes in a project receive when the create request leaves them out. A project stores them in the `ephemeral` block, and these timeouts always delete the sandbox. A macOS create does not take the project's lifecycle defaults: it gets the platform defaults, which the `effective.macos` block shows.

Daytona reads the defaults only when it creates a sandbox. Each field the create request sets overrides the matching default, and saving new defaults never changes existing sandboxes.

### Get lifecycle defaults

Get a project's stored lifecycle defaults and the policy a create without lifecycle settings receives for each sandbox type.

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click the project
3. Open the **Settings** tab and find the **Sandbox Lifecycle Policy** section


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/lifecycle-policy' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

```json
{
  "policy": {
    "ephemeral": { "timeout_seconds": 1800, "idle_timeout_seconds": 600 }
  },
  "effective": {
    "linux": {
      "ephemeral": { "timeout_seconds": 1800, "on_timeout": "delete", "timeout_source": "project", "timeout_capped": false, "idle_timeout_seconds": 600, "on_idle_timeout": "delete" }
    }
  }
}
```

The response has the following fields:

| **Field**          | **Description**                                                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **`policy`**           | Holds the stored values. An unset field is **`null`**.                                                                               |
| **`effective`**        | Holds what a create with no lifecycle settings receives.                                                                         |
| **`timeout_source`**   | The value in each **`effective`** block is **`project`**, **`platform`**, or **`none`**.                                                         |
| **`timeout_capped`**   | The value in each **`effective`** block is **`true`** when a stored timeout above the organization's [timeout cap](https://www.daytona.io/docs/en/v2/organizations.md#set-the-timeout-cap) is capped to it. |

- The request requires the projects read permission.
- The request accepts only a signed-in user token.
- The request rejects **`dtno_`** organization API keys.


### Update lifecycle defaults

Update a project's lifecycle defaults. Each block is updated as a whole: a block left out of the request is kept, an empty value clears it, and a sent block replaces the stored one, so a field left out of a sent block is unset.

1. Go to [Daytona Dashboard](https://app.daytona.com/dashboard/projects)
2. Click the project
3. Open the **Settings** tab
4. In the **Sandbox Lifecycle Policy** section, click the edit button on the **Ephemeral sandboxes** card
5. Set the timeouts, then click <Button>Save</Button>


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/lifecycle-policy' \
  --request PATCH \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --data '{
    "ephemeral": { "timeout_seconds": 1800, "idle_timeout_seconds": 600 }
  }'
```

| **Field**                      | **Type**  | **Description**                                                                                                           |
| ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------- |
| **`ephemeral`** | **`object`** | Sets the timeouts for sandboxes. **`null`** clears the block. If omitted, the stored block is kept. |
| **`ephemeral.timeout_seconds`** | **`integer`** | Sets the seconds until a sandbox is deleted, from **`1`** to the organization's maximum timeout. By default, it is unset. |
| **`ephemeral.idle_timeout_seconds`** | **`integer`** | Sets the idle seconds until a sandbox is deleted. Values below **`60`** are raised to **`60`**. By default, it is unset. |

- The request returns the defaults after the update, in the same shape as [get lifecycle defaults](#get-lifecycle-defaults).
- Daytona validates only the blocks in the request.
- Daytona accepts a timeout equal to the stored value even when the organization's maximum has since dropped below the stored value.
- The request requires the projects write permission.
- Changing the **`ephemeral`** block also requires the delete sandboxes permission in the project.
- The request accepts only a signed-in user token.
- The request rejects **`dtno_`** organization API keys.


## Project members

Control which organization members can access a non-default project. Listing members requires the members read permission; adding and removing them requires the members write permission. Organization admins are members of every project implicitly and do not need to be added.

### List project members


```bash
daytona project member list --project PROJECT_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/members' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

```json
{
  "members": [
    {
      "user_id": "USER_ID",
      "email": "jane@example.com",
      "name": "Jane Doe",
      "implicit": false,
      "added_at": "2026-09-10T12:00:00Z"
    }
  ]
}
```


The implicit flag is true for organization admins, who have access without a membership entry.

### Add project members

Add one or more members in one request.


```bash
daytona project member add USER_ID --project PROJECT_ID --org ORG_ID

daytona project member add --bulk USER_ID_1,USER_ID_2 --project PROJECT_ID --org ORG_ID
```

| **Flag** | **Type** | **Description**                                                                                      |
| -------- | -------- | ---------------------------------------------------------------------------------------------------- |
| **`--bulk`** | **`string`** | A comma-separated list of user IDs, emails, or names. Cannot be combined with a positional user.     |


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/members' \
  --request POST \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "user_ids": ["USER_ID_1", "USER_ID_2"] }'
```

```json
{ "added_user_ids": ["USER_ID_1", "USER_ID_2"] }
```

| **Field**  | **Type**   | **Description**                                                                         |
| ---------- | ---------- | --------------------------------------------------------------------------------------- |
| **`user_ids`** | **`string[]`** | Required. At most 200 user IDs per request; a larger list returns **`400 Bad Request`**.    |


### Remove project members

Remove a member from a project. Unless they are an organization admin, removal also revokes the API keys they minted in the project; admins keep access through their role.


```bash
daytona project member remove USER_ID --project PROJECT_ID --org ORG_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/members/USER_ID' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

The response has the following fields:

| **Field**      | **Description**                      |
| -------------- | ------------------------------------ |
| **`removed`**      | The ID of the removed user.          |
| **`revoked_keys`** | The number of revoked keys.          |


## Project service accounts

### List project service accounts

List the service accounts that can hold API keys in a project. A service account mints keys only in projects it has been added to. The default project is open to every service account.


```bash
daytona project service-account list --project PROJECT_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/service-accounts' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```


### Add project service accounts

Add a service account to a project. Archived service accounts cannot be added, and you must hold every permission the account holds.


```bash
daytona project service-account add SERVICE_ACCOUNT_ID --project PROJECT_ID --org ORG_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/service-accounts' \
  --request POST \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{ "service_account_id": "SERVICE_ACCOUNT_ID" }'
```


### Remove project service accounts

Remove a service account from a project, revoking every API key it holds there.


```bash
daytona project service-account remove SERVICE_ACCOUNT_ID --project PROJECT_ID --org ORG_ID
```


```bash
curl 'https://api.daytona.com/projects/PROJECT_ID/service-accounts/SERVICE_ACCOUNT_ID' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

The response has the following fields:

| **Field**      | **Description**                           |
| -------------- | ----------------------------------------- |
| **`removed`**      | The ID of the removed service account.    |
| **`revoked_keys`** | The number of revoked keys.               |


## Project secrets and registries

[Secrets](https://www.daytona.io/docs/en/v2/secrets.md) and [registry credentials](https://www.daytona.io/docs/en/v2/registries.md) belong to a project. Manage them with a credential that acts in the project: a project API key, or the access token from [exchanging a token](https://www.daytona.io/docs/en/v2/api-keys.md#exchange-a-token) for that project. The CLI uses the selected project's credential after you sign in.