Get API key

Audit logs

Who did what, when, and where to read it.

Audit logs at /settings/org/audit-logs record administrative and collaboration actions inside your org. The page is scoped to your org. You cannot see events from other orgs, and a user landing on this page can only see events from the org they belong to.

What is logged

The audit log captures state-changing actions on collaboration and admin surfaces. Read-only operations (listing teams, viewing a member, calling a chat model) are not in the audit table.

Today the logged actions are:

Action Resource type When it fires
create team A new team is created
update team A team's name, description, or settings change
delete team A team is deactivated
add_member team A user is added to a team
remove_member team A user is removed from a team
update_member_role team A team member's role changes
share source A source is shared with another user or team
respond_share source A user accepts or declines a share invitation
revoke_share source A share is revoked
invite conversation_participant A user is invited into a conversation
invite_team conversation_participant A team is invited into a conversation
respond_invite conversation_invite A user accepts or declines a conversation invitation
remove conversation_participant A participant is removed from a conversation
activate global_source A platform-managed source is activated for the org
deactivate global_source A platform-managed source is deactivated
update_config global_source A platform-managed source's configuration changes

Each entry stores:

  • Timestamp (UTC)
  • User (email and display name, resolved at query time from the user table)
  • Action and resource type (from the table above)
  • Resource ID (the affected team, source, conversation, etc.)
  • Detail: a small JSON blob, typically { method, path } for the originating HTTP request
  • IP address of the request

The Compliance API exposes the same records over a programmatic endpoint (when enabled for your org). See Compliance API below for endpoint details, or /settings/org/compliance-api in the app to mint a key.

Layout

The page has four blocks stacked top to bottom:

  • Summary cards: total events, unique users, unique actions over the selected range
  • Top Actions: pill-shaped badges sorted by count, clickable to filter the table below
  • Filters and export: date range, action filter, page size, export menu
  • Log entries table: paginated, sorted newest first

Click any row to expand it inline; the row's detail JSON renders as a preformatted block. Rows with empty detail are not expandable.

Filtering

Three filters control what you see:

  • Date range. Presets are 7, 30, and 90 days. Selecting "Custom range" reveals two date inputs. The from/to dates use the browser's local timezone for the picker, then expand to a full UTC day on the server; picking the same day twice returns the full 24-hour window.
  • Action. Either "All actions" (the default) or one of the actions present in the current summary. Clicking a badge in Top Actions sets this filter; clicking it again clears it.
  • Page size. 25, 50, or 100 rows per page.

Changing any filter resets pagination to page 1.

Exporting

The Export dropdown next to the filters offers two formats:

  • CSV: one row per event, flattened. The detail blob is stringified into its own column.
  • JSON: full fidelity, including the nested detail blob.

The export respects whatever filters are currently set on the page. There is no separate "download everything" path; narrow the range first, then export.

The download streams directly to disk through the browser. It uses your session cookies; you do not need an API key.

Retention

Audit log entries are stored indefinitely in your org's database. There is no automatic purge today. The Compliance API can read any record from the org's full history.

If you need an explicit retention policy enforced (for example, automatic deletion after 365 days for GDPR compliance) that is a data-handling configuration that lives outside the app UI; raise it with your PrivateMind account contact.

Compliance API

The Compliance API exposes audit log records over a programmatic endpoint, designed for SIEM ingestion and automated compliance pipelines. It returns the same records visible in the UI, with cursor-based pagination and no session-cookie dependency.

Enabling the API

An org admin must enable the Compliance API at Settings → Org → Compliance API (/settings/org/compliance-api), then mint a compliance key from that page. The key carries the audit_logs:read scope and is separate from the API keys used for inference.

Disabling the toggle is an immediate kill switch: outstanding keys stop working straight away, they do not need to be revoked first.

Authentication

Compliance keys are minted as an access-key-id and secret joined by a colon. Pass the whole string, exactly as shown when you minted it, as a bearer token:

Text
Authorization: Bearer <ACCESS_KEY_ID>:<SECRET_ACCESS_KEY>

Do not split the two halves across separate headers, and do not add or strip a prefix — the value is sent verbatim. A token that is not exactly two colon-separated parts returns 401.

Endpoint

cURL
curl -s "https://privatemind.com/api/v1/compliance/audit-logs?from=2026-08-01&page_size=100" \
  -H "Authorization: Bearer $COMPLIANCE_KEY"

Note the host: the Compliance API is served by the application, not by api.privatemind.com (which serves the inference API).

Query parameters

All parameters are optional. Without filters the endpoint returns the most recent events for your org.

Param Type Meaning
from RFC 3339 datetime or YYYY-MM-DD date Lower bound, inclusive (created_at >= from).
to RFC 3339 datetime or YYYY-MM-DD date Upper bound, exclusive (created_at < to).
action string Filter to a single action, e.g. user_signed_in. Enumerate valid values from event types rather than guessing.
page_size integer Page size. Default 100, maximum 1000; larger values are clamped to 1000, not rejected.
cursor opaque string Continuation token from the previous page's next_cursor.

