> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consile.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API reference: Power BI

> The twelve read-only tools in the Power BI connector: workspaces, installed apps, dataflows, reports and pages, dashboards and tiles, datasets with data sources, schema and refresh history, and read-only DAX queries. Covers the workspaceId convention, result shapes, row caps and the error contract.

All Power BI tools are read-only and are called via `tools/call` over
`POST /power-bi/mcp`. The connector reads the Microsoft Power BI service through the
Power BI REST API (not Microsoft Graph) and is connected through OAuth with a
Microsoft work or school account (see
[Connect Power BI](https://docs.consile.ai/en/connect/connect-power-bi.md)). Your organization needs access to
Power BI (a subscription, or a plan that covers it, see
[What decides access](https://docs.consile.ai/developers/authentication.md#what-decides-access)), and your
user must hold a seat or plan licence (assigned under **Team** in the portal, see
[Team](https://docs.consile.ai/en/portal/team.md)).

> [!NOTE]
>
> Endpoint: `https://mcp.consile.ai/power-bi/mcp`. Power BI is its own named
> connector with only its own tools. Tool names are namespaced `power-bi__<tool>`
> (e.g. `power-bi__list_workspaces`); the bare names are used below for
> readability. A bare name in a request returns `Unknown tool.`
>
> The connector is multi-instance: one instance is one connected Microsoft user in
> one Microsoft Entra tenant, and it sees what that user can see in Power BI. An
> administrator adds further connections (agreements, **aftaler**, in the portal),
> each served at its own endpoint, e.g. `https://mcp.consile.ai/power-bi-2/mcp`,
> with tools named `power-bi-2__<tool>`.

For an account with access to Power BI, `tools/list` returns the twelve
`power-bi__*` tools plus the unprefixed system tool
[`connect_account`](#connect_account). Without access only `connect_account` is
listed, and the `initialize` result includes `instructions` explaining how to get
access. A user without a seat still sees the tools, but every call is refused. Every
tool has a `title` and the annotations `readOnlyHint: true`,
`destructiveHint: false` and `openWorldHint: false`. A successful result contains
the payload twice: as JSON text in `content` and as the same object in
`structuredContent`. The examples below show that payload.

## Authorization and read-only enforcement

The account is connected through Microsoft's OAuth sign-in. Only work or school
accounts can connect; Microsoft refuses a personal Microsoft account. The requested
delegated Power BI Service permissions are all read permissions:

* `https://analysis.windows.net/powerbi/api/Workspace.Read.All`
* `https://analysis.windows.net/powerbi/api/Report.Read.All`
* `https://analysis.windows.net/powerbi/api/Dataset.Read.All`
* `https://analysis.windows.net/powerbi/api/Dashboard.Read.All`
* `https://analysis.windows.net/powerbi/api/App.Read.All`
* `https://analysis.windows.net/powerbi/api/Dataflow.Read.All`
* `offline_access` (keeps the connection alive)

All six Power BI permissions must be granted, or the connect is refused. The connect
is also refused if Microsoft's grant contains permissions for another resource than
the Power BI API (for example Microsoft Graph). The standard sign-in scopes
(`openid`, `profile`, `email`) are accepted. In many Entra tenants an administrator
must consent to the permissions first.

On top of the read-only grant:

* The connector has no write tools.
* Consile sends only read requests to the Power BI API.
* DAX queries can only read: a query must start with `EVALUATE`, optionally preceded
  by `DEFINE`. DAX queries run as the connected user; no other identity can be
  named.

Access is renewed automatically. Consile never hands the token to the MCP client,
and the MCP client's own token is never forwarded to Microsoft. No Power BI data is
stored at Consile; every call reads live.

## Shared conventions

### workspaceId and "My workspace"

Power BI addresses content by a workspace id. The signed-in user's personal "My
workspace" has no id.

**workspaceId** `string (GUID)`

Workspace id from [`list_workspaces`](#list_workspaces). Optional on every tool that
takes it: omit it to read "My workspace". Required on
[`list_dataflows`](#list_dataflows), because dataflows live only in workspaces.

**datasetId** `string (GUID)`

Dataset (semantic model) id from [`list_datasets`](#list_datasets), or the
`datasetId` of a report or a dashboard tile.

Ids are validated as GUIDs. An id that does not exist, or that belongs to another
workspace than the one named, returns a `404` error (see [Errors](#errors)).

### Result context

Every result except `list_workspaces` and `list_apps` echoes a `context` object with
the `workspaceId` it was read from (`null` for "My workspace"). Fields Power BI does
not return are `null`. `@odata.*` annotation keys are removed. Collections are
returned in full; the list tools take no paging arguments.

### Typical call order

1. `list_workspaces` (skip it for "My workspace").
2. `list_reports` or `list_datasets` in the workspace, to find the `datasetId`.
3. `get_dataset_schema`, to learn the exact table, column and measure names.
4. `execute_dax_query` with an aggregating query.

***

## list\_workspaces

Lists the Power BI workspaces the signed-in user is a member of, up to 5,000. "My
workspace" is not in the list.

**Arguments:** none.

**items** `array`

Each item has `id` (the `workspaceId`), `name`, `type`, `isReadOnly`,
`isOnDedicatedCapacity` and `state`.

```json Request theme={null}
{ "name": "power-bi__list_workspaces", "arguments": {} }
```

```json Result (payload) theme={null}
{
  "count": 2,
  "items": [
    { "id": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5", "name": "Management", "type": "Workspace", "isReadOnly": false, "isOnDedicatedCapacity": false, "state": null },
    { "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "name": "Sales", "type": "Workspace", "isReadOnly": true, "isOnDedicatedCapacity": false, "state": null }
  ]
}
```

***

## list\_apps

Lists the Power BI apps the signed-in user has installed. An app the user never
installed is not listed. A `403` can mean that the tenant has apps turned off; it
says nothing about the other tools.

**Arguments:** none.

**items** `array`

Each item has `id`, `name`, `description`, `publishedBy` and `lastUpdate`.

***

## list\_dataflows

Lists the dataflows in one workspace. A `403` can mean that the tenant has dataflows
turned off.

**workspaceId** `string (GUID)` *required*

**items** `array`

Each item has `objectId`, `name`, `description`, `configuredBy` and
`modifiedDateTime`.

***

## list\_reports

Lists the reports in a workspace, or in "My workspace" when `workspaceId` is
omitted.

**workspaceId** `string (GUID)`

**items** `array`

Each item has `id`, `name`, `webUrl`, `datasetId` (the dataset the report reads
from), `reportType` (`PowerBIReport` or `PaginatedReport`) and `isOwnedByMe`.

```json Request theme={null}
{
  "name": "power-bi__list_reports",
  "arguments": { "workspaceId": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5" }
}
```

```json Result (payload) theme={null}
{
  "context": { "workspaceId": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5" },
  "count": 1,
  "items": [
    {
      "id": "b7c8d9e0-1f2a-4b3c-9d4e-5f6a7b8c9d0e",
      "name": "Sales overview",
      "webUrl": "https://app.powerbi.com/groups/6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5/reports/b7c8d9e0-1f2a-4b3c-9d4e-5f6a7b8c9d0e",
      "datasetId": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
      "reportType": "PowerBIReport",
      "isOwnedByMe": false
    }
  ]
}
```

***

## get\_report

Reads one report's metadata and its pages. It does not render visuals; query the
report's `datasetId` with [`execute_dax_query`](#execute_dax_query) for the numbers.

**workspaceId** `string (GUID)`

**reportId** `string (GUID)` *required*

Report id from [`list_reports`](#list_reports).

**(report fields)** `object`

The same fields as a `list_reports` item, at the top level of the result.

**pages** `array`

Each page has `name`, `displayName` and `order`.

***

## list\_dashboards

Lists the dashboards in a workspace, or in "My workspace" when `workspaceId` is
omitted.

**workspaceId** `string (GUID)`

**items** `array`

Each item has `id`, `displayName`, `isReadOnly` and `webUrl`.

***

## list\_dashboard\_tiles

Lists the tiles pinned on one dashboard. Tiles carry no values; query the tile's
`datasetId` with [`execute_dax_query`](#execute_dax_query) for the numbers.

**workspaceId** `string (GUID)`

**dashboardId** `string (GUID)` *required*

Dashboard id from [`list_dashboards`](#list_dashboards).

**items** `array`

Each item has `id`, `title`, `subTitle`, `reportId`, `datasetId`, `rowSpan` and
`colSpan`. The result also echoes `dashboardId`.

***

## list\_datasets

Lists the datasets (semantic models) in a workspace, or in "My workspace" when
`workspaceId` is omitted.

**workspaceId** `string (GUID)`

**items** `array`

Each item has `id` (the `datasetId`), `name`, `configuredBy`, `isRefreshable`,
`isEffectiveIdentityRequired`, `isOnPremGatewayRequired`, `createdDate` and
`targetStorageMode`.

***

## get\_dataset

Reads one dataset's details, its data sources and its last 5 refreshes. Use it to see
where the data comes from and whether the latest refresh succeeded before trusting a
number.

**workspaceId** `string (GUID)`

**datasetId** `string (GUID)` *required*

**(dataset fields)** `object`

The same fields as a `list_datasets` item, at the top level of the result.

**datasources** `array | null`

Each item has `datasourceType` and `connectionDetails` (for example server and
database, as Power BI returns them; never credentials). `null` when Power BI does
not return the data sources.

**refreshes** `array | null`

The last 5 refreshes, in the shape of
[`list_dataset_refresh_history`](#list_dataset_refresh_history). `null` when Power
BI does not return the history.

**notes** `string[]`

Present only when `datasources` or `refreshes` could not be read, with the reason
(for example `datasources unavailable: <message>`).

***

## get\_dataset\_schema

Reads a dataset's model: tables, columns with data type, and measures with their DAX
expression. Call it before [`execute_dax_query`](#execute_dax_query) so names are
spelled exactly as the model has them (`'Sales'[Amount]`, `[Total Revenue]`). It
needs a current Power BI dataset; a push dataset or a very old dataset is answered
with an explanatory error, and a hand-written `execute_dax_query` still works there.

**workspaceId** `string (GUID)`

**datasetId** `string (GUID)` *required*

**includeHidden** `boolean` (default: false)

Include hidden tables, columns and measures.

**tables** `array`

Each item has `name`, `description` and `isHidden`.

**columns** `array`

Each item has `table`, `name`, `dataType` and `isHidden`.

**measures** `array`

Each item has `table`, `name`, `expression` (cut at 500 characters, ending in `…`),
`description` and `isHidden`.

```json Request theme={null}
{
  "name": "power-bi__get_dataset_schema",
  "arguments": {
    "workspaceId": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5",
    "datasetId": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f"
  }
}
```

```json Result (payload, shortened) theme={null}
{
  "context": { "workspaceId": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5" },
  "datasetId": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
  "tables": [
    { "name": "Sales", "description": null, "isHidden": false },
    { "name": "Region", "description": null, "isHidden": false }
  ],
  "columns": [
    { "table": "Sales", "name": "Amount", "dataType": "Decimal", "isHidden": false },
    { "table": "Region", "name": "Name", "dataType": "Text", "isHidden": false }
  ],
  "measures": [
    { "table": "Sales", "name": "Total Revenue", "expression": "SUM('Sales'[Amount])", "description": null, "isHidden": false }
  ]
}
```

***

## execute\_dax\_query

Runs a read-only DAX query against a dataset and returns the rows of the first
result table. The query runs as the connected user, who needs Build permission on the
dataset.

**workspaceId** `string (GUID)`

**datasetId** `string (GUID)` *required*

**query** `string` *required*

The DAX query. It must start with `EVALUATE`, optionally preceded by `DEFINE` for
query-scoped measures or variables. At most 20,000 characters.

**maxRows** `integer` (default: 200)

Rows to return, `1` to `2000`. Rows above it are cut and `truncated` is set.

**columns** `string[]`

The column keys of the first row, in Power BI's order. Columns come back as
`Table[Column]`, a measure or a named expression as `[Name]`. Empty when there are
no rows.

**rows** `array`

The rows, as objects keyed like `columns`. Nulls are included.

**rowCount** `integer`

The number of rows returned.

**truncated** `boolean`

`true` when the query returned more than `maxRows` rows and the rest were cut.
Narrow or aggregate the query rather than raising `maxRows`.

Keep results small: aggregate in DAX with `SUMMARIZECOLUMNS`, `TOPN`, `FILTER` and
`CALCULATETABLE` instead of `EVALUATE 'BigTable'`.

```json Request: revenue per region theme={null}
{
  "name": "power-bi__execute_dax_query",
  "arguments": {
    "workspaceId": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5",
    "datasetId": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
    "query": "EVALUATE SUMMARIZECOLUMNS('Region'[Name], \"Revenue\", [Total Revenue])"
  }
}
```

```json Result (payload) theme={null}
{
  "context": { "workspaceId": "6f1c2d3e-4a5b-4c6d-8e7f-90a1b2c3d4e5" },
  "datasetId": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
  "columns": ["Region[Name]", "[Revenue]"],
  "rowCount": 2,
  "truncated": false,
  "rows": [
    { "Region[Name]": "North", "[Revenue]": 1245000 },
    { "Region[Name]": "South", "[Revenue]": 987500 }
  ]
}
```

```json Request: top 20 customers with DEFINE theme={null}
{
  "name": "power-bi__execute_dax_query",
  "arguments": {
    "datasetId": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
    "query": "DEFINE MEASURE 'Sales'[Revenue 2026] = CALCULATE([Total Revenue], 'Date'[Year] = 2026) EVALUATE TOPN(20, SUMMARIZECOLUMNS('Customer'[Name], \"Revenue\", [Revenue 2026]), [Revenue], DESC)",
    "maxRows": 20
  }
}
```

***

## list\_dataset\_refresh\_history

Lists a dataset's refresh history, newest first. A dataset that is not refreshable
(DirectQuery, live connection) has no history.

**workspaceId** `string (GUID)`

**datasetId** `string (GUID)` *required*

**top** `integer` (default: 20)

Refreshes to return, `1` to `100`.

**items** `array`

Each item has `refreshType` (for example scheduled, on demand or via API),
`status` (`Completed`, `Failed`, or `Unknown` while in progress), `startTime`,
`endTime` and `hasServiceException` (`true` when a failure carried an error; the
error itself is not returned).

***

## connect\_account

`connect_account` is a system tool that the Power BI endpoint lists next to the
twelve tools. It takes no arguments and its name has no `power-bi__` prefix. It
returns `{ "connect_url": "…", "instructions": "…" }`. The user opens `connect_url`
in a browser and is sent to Microsoft to sign in and approve access. The link expires
after 10 minutes. It only works for accounts that subscribe to Power BI and for users
who have accepted Consile's terms in the portal. Use it to reconnect after the access
expired or was removed: completing the link connects the account again with the new
Microsoft sign-in.

```json Request theme={null}
{ "name": "connect_account", "arguments": {} }
```

Replacing an existing connection requires a Consile administrator; for other users
the link is refused.

***

## Errors

Tool failures come back as a normal `tools/call` result with `isError: true` and one
text block, not as a JSON-RPC error. The connector returns explanatory messages for
the common failures:

| Situation | Text in the result |
| - | - |
| Bare tool name (e.g. `list_workspaces`) or another connector's tool | `Unknown tool.` |
| No access to Power BI (no subscription, and no plan that covers it) | `You are not subscribed to power-bi. Purchase it here first: https://app.consile.ai/connectors/power-bi …` |
| Access exists, but your user has no seat | `Your company subscribes to power-bi through Consile, but your user does not hold a seat on it yet. …` (an administrator assigns seats under **Team**) |
| Microsoft account not connected yet | `power-bi is not connected yet. Connect it here: <link>` (link valid 10 minutes; an additional portal names itself, e.g. `power-bi-2`) |
| The stored access has expired and could not be renewed | `The Power BI access token has expired. The connection must be re-established: reconnect from the Consile portal.` |
| Power BI rejected the token (`401`) | `Power BI rejected the access token (401). The connection must be re-established: reconnect from the Consile portal.` |
| Power BI answered `403` | `Power BI returned 403: the user lacks access to this workspace/dataset, or the dataset requires Build permission for queries; reconnecting will not fix it. Ask the workspace owner for access (Build permission on the dataset for DAX queries).` |
| Unknown id, or an id from another workspace (`404`) | `Power BI returned 404: the workspace, report, dashboard or dataset does not exist, or the id belongs to another workspace. Get ids from list_workspaces and the list_* tools; never guess one.` |
| Power BI rejected the request (`400`) | `Power BI rejected the request (400 <code>): <message>` (at most 500 characters) |
| The DAX query failed in Power BI (syntax or evaluation error) | `Power BI could not run the DAX query (<code>): <message>` (at most 500 characters) |
| `query` does not start with `EVALUATE` or `DEFINE` | `The DAX text must be a query: … it has to start with EVALUATE (or DEFINE followed by EVALUATE). Nothing else is sent to Power BI.` |
| `query` contains only whitespace | `The DAX query is empty. Pass a query starting with EVALUATE.` |
| `query` is longer than 20,000 characters | `The DAX query is too long (<n> chars, max 20000).` |
| `get_dataset_schema` on a dataset without the model functions (push or very old dataset) | `get_dataset_schema could not read the model: the INFO.VIEW functions need a current Power BI dataset (a push or very old dataset lacks them). execute_dax_query with a hand-written EVALUATE still works. Power BI said: <message>` |
| Power BI's rate limit is still reached (`429`) | `Power BI rate limit hit; retried once and still limited. Wait a moment and try again, and ask for fewer rows.` |
| Arguments outside the input schema (e.g. an id that is not a GUID, `list_dataflows` without `workspaceId`, `maxRows` above `2000`, an empty `query`), a timeout, a `5xx` or `408` from Power BI, or a non-JSON response | `Tool invocation failed.` |

## Limits

* DAX rows: `maxRows` default 200, maximum 2,000 per query. DAX text: at most 20,000
  characters.
* Refresh history: `top` default 20, maximum 100. `get_dataset` shows the last 5.
* Measure expressions in `get_dataset_schema`: cut at 500 characters.
* Power BI applies its own request limits per user. Aggregate in DAX instead of
  fetching raw rows.

Requests to the endpoint are also rate-limited per account and connector. When the
limit is exceeded you get HTTP `429` (JSON-RPC error `-32029`, `Retry-After: 1`)
instead of a tool result. See [Errors & limits](https://docs.consile.ai/developers/errors-and-limits.md) for
the current limits and the protocol-level errors shared by all connectors.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.