Power BI Embedded: Developer Setup, Auth, and Real Code Examples (2026)
Why This Is Happening
I've walked through this exact setup with dozens of developers, and the pattern is almost always the same: you're building a product , maybe a SaaS platform, maybe a client-facing CRM , and you want Power BI reports embedded directly inside it. Clean, branded, no redirects to the Power BI service. So you start reading the docs, you register an Azure app, you write a few lines to call the embed token endpoint, and then... nothing works. The report doesn't load. You get a cryptic 403 Unauthorized or a blank iframe. Or worse, a banner reading "Free trial version" pasted across your production dashboard.
I know how maddening that is, especially when a client is watching over your shoulder.
Here's the thing Microsoft's error messages won't tell you: Power BI Embedded is not a single product. It's a family of authentication models, capacity SKUs, and embedding patterns, and if you pick the wrong combination, nothing works and the error gives you almost no signal about why. The core confusion usually comes down to three root causes.
First: "Embed for your customers" versus "embed for your organization" are fundamentally different authentication flows. With embed for your customers (also called "app owns data"), your app authenticates using a service principal or master user account, your end users never see a Power BI login screen. With embed for your organization ("user owns data"), every single end user must have a Power BI license and authenticate directly through Microsoft Entra ID. Mixing up these two paths at any point in the implementation will cause silent failures or capacity errors.
Second: free embed trial tokens are development-only. This one burns people constantly. You test everything locally, it works beautifully, then you deploy to production and users see that "Free trial version" banner or the token flat-out refuses to generate. That's because trial tokens are capped and can't be used outside dev environments. A real capacity (an A SKU from Azure, a P or EM SKU from Power BI Premium, or an F SKU from Microsoft Fabric) must be purchased and assigned before any production embed works.
Third: service principal permissions in Power BI are opt-in at the tenant level. Even after you register your Azure app perfectly, configure secrets, assign roles, you still need a Power BI tenant administrator to enable the "Allow service principals to use Power BI APIs" setting under the Admin Portal. Most developers don't know this toggle exists. The resulting 401 error gives you no hint.
The sections below will walk you through each of these problems with exact steps, real API calls, and the specific things to check when you hit a wall. Browse all Microsoft fix guides →
The Quick Fix, Try This First
If your Power BI Embedded report is showing a blank iframe, a "Free trial version" banner, or returning a 401/403 on your embed token request, run through this four-point checklist before touching any code. In my experience, 70% of broken Power BI Embedded setups fail one of these four checks, and fixing the right one takes under five minutes.
- Confirm the workspace is assigned to a capacity. Open Power BI Service → navigate to your workspace → click the three-dot menu → select Workspace settings → check the Premium tab. If you see "No capacity," that's your problem right there for production environments.
- Verify the "Allow service principals" toggle is on. Go to Power BI Admin Portal (powerbi.com/admin) → Tenant settings → search for "Allow service principals to use Power BI APIs." It must be Enabled, and your service principal's security group must be included in the "Apply to" list.
- Check that your service principal (or master user) has at least Member role on the workspace. In the workspace settings → Access tab, your service principal's app name must appear with Member or Admin role. Viewer role is not enough to generate embed tokens.
- Validate your Azure app has the correct API permissions. In Azure Portal → App registrations → your app → API permissions, you need
Report.ReadAllandDataset.ReadAllunder the Power BI Service (Delegated or Application, depending on your flow), and an admin must have clicked Grant admin consent.
If all four are green and you're still stuck, read on for the full step-by-step setup.
The foundation of any Power BI Embedded integration for the "embed for your customers" scenario is a properly registered Azure app. This is your app's identity, it's how Power BI knows your server is allowed to request embed tokens on behalf of your users.
Go to Azure Portal → Microsoft Entra ID (formerly Azure Active Directory) → App registrations → click New registration. Give it a name like MyApp-PowerBIEmbedded. For supported account types, select "Accounts in this organizational directory only" for most scenarios. Leave the Redirect URI blank for now if you're using a service principal flow.
After registration, copy your Application (client) ID and Directory (tenant) ID, you'll need both. Then go to Certificates & secrets → click New client secret → set an expiry (12 or 24 months, never "Never," that's a security risk) → copy the secret value immediately. It only shows once.
Now add API permissions. Under API permissions → Add a permission → Power BI Service → select Application permissions → add Tenant.ReadAll, Report.ReadAll, Dataset.ReadAll, and Workspace.ReadAll. Then click Grant admin consent for [your tenant]. Without that grant, every API call returns 401 Unauthorized, no further explanation.
To acquire a token programmatically using MSAL, your server-side call looks like this:
// Node.js example using @azure/msal-node
const { ConfidentialClientApplication } = require('@azure/msal-node');
const msalConfig = {
auth: {
clientId: 'YOUR_CLIENT_ID',
authority: 'https://login.microsoftonline.com/YOUR_TENANT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
}
};
const cca = new ConfidentialClientApplication(msalConfig);
const tokenRequest = {
scopes: ['https://analysis.windows.net/powerbi/api/.default'],
};
const response = await cca.acquireTokenByClientCredential(tokenRequest);
const accessToken = response.accessToken;
If this call succeeds and returns a token, your Azure app registration is correct. If it throws AADSTS700016, your client ID is wrong. AADSTS7000215 means the client secret is invalid or expired.
This step is the one most developers skip, and it causes the most head-scratching. Even with a perfectly configured Azure app and valid access token, your service principal cannot call the Power BI REST APIs until a Power BI tenant administrator explicitly unlocks it.
Sign in to app.powerbi.com with a tenant admin account. Click the gear icon in the top-right → select Admin portal. In the left sidebar, click Tenant settings. Scroll down to the Developer settings section.
Find the setting titled "Allow service principals to use Power BI APIs." Toggle it to Enabled. You have two options here: enable it for the entire organization, or restrict it to a specific security group. For production environments, I strongly recommend creating a dedicated security group (e.g., PowerBI-Embedded-ServicePrincipals) in Microsoft Entra ID, adding your app's service principal to that group, and then selecting "Specific security groups" in this setting. This gives you audit control and limits blast radius if a secret is ever compromised.
Also check "Allow service principals to create and use profiles" if you're building a multitenancy solution, this is what lets one service principal manage content across multiple customer workspaces without separate credentials per tenant, which is the recommended architecture for ISV applications.
Changes in tenant settings can take up to 15 minutes to propagate. After enabling, verify with a simple GET call:
GET https://api.powerbi.com/v1.0/myorg/groups
Authorization: Bearer {your_access_token}
If this returns a list of workspaces, you're through. A 403 Forbidden with body {"error":{"code":"Forbidden","message":"Service principal is not enabled for this tenant"}} means the toggle is still off or the security group assignment hasn't propagated yet.
Your service principal needs explicit access to the specific workspace containing the report or dashboard you want to embed. Being a tenant admin or having API permissions granted doesn't automatically give content access, Power BI workspace permissions are separate and additive.
Go to app.powerbi.com → navigate to your target workspace → click the three-dot menu next to the workspace name → Workspace settings → click the Manage access button (or the Access tab depending on your workspace version).
In the "Add people or groups" field, type the name of your Azure app (the display name you used when registering it). It should appear as a service principal in the dropdown. Select it and assign it the Member role at minimum. Admin role gives full control; Member role is typically sufficient for generating embed tokens for reports and datasets already published to the workspace. Viewer role is not sufficient, you'll get a token generation error.
If your app name doesn't appear in the search, it usually means one of two things: the service principal object hasn't been created in your tenant yet (it gets created on first sign-in or first API call), or the admin portal toggle from Step 2 isn't active. Try making one authenticated API call first to force service principal object creation in Entra ID.
For a multitenancy ISV setup, you'll create separate workspaces per customer tenant and add your service principal to each one. The Power BI REST API lets you automate workspace creation and access assignment:
POST https://api.powerbi.com/v1.0/myorg/groups/{groupId}/users
Authorization: Bearer {access_token}
Content-Type: application/json
{
"identifier": "your-app-service-principal-object-id",
"principalType": "App",
"groupUserAccessRight": "Member"
}
A 200 OK response confirms the assignment went through. You're now ready to generate embed tokens.
With auth configured and workspace access assigned, your server needs to do two things before the client can render anything: fetch the report's embed URL and generate a short-lived embed token. These are two separate API calls.
First, get the report metadata, specifically the embedUrl and id:
GET https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/reports/{reportId}
Authorization: Bearer {access_token}
The response gives you a JSON object. Grab embedUrl and id. Store them server-side; you'll pass them to the client.
Now generate the embed token. This is a different token from your service principal access token, it's a short-lived, scoped token specifically for the Power BI JavaScript SDK running in the browser:
POST https://api.powerbi.com/v1.0/myorg/GenerateToken
Authorization: Bearer {access_token}
Content-Type: application/json
{
"reports": [{ "id": "your-report-id" }],
"datasets": [{ "id": "your-dataset-id" }],
"targetWorkspaces": [{ "id": "your-workspace-id" }]
}
The response contains a token string and an expiration timestamp. Embed tokens typically expire in about 60 minutes. Build a token refresh mechanism server-side, don't bake the token into a static page or your reports will silently stop working mid-session.
If this call returns {"error":{"code":"PowerBIEntityNotFound"}}, double-check that the report and dataset IDs are from the correct workspace and that the workspace is assigned to a capacity (for production). A TokenExpired error means your service principal access token expired before you called this endpoint, regenerate it first.
The client-side rendering uses the official powerbi-client JavaScript library. Install it via npm:
npm install powerbi-client
Your server endpoint should return the three values the client needs: embedUrl, embedToken, and reportId. Then in your frontend component:
import * as pbi from 'powerbi-client';
const powerbi = new pbi.service.Service(
pbi.factories.hpmFactory,
pbi.factories.wpmpFactory,
pbi.factories.routerFactory
);
const embedConfig = {
type: 'report',
id: reportId, // from your server
embedUrl: embedUrl, // from your server
accessToken: embedToken, // short-lived token from GenerateToken
tokenType: pbi.models.TokenType.Embed,
settings: {
panes: {
filters: { visible: false },
pageNavigation: { visible: true }
}
}
};
const reportContainer = document.getElementById('report-container');
const report = powerbi.embed(reportContainer, embedConfig);
report.on('error', function(event) {
console.error('Embed error:', event.detail);
});
The container div should have explicit height set via CSS, a container with height: 0 or height: auto and no set dimensions will render a blank white box even when the embed is technically succeeding. This trips up a lot of React and Angular developers who expect the iframe to size itself.
If you see the report flash briefly then go blank, your embed token has likely expired or the token type is wrong (TokenType.Aad vs TokenType.Embed is a common mix-up). Always use TokenType.Embed for the "app owns data" / embed for your customers flow.
Once the report renders without the "Free trial version" banner and without errors in the browser console, your Power BI Embedded integration is working correctly end to end.
Advanced Troubleshooting
Most developers get unblocked by the five steps above. But enterprise environments, especially those with strict Entra ID policies, conditional access rules, or proxy configurations, throw additional complications. Here's what to check when the basics are done but things still aren't working.
Conditional Access Blocking Your Service Principal
If your organization has Conditional Access policies in Microsoft Entra ID, they may be blocking your service principal from acquiring tokens even when everything else is configured correctly. The error signature is AADSTS53003 in your token acquisition call. Go to Entra ID → Security → Conditional Access → review policies targeting "All cloud apps" or specifically "Power BI Service." Service principals should typically be excluded from user-facing Conditional Access policies, work with your security team to add your app registration to the exclusion list.
Row-Level Security (RLS) Token Failures
If your dataset uses Row-Level Security and you're seeing InvalidDatasetBinding or users seeing each other's data, your GenerateToken call must include an identities array:
{
"reports": [{ "id": "report-id" }],
"datasets": [{ "id": "dataset-id" }],
"identities": [{
"username": "customer@example.com",
"roles": ["SalesRegion_East"],
"datasets": ["dataset-id"]
}]
}
The role name must exactly match a role defined in the dataset's RLS configuration in Power BI Desktop, case-sensitive, no spaces unless your role name has them. A mismatch silently returns all data or returns nothing.
Capacity Throttling and Overload Errors
In production, if users report reports loading extremely slowly or getting CapacityThrottled errors, your A SKU capacity may be undersized. Power BI Embedded A SKUs (A1 through A8) differ significantly in memory and compute. The Autoscale feature, available on Premium P SKUs, can automatically add v-cores when demand spikes. For A SKU users on Azure, you can pause and resume capacity programmatically via the Azure REST API to manage costs during off-peak hours.
Embed Token "Free Trial" Banner in Production
This is almost always a workspace-capacity assignment issue. Even if you purchased a capacity, the workspace must be explicitly assigned to it. In Power BI Admin Portal → Capacity settings → select your capacity → under Workspaces assigned to this capacity, confirm your workspace appears. If it doesn't, add it there or from within the workspace's Premium settings tab.
Python and R Visuals Not Rendering
If your reports include Python or R visuals, be aware these are only supported in the "embed for your organization" (user owns data) flow. In "embed for your customers" (app owns data), R and Python visuals are not rendered, they simply don't appear in the embedded report with no error message. If your customers need these visuals, you either need to switch to a user-owns-data architecture or pre-render those visuals as static images in your data pipeline before embedding.
Prevention & Best Practices
Once your Power BI Embedded integration is working, the goal is to keep it working, even as your app scales, your datasets grow, and your team rotates. These practices come from production deployments I've seen succeed and a few I've seen fall apart.
Rotate client secrets before they expire. Azure app client secrets have an expiry date. When they expire, your embed token generation fails immediately, reports go blank for every user simultaneously. Set a calendar reminder 30 days before expiry, add the new secret to your app, update your configuration, verify the new secret works, then delete the old one. Consider automating secret rotation using Azure Key Vault with automatic rotation policies so your application always reads the current valid secret without manual intervention.
Scope your embed tokens tightly. The GenerateToken API lets you specify exactly which reports, datasets, and workspaces the token covers. Don't generate broad tokens. Generate a token for the specific report the user is about to view, for the specific RLS identity they represent. This limits the exposure window if a token is somehow intercepted.
Never expose your service principal credentials or access tokens client-side. The access token acquired by your service principal should only ever exist server-side. The only thing that reaches the browser is the short-lived embed token. Build a dedicated server-side endpoint that your frontend calls to get a fresh embed token on demand, never put your service principal secret in JavaScript bundles, environment variables accessible to the browser, or query strings.
Monitor capacity health proactively. For A SKU capacities on Azure, set up Azure Monitor alerts on the capacity's CPU and memory metrics. A capacity that runs at high utilization continuously degrades report load times for all embedded users simultaneously. The Power BI Admin Portal's capacity metrics app gives you a 7-day view of query durations, memory pressure, and throttling events, review it weekly during the first months after a production launch.
- Use the Power BI Embedded Playground to test new embed configurations before writing code, it gives you instant feedback on token validity and report rendering.
- Assign workspaces to capacity via the Power BI REST API in your CI/CD pipeline so new workspaces are never accidentally left unassigned in production.
- Store embed token expiration times server-side and implement proactive token refresh 5 minutes before expiry, this prevents the jarring mid-session blank report that users experience when tokens expire during a long meeting.
- Use service principal profiles for multitenancy ISV solutions rather than one service principal per customer, it's more scalable, easier to audit, and officially recommended by Microsoft for large ISV deployments.
Frequently Asked Questions
What exactly is the difference between Power BI Embedded and Power BI Premium?
Power BI Embedded is an Azure offer that gives you A SKUs, these are capacity units you pay for on an hourly or reserved basis, and you can pause them when not in use. It's built specifically for ISVs embedding reports in apps for external customers. Power BI Premium is a Microsoft 365 offer with P or EM SKUs, aimed at internal enterprise deployments. Microsoft Fabric adds a third option with F SKUs. All three can power production Power BI Embedded integrations, but A SKUs are typically the most cost-effective for external-facing applications with variable usage patterns since you can pause capacity overnight. The key thing to remember is that you must pick one, free trial tokens don't work in production regardless of which path you choose.
Do my end users need a Power BI license to view embedded reports?
It depends entirely on which embedding solution you're using. In the "embed for your customers" (app owns data) scenario, your end users do not need a Power BI license at all, they never interact with the Power BI service directly. Your app handles authentication using a service principal or master user account, and users just see your app. In the "embed for your organization" (user owns data) scenario, every user who views the embedded content must have their own Power BI Pro or Premium Per User license and authenticate through Microsoft Entra ID. If you're building a product for external customers or a public-facing app, the "embed for your customers" path is almost always the right choice.
I keep getting a 401 Unauthorized error when generating embed tokens, what am I missing?
A 401 on the GenerateToken endpoint almost always means one of three things. First, the "Allow service principals to use Power BI APIs" toggle in the Power BI Admin Portal is either off or your service principal isn't in the allowed security group, this is the most common cause. Second, your service principal hasn't been added to the target workspace with Member or Admin role. Third, the Azure access token you're using to call the Power BI REST API has expired between when you generated it and when you called GenerateToken, access tokens are short-lived (typically 60–75 minutes) so always acquire a fresh one per request cycle. Check all three before digging deeper.
Why do Python and R visuals not show up in my embedded report?
Python and R visuals are not supported in the "embed for your customers" (app owns data / service principal auth) flow, this is a documented platform limitation, not a bug in your code. The visuals silently don't render; you won't get an error message, they just disappear from the report. If these visuals are essential to your embedded experience, you have two options: switch to an "embed for your organization" (user owns data) architecture where each user authenticates directly via Entra ID and Power BI Pro, or pre-render those visual outputs as static images or cached chart data in your own data pipeline and display them natively in your application alongside the embedded Power BI report. Region restrictions on Python/R visuals also still apply in the user-owns-data flow.
What's a Power BI capacity and do I really need one for production?
A capacity in Power BI terms is a reserved pool of compute and memory resources allocated specifically to your Power BI content, reports, dashboards, and semantic models. Without a capacity, your content runs on shared infrastructure, which is fine for personal use but not for embedding in a production application. For production Power BI Embedded deployments, yes, you absolutely need a purchased capacity. Without it, the "Free trial version" banner appears on every embedded report, and free trial tokens are strictly limited in volume and only intended for development and testing. The smallest Azure A1 SKU is a reasonable starting point for low-traffic applications, with the ability to scale to A2, A3, or beyond as load increases.
How do I handle Row-Level Security (RLS) in Power BI Embedded so each customer only sees their own data?
RLS in Power BI Embedded works by passing an identities array in your GenerateToken API call. You define roles in Power BI Desktop's modeling tab (e.g., a "CustomerData" role with a DAX filter like [CustomerID] = USERNAME()), publish the report, and then at embed token generation time, your server passes the current user's identifier and the applicable role name in the token request body. Power BI enforces the filter server-side before returning data to the embedded report. The critical details: role names are case-sensitive and must exactly match what's defined in the dataset, and the dataset ID must be included in both the datasets and identities arrays of the token request. Missing either causes silent RLS bypass or empty report data.