# Projects (/docs/projects)

Projects group resources and credentials inside an [organization](/docs/organizations). Organizations contain projects, and projects contain everything else: every sandbox, image, snapshot, volume, 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 or volume 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.

<ProjectsVisual />

| **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, volumes, 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 [#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-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**.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

```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"
}
```

</TabItem>
</Tabs>

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

## Operations [#operations]

### List projects [#list-projects]

List the projects you can access in an organization, newest first.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

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

```json
{
  "projects": [
    {
      "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"
    },
    {
      "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"
    }
  ]
}
```

</TabItem>
</Tabs>

### Select project [#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.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

```bash
daytona project use PROJECT_ID
```

</TabItem>
</Tabs>

### Get by ID [#get-by-id]

Get a single project.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

```bash
daytona project get PROJECT_ID
```

</TabItem>
<TabItem label="API" icon="seti:json">

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

</TabItem>
</Tabs>

### Rename projects [#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

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

```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" }'
```

</TabItem>
</Tabs>

### Delete projects [#delete-projects]

Delete a project, revoking its API keys and removing its secrets and registries. The default project cannot be deleted.

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

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

<Callout type="note" title="Notes">
- The request requires the `delete:projects` permission.
- The project must own no sandboxes, volumes, images, snapshots, or functions.
- Delete the remaining resources first.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

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

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

<Callout type="note" title="Notes">
- The request requires the `delete:projects` permission.
- The project must own no sandboxes, volumes, images, snapshots, or functions.
- Delete the remaining resources first.
</Callout>

</TabItem>
</Tabs>

## Project budgets [#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 fails, so the reservation ends at the end of its paid period. 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.

### Get a project budget [#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.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()

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

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

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

const daytona = new Daytona()

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

</TabItem>
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

```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 the organization's grace period. The value is `null` when `exceeded_at` is `null`. The value is also `null` when `exceeded_at` plus the grace period falls in the next month. |

<Callout type="note" title="Notes">
- Getting the budget of a project by ID requires the `read:budgets` permission.
- Getting the budget of the project of the API key requires the `read:spending` permission.
</Callout>

</TabItem>
</Tabs>

### Set a project budget [#set-a-project-budget]

Set or replace a project's monthly budget.

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>

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

```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`. |

<Callout type="note" title="Notes">
- The request requires the `write:budgets` permission.
- The request requires the Team or Enterprise [plan](/docs/billing#get-the-plan).
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

```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). |

<Callout type="note" title="Notes">
- The request requires the `write:budgets` permission.
- The request requires the Team or Enterprise [plan](/docs/billing#get-the-plan).
</Callout>

</TabItem>
</Tabs>

### Remove 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>

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

<Callout type="note" title="Notes">
- The request requires the `write:budgets` permission.
- `daytona project budget remove` asks for confirmation.
</Callout>

</TabItem>
<TabItem label="API" icon="seti:json">

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

</TabItem>
</Tabs>

## Lifecycle defaults [#lifecycle-defaults]

Lifecycle defaults set the [timeouts](/docs/linux-sandboxes#timeouts) that new Linux sandboxes in a project receive when the create request leaves them out. A project stores them as defaults for ephemeral sandboxes, and these timeouts always delete the sandbox. A macOS create does not take the project's lifecycle defaults: it gets the platform defaults.

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-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

<Tabs syncKey="language">
<TabItem label="API" icon="seti:json">

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

The following response is abridged to the effective defaults of ephemeral Linux sandboxes. The full response also has the effective defaults of Windows, GPU, and macOS sandboxes.

```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. The `macos` block shows the platform defaults that a macOS create 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](/docs/organizations#set-the-timeout-cap) is capped to it. |

</TabItem>
</Tabs>

### Update lifecycle defaults [#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>

<Tabs syncKey="language">
<TabItem label="API" icon="seti:json">

```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. |

<Callout type="note" title="Notes">
- The request requires the `write:projects` permission.
- Setting, changing, or clearing a delete action also requires the `delete:sandboxes` permission.
</Callout>

</TabItem>
</Tabs>

## Project members [#project-members]

Control which organization members can access a non-default project. Organization admins are members of every project implicitly and do not need to be added.

### List project members [#list-project-members]

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

```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"
    }
  ]
}
```

</TabItem>
</Tabs>

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

### Add project members [#add-project-members]

Add one or more members in one request.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

```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 | Adds several users, as a comma-separated list of user IDs, emails, or names. This flag cannot be combined with a positional user.     |

</TabItem>
<TabItem label="API" icon="seti:json">

```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`.    |

</TabItem>
</Tabs>

### Remove project members [#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.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

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

The request returns 204 No Content.

</TabItem>
</Tabs>

## Project service accounts [#project-service-accounts]

### List 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.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

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

</TabItem>
</Tabs>

### Add project service accounts [#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.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

```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" }'
```

</TabItem>
</Tabs>

### Remove project service accounts [#remove-project-service-accounts]

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

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

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

</TabItem>
<TabItem label="API" icon="seti:json">

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

The request returns 204 No Content.

</TabItem>
</Tabs>

## Project secrets and registries [#project-secrets-and-registries]

[Secrets](/docs/secrets) and [registry credentials](/docs/registries) 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](/docs/api-keys#exchange-a-token) for that project. The CLI uses the selected project's credential after you sign in.

## Permissions [#permissions]

Projects and their budgets, members, and service accounts are governed by organization permissions, which you can grant through an [organization role](/docs/organizations#roles). A project API key or project access token reads the budget of its own project with the project spending permission.

Changing a lifecycle default whose action is delete also requires the permission to delete sandboxes. The [audit logs](/docs/audit-logs) record project creates, updates, and deletes, and budget, member, and service account changes.

| **Permission**               | **Grants**                                                     |
| ---------------------------- | -------------------------------------------------------------- |
| **`read:projects`**          | List and get projects and their lifecycle defaults             |
| **`write:projects`**         | Create and rename projects and change their lifecycle defaults |
| **`delete:projects`**        | Delete projects                                                |
| **`read:budgets`**           | Get project budgets                                            |
| **`write:budgets`**          | Set and remove project budgets                                 |
| **`read:spending`**          | Get the budget of the project of the API key or access token   |
| **`read:members`**           | List project members                                           |
| **`write:members`**          | Add and remove project members                                 |
| **`read:service_accounts`**  | List project service accounts                                  |
| **`write:service_accounts`** | Add and remove project service accounts                        |
