Region: If more than one
squaredup-*server is connected, ask the user which one they mean before running any tools, then use that same server for every SquaredUp tool call in the task.
Working with SquaredUp KPIs
KPIs in SquaredUp come in two layers, and the difference matters:
- KPI categories (a.k.a. KPI types) - top-level named buckets like "Cost" or "Performance". They define the value shape and aggregation rules. You configure one in the tenant's KPI type catalogue. They have IDs like
config-abcd1234. - KPI instances - the actual KPIs. Each one lives on a single dashboard tile, belongs to one category, and has a live computed value. Their IDs are the KPI sourceId, shaped
<dashboardId>/<tileId>/kpi.
When a user says "what KPIs do we have" they almost always mean the instances with their current values, not the empty-bucket category list. Reach for kpi_list (instances + values), not kpi_type_list (categories only).
Tool map
| Tool | What it does |
|---|---|
kpi_list |
List KPI instances with their current values. Optional filters: workspaceId, dashboardId, type (category id). |
kpi_get |
Get one KPI instance by id (<dashboardId>/<tileId>/kpi), with its current value. |
kpi_create |
Promote an existing tile into a KPI source by stamping kpi: { type } onto its config. Args: workspaceId, dashboardId, tileId, kpiTypeId. |
kpi_type_list |
List KPI categories only (Cost, Performance, …). No values, no instances. |
kpi_type_get |
Get one category's full config including aggregation rules. |
kpi_type_create |
Create a new category. |
kpi_type_update |
Update an existing category. Full overwrite - kpi_type_get first. |
Standard flows
"What KPIs do we have?" → kpi_list with no filters. One call returns every KPI in the tenant, each with its name, category, workspace, dashboard, value, formatted value, and health status.
"Show me the KPIs in the Cost category." → kpi_type_list to find the category id (the one with displayName: "Cost"), then kpi_list with type: <that id>. Single composed answer.
"What's the current value of KPI?" → kpi_list (filter by workspace/dashboard if you can narrow), match by name, then read its formattedValue and value. Or kpi_get if you already have its id.
"Why is the KPI showing the wrong number?" A KPI instance is computed from the dashboard tile it lives on. To debug:
kpi_get(orkpi_listwith filters) to confirm the current value, status, and anymessage(the message field carries warnings - shape mismatches, missing data, etc.). The response includesdashboardIdandtileIddirectly.tile_query(dashboardId, tileId)- runs the tile's stream as-configured and returns the raw rows in one step. Compare: is the stream returning unexpected rows? Wrong column? Wrong scope?- If you need to inspect the stream config itself (scope, timeframe, dataSourceConfig), use
tile_getfirst, then adjust and re-run viadata_stream_query(seequery-dataskill).
Authoring a KPI instance
A KPI instance isn't a standalone object - it's a decoration on a dashboard tile. Adding kpi: { type: <category-id> } to a tile's config promotes that tile into a KPI source. The dashboard indexer materialises the KPI graph node a few seconds after the dashboard is saved.
The kpi_create tool wraps this whole flow:
kpi_create({
workspaceId: "space-...",
dashboardId: "dash-...",
tileId: "b1c476a1-4745-44c8-a7c3-c9608392657c", // the `i` field on the tile
kpiTypeId: "config-mNtjiLScmjffl03zHOGg" // from kpi_type_list
})
Internally it dashboard_gets the dashboard, finds the tile in content.contents by i, sets tile.config.kpi = { type: kpiTypeId }, and PUTs the dashboard back. It returns the new kpiId (<dashboardId>/<tileId>/kpi) but the value won't be available immediately - wait a beat, then call kpi_get.
Concrete example of the resulting tile config (truncated for clarity - the kpi field is the only thing kpi_create changes):
{
"i": "b1c476a1-4745-44c8-a7c3-c9608392657c",
"config": {
"_type": "tile/data-stream",
"title": "Pull Requests",
"dataStream": { "id": "datastream-...", "name": "pullRequests", "dataSourceConfig": { ... } },
"scope": { ... },
"visualisation": { ... },
"kpi": { "type": "config-mNtjiLScmjffl03zHOGg" }
}
}
Picking the right kpiTypeId: the category dictates the value shape (count, percentage, currency, …). Pick one whose shape matches what the tile's stream produces - otherwise the KPI value will come back with a shapeName mismatch warning in kpi_get's message field. Use kpi_type_get to inspect the category's expected shape if unsure.
To remove a KPI from a tile, use tile_update(dashboardId, tileId, { kpi: null }). There's no dedicated removal tool.
Authoring a category
kpi_type_create and kpi_type_update operate on the category catalogue (/kpitypes):
- Find a template.
kpi_type_list, pick a similar category, andkpi_type_getit. Use that body'sconfigas the starting point. - Substitute what changes - usually
displayNameplus the aggregation rules. Keep everything else identical. kpi_type_createwith{ body }for new categories. The new category gets a freshconfig-…id.kpi_type_updatewith{ kpiTypeId, body }for edits. The body is the full payload to write back, not a patch - read the current shape withkpi_type_getfirst.
If no good template exists, ask the user for an example or admit the gap rather than guessing - KPI category configs are stream-specific and easy to get subtly wrong.
Common mistakes
- Reaching for
kpi_type_listwhen the user asked about KPIs - that returns categories only ("Cost", "Performance"), not the actual KPIs or their values. Usekpi_listfor the instances. - Treating a KPI instance as a per-dashboard tile in isolation - multiple dashboards can reference the same category, but each instance is bound to exactly one tile. The
<dashboardId>/<tileId>/kpiid is the source of truth. - Calling
kpi_getimmediately afterkpi_createand expecting a value - the graph indexer is async. Give it a few seconds. - Passing a
tileIdthat's atile-…ID rather than the tile'sifield -kpi_createlooks up tiles bycontent.contents[].i, which is a free-form string (often a UUID), not a prefixed entity ID. - Calling
kpi_type_updatewith a partial body - it's a full overwrite.kpi_type_getfirst. - Forgetting that the value comes from a stream - when debugging, bypass the KPI and run the tile's stream directly.
When to skip this skill
- The user already has a KPI instance id (
<dashboardId>/<tileId>/kpi) and just wants the value → callkpi_getdirectly. - The user wants raw stream rows, not a rollup →
query-data. - The user wants to find which entities a KPI applies to (graph traversal) →
query-graphwith.has('type', 'kpi').