Skip to content
← Back to blog

The Power Platform API Grew Up. Here's What That Looks Like in Production

16 min readBy
Editorial illustration representing Power Platform.

Credit where it’s due first: the Power Platform product group shipped api.powerplatform.com, and the rest of this post is basically a long way of saying it was worth doing. I’m glad it exists, and I’m genuinely curious to see where it goes from here.

The first time I called a Power Platform admin API, it was api.bap.microsoft.com, it only understood one audience, and getting a service principal past its front door meant running a PowerShell cmdlet to register it as an “admin management app” first. There was no role-based access. There was no unified query surface. Copilot Studio — Power Virtual Agents, at the time — wasn’t reachable from that API at all; it lived entirely in Dataverse tables you had to already know about.

I’m building Portal 360°, a single console that consolidates the Microsoft admin surfaces I touch every day, and Power Platform is one of its biggest sections. This post is a technical tour of that section, screen by screen, with the exact endpoint behind each one — BAP, api.powerplatform.com, the Dataverse Web API, and a tenant-scoped usage host I didn’t even know existed until I started building this. Every call shown below is real (trimmed and sanitized) code from that codebase.

Two hosts, one platform

The legacy surface — informally called “BAP” after its Microsoft.BusinessAppPlatform resource-provider segment — predates any concept of scoped access. A service principal calling it isn’t granted a role; it’s registered wholesale as an “admin management app” via New-PowerAppManagementApp, and from that point on it can see and touch every environment in the tenant:

// Delegated audience: https://service.powerapps.com
// App-only audience:  https://api.bap.microsoft.com
export async function getBAPClientCredentialsToken(): Promise<string> {
  const tokenRes = await fetch(
    `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/oauth2/v2.0/token`,
    {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        client_id: process.env.AZURE_CLIENT_ID!,
        client_secret: process.env.AZURE_CLIENT_SECRET!,
        scope: "https://api.bap.microsoft.com/.default",
        grant_type: "client_credentials",
      }),
    }
  );
  const { access_token } = await tokenRes.json();
  return access_token;
}

api.powerplatform.com — the Power Platform API — is the newer, RBAC-based gateway that’s meant to replace it over time. In 2025 the product team described the move as a strategy shift, not just a new host name. From the Power Platform team’s own write-up:

“We’re transforming Power Platform into an API-first ecosystem — where every feature in [the admin center] is backed by a well-documented, publicly accessible API.” The goals: centralized audit logging integrated with Microsoft Purview, granular role-based access control, and SDK parity across C#, TypeScript, and Python via Kiota-generated clients.

The RBAC piece is the part that actually changes how you build against it. The Authorization API (currently in preview) lets you assign one of four built-in roles — Power Platform owner, contributor, reader, or RBAC administrator — to a user, group, or service principal, scoped to one environment, an environment group, or the whole tenant:

POST https://api.powerplatform.com/authorization/roleAssignments?api-version=2024-10-01
Content-Type: application/json

{
  "roleDefinitionId": "ff954d61-a89a-4fbe-ace9-01c367b89f87",
  "principalObjectId": "<enterprise-app-object-id>",
  "principalType": "ApplicationUser",
  "scope": "/tenants/<tenant-id>"
}

Compare that to New-PowerAppManagementApp with no scope argument at all. What follows is what those two hosts — plus Dataverse, plus a third host neither of them owns — actually look like once you’re calling them from a real console.

Environments: capacity and add-ons are still BAP’s job

Portal 360 Environments page listing environments with type, status, database, protection level, and size for each
Environments still come from BAP: `GET .../scopes/admin/environments?api-version=2020-10-01&$expand=properties.capacity,properties.addons,properties.linkedEnvironmentMetadata`.

