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.
- Creating the organization, joining it with an invite, and signing in to it in the console. These are made with your sign-in rather than a key, and name the member they made or signed in as.
Not recorded:
- Other reads.
- Evals:
POST …/entitlements/checkandGET …/entitlements. Recording them would slow every check. - 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 |
created_id |
The entry that created this id: a feature or plan key, a subscriber, subscription, override, member, invite, service account, or key 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-k-2-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 } }
}
},
"created_id": null
}
],
"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), member_session (the console), or jwt (your sign-in: creating the organization, joining it, signing in to it) |
actor.key_prefix |
The key's prefix, as on the key list. For the console, the session's. null for jwt |
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 |
created_id |
On a create, the id it made (filter with created_id). Otherwise null |
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; for a config, value_type and an enum's options |
PATCH /org/features/{key} |
name, description, and an enum's options 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, limit, or value, expires_at. Existing: allowed, limit, or value, 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 as a change |
POST /org/members |
member_id, role |
PATCH /org/members/{member_id} |
role, status, display_name 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" }, { "kind": "limit", "limit": 25 }, or { "kind": "config", "value": "priority" }. 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 } },
"support_tier": { "from": { "kind": "config", "value": "basic" }, "to": { "kind": "config", "value": "priority" } }
}
}
A JSON config value in another key order is the same value, not a change.
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.
Your own log¶
With your sign-in, not a key. What you did with your own account: changing your display name, deleting your account, and creating, joining, or signing in to an organization (with the organization in organization_id and your member in actor.member_id). The same query parameters and entry, plus user_id: yours. Only you can read it. What you do inside an organization is in that organization's log.