Skip to content

Event logs

HappyView logs system events: lexicon changes, record operations, script errors, user actions, and more. See the Event logs guide for details on event types and retention.

const TOKEN = "hv_..."; // your API key
const headers = { Authorization: `Bearer ${TOKEN}` };
GET /admin/events
interface EventLog {
id: string;
event_type: string;
severity: string;
actor_did: string | null;
subject: string | null;
detail: Record<string, unknown>;
created_at: string;
}
interface EventLogResponse {
events: EventLog[];
cursor?: string;
}
const response = await fetch(
"http://127.0.0.1:3000/admin/events?severity=error&limit=10",
{ headers },
);
const data: EventLogResponse = await response.json();
ParamTypeRequiredDescription
event_typestringnoFilter by exact event type (e.g. script.error)
categorystringnoFilter by category prefix (e.g. lexicon matches every lexicon.* event). Separate several with commas to match any of them
severitystringnoFilter by severity: info, warn, or error. Separate several with commas to match any of them
subjectstringnoFilter by subject (lexicon ID, record URI, admin DID, etc.). Matches any subject containing the value
afterstringnoOnly events at or after this RFC 3339 time
beforestringnoOnly events before this RFC 3339 time
cursorstringnoPagination cursor: the cursor from a previous response
limitnumbernoResults per page (default 50, max 100)

Response: 200 OK

{
"events": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "script.error",
"severity": "error",
"actor_did": "did:plc:abc123",
"subject": "com.example.feed.like",
"detail": {
"error": "attempt to index nil value",
"script_source": "function handle(input, ctx) ... end",
"input": { "status": "hello" },
"caller_did": "did:plc:abc123",
"method": "com.example.feed.like",
"duration_ms": 12
},
"created_at": "2026-03-01T12:00:00Z"
}
],
"cursor": "2026-03-01T11:59:00+00:00"
}

Events are returned in reverse chronological order (newest first). actor_did and subject are null for an event that has none. Pass the cursor value from the response to fetch the next page. A cursor comes back whenever a page is full, so the page after the last full one can be empty; a response with fewer events than limit has no cursor key. An empty parameter counts as unset, and an unreadable after or before is a 400.

Permission: events:read

GET /admin/events/count

Counts the events that match the same filters as List event logs: event_type, category, severity, subject, after and before. cursor and limit are ignored.

Permission: events:read

Response: 200 OK

{ "count": 1284 }
POST /admin/events/purge

Deletes the events that match a filter, in a background job. The body takes the same filters as the list, all optional:

{ "category": "record", "before": "2026-09-01T00:00:00Z" }

Permission: events:purge

Response: 202 Accepted

{ "job_id": "550e8400-e29b-41d4-a716-446655440000" }

The job is a happyview.purge-event-logs job; watch it there. The purge itself is logged as an event_logs.purged event. An unreadable after or before is a 400.