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

# Power BI

> Connect Power BI semantic models to Bag of words and query them with DAX

Bag of words connects to the **Power BI REST API** to auto-discover the workspaces, semantic models (datasets), tables, and reports an identity can access, and to run **DAX** queries against them. This page covers everything you set up on the Microsoft side (app registration, tenant settings, workspace membership, permissions) and how to enter it in Bag of words.

<Note>
  The Power BI connector is part of the **Enterprise Edition**. Per-user (delegated) authentication in particular requires an Enterprise license.
</Note>

## How it works

Bag of words authenticates to Power BI as an **Azure AD (Microsoft Entra ID) application**, then:

1. Lists the **workspaces** the identity belongs to (`GET /v1.0/myorg/groups`).
2. Lists the **semantic models** (datasets) and **reports** in each workspace.
3. Reads each model's **tables and columns** so the AI agent knows the schema. Discovery uses the **read-only Admin (Scanner) API** when available, and falls back to a DAX `COLUMNSTATISTICS()` probe per model otherwise.
4. Runs **DAX** queries at request time (`POST .../executeQueries`).

Each Power BI table is exposed to the agent as a schema table named `Dataset/Table` (for example `Sales Model/Customers`).

## Authentication modes

Power BI supports two authentication modes. You can use either, or both.

<CardGroup cols={2}>
  <Card title="Service Principal (Azure AD)" icon="bot">
    A single shared application identity (a **system** credential) discovers and queries Power BI for everyone. Simplest to operate. Best when all users may see the same models.
  </Card>

  <Card title="Sign in with Microsoft (per-user)" icon="user-lock">
    Each user signs in with their own Microsoft account (OAuth / on-behalf-of). Discovery and queries run with that user's own permissions, so each person sees only what they are allowed to. Requires Enterprise — see [Authentication](/data-sources/authentication).
  </Card>
</CardGroup>

<Note>
  With per-user authentication, a semantic model is selectable for a user as long as **their own** account can see it — even if the shared service principal cannot. Usage and instructions are still tracked at the organization level per model.
</Note>

## Before you start

* A **Microsoft Entra ID (Azure AD)** tenant where you can create an app registration.
* A **Fabric / Power BI administrator** who can change tenant settings.
* Workspace admin rights on the workspaces you want to expose, so you can add the identity as a Member or Contributor.
* Outbound network access from Bag of words to `https://api.powerbi.com` and `https://login.microsoftonline.com`.

## Step 1 — Register an Azure AD application

