Skip to content

Audit events

Every meaningful action in an Evershell tenant lands an immutable row in the audit log: configuration mutations, member changes, proxy-decided HTTP allows and denies, workspace lifecycle events, task submissions. This page is the reference catalog — every event type the runtime emits, what fires it, and what’s in its detail.

The customer-facing query endpoint is GET /v1/audit. See Querying below.

Every event carries a category discriminator:

CategoryMeaning
auditConfiguration mutations, credential state transitions, and HTTP / DNS policy decisions. The compliance signal: “Alice deleted role X at 14:23.”
activityWorkspace and task lifecycle events — what’s running, what completed. The narrative signal: “task task_123 completed in 42s.”

Both ride the same /v1/audit endpoint; filter on category to split them.

Every row carries the same set of columns, regardless of event type. Most columns are populated only for certain events:

ColumnAlways populated?Meaning
idyesUUIDv7. Pagination tiebreaker.
org_idyesTenant identifier.
user_idyesActor attribution. A real WorkOS user id on session-authenticated requests and proxy-emitted events (lifted from the workspace’s creator); "system" on API-key-authenticated requests and WorkOS webhook-driven events.
timestampyesEvent emission time (set at the source, not at storage). RFC3339.
event_typeyesThe closed-enum slug — see catalog below.
categoryyesaudit or activity.
actoryes"control-plane", "policy-engine" (proxy), or "agent".
workspace_idwhen scopedThe workspace this event belongs to.
task_idwhen scopedThe task within the workspace.
agent_roleworkspace eventsRole name, auto-derived from workspace → role. Empty on non-workspace events (config mutations, membership changes).
agent_providerworkspace eventsProvider name, auto-derived from workspace → role → provider. Empty on non-workspace events.
destinationproxy eventsUpstream hostname (proxy policy_decision).
methodproxy eventsHTTP verb.
pathproxy eventsHTTP path.
decisionproxy eventsallow or deny.
reasonproxy deniesDeny reason — closed enum, see Deny reasons.
latency_msproxy eventsWall-clock latency across all callbacks.
transforms_appliedproxy eventsCount of transforms fired.
validations_appliedproxy eventsCount of schema validations run.
policy_typeproxy eventshttp or dns.
phaseproxy eventsrequest_headers, request_body, response_headers, response_body, dns.
capability_nameproxy eventsMatched capability from the workspace’s caps.yaml.
detailmost eventsEvent-specific JSON payload (see catalog).
debitsproxy allowsPer-counter amounts committed at terminal-allow.
budget_stateproxy allowsPer-counter snapshot at commit point.
flooredproxy allowsPer-counter “ceiling applied” reasons.
transforms_failedproxy eventsPer-transform failure reasons.
auth_usedproxy eventsCredentials whose mint succeeded on the request.
auth_failuresproxy eventsPer-credential mint failures.

Configuration mutations (category: audit, actor: control-plane)

Section titled “Configuration mutations (category: audit, actor: control-plane)”

Each fires when an operator changes the corresponding resource via the control plane API. Detail always carries resource_id (the canonical id of the mutated resource) plus a per-event summary.

