Activity
The tamper-evident, hash-chained audit log - every attempted and executed action, cursor-paginated.
Activity is Botanary's audit trail. Its own tag description is exact about what it is: "the tamper-evident, hash-chained audit log (paginated; executed vs declined)."
Endpoints
GET /activitylists the log: "every attempted/executed action and every grant/revoke," cursor paginated. Query params:cursor,limit(1 to 100, default 25),status,delegationId,from/to(ISO-8601), andaccountId. It returns anActivityPage.GET /activity/anchorreports how much of the log is committed on-chain. Its description explains exactly why that distinction matters: "unanchoredCountis the window of records written since the last anchor - tamper-evident (the hash chain) but not yet tamper-proof (nothing on-chain commits to them). Reported, never hidden." It returns anAnchorStatus.GET /activity/{eventId}returns oneAuditEvent, with its hash-chain fields.
On the backend, the anchor route is declared ahead of the {eventId} route deliberately, so a literal
path like /activity/anchor doesn't get swallowed by the dynamic {eventId} matcher.
The AuditEvent shape
Fields: id, timestamp, optional dateLabel/timeLabel, actor, action, optional
amount/token, chain, chainId, status, an optional declineReason, provenance, an optional
txHash, an optional auditNonce (AgentGuard's own monotonic counter, present only on executed
actions), an optional prevHash, hash, and an optional delegationId.
A real example from the contract, illustrating a declined action:
id: a2
status: declined
declineReason: per_action_exceeded
provenance: offchain_only
delegationId: d1ActivityPage wraps a page of results as { items: AuditEvent[], pageInfo: PageInfo }, where
PageInfo is { nextCursor?, hasMore } - the standard cursor-pagination shape used across the list
endpoint. AnchorStatus requires available and unanchoredCount, with an optional anchoredHead
hash.
When an entry gets written
An audit entry is appended exactly once per operation, at the moment it reaches a terminal status - never speculatively, never twice. See User operations for the submit/reconcile flow that guarantees that "exactly once."
On the frontend
The frontend's Activity page merges this log with a separate "My jobs" tab (marketplace jobs), filters between all, executed, and blocked, and paginates in small pages. Every audit event it renders is typed straight off the generated schema:
type AuditEvent = components['schemas']['AuditEvent'];