Every column here — type, managed-environment badge, database size — comes from one BAP call, expanded with $expand=properties.capacity and properties.linkedEnvironmentMetadata. There is a newer environment-listing endpoint on api.powerplatform.comList Environments For User, still marked preview — and it does return type, state, and protectionLevel. But its EnvironmentResponse shape has no capacity, add-on, or linkedEnvironmentMetadata fields, so the database-size and add-on columns on this page still have to come from BAP’s $expand. That’s the piece of BAP’s job api.powerplatform.com hasn’t taken over yet.

Governance: three different sources, reconciled on one page

Portal 360 Environment Governance Policies page listing each environment with its Advanced Connector Policy and DLP policies
The page's own subtitle names its sources: “resolved from api.powerplatform.com and the Power Platform admin API.”

This page literally prints its own data lineage in the UI: every environment’s Advanced Connector Policy (ACP) and Data Loss Prevention (DLP) policies, resolved from two different hosts and joined by environment ID. That split is worth unpacking, because it’s a good example of “old and new coexisting” rather than one clean migration:

Portal 360 Advanced Connector Policies detail showing a rule-based policy's allowed, non-business, and blocked connector lists
ACP is a governance Rule-Based Policy: `GET https://api.powerplatform.com/governance/ruleBasedPolicies?api-version=2024-10-01`.

Advanced Connector Policies are the newer model: a rule-based policy holding a ConnectorManagement rule set, with three buckets — business, non-business, blocked — served entirely from api.powerplatform.com.

Portal 360 DLP Policies detail modal showing enforcement tier, connector groups, and policy definition for a data loss prevention policy
Classic DLP policies are still BAP: `GET https://api.bap.microsoft.com/providers/PowerPlatform.Governance/v1/policies?$top=100`.

Classic DLP policies — the ones admins have used for years — are still served from api.bap.microsoft.com, under a PowerPlatform.Governance provider namespace that, confusingly, has nothing to do with the new governance/ routes on api.powerplatform.com. Same word, two different hosts, two different data models. If you’re building against both, that naming collision is the first thing that will trip you up.

Tenant settings: one POST, one enormous payload

Portal 360 Power Platform Tenant Settings page with categories for Governance, Power Apps, AI & Intelligence, Environment Management, and more
One BAP call, `POST .../providers/Microsoft.BusinessAppPlatform/listtenantsettings`, returns every one of these categories in a single response.
Portal 360 Tenant Settings page with the Power Apps category expanded, showing individual boolean toggles with their current values
Each toggle here — 'Disable Create from Figma,' 'Disable Power Automate Copilot' — is one field in that same payload.

listtenantsettings hasn’t moved off BAP, and it’s still the single widest call in the whole surface: one POST returns every governance, licensing, Power Apps, Power Automate, and AI toggle in the tenant as one JSON object. Microsoft adds fields to it several times a year, which is exactly why Portal 360° runs a scheduled job that diffs its own settings registry against the Terraform provider’s tenant_settings schema and opens a pull request when they drift — learn.microsoft.com tends to catch up to new fields later than that provider does.

Apps: one query, not one loop per environment

Portal 360 Power Apps page listing canvas and model-driven apps across an environment with owner, created, and modified columns
Backed by the inventory / resource-query endpoint, filtered to `microsoft.powerapps/canvasapps` and `microsoft.powerapps/modeldrivenapps`.

This is where api.powerplatform.com earns its keep. Before the inventory / resource query endpoint existed, listing every app across every environment meant looping environments and calling a separate endpoint per resource type. Now it’s one query, with a type filter that decides what comes back:

// src/lib/power-platform-inventory-api.ts
export const INVENTORY_API_BASE = "https://api.powerplatform.com";
export const INVENTORY_API_VERSION = "2024-10-01";

export const INVENTORY_QUERY_URL =
  `${INVENTORY_API_BASE}/resourcequery/resources/query?api-version=${INVENTORY_API_VERSION}` as const;
POST https://api.powerplatform.com/resourcequery/resources/query?api-version=2024-10-01
Content-Type: application/json