In the [Azure Portal](https://portal.azure.com) → **Microsoft Entra ID** → **App registrations** → **New registration**.

<Steps>
  <Step title="Create the app registration">
    Give it a name (e.g. `Bag of words - Power BI`). Under **Supported account types**, choose **Accounts in this organizational directory only** unless you specifically need multi-tenant.
  </Step>

  <Step title="Copy the Tenant ID and Client ID">
    From the app's **Overview** page, copy the **Directory (tenant) ID** and the **Application (client) ID**. You will paste these into Bag of words.
  </Step>

  <Step title="Create a client secret">
    Go to **Certificates & secrets** → **New client secret**. Copy the **secret value** immediately (it is only shown once). This is the `client_secret` field in Bag of words.
  </Step>

  <Step title="Add API permissions">
    Under **API permissions** → **Add a permission** → **Power BI Service**, add the delegated **Tenant.Read.All** (or the read scopes your policy allows). For per-user sign-in, keep the delegated **profile**, **email**, and **openid** scopes. Grant admin consent for the directory.
  </Step>

  <Step title="(Per-user only) Add the redirect URI">
    For **Sign in with Microsoft**, add a **Web** redirect URI under **Authentication**:

    ```text theme={null}
    https://<your-bow-host>/api/connections/oauth/callback
    ```

    Replace `<your-bow-host>` with your Bag of words base URL. Enable **offline\_access** so the token can be refreshed.
  </Step>
</Steps>

## Step 2 — Enable the Power BI tenant settings

These live in the **Power BI / Fabric Admin portal** → **Tenant settings** (a Fabric/Power BI administrator must change them). Applying each to a dedicated **security group** and adding your app to that group is the recommended pattern.

| Tenant setting | Why it matters |
| :- | :- |
| **Service principals can use Fabric / Power BI APIs** | Required for the service principal to call the Power BI REST API at all. Without it, discovery and queries fail. |
| **Service principals can access read-only admin APIs** | Lets Bag of words read model schemas via the Admin (Scanner) API — the most reliable discovery path, and the only one that reads schema without per-model query permission. |
| **Enhanced admin API responses with detailed metadata** | Makes the Scanner API return table/column schema (datasetSchema). Strongly recommended, especially for **DirectLake** and Fabric default semantic models. |

<Warning>
  If the read-only admin APIs are **not** enabled, Bag of words falls back to a per-model DAX probe. That probe requires **Build** permission on each model and does not work for some **DirectLake** models — which is the most common reason a model appears in Power BI but is missing from the schema list. Enabling the two admin settings above resolves this for most tenants.
</Warning>

<Note>
  After enabling **Enhanced admin API responses**, models that have not been refreshed since the setting was turned on may still return no schema until their next refresh. Refresh such models once.
</Note>

## Step 3 — Grant workspace and dataset access

For the **service principal**, discovery only sees workspaces where it holds a workspace **role** — sharing a single report or dataset directly is not enough. With **per-user** authentication this is looser: a model shared **directly** with a user (Build permission) is also discovered, even when they have no workspace role at all. That item-level path is exactly what row-level security requires (see the RLS step).

<Steps>
  <Step title="Add the identity as a workspace Member or Contributor">
    In each Power BI workspace → **Manage access** → **Add people or groups**, add the service principal (or the security group containing it), and any per-user accounts, as **Member** or **Contributor**.

    <Warning>
      For the **service principal** relying on the DAX discovery fallback, **Viewer is not enough** — a Viewer cannot run `executeQueries`. Use **Member** or **Contributor**, or enable the read-only admin APIs above. **Row-level security is the exception:** a per-user identity that must be RLS-filtered should be **Viewer or lower** (Member/Contributor bypass RLS) — see the RLS step below.
    </Warning>
  </Step>

  <Step title="Grant Build permission on the semantic models">
    **Build** is the permission that actually lets an identity run DAX (`executeQueries`) — Read alone is not enough. Workspace Member/Contributor typically confers Build; you can also grant it **directly on the model** (the model's **Manage permissions → Add user → Build**). Direct Build with no workspace role is exactly what lets a per-user identity query a model under row-level security (see the next step).
  </Step>

  <Step title="(Row-level security) Use per-user auth and map each user into an RLS role">
    Row-level security is enforced against the **querying identity**, and two Microsoft rules decide the setup:

    * **A service principal cannot query an RLS model at all.** It cannot be added to an RLS role, so `executeQueries` returns `401` — even if the service principal is a workspace Member. Use **Sign in with Microsoft** (per-user) for any RLS-protected model; the service principal can still index non-RLS models in the same tenant.
    * **An edit-level workspace role bypasses RLS.** Admin, Member, and Contributor have edit permission, so RLS does **not** apply to them. For RLS to take effect a user must hold at most **Viewer**, or no workspace role with the model shared to them directly.

    So the correct setup for an RLS model is, per user: **Build** permission on the model **and** membership in the appropriate **RLS role**, with **no** edit-level workspace role. Bag of words discovers and queries the model with that user's own token, and Power BI filters the rows to their role.
  </Step>
</Steps>

## Connect in Bag of words

Go to **Settings → Data Sources → Add data source → Power BI** and fill in the form. See [Connecting a data source](/data-sources/connecting) for the general flow.

| Field | Required | Default | Notes |
| :- | :- | :- | :- |
| `tenant_id` | Yes | — | Azure AD Tenant ID (Directory ID) from the app's Overview page. |
| `client_id` | Yes | — | Azure AD App Registration Client ID. |
| `client_secret` | Yes | — | Azure AD App Registration secret value you copied in Step 1. |
| `oauth_client_id` | No | — | App Registration Client ID used for **user sign-in** (authorization code flow). If blank, falls back to `client_id`. |
| `oauth_client_secret` | No | — | Secret for the user-sign-in app. If blank, falls back to `client_secret`. |
| `workspaces` | No | — | Workspace name(s) or ID(s), comma-separated, to limit discovery. If empty, **all** accessible workspaces are discovered. |

<Steps>
  <Step title="Choose the authentication">
    Pick **Service Principal (Azure AD)** for a shared identity, or **Sign in with Microsoft** for per-user (delegated) access. The **Sign in with Microsoft** mode has no fields of its own — it reuses the app registration configured above.
  </Step>

  <Step title="Test the connection">
    Click **Test Connection**. Bag of words authenticates, lists workspaces, and probes a semantic model to verify query access. A model that is empty or not queryable does not fail the test — it reports that query access was verified.
  </Step>

  <Step title="Require user authentication (optional)">
    To force every user to sign in with their own Microsoft account instead of using the shared service principal, enable **Require user authentication** under the data source's access settings. Each user then clicks **Connect** to sign in before they can query. See [Authentication](/data-sources/authentication).
  </Step>

  <Step title="Select tables and save">
    After the connection indexes, choose which tables the AI agent may use, then save. See [Data sources overview](/data-sources/overview) for managing tables, instructions, and access.
  </Step>
</Steps>

## Querying with DAX

Once connected, the AI agent writes and runs **DAX** against your semantic models automatically. Tables are addressed as `Dataset/Table`, and relationships defined in the model are available for joins. You do not need to configure anything further to start asking questions.

## Personalized dashboards over Power BI RLS

With **per-user** authentication, a dashboard built on a Power BI model can be shared so that each viewer's queries run under **their own** token. Power BI's row-level security then filters the rows per person — one shared dashboard, and every viewer sees only what their RLS role allows. Configure this with **Viewer** run identity when sharing; see [personalizing a dashboard](/using-bow/dashboards#personalize-a-dashboard-per-viewer).

<Note>
  **View-as** on a Power BI (delegated) source previews the target identity's parameter binding but keeps *your* token, so it shows your source-level rows, not theirs. To confirm the exact rows another person gets under RLS, have them open the shared dashboard themselves.
</Note>

## Troubleshooting: a model is missing from the schema

If a semantic model is visible in `app.powerbi.com` but not selectable in Bag of words, work through these in order:

<AccordionGroup>
  <Accordion title="The identity is not a Member/Contributor of the workspace">
    For the **service principal**, discovery only sees workspaces where it has a role — add it as **Member** or **Contributor**, and note that content in **My Workspace** is not discovered. With **per-user** authentication this is looser: a model shared directly with the signed-in user (Build permission) is discovered even with no workspace role, so a direct dataset share **is** sufficient there — which is the setup RLS requires.
  </Accordion>

  <Accordion title="The model cannot be introspected (DirectLake / no Build / RLS)">
    Without the read-only admin APIs, schema comes from a DAX probe that needs **Build** permission and does not work for some **DirectLake** models. Enable **Service principals can access read-only admin APIs** + **Enhanced admin API responses with detailed metadata** in the tenant settings, and grant **Build** on the model. The connection's indexing report lists models that were found but could not be read, with the reason.
  </Accordion>

  <Accordion title="The model was not refreshed after enabling enhanced metadata">
    The Scanner API returns no schema for models not refreshed since detailed-metadata scanning was enabled. Refresh the model once and re-index the connection.
  </Accordion>

  <Accordion title="A per-user model isn't showing for a signed-in user">
    With per-user authentication, the user must have completed **Connect** (Microsoft sign-in). Once signed in, models their own account can access become selectable even if the shared service principal cannot see them. If access was **just changed** in Power BI, note that Power BI caches user permissions — Bag of words refreshes them on sign-in and on **Reload tables**, so click **Reload** if a newly granted model hasn't appeared yet.
  </Accordion>

  <Accordion title="A per-user model is discovered but the agent can't query it yet">
    A model that only a signed-in user can see (for example an RLS model the service principal cannot index) is added to the catalog but starts **unselected**. An admin or the data source owner must enable it on the **Select tables** step before the agent can query it. Until then it appears in the user's table list but not in the agent's context.
  </Accordion>

  <Accordion title="The workspaces filter is excluding it">
    If you set `workspaces`, only those workspaces are discovered. Clear it to discover everything, or add the workspace's name/ID.
  </Accordion>
</AccordionGroup>

## Reference

* **Service principal scope**: `https://analysis.windows.net/powerbi/api/.default`
* **Per-user (delegated) scope**: `https://analysis.windows.net/powerbi/api/.default offline_access`
* **OAuth redirect URI**: `https://<your-bow-host>/api/connections/oauth/callback`

For the on-premises product, see [Power BI Report Server](/data-sources/connectors/power-bi-report-server).


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