# Audit Logs

Audit logs provide a detailed record of user and system activity across your organization. Each entry records an action, the actor and credential behind it, the resource it targeted, and the result.

Reading audit logs requires the audit read organization permission. [Administrators](https://www.daytona.io/docs/en/v2/organizations.md#roles) hold it, as does the dedicated **Auditor** role, which grants read access to the log without the ability to turn logging off. Contact your organization administrator to get access to audit logs.

Audit logs are retained for 365 days.

## Use cases

Audit logs are used when an organization must know who did what, when, and with which credential.

- **Security audits**: unauthorized access or sandbox misuse is traced to a user, an API key, an IP address, and a time. The key prefix identifies the key without exposing it.
- **Debugging**: sandbox lifecycle problems, such as a failed start, are traced to the request that caused them. Each entry carries the HTTP status code and the error message.
- **Compliance reviews**: actions across the organization stay on record for 365 days. Management-plane actions, such as changes to members and credentials, are always recorded and cannot be turned off.
- **Independent review**: the Auditor role reads the log without the ability to turn logging off. Turning logging off is itself recorded as an update on the organization.
- **Access reviews**: member role changes and removals, invitations, and the creation and role updates of service accounts are recorded with the previous and the new roles. A review shows who granted which access and when.
- **Credential changes**: API key creation is recorded, along with the number of keys revoked when a project, a membership, or the organization is deleted. Keys minted for a service account carry the account ID.
- **Billing changes**: wallet top-ups, plan changes, coupon redemptions, and billing contact changes are recorded as updates on the organization.
- **Live monitoring**: new entries appear as they happen. Auto-refresh fetches the list every 10 seconds, 30 seconds, or 1 minute.
- **Scripted reviews**: the CLI and the API filter entries by action, target, actor, status code, and time range. A script pages through the results to collect the entries for a review.

## Access from Dashboard

1. Go to [Daytona Dashboard ↗](https://app.daytona.com/dashboard)

Access the audit logs page directly from [Daytona Dashboard ↗](https://app.daytona.com/dashboard/audit-logs). The audit logs page displays a list of all audit logs for your organization, including the following columns:

| **Column**                | **Description**                                |
| ------------------------- | ---------------------------------------------- |
| **Time**                  | The timestamp of the action.                   |
| **Actor**                 | The user or API key that performed the action. |
| **Action**                | The action performed.                          |
| **Target**                | The resource affected by the action.           |
| [**Outcomes**](#outcomes) | The result of the action.                      |

Select any row to open the full log entry, including its metadata.

To filter audit logs by time, use the date range picker in the top-left corner of the page. The filter menu next to it adds equality filters on action, target type, target ID, actor, and status code.

## Real-time updates

Refresh the audit log list as new events occur.

1. Go to [Daytona Audit Logs ↗](https://app.daytona.com/dashboard/audit-logs)
2. Use the refresh control in the top-right corner of the page

The refresh icon fetches the current page immediately. The interval selector repeats that fetch every **10s**, **30s**, or **1m**, or on **Auto**, which polls every 30 seconds. Auto-refresh is **Off** by default.

## Log structure

Each audit log entry contains the following fields:

| Field                  | Type   | Description                                                                       |
| ---------------------- | ------ | --------------------------------------------------------------------------------- |
| **`id`**               | string | Unique log entry identifier                                                       |
| **`org_id`**           | string | ID of the organization the action belongs to                                      |
| **`project_id`**       | string | ID of the project the action belongs to, or **`null`** for organization-level actions |
| **`actor_kind`**       | string | Credential type of the actor: **`user`**, **`api_key`**, **`org_key`**, **`vm`**, or **`system`**     |
| **`actor_user_id`**    | string | ID of the user who performed the action, or **`null`** for a machine credential       |
| **`actor_email`**      | string | Email of the user who performed the action                                        |
| **`actor_key_id`**     | string | ID of the API key used, or **`null`** when the actor is a signed-in user              |
| **`actor_key_prefix`** | string | Prefix of the API key used, for identifying a key without exposing it             |
| **`action`**           | string | Operation executed (e.g. **`create`**, **`start`**, **`stop`**)                               |
| **`target_type`**      | string | Resource type affected (e.g. **`sandbox`**, **`snapshot`**)                               |
| **`target_id`**        | string | ID of the affected resource                                                       |
| **`status_code`**      | number | HTTP status code of the result                                                    |
| **`error_message`**    | string | Error message if the action failed                                                |
| **`ip_address`**       | string | IP address of the request origin                                                  |
| **`user_agent`**       | string | User agent of the request origin                                                  |
| **`metadata`**         | object | Additional context about the action                                               |
| **`created_at`**       | string | RFC 3339 timestamp of when the action occurred                                    |

## Get audit logs for organization

Get audit logs for a specific organization, newest first.

This endpoint accepts an organization-scoped API key carrying the audit read permission, or a signed-in dashboard session. A project-scoped API key is rejected.


```bash
daytona audit list --org {organizationId}
```


```bash
curl 'https://api.daytona.com/orgs/{organizationId}/audit-logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```


The response contains one page of entries and a cursor for the next page:

```json
{
  "next_cursor": "MjAyNi0wOS0wOVQxMDoxMjozMy4xMjM0NTY3ODlafDAxOTliMmMx...",
  "audit_logs": [
    {
      "id": "{auditLogId}",
      "org_id": "{organizationId}",
      "project_id": "{projectId}",
      "actor_kind": "user",
      "actor_user_id": "{userId}",
      "actor_email": "user@example.com",
      "actor_key_id": null,
      "actor_key_prefix": null,
      "action": "create",
      "target_type": "sandbox",
      "target_id": "{sandboxId}",
      "status_code": 200,
      "error_message": null,
      "ip_address": "203.0.113.42",
      "user_agent": "daytona-sdk-python",
      "metadata": { "class": "linux", "region": "{regionId}" },
      "created_at": "2026-09-09T10:12:33.123456789Z"
    }
  ]
}
```

### Query parameters

All filters are optional and combine with AND. A filter written as **`field=value`** matches exactly. The API also accepts **`field[op]=value`**, where **`op`** is **`eq`**, **`not`**, **`in`**, **`not_in`**, **`gt`**, **`gte`**, **`lt`**, or **`lte`**. The **`in`** and **`not_in`** operators take a comma-separated list, and the range operators apply only to **`status`**. A request accepts at most 10 filters.


```bash
daytona audit list --org {organizationId} --limit 100 --action create --target-type sandbox
```

| **Flag**   | **Type** | **Description**                           |
| ---------- | -------- | ----------------------------------------- |
| **`--limit`**  | **`int`**    | Sets the number of entries per page. The default is **`100`**. |
| **`--cursor`** | **`string`** | Passes a **`next_cursor`** value.             |
| **`--all`**    | **`bool`**   | Follows every page until the last one.    |

- Each filter flag sets the matching query parameter.
- For example, **`--target-id`** sets **`target_id`**.


```bash
curl 'https://api.daytona.com/orgs/{organizationId}/audit-logs?limit=100&action=create&target_type=sandbox' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

| **Field**     | **Type**  | **Description**                                                                                                            |
| ------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| **`limit`**       | **`integer`** | Sets the number of entries per page. The maximum is **`200`**, and larger values are clamped. The default is **`100`**. |
| **`cursor`**      | **`string`**  | Passes an opaque continuation token. Pass the **`next_cursor`** of the previous page. Pass back only cursors that the API returned. |
| **`action`**      | **`string`**  | Returns only entries with this action.                                                                                     |
| **`target_type`** | **`string`**  | Returns only entries with this target type.                                                                                |
| **`target_id`**   | **`string`**  | Returns only entries affecting this resource.                                                                              |
| **`actor`**       | **`string`**  | Returns only entries with this user ID or API key ID. The value matches either **`actor_user_id`** or **`actor_key_id`**. A value that is not a UUID returns **`400`**. |
| **`status`**      | **`integer`** | Returns only entries with this HTTP status code.                                                                           |
| **`start_at`**    | **`string`**  | Returns only entries recorded at or after this RFC 3339 timestamp. The bound is inclusive.                                 |
| **`end_at`**      | **`string`**  | Returns only entries recorded before this RFC 3339 timestamp. The bound is exclusive.                                      |


## List audit actions

List audit actions. The response lists every target type the audit log records and the actions possible for each, derived from the audited operations. Use it to build valid filters for audit log queries. The list is the same for every organization, and the request requires a credential.


```python
from daytona import Daytona

daytona = Daytona()
actions = daytona.v2.list_audit_actions()

for target in actions.targets:
    print(target.target_type, target.actions)
```


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

const daytona = new Daytona()
const actions = await daytona.v2.listAuditActions()

for (const target of actions.targets) {
  console.log(target.target_type, target.actions)
}
```


```bash
curl 'https://api.daytona.com/audit/actions' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```


The response contains one entry per target type:

```json
{
  "targets": [
    {
      "target_type": "api_key",
      "actions": ["create", "delete"]
    },
    {
      "target_type": "checkpoint",
      "actions": ["create"]
    },
    {
      "target_type": "function",
      "actions": ["create", "delete", "update"]
    },
    {
      "target_type": "image",
      "actions": ["create", "delete"]
    },
    {
      "target_type": "org_api_key",
      "actions": ["create", "delete"]
    },
    {
      "target_type": "organization",
      "actions": ["create", "delete", "update"]
    },
    {
      "target_type": "organization_invitation",
      "actions": ["create", "decline", "delete", "update"]
    },
    {
      "target_type": "organization_user",
      "actions": ["delete", "update"]
    },
    {
      "target_type": "otel_config",
      "actions": ["delete", "update"]
    },
    {
      "target_type": "platform_token",
      "actions": ["create"]
    },
    {
      "target_type": "preview_endpoint",
      "actions": ["create", "delete", "expire", "update"]
    },
    {
      "target_type": "project",
      "actions": ["create", "delete", "update"]
    },
    {
      "target_type": "project_member",
      "actions": ["create", "delete"]
    },
    {
      "target_type": "project_service_account",
      "actions": ["create", "delete"]
    },
    {
      "target_type": "registry",
      "actions": ["delete", "update"]
    },
    {
      "target_type": "sandbox",
      "actions": ["create", "delete", "fork", "pause", "resume", "start", "stop", "update"]
    },
    {
      "target_type": "secret",
      "actions": ["delete", "update"]
    },
    {
      "target_type": "service_account",
      "actions": ["create", "delete", "update"]
    },
    {
      "target_type": "snapshot",
      "actions": ["create", "delete", "replicate"]
    },
    {
      "target_type": "volume",
      "actions": ["create", "delete"]
    }
  ]
}
```

Some pairs cover more than one operation:

- **`organization`** **`update`** is recorded for changes to the organization itself, for the [audit logging toggle](#turn-audit-logging-off), and for billing changes: wallet top-ups, the automatic top-up rule, coupon redemption, plan changes, the balance notification rule, and the billing contact email.
- **`organization_invitation`** **`update`** is the invitee accepting the invitation. **`delete`** is the organization revoking it. **`decline`** is the invitee refusing it.
- **`sandbox`** **`create`** is also recorded the first time a sandbox's signing key is fetched, since that request mints the key. Later fetches of the same key are reads and are not recorded.
- **`sandbox`** **`update`** covers label changes, lifecycle policy changes, and signing key rotation.
- **`service_account`** **`delete`** archives the account and its keys; the record survives.

## Metadata

The metadata field carries context that the handler for the action already resolved. It never contains secret values. Entries for pairs not listed here have empty or null metadata.

| Target type                   | Action    | Keys                                                                                                                       |
| ----------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| **`sandbox`**                 | **`create`**  | **`class`**, **`region`**                                                                                                           |
| **`sandbox`**                 | **`fork`**    | **`forked_from`**: ID of the source sandbox                                                                                    |
| **`snapshot`**                | **`create`**  | **`kind`**: **`disk`** or **`full`**                                                                                                   |
| **`image`**                   | **`create`**  | **`region`**                                                                                                                   |
| **`api_key`**                 | **`create`**  | **`service_account_id`** when the key was minted for a service account. Otherwise no metadata                                  |
| **`project`**                 | **`update`**  | **`previous_name`**, **`name`**                                                                                                    |
| **`project`**                 | **`delete`**  | **`revoked_keys`**: number of API keys revoked with the project                                                                |
| **`project_member`**          | **`create`**  | **`user_ids`** on a bulk add, where **`target_id`** is the project ID. A single add has no metadata and **`target_id`** is the user ID |
| **`project_member`**          | **`delete`**  | **`revoked_keys`**: number of API keys revoked with the membership                                                             |
| **`project_service_account`** | **`delete`**  | **`revoked_keys`**: number of API keys revoked with the membership                                                             |
| **`organization`**            | **`update`**  | **`audit_logs_disabled`** (boolean) when the entry records the audit logging toggle. Other updates have no metadata            |
| **`organization`**            | **`delete`**  | **`revoked_keys`**: number of API keys revoked, **`projects`**: number of projects deleted with the organization                   |
| **`organization_user`**       | **`update`**  | **`previous_roles`**, **`roles`**, and **`dry_run: true`** when the change was only validated                                          |
| **`organization_user`**       | **`delete`**  | **`previous_roles`**, **`roles`** (empty), **`self_removal`** (boolean)                                                                |
| **`organization_invitation`** | **`create`**  | **`email`**, **`previous_roles`**, **`roles`**                                                                                         |
| **`organization_invitation`** | **`delete`**  | **`email`**, **`previous_roles`**, **`roles`** (empty)                                                                                 |
| **`organization_invitation`** | **`decline`** | **`previous_roles`**, **`roles`** (empty)                                                                                          |
| **`service_account`**         | **`create`**  | **`previous_roles`** (empty), **`roles`**                                                                                          |
| **`service_account`**         | **`update`**  | **`previous_roles`**, **`roles`**, and **`dry_run: true`** when the change was only validated                                          |
| **`service_account`**         | **`delete`**  | **`already_archived`** (boolean)                                                                                               |

The roles and previous roles fields are arrays of role slugs.

## Outcomes

The outcome field indicates the result of the action. Statuses follow standard HTTP semantics:

| **Outcome** | **Description**                          |
| ----------- | ---------------------------------------- |
| Info        | Informational (1xx codes)                |
| Success     | Action succeeded (2xx codes)             |
| Redirect    | Redirects (3xx codes)                    |
| Error       | Client/server error (4xx/5xx)            |
| Unknown     | No status code was recorded on the entry |

## Turn audit logging off

Audit logging is on by default. Organizations that do not want sandbox activity recorded can turn off capture on the workload plane. Management-plane actions, such as changes to the organization, its members, and its credentials, are always recorded and cannot be turned off. Neither can this setting itself: toggling it is recorded as an update on the organization target.

Turning capture off is not retroactive: earlier entries stay readable until retention ends.

Reading the setting requires audit read permission; changing it requires audit write permission, which no API key holds. This lets the **Auditor** role read the log but not disable it.


```bash
# Read the current setting
curl 'https://api.daytona.com/orgs/{organizationId}/audit-logs/config' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'

# Turn workload-plane capture off
curl 'https://api.daytona.com/orgs/{organizationId}/audit-logs/config' \
  --request PATCH \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --data '{ "disabled": true }'
```

| **Field**  | **Type**  | **Description**                                                       |
| ---------- | --------- | --------------------------------------------------------------------- |
| **`disabled`** | **`boolean`** | Turns workload-plane capture off with **`true`** and on with **`false`**. This field is required. |

- The **`GET`** and **`PATCH`** endpoints require a signed-in dashboard session.
- The **`GET`** and **`PATCH`** endpoints reject API keys.


Both return the current state:

```json
{
  "disabled": true
}
```