{
  "TableName": "PowerPlatformResources",
  "Clauses": [
    {
      "$type": "where",
      "FieldName": "type",
      "Operator": "==",
      "Values": ["'microsoft.powerapps/canvasapps'"]
    },
    {
      "$type": "project",
      "FieldList": [
        "name",
        "properties.displayName",
        "properties.environmentId",
        "properties.createdAt",
        "properties.ownerId"
      ]
    }
  ]
}

The body isn’t a raw KQL string — it’s a TableName plus an ordered array of Clauses, each one a small JSON object the API translates into a KQL operator before handing the whole thing to Azure Resource Graph. The two clauses above become one where and one project:

PowerPlatformResources
| where type == 'microsoft.powerapps/canvasapps'
| project name, properties.displayName, properties.environmentId, properties.createdAt, properties.ownerId

Swap the type value in that first clause and the same endpoint hands back model-driven apps, cloud flows — or, as you’ll see below, Copilot Studio agents.

The same table, no Power Platform API in between

PowerPlatformResources isn’t exclusive to api.powerplatform.com. It’s an Azure Resource Graph table, so anything that already speaks Resource Graph — Resource Graph Explorer in the Azure portal, az graph query, the Az.ResourceGraph PowerShell module, or the Resource Graph REST API — can query it directly, authenticated the normal Azure way, with no Power Platform-specific client or request shape to learn:

az graph query -q "PowerPlatformResources | where type == 'microsoft.powerapps/canvasapps' | project name, properties.displayName, properties.environmentId, properties.createdAt | order by properties.createdAt desc"
Azure Resource Graph Explorer running a KQL query against PowerPlatformResources, returning three Copilot Studio agents with display name, environment ID, created and last published timestamps, and model.
Resource Graph Explorer against that same PowerPlatformResources table — this particular query filters to Copilot Studio agents on the GitHub Copilot harness; see [Finding GitHub Copilot Harness Agents Before PPAC Shows Them](/blog/find-github-copilot-harness-agents-resource-graph/) for that walkthrough.

Same rows, same schema, two front doors. api.powerplatform.com is the better fit when a service that already authenticates as a Power Platform service principal needs a stable, versioned, tenant-scoped endpoint. Resource Graph is the better fit when you’re already living in Azure tooling and want to run a query without standing up a separate call — it’s also how the Power Platform admin center’s own inventory grid gets its data under the hood. Microsoft’s Power Platform inventory documentation lists both paths, plus the Power Platform for Admins V2 connector, as first-class ways to reach the same data.

Automation: the same governance an admin console does, as a low-code flow

Portal 360 flow detail page for the same flow with an AI assistant panel explaining each action in plain language
Portal 360's flow-explainer panel narrating the trigger, condition, and quarantine actions in this flow, step by step.

This cloud flow does something a Portal 360° API route could also do over REST: it walks environments on a recurrence trigger and quarantines Copilot Studio agents that trip a governance rule, using the Power Platform for Admins V2 connector alongside Dataverse and Office 365 Users. That connector is the low-code face of the same API-first push — it’s Kiota-generated from the same OpenAPI description as the C# and Python SDKs, so a citizen developer building a flow and a service calling the REST API directly are ultimately hitting the same contract.

Copilot Studio: no longer a separate universe

Portal 360 Agent Builder wizard configuring a Copilot Studio agent's display name, language, system instructions, and AI model
Agent creation itself is still Dataverse: a `bots` row plus `botcomponents` rows for instructions and topics.

Building an agent programmatically means writing directly to Dataverse’s bots table, then adding botcomponents rows for the language-model configuration and each topic — there’s no api.powerplatform.com endpoint for agent authoring:

