# Dashboards API

> Create, list, read, change, and delete organization dashboards, and manage who sees each one, over the integrator API.

A dashboard belongs to the organization, not to a project. Each one has a server-assigned ULID, a name, an optional description, an owner (the member who created it), and a visibility: `public` (every member of the organization can see it) or `private`.

Dashboards are available on the **Pro** and **Enterprise** plans. On any other plan every endpoint on this page, reads included, returns `403 forbidden`; `dashboardsEnabled` on [`GET /api/v1/orgs/current`](https://docs.probara.net/en/developers/api-reference/) tells you in advance. A downgrade deletes nothing: the organization's dashboards come back as they were when the plan includes dashboards again.

A dashboard's widgets have their own endpoints: see [Dashboard widgets](https://docs.probara.net/en/developers/api/dashboard-widgets/).

## Who sees and who changes a dashboard

A member sees a dashboard when any of these holds:

- it is `public`, and their own access to it was not revoked;
- they created it;
- they, or one of their [user groups](https://docs.probara.net/en/developers/api/user-groups/), were granted access to it.

Organization owners and admins see every dashboard. A member whose own access was revoked does not see the dashboard, even when it is public or one of their groups was granted it.

Only the dashboard's creator and organization owners and admins can rename, redescribe, or delete it, or read and change its [access](#access), whatever other access a member holds. If the creator's account is deleted, the dashboard stays and only owners and admins can change it.

The two refusals are kept apart on purpose:

- a dashboard you cannot see answers **`404 not_found`** on every endpoint, exactly like a ULID that does not exist, so its existence is never revealed;
- a dashboard you can see but cannot change answers **`403 forbidden`** on `PATCH`, `PUT` and `DELETE`, and on every [access](#access) endpoint, reads included.

## Create

`POST /api/v1/dashboards`

Requires the `dashboards.write` permission (held by `owner`/`admin`/`member` by default); a `viewer` receives `403 forbidden`. The caller becomes the dashboard's owner. Send `accessMode: "private"` to create a dashboard that is private from the start; without it, the dashboard starts `public`. A private dashboard can start with its members and groups already granted: the dashboard and its grants are created together, or not at all.

### Request body

| Field         | Required | Notes                                                                                                            |
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `name`        | yes      | Trimmed, 1–200 characters                                                                                        |
| `description` | no       | Up to 2000 characters, or omitted/`null` for none                                                                |
| `accessMode`  | no       | `public` (default) or `private`; change it later with [Replace settings](#replace-settings) or [Access](#access) |
| `members`     | no       | User ULIDs of current organization members, each given a `granted` grant; private only                           |
| `groups`      | no       | User group ULIDs, each granted the dashboard; private only                                                       |

`members` and `groups` take at most 200 ULIDs each, without repeats, and must be empty or omitted when the dashboard is public; otherwise `422 validation_failed`. A ULID that is not a current member, or not a user group of the organization, returns `404 not_found` and nothing is created. Listing yourself, an owner, or an admin is accepted, though they see the dashboard anyway. The body is strict: any other key returns `422 validation_failed`.

### Response

`201` with the created dashboard. See [Response fields](#response-fields).

### Example

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Release health","accessMode":"private","members":["01J9Z3Q4M8R2T6V1W5X7Y9A0BC"],"groups":["01J9Z3R2K4N6P8Q0S2T4V6W8XY"]}' \
  "https://app.probara.net/api/v1/dashboards"
```

## List

`GET /api/v1/dashboards`

Returns only the dashboards the caller can see, most recently updated first. Page-based pagination.

| Parameter  | Notes                                    |
| ---------- | ---------------------------------------- |
| `page`     | Page number (1-based, default `1`)       |
| `pageSize` | Items per page (default `50`, max `200`) |

`200` with `{ items, page, pageSize, total }`. `total` counts only the dashboards the caller can see.

## Get

`GET /api/v1/dashboards/{dashboardUlid}`

`200` with the dashboard, or `404 not_found` when the caller cannot see it. A malformed ULID returns `422 validation_failed`.

## Update

`PATCH /api/v1/dashboards/{dashboardUlid}`

Changes `name` and/or `description` (`null` clears the description). At least one field is required, and the body is strict: any other key, `accessMode` included, returns `422 validation_failed` (visibility changes through [Replace settings](#replace-settings) or [Access](#access)). A body that changes nothing returns the dashboard as it is, without moving `updatedAt` or recording an audit event.

`200` with the updated dashboard. `403 forbidden` when the caller can see the dashboard but neither created it nor is an organization owner or admin; `404 not_found` when the caller cannot see it.

## Replace settings

`PUT /api/v1/dashboards/{dashboardUlid}`

Sets the whole settings state of a dashboard in one call, all or nothing. This is what the dashboard's settings dialog in the app saves. Same `403`/`404` rules as [Update](#update).

| Field         | Required | Notes                                                                                |
| ------------- | -------- | ------------------------------------------------------------------------------------ |
| `name`        | yes      | Trimmed, 1–200 characters                                                            |
| `description` | yes      | Up to 2000 characters, or `null` for none                                            |
| `accessMode`  | yes      | `public` or `private`                                                                |
| `members`     | yes      | User ULIDs of current organization members who hold a `granted` grant after the call |
| `groups`      | yes      | User group ULIDs granted the dashboard after the call                                |

The lists follow the rules of [Create](#create): at most 200 ULIDs each, no repeats, empty on a public dashboard, and `404 not_found` for a ULID that is not a current member or a user group of the organization (nothing changes).

After the call, exactly the listed members hold a `granted` grant and exactly the listed groups are granted the dashboard: missing ones are added, the rest are removed. `revoked` grants are not part of this state. One you do not list stays as it is; listing a member whose grant is `revoked` turns it into `granted`. Making the dashboard public removes every `granted` grant and every group grant, and keeps the `revoked` ones.

Each change is recorded like the matching [Access](#access) call. A body that changes nothing returns the dashboard as it is and records nothing. `updatedAt` moves when the name, the description, or the visibility changes; changing only members or groups leaves it, as the [Access](#access) calls do.

`200` with the dashboard. See [Response fields](#response-fields).

## Delete

`DELETE /api/v1/dashboards/{dashboardUlid}`

Permanently deletes the dashboard and every access grant it holds. Same `403`/`404` rules as [Update](#update).

`204` with no body.

## Access

Who sees a dashboard is managed under `/api/v1/dashboards/{dashboardUlid}/access`, shaped like [project access](https://docs.probara.net/en/developers/api/project-access/). Every endpoint here, reads included, follows the `403`/`404` rules of [Update](#update): only the creator and organization owners and admins use them. Every change records a `dashboard.access_changed` [audit event](#audit-events); a request that changes nothing records nothing.

### Read the access state

`GET /api/v1/dashboards/{dashboardUlid}/access`

`200` with:

| Field           | Type           | Notes                                                             |
| --------------- | -------------- | ----------------------------------------------------------------- |
| `mode`          | string         | `public` or `private`                                             |
| `ownerUserUlid` | string \| null | The creator, who always sees and edits the dashboard              |
| `grantedCount`  | number         | Members holding a `granted` grant, members who have left included |
| `revokedCount`  | number         | Members holding a `revoked` grant, members who have left included |

### Make a dashboard public or private

`PATCH /api/v1/dashboards/{dashboardUlid}/access` with `{ "mode": "public" }` or `{ "mode": "private" }`.

- **Private** hides the dashboard from every member without a grant. Its creator and organization owners and admins keep seeing it without one.
- **Public** removes every `granted` grant and every group grant: making it private again starts from empty lists. `revoked` grants stay.

`mode` is required and is the only field: a dashboard's owner is always its creator and cannot be transferred. `200` with the access state; `updatedAt` on the dashboard moves when the mode changes.

### Member grants

A member grant has an `access` value:

- `granted` lets the member into a private dashboard. It is refused on a public dashboard with `409 conflict`, because every member already sees it.
- `revoked` keeps the member out: out of a public dashboard, and out of a private one even when one of their user groups was granted it.

| Endpoint                                                              | Result                                                         |
| --------------------------------------------------------------------- | -------------------------------------------------------------- |
| `GET /api/v1/dashboards/{dashboardUlid}/access/members`               | `200`, page of members holding a grant, each with its `access` |
| `POST /api/v1/dashboards/{dashboardUlid}/access/members`              | `201`, the member with its grant                               |
| `DELETE /api/v1/dashboards/{dashboardUlid}/access/members/{userUlid}` | `204`; the dashboard's mode decides again                      |

The list is page-based (`page`, `pageSize`), oldest member first, and shows only current members. Items carry the same member fields as the [project access members list](https://docs.probara.net/en/developers/api/project-access/#list-access-members) plus `access`. The creator and owners and admins appear only if they hold a grant.

`POST` takes `{ "userUlid": "…", "access": "revoked" }`; `access` defaults to `granted`. It creates the grant or changes its value. The target must be a current member of the organization, otherwise `404 not_found`. `DELETE` removes the grant whatever its value, also for a member who has since left the organization; `404 not_found` when the member holds none.

### Group grants

| Endpoint                                                                  | Result                                 |
| ------------------------------------------------------------------------- | -------------------------------------- |
| `GET /api/v1/dashboards/{dashboardUlid}/access/groups`                    | `200`, page of granted groups, by name |
| `POST /api/v1/dashboards/{dashboardUlid}/access/groups`                   | `201`, the group                       |
| `DELETE /api/v1/dashboards/{dashboardUlid}/access/groups/{userGroupUlid}` | `204`                                  |

`POST` takes `{ "userGroupUlid": "…" }`. Every current and future member of the group sees the dashboard while it is private. A group grant on a public dashboard is refused with `409 conflict`, because every member already sees it. Group items carry `ulid`, `name`, `description` and `userCount` (current members). An unknown group, or a `DELETE` for a group that holds no grant, returns `404 not_found`.

### Example

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userUlid":"01J9Z3Q4M8R2T6V1W5X7Y9A0BC","access":"revoked"}' \
  "https://app.probara.net/api/v1/dashboards/$DASHBOARD_ULID/access/members"
```

## Response fields

| Field           | Type           | Notes                                                                                                                                       |
| --------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `ulid`          | string         | The dashboard's ULID                                                                                                                        |
| `name`          | string         |                                                                                                                                             |
| `description`   | string \| null |                                                                                                                                             |
| `ownerUserUlid` | string \| null | The member who created it; `null` once that account is deleted                                                                              |
| `accessMode`    | string         | `public` or `private`                                                                                                                       |
| `createdAt`     | number         | Epoch milliseconds                                                                                                                          |
| `updatedAt`     | number         | Epoch milliseconds; moves on every rename, description change, visibility change, or [widget](https://docs.probara.net/en/developers/api/dashboard-widgets/) change |
| `canEdit`       | boolean        | Whether **you** may rename and delete it: `true` for its creator and owners and admins                                                      |

## Audit events

Every create, update, delete, and access change records an event in the organization's activity feed with entity type `dashboard`, labelled with the dashboard's name: `dashboard.created` (metadata `accessMode`), `dashboard.updated` (metadata `changes`, one entry per changed field), `dashboard.deleted`, and `dashboard.access_changed`. The metadata `reason` of an access change is one of:

| `reason`         | Other metadata                   | Change                        |
| ---------------- | -------------------------------- | ----------------------------- |
| `mode_toggle`    | `mode`                           | Made public or private        |
| `member_granted` | `targetUserId`                   | Member grant set to `granted` |
| `member_revoked` | `targetUserId`                   | Member grant set to `revoked` |
| `member_cleared` | `targetUserId`, `previousAccess` | Member grant removed          |
| `group_granted`  | `userGroupUlid`                  | Group granted                 |
| `group_revoked`  | `userGroupUlid`                  | Group grant removed           |

A create that grants members and groups records `dashboard.created` followed by one `member_granted` or `group_granted` event per grant. A [settings replace](#replace-settings) records `dashboard.updated` when the name or description changed, then one `dashboard.access_changed` event per change: `mode_toggle`, `member_granted`, `member_cleared` (with `previousAccess: "granted"`), `group_granted`, `group_revoked`. Making a dashboard public with `PATCH …/access` records `mode_toggle` followed by one `group_revoked` per group grant it removed.

See [Audit events](https://docs.probara.net/en/developers/api/audit-events/).

## MCP

The same operations are available as the MCP tools `list_dashboards`, `get_dashboard`, `create_dashboard` (with `members` and `groups`), `update_dashboard`, `replace_dashboard_settings`, and `delete_dashboard`, and for access `get_dashboard_access`, `update_dashboard_access`, `list_dashboard_access_members`, `set_dashboard_access_member`, `remove_dashboard_access_member`, `list_dashboard_access_groups`, `add_dashboard_access_group`, and `remove_dashboard_access_group`, with the same plan and access rules. See the [MCP tool reference](https://docs.probara.net/en/developers/mcp-tools/).
