---
title: "Platform-wide CRM dashboard"
method: GET
path: "/api/crm/dashboard/platform"
tags: ["crm-dashboard"]
---

# Platform-wide CRM dashboard

`GET /api/crm/dashboard/platform`

## Request

- Base URL: `https://api.corabea.it` (the document declares no server; this is the origin it was published from)
- URL: `https://api.corabea.it/api/crm/dashboard/platform`
- Auth: HTTP bearer

Platform-wide landing-page data: today's appointment count, patient and operator headcounts, the work queue and the open operator alerts across every operator.

The counterpart of /crm/dashboard/me, not a variant of it — that one is scoped to the calling operator and shares no field with this. Separate routes so each keeps its own permission and its own response shape.

`from_date`/`to_date` select the period the appointment counters cover and default to today; the response echoes the resolved window back in `period`. They do not narrow the patient, operator and queue figures, which are the platform's current state rather than a count of events in a window — each field's description says which kind it is.

## Query parameters

- `alert_limit` integer: Max alerts to return.
- `from_date` string, date, nullable: First day counted, inclusive (YYYY-MM-DD, Europe/Rome). Defaults to today.
- `to_date` string, date, nullable: Last day counted, inclusive (YYYY-MM-DD, Europe/Rome). Defaults to today.

## Response `200`

Successful Response

- CrmDashboardResponse: Aggregated landing-page payload for admin and customer care.
  - `date` string, required: Server-local date for 'today', ISO 8601 (Europe/Rome).
  - `period` CrmDashboardPeriod, required: The window the range-scoped counters were actually computed over. Echoed back rather than assumed, so the card can label itself ("oggi", "ultimo mese") from the response instead of from what the caller believed it asked for. Both ends are inclusive dates in Europe/Rome.
    - `from_date` string, date, required: First day counted, inclusive.
    - `to_date` string, date, required: Last day counted, inclusive.
    - `is_today` boolean, required: Whether the window is the single current day, i.e. no explicit range was asked for.
  - `stats` CrmDashboardStats, required: The headline counters — the top stat row of the dashboard. Two kinds of counter live here and they answer different questions. The *range-scoped* ones count appointments whose datetime falls in the dashboard's period (today by default, or the `from`/`to` window the caller asked for). The *point-in-time* ones are a snapshot of the current state of the platform and ignore the period entirely — "patients at risk in March" is not a question the history can answer, so the period is not pretended to apply. Each field says which it is.
    - `appointments_today` integer: Range-scoped. Appointments in the period platform-wide, cancelled and refunded ones excluded. Named `_today` because today is the default period; with `from`/`to` it counts that window instead.
    - `free_calls_today` integer: Range-scoped. CALL_GRATUITA appointments in the period. A subset of `appointments_today`, not an addition to it.
    - `appointments_today_online` integer: Range-scoped. The `appointments_today` subset held online.
    - `appointments_today_in_person` integer: Range-scoped. The `appointments_today` subset held in person. Online + in person = `appointments_today`, since `location` is NOT NULL and only ever holds those two values.
    - `appointments_to_reschedule` integer: Point-in-time backlog. Every appointment currently in DA_RIPROGRAMMARE, whenever it was scheduled — deliberately not period-scoped, because a reschedule backlog is only useful whole.
    - `appointments_to_reschedule_stale` integer: Point-in-time backlog. The `appointments_to_reschedule` subset waiting more than 7 days, measured from the history event that last set DA_RIPROGRAMMARE, or from the appointment's own datetime when no such event was ever recorded.
    - `appointments_to_reschedule_stale_by_type` object: `appointments_to_reschedule_stale` split by appointment type — the part waiting more than 7 days only, not the whole arrear. A selection, not a partition: it carries the four types product asked to see, so its values do NOT add up to `appointments_to_reschedule_stale`. Every other type — CALL_SUPPORTO among them — still counts in that total and in `appointments_to_reschedule`, it just has no line of its own here. Do not sum these to rebuild a total. Keyed by `appointment_type_code`, so the call product calls 'informativa' appears under its stored name CALL_GRATUITA. All four keys are always present, zero when the type has nothing waiting.
    - `patients_active` integer: Point-in-time. Distinct non-deleted patients whose latest history status is 'attivo' or 'da riprenotare' on either discipline.
    - `patients_at_risk` integer: Point-in-time. Patients whose latest history status is 'rischio abbandono' on either discipline.
    - `operators_active` integer: Point-in-time. Operators with status 'active'.
    - `supervisors_active` integer: Point-in-time. Distinct active operators supervising at least one other operator.
  - `queue` CrmDashboardQueue, required: Work waiting on someone — the 'Da gestire' block. Only the counters with an unambiguous source are here. The design also calls for reports gravi, comunicazioni, richieste BIA, fatture/rimborsi and mini équipe; each needs a state the schema does not currently carry (severity and a reviewed flag live inside `multidisciplinary_reports.*_answers` JSONB, and there is no "to verify" state on invoices or refunds), so they are left out rather than guessed at. These are backlogs, not period counts: they answer "what is waiting right now", so `from`/`to` does not narrow them.
    - `open_tickets` integer: Tickets not CLOSED, of any type.
    - `open_alerts` integer: Open operator alerts platform-wide.
    - `patients_to_assign` integer: Non-deleted patients with a CALL_CONOSCITIVA appointment, neither expert assigned, and no INCONTRO_PSICOLOGO or INCONTRO_NUTRIZIONISTA appointment, regardless of appointment status. Also used by the patient filter for `status_filter=da_assegnare`.
    - `past_appointments_to_close` integer: Appointments still DA_FARE more than 48 hours after their scheduled datetime.
  - `alerts` CrmDashboardAlert[]: Open alerts platform-wide, newest first, capped by the route's `alert_limit`.
    - `id` string, uuid, required
    - `operator_id` string, uuid, nullable
    - `type` string, nullable: Alert type
    - `title` string, nullable
    - `description` string, nullable
    - `operator_name` string, nullable: Operator the alert belongs to; the per-operator feed has no use for it.
    - `is_blocking` boolean
    - `created_at` string, date-time, nullable

## Other responses

- `422`: Validation Error

## Changes

- **2026-09-11** `6afa01417695`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/corabea/apis/corabea-api/changes/api/crm/dashboard/platform/get.md)

---

[API](https://skmtc.dev/corabea/apis/corabea-api.md) · [All operations](https://skmtc.dev/corabea/apis/corabea-api/llms.txt) · [OpenAPI document](https://skmtc.dev/corabea/apis/corabea-api/revisions/4a8dd89893b8?raw)