// src/app/api/power-platform/copilot-studio/create-agent/route.ts (trimmed)
const body = {
  name: cfg.displayName.substring(0, 100),
  schemaname: toBotSchemaName(prefix, cfg.displayName),
  language: lcid,
  authenticationmode: 2, // Integrated (authenticate with Microsoft)
  runtimeprovider: 0,    // Power Virtual Agents
  configuration: JSON.stringify({
    $kind: "BotConfiguration",
    defaultLocale: cfg.language,
    settings: { GenerativeActionsEnabled: cfg.enableGenerativeAnswers },
  }),
};
const result = await dataverseRequest(instanceUrl, token, "bots", "POST", body);
Portal 360 Agent to Connector Report listing Copilot Studio agents, their connection references, connectors, and owning solutions
Flattened from Dataverse `botcomponents` joined to `connectionreferences` — what each agent is *configured* to reach for, not what it has actually called.

That’s a design-time view, not a usage view: it shows what an agent is wired to, with no timestamps at all. It’s exactly the join Portal 360°‘s own docs warn against over-trusting — a connector listed here could have fired ten thousand times or never once.

Portal 360 Copilot Studio Dashboard showing total agent count, active count, published count, environment count, and a distribution donut chart
Agent counts here come from the inventory endpoint, filtered to `type == 'microsoft.copilotstudio/agents'` — the same resourcequery call used for canvas apps.
Portal 360 Copilot Studio Dashboard scrolled to show agent generation split between classic and modern, authentication type distribution, and top connectors by agent count
`properties.orchestration`, `properties.authentication`, and connector-capability counts, all read straight off the inventory row.

This is the detail worth sitting with. Copilot Studio agents used to be reachable only through Dataverse — the bots table, plus whatever transcript JSON you were willing to parse yourself. Today, the same resourcequery call that lists a tenant’s canvas apps also lists its Copilot Studio agents, with orchestration mode, auth mode, and channel data right there in the payload. Governance stopped being a Copilot-Studio-shaped exception and became one more row type in the platform’s own inventory.

Portal 360 User Prompt Report showing prompts per day, distinct users, agents used, and environments over a 30-day window
Built from Dataverse `conversationtranscripts` — the one place agent *content and conversation history* still lives.

That’s the honest seam: agent governance and inventory live on the Power Platform API now; agent content and conversation history still live in Dataverse, because a Copilot Studio agent is a Dataverse record under the hood. The API grew a front door for the tenant-wide questions — which agents exist, who owns them, are they quarantined — without needing to reinvent Dataverse for the questions that were already Dataverse’s job.

Licensing and capacity: two APIs that don’t quite agree

Portal 360 Tenant Capacity Details page showing database and file storage capacity, current usage, and rated usage
`GET https://api.powerplatform.com/licensing/tenantCapacity?api-version=2024-10-01` — Microsoft's licensing system's view of allocated and billed capacity.
Portal 360 Dataverse Storage page ranking environments by database, file, and log consumption
Per-environment actual consumption, still summed from BAP's `$expand=properties.capacity` on the environments call.

These two pages can — and do — disagree slightly for the same tenant on the same day, and that’s normal rather than a bug. The Tenant Capacity API reports Microsoft’s licensing system’s view: what you’re allocated and billed for, updated on a daily cycle. The Dataverse Storage page sums real-time, per-environment consumption straight off BAP’s environments call. Neither number is wrong; they’re two systems of record that sync periodically, not continuously, and the migration from one to the other is still visibly a work in progress rather than a finished cutover.

Usage: a third host neither BAP nor api.powerplatform.com owns

Portal 360 Usage page showing a Power Automate active-runs and total-actions trend line over a 30-day window
`GET https://{tenant-host}/usage/PowerAutomateTimeSeries?timeGrain=Day&...` — the same endpoint the admin center's own Usage page calls.
Portal 360 Usage page showing a Copilot Studio active-sessions and active-users trend line over the same window
Same host, same shape, different dataset: `CopilotStudioTimeSeries` instead of `PowerAutomateTimeSeries`.

