# Audit API
URL: https://openship.io/docs/api/audit.md

Read the append-only audit log for your organization — who did what, when — with cursor or page pagination and filters.

import { TypeTable } from 'fumadocs-ui/components/type-table';

The Audit API is a read-only feed of the append-only audit log for your active organization: an event per
significant action (logins, member changes, deployments, settings edits, and more), newest first. In the
dashboard this is the **Audit** page; there's no dedicated CLI command — reach it through
[`openship api`](/docs/cli/access).

<Callout title="Base path & auth">
All paths are relative to your instance, under **`/api`** — e.g. `https://your-host/api/audit`.
Send a personal access token as a bearer header (`Authorization: Bearer <token>`), created with
[`openship token create`](/docs/cli/access). The dashboard uses your session cookie instead. See the
[API overview](/docs/api) for the full auth model.
</Callout>

## Endpoints

| Method & path | Permission | What it does |
|---|---|---|
| `GET /api/audit` | `audit:read` | List audit events for the active org (newest first), with cursor or page pagination and filters. |

## List audit events

```
GET /api/audit
```

Every event is scoped to your active organization. Access is gated by role: owners and admins always see the
log, plain members are denied, and restricted users need an explicit `audit:read` grant on the org-level
`audit` resource. See [permissions & roles](/docs/security/auth).

### Query parameters

All parameters are optional. Choose **one** pagination mode: pass `cursor` (keyset) *or* `page`/`perPage`
(offset). Cursor mode is recommended for anything that streams pages — it survives concurrent writes without
shifting rows; page mode powers the dashboard's "Showing 1–50 of N" count.

<TypeTable
  type={{
    cursor: { type: 'string', description: 'Opaque keyset cursor from a previous response’s pageInfo.endCursor. Presence of this parameter (even empty) selects cursor mode.' },
    limit: { type: 'number', description: 'Page size in cursor mode. Clamped to a maximum of 200.', default: '50' },
    page: { type: 'number', description: '1-based page number in offset mode.', default: '1' },
    perPage: { type: 'number', description: 'Page size in offset mode. Clamped to a maximum of 200.', default: '50' },
    eventType: { type: 'string', description: 'Filter by dot-namespaced event taxonomy, e.g. deployment.succeeded, member.invited, settings.updated.' },
    actorUserId: { type: 'string', description: 'Filter to events performed by a single user.' },
    resourceType: { type: 'string', description: 'Filter by resource kind, e.g. project, deployment, server, settings, member, organization.' },
    resourceId: { type: 'string', description: 'Filter to a single resource. Typically combined with resourceType.' },
  }}
/>

### Response

Each event row carries `id`, `eventType`, `resourceType`, `resourceId`, `before`/`after` JSON snapshots
(mutation events only; otherwise null), `ipAddress`, `userAgent`, and `createdAt`. Rows are enriched with an
`actor` object (`{ id, email, name }`) resolved from `actorUserId`, or `null` for system-emitted events.

The pagination envelope depends on the mode:

- **Cursor mode** → `{ data, pageInfo: { hasNextPage, endCursor } }`. Pass `endCursor` back as `cursor` for
  the next page; there is no `total`.
- **Offset mode** → `{ data, total, page, perPage }`.

```bash
# Cursor mode — newest 50 deployment-failure events
curl -G https://your-host/api/audit \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  --data-urlencode "cursor=" \
  --data-urlencode "limit=50" \
  --data-urlencode "eventType=deployment.failed"
```

```bash
# Same thing via the CLI escape hatch (path is relative to /api):
openship api /audit -q eventType=deployment.failed -q limit=50
```

## Errors you might see

<Callout title="403 — not permitted" type="warn">
Your role can't read the audit log. Members are denied by default; ask an owner/admin to grant `audit:read`
on the org's `audit` resource, or view the log from an admin account. See
[permissions & roles](/docs/security/auth).
</Callout>

<Callout title="Empty results, not an error" type="info">
Filters that match nothing return `200` with an empty `data` array. Double-check `eventType` spelling
(it's the exact dot-namespaced tag) and that `resourceId` belongs to your organization. Retention is
org-configurable (default 90 days), so events older than the window are pruned and won't appear.
</Callout>
