The dashboard module has one read-only endpoint. It gathers the numbers for the coach home screen in one parallel wave of twelve queries and shapes them in the service. It writes nothing.

Mount point and auth

Source: apps/core-api/src/modules/dashboard/. There is no role guard and no coach access scoping: every caller gets studio-wide KPIs. The only per-caller part is myTasksToday.

GET /v1/web/dashboard

Returns the overview for the caller’s studio. Auth: web lane, any role.
string
default:"week"
today, week or month. Controls kpis.resolvedInPeriod and kpis.outputPct only. today is the studio’s calendar day in Studio.timezone. week and month are rolling windows of 7 and 30 days back from now.

How the caller is resolved

The controller looks up the Coach row with studioId and externalUserId equal to req.auth.userId and passes its id to the service. If there is no such row, coachId is null and myTasksToday is not filtered.

Response fields

string
The period that was applied.
object
Studio-wide counters.
object[]
Up to 8 open tasks, ordered by priority descending then createdAt descending. When the caller has a Coach row, a task is included if it is unassigned (empty coachIds and no coachId) or assigned to that coach through coachId or coachIds. Each entry is the light task shape below.
object[]
Up to 8 clients, soonest first. Each has id, name, planName and endsOn. endsOn here is the coverage end from coverageEndsOn in modules/client-subscriptions/coverage.ts, which accounts for a queued next plan, so it can be later than Client.endsOn. The repository loads 40 candidates ordered by Client.endsOn, recomputes coverage, sorts again and keeps 8.
object[]
Seven entries for the current studio week, Sunday to Saturday, in the studio’s timezone. Each has date (YYYY-MM-DD), done (items resolved that day), created (items created that day) and future (true for days after today). Created items are loaded for the last 8 days.
object[]
One entry per active coach, in createdAt order. Each has coach (id, name), open (open tasks assigned to the coach), doneWeek (tasks assigned to the coach and resolved in the last 7 days), overdue (open tasks with dueAt in the past) and tasks (the first 3 open tasks in the light shape). Unassigned tasks count for nobody here.
object[]
The 10 highest priority open InboxItem rows, as full Prisma rows (id, studioId, coachId, coachIds, clientId, type, refId, title, detail, source, autoKey, dueAt, metadata, priority, status, snoozeUntil, resolvedAt, createdAt, updatedAt). No client name is joined.
object[]
One entry per active coach with the full Coach row under coach, plus clientCount and activeClients. Both counts come from the ClientCoach join table, so a trainee with two coaches counts for both. Despite the name, the list includes head coaches.
The light task shape used by myTasksToday and tasksByCoach[].tasks:

Example

The coach object under subCoaches is shortened in the example. The real response carries every Coach column, including email, phone, permissions and lastActiveAt. The type value shown is an example. See Tasks for the task types the generator creates. Errors: VALIDATION (422) for an unknown period, UNAUTHORIZED (401).

Limits to know

  • Open tasks are fetched once with a cap of 500 (OPEN_TASKS_CAP), ordered by priority then age. myTasksToday, tasksByCoach[].open, overdue and tasks are all computed from that slice, so a studio with more than 500 open items sees per-coach numbers that undercount. kpis.openInbox is a real count and is not capped.
  • The task assignment lives in the coachIds array column plus the older single coachId. The service groups in memory instead of in SQL for that reason.
  • upcomingRenewals shows 8 rows (RENEWALS_SHOWN) from 40 candidates (RENEWAL_CANDIDATES). kpis.renewals30d is the full count for the same filter.
  • The studio timezone is read with resolveTimeZone(studio.timezone) from lib/timezone.ts.