This one surprised me. Adoption trends for Power Apps, Power Automate, and Copilot Studio don’t come from api.powerplatform.com or from BAP — they come from a tenant-scoped host derived from the Entra tenant ID, of the shape https://{tenant-host}/usage/{Dataset}(from=...,to=...)?api-version=1, authenticated with a delegated token for the api.powerplatform.com audience even though the host itself is different. It’s the exact endpoint the Power Platform admin center’s own Usage pages call — Portal 360° just calls it directly instead of going through the UI. PowerAppsUsage, PowerAutomateUsage, and CopilotStudioUsage are the per-resource summary datasets; append TimeSeries for the trend line.

Recommendations: the Advisor API, still in preview

Portal 360 Power Platform Recommendations page listing security and governance recommendations with affected resource counts and priority
`GET https://api.powerplatform.com/analytics/advisorRecommendations?api-version=2022-03-01-preview`.

Advisor-style recommendations — “enable sharing limits on this canvas app,” “enable Global Secure Access for agents” — come from the Power Platform API’s analytics/advisorRecommendations endpoint. The api-version Portal 360° calls today (2022-03-01-preview) is older than the one Microsoft Learn currently documents (2024-10-01) for the same route, which is a small, honest reminder that pinning an api-version means someone has to go back and bump it — nothing about “API-first” makes that maintenance optional.

Known issues: not a Power Platform API at all

Portal 360 Power Platform Known Issues page listing service disruptions with severity, impacted services, and publish dates
Sourced from Microsoft Graph service communications, filtered to Power Platform service names — not from api.powerplatform.com or BAP.

Worth naming precisely because it’s the one page in this tour that isn’t Power Platform API-shaped: it’s Microsoft Graph’s service-communications data, filtered client-side to service names like “power apps” and “power automate.” It sits in the Power Platform section of Portal 360° because that’s where an admin looks for it, not because Power Platform owns the data behind it.

What “growing up” adds up to

None of this makes Portal 360° a Microsoft product — it’s an independent, unaffiliated project I build in my own time, and for anything compliance- critical you should still reach for the official admin center. But as a stress test, it’s a decent one: an API that started as one host, one audience, and a PowerShell registration step now backs a console where environment capacity and add-on data still come from BAP, apps and Copilot Studio agents come from one shared query endpoint, RBAC replaces an all-or-nothing trust model, and a handful of surfaces — Dataverse, a tenant-scoped usage host, Microsoft Graph — still do the jobs api.powerplatform.com hasn’t reached yet. The clearest evidence it’s still moving is the pace of the monthly changelog itself: a single recent month added environment failback and reset endpoints, tenant-wide license-consumption-by-user reporting, and Power Pages web application firewall policy management, alongside routine SDK releases for the C# and Python packages and the Power Platform for Admins V2 connector. That’s the distance it’s covered, and it isn’t done covering ground.

Last verified: August 2026, against the Power Platform API reference on Microsoft Learn, the RBAC role-assignment tutorial, and Portal 360°‘s own Power Platform integration code.

Get the latest learnings

Occasional notes on Azure, AI, and cloud architecture. No spam, unsubscribe anytime.

49 min readAzure

An Hour with Azure SRE Agent

What Azure SRE Agent is, what setup actually looks like, and what happened when I pointed it at my own repo, my Application Insights telemetry across Dataverse, Copilot Studio, and Foundry, and then asked it for a health check on everything at once. Plus what connecting an incident platform buys you — autonomous mitigation, a verification loop, and a handoff straight to the GitHub Copilot coding agent — the five switches worth flipping that you don't get turned on by default, and what the newly published Zero Ops framework recommends, including the two governance layers above per-tool Allow and Ask and three details worth verifying against the current docs.

12 min readCopilot Studio

Finding GitHub Copilot Harness Agents Before PPAC Shows Them

The Power Platform admin center still doesn't flag which Copilot Studio agents run on the GitHub Copilot harness. The isCLIAgent property does, and Microsoft has now published governance guidance built on it.

Comments

Comments are hosted by GitHub Discussions. Loading them connects your browser to giscus.app and GitHub.