Skip to content

Audit log

Who changed what in your organization. e10s records each change made in the console or with a member API key: who sent it, what it did, and how it ended. Admins read the log in the console or with the API.

What's recorded

One entry per request:

  • Every write under /org/…: features, plans, subscribers, subscriptions, overrides, the webhook, the organization, members, invites, service accounts, and keys.
  • Listing invites, because the list returns live invite links.

Not recorded:

  • Other reads.
  • Evals: POST …/entitlements/check and GET …/entitlements. Recording them would slow every check.
  • Creating an organization, joining one with an invite, and signing in. These happen before there is an organization to log into. What you do after signing in is recorded.
  • Requests without a valid key (401) and rate-limited requests (429). A request refused for its role (403) is recorded as rejected.

An entry names the member and the key or session. It doesn't include request or response bodies, query strings, or secrets: never a key, invite link, or webhook secret.

Nothing in the API or the console edits or deletes an entry. The log starts when e10s turned it on; there are no entries from before.

Read the log

GET /org/audit-events

Label: org:audit:read (admin only). Entries name each member's IP address and user agent.

Query
limit, starting_after As Lists, but newest first: starting_after walks to older entries
actor_member_id Entries by one member. A service account's member id is on its service account
outcome pending, rejected, responded, or no_response
method POST, PUT, PATCH, DELETE, or GET
route Exact template, e.g. /org/plans/{key}
params[<name>] A path value, e.g. params[key]=pro or params[subscriber_id]=cust_123. Several names AND
service api (features through the webhook) or identity (organization, members, invites, service accounts, keys)
request_id One request, by its request_id
occurred_after, occurred_before RFC 3339. After is inclusive, before is exclusive

Filters AND. An unknown query param or a malformed value is 422 VALIDATION_ERROR.

{
  "audit_events": [
    {
      "id": "01JA2Z6Q3Y8D5V2K9N4R7T1W0X",
      "occurred_at": "2026-10-08T18:30:00.123Z",
      "request_id": "7d6f1c2e-4b0a-4e8e-9a51-3f2b8c1d9e04",
      "organization_id": "01J9ZX4K7M2P5Q8R1S3T6V9W0Y",
      "actor": {
        "member_id": "01J9ZX5B8C1D4E7F0G3H6J9K2M",
        "auth_type": "member_apikey",
        "key_prefix": "e10s-mk-1-x9Qa"
      },
      "service": "api",
      "method": "PATCH",
      "route": "/org/plans/{key}",
      "params": { "key": "pro" },
      "ip": "203.0.113.7",
      "user_agent": "node",
      "outcome": "responded",
      "status": 200,
      "details": {
        "grants": {
          "seats": { "from": { "kind": "limit", "limit": 10 }, "to": { "kind": "limit", "limit": 25 } }
        }
      }
    }
  ],
  "has_more": false
}

Entry

Field
id Entry id. Newest first by id
occurred_at When e10s received the request. UTC, milliseconds
request_id The request's id, as on an error body. Quote it to support
actor.member_id The member who sent it. A service account is a member
actor.auth_type member_apikey (a service-account key) or member_session (the console)
actor.key_prefix The key's prefix, as on the key list. For the console, the session's
service, method, route What was called. route is the template, not the raw path
params The path values in route, by name. {} when there are none
ip, user_agent Where it came from. user_agent is null when absent
outcome, status How it ended. See below
details What changed. See below. null when there is nothing to say
outcome Means Did it change anything?
responded e10s answered with status Per status: a 2xx did
rejected Refused before it ran, e.g. 403 No
no_response It ran and never answered Unknown
pending Recorded before it ran, no outcome since Unknown

pending and no_response are rare. They mean the outcome was lost, not that the change failed: check the resource.

Details

What a successful write changed. A change is { "from", "to" }, for the fields that changed. A create names what it created, since the new id or key isn't in the path. A write that changed nothing has details: null, as do deletes: the route and params say what was deleted.

Write details
POST /org/features key, name, kind
PATCH /org/features/{key} name, description as changes
POST /org/plans key, name, kind, grants (each grant by feature key)
PATCH /org/plans/{key} name as a change. grants: a change for each feature whose grant changed
POST /org/plans/{key}/archive, …/unarchive archived as a change. Nothing when it already was
POST /org/subscribers subscriber_id, name
PATCH /org/subscribers/{subscriber_id} name as a change
POST …/subscriptions subscription_id, plan_id
POST …/subscriptions/change subscription_id (the new one), canceled_subscription_id, plan_id as a change
POST …/subscriptions/{subscription_id}/cancel plan_id (the plan it ended)
PUT …/overrides/{feature_key} New: override_id, kind, allowed or limit, expires_at. Existing: allowed or limit, expires_at as changes
PUT /org/webhook New: url, secret_prefix. Existing: url, disabled as changes
POST /org/webhook/rotate secret_prefix as a change
POST /org/webhook/enable, …/disable disabled as a change
PATCH /org name, slug as changes
POST /org/members member_id, role
PATCH /org/members/{member_id} role, status as changes
POST /org/invites invite_id, role
POST /org/service-accounts service_account_id, member_id, name, role
PATCH /org/service-accounts/{service_account_id} name as a change
POST /org/service-accounts/{service_account_id}/api-keys key_id, key_prefix, name. key_prefix is what later entries show as the key's actor.key_prefix

A grant is as on the plan, without feature: { "kind": "flag" } or { "kind": "limit", "limit": 25 }. null means the feature isn't on the plan:

{
  "grants": {
    "sso": { "from": null, "to": { "kind": "flag" } },
    "seats": { "from": { "kind": "limit", "limit": 10 }, "to": { "kind": "limit", "limit": null } }
  }
}

An override limit of null is unlimited, as on the override.

A field too large to record (a long description, a large grants change) reads { "too_large": true } in place of its value. The entry still names the field.

When the log is unavailable

A write that can't be recorded doesn't run. It fails with 503 SERVICE_UNAVAILABLE, and nothing changed: retry with backoff. Checks and reads keep working.