There is no resource_type filter, and no limit/offset — pagination is cursor-based (see below).

A from or to value that is not a parsable date is ignored silently rather than rejected, so the response comes back unfiltered on that bound. Validate your date formatting before trusting a result set.

Filtering by action matches across the legacy action rename map in both directions — a filter on login also returns rows stored as user_signed_in, and vice versa. Stored rows are never rewritten, so this is what keeps a filter stable across the taxonomy cutover.

Response

JSON
{
  "data": [
    {
      "id": "18472",
      "timestamp": "2026-08-15T09:31:12.442Z",
      "actor": "jane.smith@example.com",
      "api_key_id": null,
      "action": "team_created",
      "resource_type": "team",
      "resource_id": "team-abc123",
      "metadata": { "method": "POST", "path": "/v1/teams" }
    }
  ],
  "pagination": { "next_cursor": "eyJ0IjoiMjAyNi0wOC0xNVQwOTozMToxMi40NDJaIiwiaSI6MTg0NzJ9", "has_more": true }
}
Field Meaning
id Record id, serialised as a string.
timestamp UTC timestamp of the event.
actor For key-authenticated actions, the key's label (falling back to its id). For human actions, the user's email. null for system-driven events — surfaced explicitly rather than masked behind a placeholder.
api_key_id The key that performed the action, or null when a human did.
action The action name.
resource_type The type of resource affected.
resource_id Id of the affected team, source, conversation, and so on.
metadata Small JSON object, { method, path } for the originating HTTP request. {} when there is no detail.

Records are sorted newest-first (timestamp descending, id descending as a tiebreaker so pages stay stable when several rows land in the same millisecond).

There is no success field on this endpoint's success responses, and no user_email, user_display_name, detail, ip_address, or created_at field. Caller IP is recorded server-side but is not returned.

Pagination

Pagination is keyset (cursor) based, not offset based. To walk the full set, pass the previous response's next_cursor back as cursor, and stop when has_more is false:

cURL
CURSOR=""
while : ; do
  PAGE=$(curl -s "https://privatemind.com/api/v1/compliance/audit-logs?page_size=1000${CURSOR:+&cursor=$CURSOR}" \
    -H "Authorization: Bearer $COMPLIANCE_KEY")
  echo "$PAGE" | jq -c '.data[]'
  [ "$(echo "$PAGE" | jq -r '.pagination.has_more')" = "true" ] || break
  CURSOR=$(echo "$PAGE" | jq -r '.pagination.next_cursor')
done

next_cursor is null on the last page. The cursor is opaque — round-trip it unchanged. Do not decode, construct, or increment it; a malformed or stale cursor returns 400 invalid_cursor.

Event types

cURL
curl -s "https://privatemind.com/api/v1/compliance/audit-logs/event-types" \
  -H "Authorization: Bearer $COMPLIANCE_KEY"

Returns the declared event surface, so filters and dashboards can be built against the registry instead of scraping stored history:

JSON
{
  "data": [
    {
      "action": "team_created",
      "category": "org",
      "resource_type": "team",
      "description": "A team was created.",
      "legacy_actions": ["create"]
    }
  ]
}

legacy_actions lists the older action strings an event replaced. Stored history keeps those values, which is why filtering on either convention matches both.

Rate limits

Requests are limited per compliance key, at the key's configured rate (60 per minute by default). Every response carries:

  • x-ratelimit-limit-requests
  • x-ratelimit-remaining-requests

A throttled request returns 429 and additionally carries Retry-After (seconds) and x-ratelimit-reset-requests (an ISO timestamp).

Reads are themselves audited

Every successful call writes its own audit record, with action compliance_audit_logs_read and resource type compliance_api_key, attributed to the calling key. Expect to see your own polling in the feed — that is deliberate, so that "who read the audit log" is itself auditable.

Error responses

Status error.type Meaning
400 invalid_cursor The cursor is malformed or no longer decodable. Restart the walk without a cursor.
401 — Missing, malformed, expired, or invalid key. The body carries a message rather than an error object.
403 insufficient_scope The key does not carry audit_logs:read. Also returned when no bearer token is supplied at all.
403 feature_disabled The Compliance API is not enabled for the org, or the key resolves to no org context.
429 rate_limit_error Rate limit exceeded. Honour Retry-After.
500 — The query failed. Safe to retry.
503 — Key authentication is temporarily unavailable. Retry with backoff.

See Errors for the general error-envelope contract; note that this endpoint's success responses deliberately depart from it.

What is not logged

  • Chat messages and model calls. The content of user prompts and model responses is not in the audit log. Conversation history lives in your org's conversation tables and has its own retention model.
  • Reads. Listing teams, viewing a user's profile, opening a conversation: none of these produce audit entries.
  • Failed requests. Audit entries are only written for requests with status < 400. A 403 attempt to delete someone else's team is not recorded in this table.
  • Sign-in events. Authentication is handled by the identity provider; check the IdP's own audit log for sign-in trails.

Where next

  • Org settings: the feature toggles whose flips you'd often want to cross-reference here
  • Users and teams: what the add_member / update_member_role entries refer to