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/checkandGET …/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¶
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.