# Audit log

Every change to your account and its projects is recorded: config changes, deploys, secret and environment changes, member changes, data-editor writes, impersonation and cap events. Each entry says who did it, what they did, to which project and when. Entries are kept for 365 days, then deleted by a daily job.

Who: owners and admins, signed in to the dashboard. Developers and viewers get `403 forbidden`. So do tokens, an owner's included: no token scope carries `audit:read`, so the CLI and MCP can't read the log.

In the dashboard, open **Security**, then **Audit log**. You can filter by member, project, action and date, and export what you're looking at as CSV or JSON.

Every query is limited to your own account. A filter for another account's project, or a cursor from another account's page, finds nothing.

## `GET /v1/audit-log`

Lists entries, newest first.

| Query | What it does |
| --- | --- |
| `actorId` | Only entries by this member (a member ID from `GET /v1/members`) or token. |
| `projectId` | Only entries for this project. |
| `action` | Only this action, such as `project.created` or `secret.set`. |
| `from`, `to` | ISO timestamps, inclusive. The dashboard sends whole UTC days. |
| `limit` | 1 to 100, default 50. |
| `cursor` | The `nextCursor` from the previous page. |

```json
{
  "entries": [
    {
      "id": "aud_…",
      "createdAt": "2026-11-02T14:05:00.000Z",
      "actorType": "member",
      "actorId": "mem_…",
      "action": "secret.set",
      "projectId": "prj_…",
      "targetType": "secret",
      "targetId": "STRIPE_SECRET_KEY",
      "metadata": {},
      "ip": "203.0.113.9",
      "userAgent": "Mozilla/5.0 …"
    }
  ],
  "nextCursor": "MjAyNi0xMS0wMlQxNDow…"
}
```

`actorType` is `member`, `token` or `system` (things Flatstack did itself, such as a cap being reached or a domain going live; `actorId` is then `null`). `metadata` is redacted before it's stored: values whose names look like a token, secret, password, key or authorization never appear. `nextCursor` is `null` on the last page. A malformed filter or cursor is `400 validation_failed`.

An entry made by a project's route carries that project's ID, so filtering by project finds it. Account-wide changes, such as inviting a member, have `projectId: null`.

## `GET /v1/audit-log/actions`

The actions your account's log holds, sorted, for building a filter.

```json
{ "actions": ["member.invited", "project.created", "secret.set"] }
```

## `GET /v1/audit-log/export`

Downloads every entry that matches the filter, with no paging. It takes the same `actorId`, `projectId`, `action`, `from` and `to` as the list, plus `format`: `csv` (the default) or `json`. The response is an attachment named `flatstack-audit-log-<date>.csv` (or `.json`).

The CSV has one header row: `id,createdAt,actorType,actorId,action,projectId,targetType,targetId,ip,userAgent,metadata`, with `metadata` as JSON. A cell that a spreadsheet would run as a formula (one starting with `=`, `+`, `-` or `@`) is prefixed with `'`. The JSON is an array of the same entries as the list.