ARM API vs Fabric Admin API: Which One for Capacity Metrics?
You want to monitor Fabric capacity. You look at the docs. There are two APIs that seem to do the same thing. Azure Resource Manager API and Fabric Admin API both return capacity information.
So which one do you use?
Short answer: probably both. They return different data and serve different purposes. This post breaks down exactly what each API gives you, when to use them, and how to combine them for complete monitoring.
The confusion
Here's why this is confusing. Both API calls include "capacity" and return JSON about your Fabric capacity:
ARM API: GET /subscriptions/{sub}/providers/Microsoft.Fabric/capacities
Fabric API: GET https://api.fabric.microsoft.com/v1/admin/capacities
Both require authentication. Both return capacity names and IDs. But the similarity ends there.
ARM API: infrastructure view
The Azure Resource Manager API treats Fabric capacity as an Azure resource. Like a VM or storage account. It tells you about the infrastructure.
What you get
# ARM API request
GET https://management.azure.com/subscriptions/12345678-1234-1234-1234-123456789012/providers/Microsoft.Fabric/capacities?api-version=2023-11-01
Authorization: Bearer {arm_token}
Response:
{
"value": [
{
"id": "/subscriptions/12345678-.../resourceGroups/fabric-rg/providers/Microsoft.Fabric/capacities/fabriccap1",
"name": "fabriccap1",
"type": "Microsoft.Fabric/capacities",
"location": "westeurope",
"sku": {
"name": "F64",
"tier": "Fabric"
},
"properties": {
"provisioningState": "Succeeded",
"state": "Active",
"administration": {
"members": ["admin@contoso.com"]
}
},
"tags": {
"environment": "production",
"costCenter": "data-platform"
}
}
]
}
This tells you:
- Location: Which Azure region (westeurope, eastus, etc.)
- SKU: Capacity size (F2, F64, F128, etc.)
- State: Active, Paused, Deleting, etc.
- Provisioning state: Whether deployment succeeded
- Admins: Who can manage the capacity
- Tags: Custom metadata for cost tracking
- Resource ID: Full ARM path for automation
What you don't get
Notice what's missing? No utilization metrics. No throttling information. No workload data. The ARM API has no idea if your F64 is sitting at 10% or 150% utilization.
You can get some metrics via Azure Monitor:
# ARM API - Azure Monitor metrics
GET https://management.azure.com/subscriptions/.../resourceGroups/.../providers/Microsoft.Fabric/capacities/fabriccap1/providers/microsoft.insights/metrics?api-version=2023-10-01&metricnames=CapacityUtilization
But the metric granularity is limited and you're still missing Fabric-specific data like interactive vs background utilization.
Authentication
ARM API uses standard Azure RBAC. A service principal with Reader role on the subscription can access it:
from azure.identity import ClientSecretCredential
from azure.mgmt.resource import ResourceManagementClient
credential = ClientSecretCredential(tenant_id, client_id, client_secret)
client = ResourceManagementClient(credential, subscription_id)
# List all Fabric capacities in subscription
capacities = client.resources.list_by_resource_group(
resource_group_name="fabric-rg",
filter="resourceType eq 'Microsoft.Fabric/capacities'"
)
This is the easier path for enterprises. Your customer probably already has Azure RBAC set up. Adding Reader role takes 30 seconds.
When to use ARM API
- Capacity inventory: What capacities exist across subscriptions?
- Cost tracking: Match capacity SKUs to Azure billing
- Infrastructure automation: Pause/resume capacities on schedule (requires Contributor role)
- Deployment validation: Did the capacity provision successfully?
- Tag-based filtering: Find all production capacities
Fabric Admin API: performance view
The Fabric Admin API is part of the Fabric platform, not Azure infrastructure. It tells you how the capacity is actually performing.
What you get
# Fabric Admin API request
GET https://api.fabric.microsoft.com/v1/admin/capacities
Authorization: Bearer {fabric_token}
Response:
{
"value": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"displayName": "fabriccap1",
"sku": "F64",
"region": "West Europe",
"state": "Active",
"admins": ["admin@contoso.com"],
"capacityUserAccessRight": "Admin"
}
]
}
That looks similar to ARM. But the real value is in the metrics endpoint:
# Fabric Admin API - Capacity metrics
GET https://api.fabric.microsoft.com/v1/admin/capacities/a1b2c3d4-.../metrics?startDateTime=2026-01-22T00:00:00Z&endDateTime=2026-01-23T00:00:00Z
Authorization: Bearer {fabric_token}
Response:
{
"value": [
{
"timestamp": "2026-01-22T14:30:00Z",
"metrics": {
"CUPercentage": 78.5,
"CUPercentageInteractive": 45.2,
"CUPercentageBackground": 33.3,
"ThrottlingPercentage": 0,
"OverloadMinutes": 0,
"AutoscaleState": "NotTriggered"
}
},
{
"timestamp": "2026-01-22T14:35:00Z",
"metrics": {
"CUPercentage": 112.3,
"CUPercentageInteractive": 85.1,
"CUPercentageBackground": 27.2,
"ThrottlingPercentage": 12.3,
"OverloadMinutes": 3,
"AutoscaleState": "ScalingUp"
}
}
]
}
Now we're talking. This tells you:
- CU utilization: Total, interactive, and background separately
- Throttling: Is the capacity rejecting requests?
- Overload events: How many minutes spent over 100%?
- Autoscale triggers: Is it scaling up/down?
- Time series data: 5-minute granularity
What you don't get
- Azure resource metadata: No tags, no ARM resource ID
- Subscription context: No link to Azure billing
- Historical data beyond 14 days: Fabric doesn't retain longer
- Cross-tenant visibility: Each tenant's API only shows their capacities
Authentication
This is where it gets tricky. Fabric Admin API requires different permissions than ARM:
from azure.identity import ClientSecretCredential
import requests
# Get token for Fabric API (different scope than ARM)
credential = ClientSecretCredential(tenant_id, client_id, client_secret)
token = credential.get_token("https://api.fabric.microsoft.com/.default")
# Call Fabric Admin API
response = requests.get(
"https://api.fabric.microsoft.com/v1/admin/capacities",
headers={"Authorization": f"Bearer {token.access_token}"}
)
The key difference: scope is https://api.fabric.microsoft.com/.default instead of https://management.azure.com/.default.
And the permissions are Fabric-specific:
Capacity.Read.All- Read capacity info (what you want for monitoring)Capacity.ReadWrite.All- Also modify capacities
These are configured in the app registration under API permissions > Microsoft Power BI Service (yes, it still shows Power BI, not Fabric).
The Fabric admin problem
Here's the painful part. To use Admin API endpoints, your service principal either needs:
- Delegated permissions with a user who is a Fabric Administrator
- Application permissions with admin consent and tenant-level access
Option 1 doesn't work for automated monitoring (requires user sign-in). Option 2 requires more trust than most customers want to give.
The workaround: some customers make your service principal a Capacity Admin on specific capacities only. This grants access to that capacity's metrics without tenant-wide permissions.
# Customer adds your service principal as capacity admin
# Via Fabric Admin Portal > Capacity Settings > Capacity admins
# Or via API if they already have admin access
When to use Fabric Admin API
- Performance monitoring: Track CU utilization over time
- Throttling alerts: Know when capacity is overloaded
- Capacity planning: Analyze usage patterns for sizing decisions
- Autoscale monitoring: Track when scaling events occur
- Workload analysis: See interactive vs background distribution
Side-by-side comparison
Here's the same capacity from both APIs:
| Data Point | ARM API | Fabric Admin API |
|------------|---------|------------------|
| Capacity name | ✓ name | ✓ displayName |
| Capacity ID | ARM resource ID | Fabric GUID |
| SKU/Size | ✓ sku.name | ✓ sku |
| Region | ✓ location | ✓ region |
| State (Active/Paused) | ✓ properties.state | ✓ state |
| Admins | ✓ administration.members | ✓ admins |
| Azure tags | ✓ tags | ✗ |
| Resource group | ✓ (in resource ID) | ✗ |
| Subscription ID | ✓ (in resource ID) | ✗ |
| CU utilization | ✗ | ✓ (time series) |
| Throttling % | ✗ | ✓ |
| Overload minutes | ✗ | ✓ |
| Interactive vs background | ✗ | ✓ |
| Autoscale state | ✗ | ✓ |
| Workspace list | ✗ | ✓ (separate endpoint) |
The hybrid approach
For Fabric Capacity Monitor, I use both APIs. Here's why and how.
Discovery via ARM
ARM API is the source of truth for "what capacities exist." It covers the entire Azure subscription and includes capacities that might not be visible in Fabric yet (newly created) or removed from Fabric but still provisioned.
def discover_capacities(subscription_id: str, credential) -> list[dict]:
"""Find all Fabric capacities in a subscription via ARM."""
client = ResourceManagementClient(credential, subscription_id)
capacities = []
for resource in client.resources.list(
filter="resourceType eq 'Microsoft.Fabric/capacities'"
):
capacities.append({
"arm_id": resource.id,
"name": resource.name,
"sku": resource.sku.name if resource.sku else None,
"location": resource.location,
"resource_group": resource.id.split("/")[4],
"tags": resource.tags or {}
})
return capacities
Metrics via Fabric Admin API
Once I know what capacities exist, I use Fabric Admin API to get the actual performance data:
def collect_metrics(capacity_id: str, fabric_credential) -> list[dict]:
"""Collect CU metrics from Fabric Admin API."""
token = fabric_credential.get_token("https://api.fabric.microsoft.com/.default")
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=1)
response = requests.get(
f"https://api.fabric.microsoft.com/v1/admin/capacities/{capacity_id}/metrics",
headers={"Authorization": f"Bearer {token.access_token}"},
params={
"startDateTime": start_time.isoformat() + "Z",
"endDateTime": end_time.isoformat() + "Z"
}
)
return response.json().get("value", [])
Handling disagreements
The APIs can disagree. Common scenarios:
Capacity in ARM but not in Fabric Admin API
- Capacity was just created, Fabric hasn't picked it up yet
- Solution: Retry after a few minutes, or skip metrics collection for now
Capacity in Fabric Admin API but not in ARM
- Capacity was deleted from Azure but Fabric API cache is stale
- Solution: Mark as "pending deletion" in your system
Different states
- ARM says "Active" but Fabric API says capacity is throttling
- This is normal. ARM shows provisioning state, Fabric shows runtime state
def reconcile_capacity_data(arm_data: dict, fabric_data: dict | None) -> dict:
"""Merge ARM and Fabric API data for a single capacity."""
result = {
# From ARM (always present)
"arm_id": arm_data["arm_id"],
"name": arm_data["name"],
"sku": arm_data["sku"],
"location": arm_data["location"],
"resource_group": arm_data["resource_group"],
"tags": arm_data["tags"],
# From Fabric API (may be missing)
"fabric_id": None,
"cu_utilization": None,
"throttling": None,
"metrics_available": False
}
if fabric_data:
result["fabric_id"] = fabric_data.get("id")
result["metrics_available"] = True
# Add latest metrics if available
if "metrics" in fabric_data:
latest = fabric_data["metrics"][-1] if fabric_data["metrics"] else {}
result["cu_utilization"] = latest.get("CUPercentage")
result["throttling"] = latest.get("ThrottlingPercentage")
return result
Permission requirements
Getting permissions right is critical. Here's the minimum for each API.
ARM API permissions
For the Azure Resource Manager API, you need RBAC roles:
| Role | What it allows | Notes | |------|---------------|-------| | Reader | List and view capacities | Minimum for monitoring | | Contributor | Reader + modify/delete | Needed for pause/resume | | Owner | Contributor + assign roles | Usually unnecessary |
Assign at subscription or resource group level:
# Grant Reader on entire subscription
New-AzRoleAssignment `
-ObjectId $servicePrincipalObjectId `
-RoleDefinitionName "Reader" `
-Scope "/subscriptions/$subscriptionId"
# Or grant Reader on specific resource group only
New-AzRoleAssignment `
-ObjectId $servicePrincipalObjectId `
-RoleDefinitionName "Reader" `
-Scope "/subscriptions/$subscriptionId/resourceGroups/fabric-rg"
Fabric Admin API permissions
For the Fabric Admin API, options are more complex. See the security documentation for detailed setup.
Option 1: Application permissions (tenant-wide)
Add these in Azure AD app registration:
Capacity.Read.All(Application permission, not Delegated)- Requires admin consent
# Grant admin consent for Fabric API permissions
# Run as Azure AD admin
$app = Get-AzADApplication -DisplayName "YourMonitoringApp"
# Then consent via Azure Portal: Azure AD > App registrations > API permissions > Grant admin consent
Option 2: Capacity admin assignment (per-capacity)
Customer adds your service principal as admin on specific capacities:
- No tenant-wide permissions needed
- Customer controls exactly which capacities you access
- More setup work but better security
Option 3: Delegated permissions with user context
Not practical for automated monitoring since it requires user sign-in.
Troubleshooting
ARM API errors
404 Not Found
{
"error": {
"code": "ResourceNotFound",
"message": "Resource 'fabriccap1' not found in resource group 'fabric-rg'"
}
}
Capacity was deleted, or you're looking in the wrong resource group/subscription.
403 Forbidden
{
"error": {
"code": "AuthorizationFailed",
"message": "The client does not have authorization to perform action 'Microsoft.Fabric/capacities/read'"
}
}
Missing Reader role. Assign RBAC permissions.
Fabric Admin API errors
401 Unauthorized
{
"error": {
"code": "Unauthorized",
"message": "The access token is invalid or has expired"
}
}
Token expired or wrong scope. Make sure you're using https://api.fabric.microsoft.com/.default.
403 Forbidden
{
"error": {
"code": "PowerBINotAuthorizedException",
"message": "User does not have permission to access this capacity"
}
}
Service principal isn't a capacity admin and doesn't have tenant-wide permissions.
429 Too Many Requests
{
"error": {
"code": "TooManyRequests",
"message": "Rate limit exceeded. Retry after 60 seconds."
}
}
You're hitting the API too hard. Add exponential backoff:
import time
from requests.exceptions import HTTPError
def call_with_retry(func, max_retries=3):
for attempt in range(max_retries):
try:
return func()
except HTTPError as e:
if e.response.status_code == 429:
wait_time = 2 ** attempt * 30 # 30s, 60s, 120s
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded")
Decision tree
Here's when to use what:
What do you need?
│
├─ Capacity inventory (what exists)
│ └─ Use ARM API
│ └─ Reader role on subscription
│
├─ Basic state monitoring (active/paused)
│ └─ ARM API is enough
│ └─ Reader role on subscription
│
├─ CU utilization metrics
│ └─ Use Fabric Admin API
│ └─ Capacity.Read.All or capacity admin
│
├─ Throttling alerts
│ └─ Use Fabric Admin API
│ └─ Capacity.Read.All or capacity admin
│
├─ Complete monitoring solution
│ └─ Use both APIs
│ └─ ARM for discovery, Fabric for metrics
│ └─ Reader role + Fabric permissions
│
└─ Cost tracking with Azure billing
└─ ARM API only
└─ Tags + resource IDs for billing integration
The full picture
Neither API alone gives you everything. ARM API knows what infrastructure exists but not how it's performing. Fabric Admin API knows performance but lacks Azure context like tags and resource groups.
For production monitoring, you want both. The implementation in Fabric Capacity Monitor shows how to combine them. ARM API runs discovery every hour (capacities don't change often). Fabric Admin API collects metrics every 5 minutes.
The complexity is worth it. You get a complete view that neither API provides alone.
related posts
Building Fabric Capacity Monitor: From Morning Frustration to Open Source Tool
How logging into 10+ Azure portals every morning led me to build an open source capacity monitoring tool. The tech decisions and why self-hosted makes sense.
Microsoft Fabric Migration: 3-Day Implementation Plan
Moving to fabric doesn't have to be a month-long ordeal. Here's a practical 3-day roadmap to get your first end-to-end solution running in production.
Spark Optimization in Fabric Notebooks: Performance Tuning Guide
Your notebook code is logic. Your spark configuration is physics. Understanding this split and what you can actually control at each fabric SKU level makes everything faster and cheaper.