Event typeFires whenDetail keys (besides resource_id)
provider_createdPOST /v1/providersname, display_name, kind, domain
provider_updatedPATCH /v1/providers/{id}Same + patched_fields
provider_deletedDELETE /v1/providers/{id}name
provider_archivedPOST /v1/providers/{id}/archivename, cascaded_roles (count of agent roles cascade-archived)
provider_unarchivedPOST /v1/providers/{id}/unarchivename
agent_role_createdPOST /v1/agent-rolesname, display_name, provider_id, model
agent_role_updatedPATCH /v1/agent-roles/{id}Same + patched_fields
agent_role_deletedDELETE /v1/agent-roles/{id}name
agent_role_archivedPOST /v1/agent-roles/{id}/archivename
agent_role_unarchivedPOST /v1/agent-roles/{id}/unarchivename
content_pack_createdPOST /v1/packs or /uploadname, display_name, version, visibility, tier, image_origin
content_pack_updatedPUT /v1/packs/{id}, file edits, visibility flipsname, version, image_ref, kind (one of reupload / image_ref / file_edit / visibility), edited (file path on file_edit only)
content_pack_deletedDELETE /v1/packs/{id}name, version
content_pack_clonedPOST /v1/packs/{id}/clonename, source_pack_id, source_name
credential_createdPOST /v1/credentialsname, provider, kind, scopes, status
credential_deletedDELETE /v1/credentials/{id}name, provider, kind
credential_authorizedOAuth callback completesname, provider, kind, scopes, status
credential_statusProxy-side mint status flipsname, provider, kind, from_status, to_status, reason (proxy terminal-error string), terminal (bool)
secret_createdPOST /v1/secretsname (= resource_id)
secret_deletedDELETE /v1/secrets/{name}name
api_key_createdPOST /v1/api-keyslabel, prefix, permission_set
api_key_revokedDELETE /v1/api-keys/{id}label, prefix, permission_set
membership_invitedPOST /v1/orgs/{id}/membershipsemail, role, invitation_id
membership_acceptedWorkOS invite-accepted webhookemail, role, workos_user_id, invitation_id
membership_updatedPATCH /v1/orgs/{id}/memberships/{user_id}email, from_role, to_role
membership_removedDELETE /v1/orgs/{id}/memberships/{user_id}email, role, was_pending, credentials_reaped
membership_first_seenFirst /v1/* request after invite-acceptemail, role, source (currently always "self_heal")
workspace_forkedPOST /v1/workspaces/{id}/fork (parent row)child_workspace_id, fork_point_task_count, fork_source_snapshot_ref
workspace_fork_createdPOST /v1/workspaces/{id}/fork (child row)parent_workspace_id, fork_point_task_count, fork_source_snapshot_ref

The detail payload never carries secret values — API key secrets, credential refresh tokens, vault content. Only metadata.

Workspace + task lifecycle (category: activity)

Section titled “Workspace + task lifecycle (category: activity)”
Event typeActorFires whenDetail keys
workspace_createdcontrol-planePOST /v1/workspaces or POST /v1/tasksforked, parent_workspace_id, fork_point_task_count, fork_source_snapshot_ref (forks only)
workspace_provisionedcontrol-planeKubernetes pod becomes ready and the workspace finishes provisioning
workspace_activatedpolicy-engineFirst inbound request lands on the proxy for this workspace
workspace_deactivatedpolicy-engineWorkspace’s proxy state torn down (stop / archive)
workspace_evictedpolicy-engineWorkspace’s in-memory state aged out of the proxy (TTL expiry)
workspace_restartedcontrol-planePod respawned to pick up role / pack changes
workspace_stoppedcontrol-planePod terminated and snapshot saved
workspace_archivedcontrol-planePOST /v1/workspaces/{id}/archive
workspace_unarchivedcontrol-planePOST /v1/workspaces/{id}/unarchive
task_submittedcontrol-planePOST /v1/tasks or POST /v1/workspaces/{id}/tasksdescription
task_completedcontrol-planeTask reaches a terminal state (completed / failed / cancelled)status, optional reason

Proxy HTTP + DNS decisions (category: audit, actor: policy-engine)

Section titled “Proxy HTTP + DNS decisions (category: audit, actor: policy-engine)”

The single most-queried event type. Every request that hits the proxy fires exactly one policy_decision event at its terminal phase — allow if it made it through, deny if not.

Event type: policy_decision

Phase values (where in the request lifecycle the decision was made):

phaseMeaning
request_headersEarly admission gate (token verification, workspace lookup).
request_bodyBody-shape checks (schema, format, body-matching CEL, body transforms, budget reservations).
response_headersResponse-side counters that don’t need body inspection.
response_bodyStreaming / body-inspecting counters (token usage, etc.).
dnsDNS-level policy decision (policy_type: dns).

Allow events populate: decision: allow, debits, budget_state, floored (when extraction failure forced the FailureCharge floor), transforms_applied, validations_applied, auth_used, transforms_failed, auth_failures, capability_name.

Deny events populate: decision: deny, reason (see below), plus detail.allowrule_eval for no_match / schema_violation / format_mismatch denies, plus detail.counter (+ optional detail.scope) for budget_exhausted / concurrency_exhausted denies.

Closed enum on the reason column:

reasonTriggered by
no_schemeProxy bypass — the request reached policy enforcement without passing through the proxy’s TLS front-end.
no_tokenThe request carried no capability token.
invalid_tokenCapability token failed verification.
workspace_not_activatedWorkspace id from token isn’t registered on this proxy.
workspace_goneWorkspace deactivated mid-request.
no_policyWorkspace has no OPA policy attached.
policy_errorOPA evaluation panicked or returned a type error.
policy_emptyPolicy returned no result.
policy_type_errorPolicy returned an unexpected result shape.
no_matchNo allow rule matched the request (details in detail.allowrule_eval).
invalid_jsonRequest body not valid JSON when a JSON schema is declared.
format_mismatchContent-Type disagreed with rule’s request_format.
schema_violationRequest body failed request_schema validation.
budget_exhaustedCounter hit its ceiling (detail.counter, detail.scope).
concurrency_exhaustedCounter hit its max_concurrent (detail.counter).
internal_errorgRPC handler panic.

DNS-phase audit rows are emitted only on allow — the proxy’s DNS resolver responds to denied lookups with a synthetic A record so the agent’s connect() succeeds, then the eventual HTTP request lands at the proxy’s request-phase handler and the actual deny is recorded as a standard policy_decision event with policy_type: http. There is no separate DNS-phase deny vocabulary; all 16 reasons above apply to HTTP and DNS-redirected denies alike.

When a header / path / query / body / auth transform fails to apply but the request is still allowed (transform mutations are observation, not policy), the proxy stamps transforms_failed.<transform_name> with one of:

transforms_failed.<name>Meaning
compile_errorRuntime compile failure: a nil compiled CEL program reached Apply. Rare — programs compile at save time.
eval_errorCEL Program.Eval returned an error (timeout, runtime CEL error).
type_mismatchCEL evaluated but produced a value the variant can’t consume (header expected string, body merge expected map, prefix target field non-string).
apply_errorGo-side apply step failed after eval (secret fetch, JSON parse / remarshal, query parse).
body_absentBody transform skipped because the request carried no body. Distinguishes “nothing went wrong, just nothing to rewrite” from real failures.
auth_unavailableAuth broker couldn’t mint a token for the referenced credential. Covers terminal failures (invalid_grant, revoked, key removed) and transient ones (network error to provider, 5xx). Coarse on purpose — the audit never echoes provider error text.

The key is the transform’s operator-assigned name when set, otherwise the positional transform[N] identifier.

auth_failures.<credential_name> uses the same closed enum as transforms_failed, but auth transforms carry no operator CEL so only auth_unavailable surfaces in practice. The key is the credential name (the value an auth: { credential: <name> } transform references), matching the broker’s resolve key.

When an auth transform fails, both maps are populated on the same audit row: transforms_failed[<transform-name-or-position>] records the failure class with the transform’s identifier, and auth_failures[<credential-name>] records the same class keyed by credential. The dual entry exists so operators can look up the failure either by which transform broke (timeline / debugging) or by which credential to repair (operations). Both carry the same reason string.

When a response counter’s extraction fails but the request is still allowed, the proxy charges FailureCharge against the counter and stamps floored.<counter_name> with one of:

floored.<counter>Meaning
framing_mismatchObserved Content-Type framing (sse vs single_document) disagreed with counter.framing.
format_mismatchObserved format disagreed with counter.format.
encoding_mismatchObserved encoding disagreed with counter.encoding.
parse_failedJSON decode failure on the counter body payload.
cel_eval_errorcounter.value CEL expression returned an error.
extracted_below_failure_chargeExtraction succeeded but produced a value below FailureCharge.
terminator_not_observedStream closed before the terminal SSE marker.

Agent events (category: activity, actor: agent)

Section titled “Agent events (category: activity, actor: agent)”

Agents call POST /task/{id}/event (proxied to the CP) with a type-schemed body. The proxy validates the body against the workspace’s AgentEventSchemas, gates on the agent_events budget counter (default name requests, 1 unit), and forwards to the audit pipeline.

Event type: one of a closed enum the platform ships: session_start, session_end, session_wrap_up, task_start, task_end, iteration_start, iteration_end, agent_thinking, agent_response, current_status, tool_call, tool_result, compaction_start, compaction_end, step_status, progress_status, file_change. The agent’s callback body must include the matching type field; the proxy validates it against the schema for that type and rejects anything else.

Detail: the raw callback body. The exact shape is workspace-policy-defined; treat it as opaque if you’re shipping the audit log to a SIEM.

GET /v1/audit returns events. Query parameters:

?filter=<col>=<val>[,<val2>...] exact-match (IN list)
?filter=<col>!=<val>[,<val2>...] not-match
?filter=<col>!= non-empty (column populated)
&from=<RFC3339> lower time bound
&to=<RFC3339> upper time bound
&order=asc|desc default desc
&limit=<int> page size
&cursor=<opaque> continue from previous page

Repeat filter for AND across columns. The whitelisted columns (server-side) are:

workspace_id, task_id, decision, destination, capability_name, event_type, category, agent_role, agent_provider, method, path, actor, counter, phase, policy_type.

counter is a virtual dimension that filters on JSONB key existence across debits and budget_state — handy for “show me every event that touched the tokens counter.”

Permission gating: callers with audit:read see every event in the org; callers with only audit:read:own see events attributed to their own user_id.

Every deny in the last hour:

Terminal window
curl -H "Authorization: Bearer $TOKEN" \
"https://<slug>.evershell.ai/v1/audit?filter=decision=deny&from=$(date -u -d '1 hour ago' +%FT%TZ)"

Every budget exhausted on the tokens counter:

Terminal window
curl "https://<slug>.evershell.ai/v1/audit?filter=event_type=policy_decision&filter=decision=deny&filter=counter=tokens"

Every API key creation:

Terminal window
curl "https://<slug>.evershell.ai/v1/audit?filter=event_type=api_key_created"

provider_created:

{
"id": "0191234d-25fa-7abc-be23-8e7f4abc1234",
"org_id": "org_acme",
"user_id": "user_alice",
"timestamp": "2026-05-23T14:32:15.123456Z",
"event_type": "provider_created",
"category": "audit",
"actor": "control-plane",
"detail": {
"resource_id": "prov_anthropic_01",
"name": "anthropic",
"display_name": "Anthropic",
"kind": "anthropic",
"domain": "api.anthropic.com"
}
}

policy_decision (allow, terminal at response_body):

{
"id": "0191234e-3a17-72cd-8f44-9b8e5dcd5678",
"org_id": "org_acme",
"user_id": "user_alice",
"workspace_id": "ws_test_01",
"task_id": "task_abc123",
"timestamp": "2026-05-23T15:10:42.987654Z",
"event_type": "policy_decision",
"category": "audit",
"actor": "policy-engine",
"agent_role": "researcher",
"agent_provider": "anthropic",
"policy_type": "http",
"phase": "response_body",
"decision": "allow",
"method": "POST",
"path": "/v1/messages",
"destination": "api.anthropic.com",
"capability_name": "anthropic-api",
"latency_ms": 1247,
"transforms_applied": 3,
"validations_applied": 1,
"auth_used": ["anthropic-api-key"],
"debits": { "requests": 1, "input_tokens": 1842, "output_tokens": 376 },
"budget_state": {
"input_tokens": { "workspace": { "used": 18420, "max": 1000000 } }
}
}

policy_decision (deny, budget exhausted):

{
"id": "0191234e-3a17-72cd-9012-a3c4e7f6abcd",
"org_id": "org_acme",
"user_id": "user_alice",
"workspace_id": "ws_test_01",
"timestamp": "2026-05-23T15:11:03.456789Z",
"event_type": "policy_decision",
"category": "audit",
"actor": "policy-engine",
"agent_role": "researcher",
"agent_provider": "anthropic",
"policy_type": "http",
"phase": "request_body",
"decision": "deny",
"method": "POST",
"path": "/v1/messages",
"destination": "api.anthropic.com",
"capability_name": "anthropic-api",
"reason": "budget_exhausted",
"detail": { "counter": "input_tokens", "scope": "workspace" }
}