Dashboards
A dashboard is a channel of widgets: clients waiting on a reply, Tables changed since you looked, upcoming bookings, signatures out, who's live, open groups and more — plus numbers from other services. Each person sees only what they can already open: two people looking at the same dashboard can see different numbers, because every widget asks its own feature who may see what.
Dashboard channels are on the Ministry, Creator and Business plans. Each widget also needs its own feature (a client portal widget needs Client portals, and so on). This guide is about building and feeding dashboards from your own code, an automation or an AI assistant.
Who can do what
| Who | Can | Scope |
|---|---|---|
| A person (their own token, an OAuth connection or an assistant) | read the dashboards they can open; make one; add, change, arrange and remove widgets where they arrange | dashboards:read, dashboards:write |
| An app (a bot with its own token) | add its own API widgets to a dashboard it was added to, send them values, rename or remove them | dashboards:push |
dashboards:read and dashboards:write are for people only: an app can never hold them, and an assistant's consent
page asks for them in their own unticked box. An app never reads a dashboard — not even its own widgets' neighbours —
because what a dashboard shows is made of many people's own views.
People who arrange a dashboard are its makers, Owners and Admins, and anyone with Manage channel on it. Widgets fed from outside (a webhook address, a watched address, a connector) also need Manage integrations.
Make a dashboard
POST /v1/orgs/{org}/dashboards
Idempotency-Key: 6a3f…
{ "name": "client-follow-ups", "topic": "What needs a reply this week" }
The name follows the ordinary channel rules (and the workspace's word filter). A dashboard made through a token starts private: members and Guests can't see it until the person opens it to others in Missived. It is listed in their Activity with Undo (which deletes it, as long as nobody else has added to it since).
GET /v1/orgs/{org}/dashboards lists the dashboards you can open; GET /v1/dashboards/{id} answers the layout, every
widget and what each shows you (data.status is ok, empty, hidden — not for you, or the feature is off — locked
— the plan doesn't have it — or error).
Widgets
Start with GET /v1/widget-types?org_id={org}: every native kind (what it shows, its settings and defaults, its size,
and whether this workspace's plan has it), the connected sources, the six shapes with a JSON schema and a sample each, and
the limits. POST /v1/widget-types/validate checks a widget or a payload without saving anything (add ?org_id= to check a watched
address's interval against that workspace's plan).
POST /v1/dashboards/{id}/widgets
Idempotency-Key: 1c2d…
{ "kind": "portal_waiting", "settings": { "scope": "mine", "rows": 5 } }
Each id you choose in settings (a Table, a portal tag, a channel) must be something you can open. PATCH /v1/dashboards/{id}/widgets/{widgetId} changes the title, settings or a Notes widget's text; DELETE removes it. Both are
in your Activity with Undo.
Tickets widgets need the Tickets add-on: tickets_waiting, tickets_overdue, tickets_unassigned,
tickets_oldest (up to five unanswered tickets) and tickets_week (figures from queues the viewer manages).
Their optional ids setting selects one queue; leave it empty for every queue the viewer can open. Overdue also
accepts scope: "mine" (default) or "all". Requesters and people outside the queues receive no ticket slice.
Arrange
A layout is an order and a size per widget (w: 3, 4, 6, 8 or 12 columns; h: 1, 2 or 3 rows). Send all of them, against
the layout you read:
PUT /v1/dashboards/{id}/layout
If-Match: 14
{ "items": [ { "widget_id": "…", "w": 6, "h": 2 }, { "widget_id": "…", "w": 3, "h": 1 } ] }
If someone arranged it since, the answer is 409 stale with the current layout_rev and items — merge yours and send
again. Without If-Match (or layout_rev in the body) the answer is 428. The layout it replaced is kept for 7 days
(Restore in Missived, Undo in Activity).
Shapes
Widgets you feed show one of six fixed shapes, and nothing else — no HTML, scripts, styles, images, frames or colours
(tones map to the theme). An unknown field such as html or style is refused, never dropped silently.
| Shape | Fields |
|---|---|
stat |
value (a number, or text up to 24 characters), format (number, percent, currency:USD, duration), note ≤ 80, tone, series ≤ 60 numbers |
status |
rows ≤ 12 × { label ≤ 60, state: ok / warn / bad / idle, detail ≤ 80 } |
list |
rows ≤ 20 × { title ≤ 120, subtitle ≤ 120, meta ≤ 24, tone, link } |
bars |
labels ≤ 31 × ≤ 12 characters, values, note |
table |
columns ≤ 6 × ≤ 24, rows ≤ 20 × cells ≤ 60 |
card |
title ≤ 80, lines ≤ 4 × ≤ 120, link |
tone is ok, warn, bad, info or idle. A link must be https:; it is shown with its host and never fetched.
Every payload is at most 16 KB, its text passes the workspace's word filter (refused: 422 content_blocked, nothing
shown), and it is stored sealed like message text. A stat without series keeps its own trend of the last values.
Feed a widget from your app
- In Missived, give the app the
dashboards:pushscope and add it to the dashboard (Add app on the channel; an app without that scope can't be added to a dashboard). - The app adds its widget, then sends values whenever they change — at most 60 updates a minute per widget:
import { RestClient } from "@missived/bot";
const rest = new RestClient({ token: process.env.MISSIVED_BOT_TOKEN });
const { data: widget } = await rest.call("addDashboardWidget", {
path: { id: dashboardId }, body: { source: "api", shape: "stat", title: "Sales today" },
});
await rest.widget(widget.id).set({ shape: "stat", value: 18420, format: "currency:USD", note: "Raised today" });
rest.widget(id).set() checks the payload against the same limits as the server (widgetPayloadProblem, with the
server's words) before it sends. Only the credential that made a widget can feed it: the app's install, or the person's
own token or assistant. Taking the app out of the dashboard (or uninstalling it) removes its widgets there at once; an
app can always remove its own widget, even after the workspace's plan or switches stop dashboards.
Without the SDK it's one request: POST /v1/widgets/{widgetId}/data with the shape as the body; the answer says whether
what the widget shows changed.
Webhook and watched widgets
A webhook widget has its own address that a service posts to — your own JSON in one of the shapes, or GitHub, Sentry, Linear, Stripe, PagerDuty or GitLab mapped for you. A watched widget checks a public https address (a status page, a JSON address or a feed) every few minutes from our servers. Both need Manage integrations and count toward the plan's widget caps.
From a token or an assistant they always wait for the person: POST …/widgets with source: "push" or "pull"
answers 202 pending_confirmation with a confirm_url. The person opens it in Missived and confirms; the webhook's
address is shown there, to them, once — it is never answered to the token. When the service needs a signing secret
(Sentry, Stripe…), the person pastes it on that page too, so it never passes through the assistant. A new address
(POST …/widgets/{widgetId}/address) waits the same way.
AI assistants
A person's own assistant (connected over MCP) has the tools list_widget_types, get_dashboard,
create_dashboard, add_widget, update_widget, arrange_dashboard, remove_widget, push_widget_data and
preview_widget. Each is the same /v1 call with the person's own rights: it sees only their slice, builds only where
they arrange, and everything it builds is in their Activity with Undo. It is told to start from the catalogue, prefer
native widgets, check before adding, never put secrets in a widget and never ask for a webhook address.
Try: "Make me a dashboard for client follow-ups: clients waiting on a reply and my upcoming bookings."
Zapier, Make and n8n
Each has Add a dashboard widget and Update a dashboard widget. A connection feeds only the widgets it made, so run Add once (it answers the widget's id), then map that id into Update and fill in the shape's fields (stat, status, list, bars or card in Zapier and Make; any shape as JSON in n8n). See Zapier, Make and n8n.
Limits
| Dashboards per workspace | dashboardChannels (see Plans and availability) |
| Widgets per dashboard | dashboardWidgets |
| Webhook and API widgets per workspace | dashboardPushWidgets |
| Updates per widget | 60 a minute |
| Payload | 16 KB |