1Purpose
We are wiring a PayCargo AI Platform cartridge to the Power BI REST API. The cartridge will call Power BI on behalf of the signed-in user (delegated access, OAuth 2.0 auth-code flow), so each employee only sees the reports they already have access to in Power BI. This mirrors the shape of the Salesforce Connected App handoff earlier this year, different vendor.
2Steps in the Microsoft Entra admin center
- Open portal.azure.com › Microsoft Entra ID › App registrations › New registration.
- Name:
PayCargo AI Platform — Power BI (delegated). Supported account types: Accounts in this organizational directory only (single tenant). Redirect URI: leave blank for now (see Section 4).
- Click Register. On the Overview blade, copy the Application (client) ID and the Directory (tenant) ID.
- Open API permissions › Add a permission › Power BI Service › Delegated permissions. Tick the scopes in Section 3 and click Add permissions.
- At the top of the API permissions blade, click Grant admin consent for [PayCargo tenant]. All Power BI
.All scopes are high-privilege delegated permissions and require tenant admin consent; individual users cannot self-consent.
- Open Certificates & secrets › New client secret. Description:
forge-cartridge-powerbi. Expiry: 24 months. Copy the Value immediately — Azure only shows it once.
- Send back the four values in Section 5. Also note the secret expiry date so we can add it to the shared renewal calendar.
3Tenant setting — Fabric admin portal (confirmed required)
A live smoke test on 2026-07-08 hit this blocker: PowerBIFeatureDisabled — 'DatasetExecuteQueries' is not enabled for the tenant. The cartridge's core operation (running DAX queries via the REST API) is switched off tenant-wide. Without this toggle, the app registration works but every query returns this error.
This lives in the Fabric admin portal, not Entra — it may be a different admin than the person doing Section 2:
- Open app.powerbi.com › gear icon › Admin portal › Tenant settings.
- Search the tenant settings for Execute Queries. In current portals the setting is named Semantic Model Execute Queries REST API (Microsoft renamed datasets to semantic models; the API docs and the error string still say Dataset — same setting). It lives under Integration settings.
- Set to Enabled. Recommend scoping to Specific security groups rather than the entire organization — create or reuse a group for platform API users and add the pilot users (starting with
mtang@paycargo.com).
- Changes propagate within ~15 minutes. Mike re-runs the smoke test to confirm.
If the setting shows Enabled but the API still returns the error, the gap is almost always in the group scoping: the caller must be a direct member of a security group in the Apply-to list. Three silent killers: (a) M365 groups and distribution lists do not count, security groups only; (b) membership via a nested group resolves unreliably; (c) group-membership changes reach Power BI through a cache that can lag up to 24 hours, so a same-day retest after a membership change can false-negative.
4Power BI delegated scopes
Minimum read-only set. Tick these under Power BI Service › Delegated permissions.
| Delegated scope | What it unlocks |
Report.Read.All | List and read reports in My workspace and in workspaces the user belongs to |
Dataset.Read.All | Read dataset metadata, tables, and query results |
Workspace.Read.All | List workspaces the user has access to |
Dashboard.Read.All | Read dashboards and tiles (add only if we surface dashboards) |
offline_access | Refresh tokens; required so the Lambda can refresh without re-prompting the user |
openid, profile, email | Standard OIDC scopes for the sign-in |
Do NOT grant Dataset.ReadWrite.All, Report.ReadWrite.All, Workspace.ReadWrite.All, or anything under Tenant.Read.All / Tenant.ReadWrite.All. Those are admin or destructive and out of scope for this cartridge.
5Redirect URI (Mike will send after cartridge deploy)
The cartridge Lambda is the OAuth client; the redirect URI is the cartridge-owned callback endpoint (not Cognito's hosted UI — that is a separate SAML flow for sign-in identity). Shape:
https://<cartridge-frontend-domain>/oauth/callback
Configure it under Authentication › Add a platform › Web. Leave Implicit grant and Hybrid flows both unchecked — we use auth code + PKCE, no implicit tokens.
6What Mike needs back
| Value | Where it lands on the Forge side |
| Directory (tenant) ID | /forge/oauth/powerbi/tenant-id (AWS Secrets Manager) |
| Application (client) ID | /forge/oauth/powerbi/client-id (AWS Secrets Manager) |
| Client secret value | /forge/oauth/powerbi/client-secret (AWS Secrets Manager, encrypted with our KMS CMK) |
| Secret expiry date | Shared renewal calendar |
Security note: the client secret is a bearer credential — equivalent to a password for the app. Paste it into a 1Password shared vault or Slack DM with disappearing messages. Do not put it in email or a ticket. If it ever leaks into a log, revoke and rotate immediately.
7Consent model
- Admin consent required — done once in Section 2 step 5. All Power BI
.All scopes are high-privilege delegated permissions.
- First-user sign-in experience — after tenant admin consent, no per-scope consent prompt appears. If a user does see one, admin consent didn't stick; reach out.
- Revocation — tenant-wide via Enterprise applications › PayCargo AI Platform — Power BI › Permissions › Review permissions. Individual users can revoke at
myapps.microsoft.com.
8Gotchas we already know about
- Every calling user needs a Power BI license — Fabric, Power BI Pro, or PPU. Free-tier users get 401s even with a valid token. Confirm the pilot users have licenses before we test.
- ExecuteQueries hard limits (from Microsoft's API docs; these bound the cartridge design): one query per API call, one table per query, max 100,000 rows or 1,000,000 values per query (whichever hits first), 15 MB of data per query, 120 query requests per minute per user, DAX only (no MDX). Datasets on Azure Analysis Services or live-connected to on-prem AAS are not supported.
- Workspace scoping —
GET /myorg/reports only returns reports in the user's My workspace. For shared workspace content the Lambda calls GET /myorg/groups/{groupId}/reports. The calling user must be at least Viewer on each target workspace.
- Admin endpoints (
/admin/...) do not work with delegated tokens for regular users. Not in scope for this cartridge.
- Service-principal-only features (e.g. "embed for your customers") cannot be reached with a user delegated token. We are doing "embed for your organization" — user owns data.
- Refresh token lifetime defaults to a 90-day sliding window for confidential clients. If a user does not open the cartridge for 90 days, they re-authenticate. Tune later via Conditional Access if needed.
- Permission propagation lag — new workspace membership may take minutes to appear. Our Lambda will call
POST /myorg/RefreshUserPermissions once on session start to avoid stale-authorization failures.
9Pilot target
The first cartridge use case reads one dataset on behalf of the signed-in user:
| Dataset ID | cfdb756f-c3c0-4113-a504-32a161012c49 |
| Pilot user | mtang@paycargo.com |
Nothing dataset-specific goes into the Azure app registration. Delegated access means the cartridge sees exactly what the signed-in user sees. This table is context: the pilot user must hold Viewer (or better) on the dataset's workspace and a Power BI Pro/PPU license, or the API returns 401/404 even with a perfect app registration.
Access model confirmed 2026-07-08: the pilot dataset is shared with the pilot user at the item level (Build permission verified — Explorations works in the UI); the user is not a member of the dataset's home workspace. The cartridge therefore uses the dataset-scoped endpoint POST /v1.0/myorg/datasets/{datasetId}/executeQueries, which honors item-level shares — no workspace membership or workspace ID needed. The only remaining blocker on the API path is the tenant setting in Section 3.