Overview

The Dashboard API provides endpoints for the organization-level analytics dashboard. It includes dashboard layout management (save/restore widget positions per user), summary statistics, and performance widgets.

Dashboard layouts are stored per-user and per-organization, allowing each user to customize their dashboard independently across different organizations.

Save Layout

Save (create or update) the current user’s dashboard widget layout. Uses PostgreSQL upsert to handle both first-time saves and subsequent updates in a single operation.

POST /api/trpc/dashboard.saveLayout

Request:

{
  "json": {
    "layout": {
      "lg": [
        { "i": "stats", "x": 0, "y": 0, "w": 12, "h": 2, "minW": 6, "minH": 2 },
        { "i": "kpi-summary", "x": 0, "y": 2, "w": 6, "h": 4, "minW": 4, "minH": 3 },
        { "i": "action-summary", "x": 6, "y": 2, "w": 6, "h": 4, "minW": 4, "minH": 3 },
        { "i": "my-tasks", "x": 0, "y": 6, "w": 8, "h": 5, "minW": 4, "minH": 3 },
        { "i": "recent-activity", "x": 8, "y": 6, "w": 4, "h": 5, "minW": 3, "minH": 3 }
      ],
      "md": [
        { "i": "stats", "x": 0, "y": 0, "w": 10, "h": 2, "minW": 6, "minH": 2 },
        { "i": "kpi-summary", "x": 0, "y": 2, "w": 5, "h": 4, "minW": 4, "minH": 3 },
        { "i": "action-summary", "x": 5, "y": 2, "w": 5, "h": 4, "minW": 4, "minH": 3 }
      ]
    }
  }
}
FieldTypeRequiredDescription
layoutobjectYesreact-grid-layout Layouts object mapping breakpoint names to widget position arrays

Layout Item Fields

Each layout item within a breakpoint array has the following fields:

FieldTypeRequiredDescription
istringYesWidget identifier (e.g., stats, kpi-summary, my-tasks)
xnumberYesX position in grid units
ynumberYesY position in grid units
wnumberYesWidth in grid units
hnumberYesHeight in grid units
minWnumberNoMinimum width constraint
minHnumberNoMinimum height constraint

Breakpoint Names

BreakpointDescription
lgLarge screens (desktop)
mdMedium screens (tablet landscape)
smSmall screens (tablet portrait)
xsExtra-small screens (mobile landscape)
xxsSmallest screens (mobile portrait)

Response:

{
  "result": {
    "data": {
      "json": {
        "id": "lay_01HXK5QJBN3YZXM8KJP2RSNV4C",
        "updatedAt": "2026-03-31T08:15:00.000Z"
      }
    }
  }
}

The userId and organizationId are always taken from the authenticated session context, never from client input. This ensures users can only modify their own dashboard layout and prevents cross-tenant access.

Get Layout

Retrieve the current user’s saved dashboard layout for the current organization. Returns null if no layout has been saved yet, allowing the frontend to fall back to the default layout.

GET /api/trpc/dashboard.getLayout

Response:

{
  "result": {
    "data": {
      "json": {
        "id": "lay_01HXK5QJBN3YZXM8KJP2RSNV4C",
        "layout": {
          "lg": [
            { "i": "stats", "x": 0, "y": 0, "w": 12, "h": 2, "minW": 6, "minH": 2 }
          ]
        },
        "updatedAt": "2026-03-31T08:15:00.000Z"
      }
    }
  }
}

Returns null when no layout has been saved:

{
  "result": {
    "data": {
      "json": null
    }
  }
}

When the response is null, the dashboard frontend should render widgets using the default layout configuration. The first time the user rearranges widgets, saveLayout is called to persist their customization.

Add Widget

Add a new widget to the user’s dashboard. If no layout exists yet, one is created automatically.

POST /api/trpc/dashboard.addWidget

Request:

{
  "json": {
    "widgetType": "kpi-summary",
    "title": "KPI Overview",
    "config": {
      "boardId": "brd_01HXK5...",
      "refreshInterval": 300
    },
    "position": {
      "x": 0,
      "y": 6,
      "w": 6,
      "h": 4
    }
  }
}
FieldTypeRequiredDescription
widgetTypestringYesWidget type identifier
titlestringYesDisplay title for the widget
configobjectNoWidget-specific configuration (stored as JSONB)
positionobjectYesGrid position (x, y, w, h)
layoutIdstringNoTarget layout ID (auto-resolved if omitted)

Remove Widget

Remove a single widget from the user’s dashboard by widget ID.

POST /api/trpc/dashboard.removeWidget

Request:

{
  "json": {
    "widgetId": "wgt_01HXK5QJBN3YZXM8KJP2RSNV4C"
  }
}
FieldTypeRequiredDescription
widgetIdstringYesThe widget ID to remove

Widget ownership is verified through the parent layout record, which must belong to the current user in the current organization. This prevents cross-user widget deletion.