agent_view, agent_atc, agent_purchase, …) with custom event types registered through the Custom Event Types registry.
Composition
A dashboard belongs to a single brand. Each widget owns:- type — one of six starter types (
counter,time_series,breakdown_bar,funnel,table,top_n) - config — type-specific options validated against the widget’s zod schema. Title and the per-widget aggregation parameters live inside
config(there is no separatetitleoraggregationcolumn on the widget row) - position — grid coordinates
(x, y, w, h)for react-grid-layout
agent_events with parameterized SQL — workspace_id and brand_id are always added to the predicate from the request context, regardless of user-supplied filters, giving brand isolation by construction. Results are cached in Redis for 60 seconds.
Roles
- Members can view dashboards
- Owners and admins can create, edit, and delete
Brand context
Every read and write requires brand context. Send it as either:X-Active-Brand: <uuid>header (preferred), or?brand_id=<uuid>query parameter (fallback for callers that cannot send headers)
POST /aggregate endpoint the header / query is the only source of brand context — the request body does not carry brand. This eliminates the class of bugs where a body field disagrees with the header.
See also
- Dashboards API — REST operations
- Brands — the unit of isolation that owns each dashboard