Connect Azure to Xplorr: Service Principal Setup for Cost Data
Connect Azure to Xplorr
Section titled “Connect Azure to Xplorr”Xplorr reads your Azure costs through the Cost Management query API, your cost recommendations through Azure Advisor, and your resources through Azure Resource Manager. It signs in as a service principal (an app registration with a client secret) that you create. Every call is a read: nothing is created, modified or deleted in your subscription.
One Xplorr cloud account is one Azure subscription.
Prefer Terraform or Bicep? See Set it up with code.
Before you start
Section titled “Before you start”- You need the member or admin role in Xplorr. Viewers cannot connect accounts. Whoever connects an account owns it and controls who else in the organization can see it.
- In Azure you need permission to create app registrations in Microsoft Entra ID, and Owner or User Access Administrator on the subscription so you can assign a role.
- Cost visibility must be switched on for your agreement. On an Enterprise Agreement, the Account owners can view charges policy must be on. On a Microsoft Customer Agreement, the billing profile’s Azure charges policy must be on. Without these, Azure refuses cost queries even when the role is right.
- Azure cost data lags by up to 24 to 48 hours. Recent days fill in as Azure processes them. This is Azure’s refresh rate, not Xplorr’s.
Which role to assign
Section titled “Which role to assign”Assign Reader on the subscription. It is read only, and it is the one built-in role that covers everything Xplorr calls:
| What Xplorr reads | Azure API | Reader | Cost Management Reader |
|---|---|---|---|
| Daily costs, network costs, per-resource costs | Cost Management query (Microsoft.CostManagement) | Yes | Yes |
| Cost recommendations | Azure Advisor (Microsoft.Advisor/recommendations/read) | Yes | Yes |
| Reservation and savings plan purchase recommendations | Microsoft.Consumption/reservationRecommendations/read, Microsoft.CostManagement/benefitRecommendations/read | Yes | Yes |
| The connect check | Subscription read (Microsoft.Resources/subscriptions/read) | Yes | Yes |
| Resource inventory | Resource list (Microsoft.Resources/subscriptions/resources/read) | Yes | No |
| Running or stopped state of VMs, App Service apps and AKS clusters | Microsoft.Compute, Microsoft.Web, Microsoft.ContainerService reads | Yes | No |
| Activity Log: every sync checks it is readable, and anomaly root cause reads it | Activity Log events (Microsoft.Insights/eventtypes/values/read) | Yes | No |
So assign Reader, or Cost Management Reader plus Reader if your policy requires Cost Management Reader. Reader is what reads the resource inventory and the subscription Activity Log. Every sync checks whether the Activity Log is readable and Cloud Accounts shows a warning on the account when it is not; anomaly root cause uses it to show what changed before a spend spike. Monitoring Reader can stand in for Reader for the Activity Log only; with Cost Management Reader plus Monitoring Reader the inventory stays empty.
Some features need further roles, outside this table. See Optional roles for specific features.
Step 1: Create the service principal
Section titled “Step 1: Create the service principal”In the Azure portal
Section titled “In the Azure portal”-
Register an application
Go to Microsoft Entra ID → App registrations → New registration.
- Name:
xplorr-reader(any name works) - Supported account types: Accounts in this organizational directory only
- Redirect URI: leave blank
Click Register. From the overview page, copy the Application (client) ID and the Directory (tenant) ID.
- Name:
-
Create a client secret
In the app registration, go to Certificates & secrets → Client secrets → New client secret. Pick an expiry, click Add, and copy the Value straight away. Azure shows it once. Copy the Value, not the Secret ID.
-
Assign Reader on the subscription
Go to Subscriptions → your subscription → Access control (IAM) → Add role assignment. Choose Reader, assign access to User, group, or service principal, select the app you registered, and save.
Role assignments can take a few minutes to take effect.
-
Copy the subscription ID
It is on the subscription’s overview page.
With the Azure CLI
Section titled “With the Azure CLI”az loginSUBSCRIPTION_ID="<your-subscription-id>"
# Creates the app registration, its service principal and a client secret,# and assigns Reader on the subscription in one stepaz ad sp create-for-rbac \ --name "xplorr-reader" \ --role "Reader" \ --scopes "/subscriptions/$SUBSCRIPTION_ID"The output contains appId (the client ID), password (the client secret)
and tenant (the tenant ID).
Step 2: Connect it in Xplorr
Section titled “Step 2: Connect it in Xplorr”-
Open the form
In Xplorr go to Infrastructure → Cloud Accounts and click Connect account. Set Provider to Microsoft Azure.
-
Fill in the fields
-
Account name: whatever you want to call it in Xplorr
-
Azure subscription ID: the subscription ID from step 1
-
Credentials (JSON): one JSON object. The Fill template button gives you this shape:
{"clientId": "00000000-0000-0000-0000-000000000000","tenantId": "00000000-0000-0000-0000-000000000000","clientSecret": "your-client-secret-value","subscriptionId": "00000000-0000-0000-0000-000000000000"}All four keys are required. The
subscriptionIdinside the JSON is the one Xplorr actually queries, so make it match the Azure subscription ID field. The uppercase namesAZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRETandAZURE_SUBSCRIPTION_IDare accepted too.
-
-
Test, then connect
Test connection runs the check below without saving anything. Then click Connect account.
What happens when you click Connect
Section titled “What happens when you click Connect”- Xplorr checks the credentials before saving anything. The check is a single read of the subscription itself through Azure Resource Manager, not a cost query. It has 20 seconds; past that you get an error saying the account was not added and nothing was saved.
- What the check proves: the tenant ID, client ID and secret are valid, and the service principal has some role on that subscription. What it does not prove: that the role can read costs, Advisor or resources. Those are first exercised by the first sync.
- The errors you can get at this point:
- The Azure service principal signed in but cannot read the subscription: the secret is fine but no role is assigned on the subscription yet, or it has not propagated.
- That Azure subscription was not found in this tenant: the
subscriptionIdortenantIdis wrong. - Azure authentication failed: the client ID, secret or tenant ID is wrong, or the secret has expired.
- If Azure answers that it is busy (throttling or a server error), the credentials are treated as valid and the account is saved.
- If the subscription is already connected to your organization, you get a message saying so rather than a duplicate.
- On success the account is saved and the first sync starts straight away, backfilling 90 days so last month is complete on the day you connect. It usually takes 10 to 30 minutes, and you get an email when it finishes. After that, every account syncs once a day at 02:00 UTC.
The status column shows Active once the account is connected. A failed sync shows Error with the reason underneath it.
Optional roles for specific features
Section titled “Optional roles for specific features”Each role below is needed only for the feature named. Costs, Advisor recommendations and the inventory work without any of them. Without one, that feature shows no data or names the missing role; the rest of the sync is not affected.
| Feature | Role | Scope |
|---|---|---|
| Carbon emissions | Carbon Optimization Reader | Each subscription |
| FOCUS export ingestion, if you point Xplorr at one | Storage Blob Data Reader | The storage account (or container) the export writes to |
| Commitments: each reservation’s term, price and end date | Reservations Reader | Tenant, /providers/Microsoft.Capacity |
| Commitments: each savings plan’s term, price and end date | Savings plan reader | Tenant, /providers/Microsoft.BillingBenefits |
| Credits, Enterprise Agreement (Azure Prepayment balance and commitment lots) | EnrollmentReader | The enrollment (billing account) |
| Credits, Microsoft Customer Agreement (credit balance and credit lots) | Billing profile reader | The billing profile |
| Credits, Microsoft Customer Agreement consumption commitment (MACC) lots | Billing account reader | The billing account |
| Invoices | Billing account reader, or Invoice manager on a Microsoft Customer Agreement billing profile (Enterprise Agreement: EnrollmentReader) | The billing account or billing profile |
Neither Reader nor Cost Management Reader reaches any of the tenant or billing scopes above. Commitment coverage and daily reservation and savings plan utilization do not need the tenant roles: they come from amortized cost, which the cost sync already reads with Reader or Cost Management Reader on the subscription.
Commitments need elevated access to assign. Reservations and savings plans are tenant level resources, so a subscription or management group role does not reach them. Assigning at tenant scope needs User Access Administrator there, which usually means a Global Administrator with elevated access. Use the service principal’s object ID (the Enterprise application’s, not the app registration’s):
SP_OBJECT_ID="<service-principal-object-id>"
az role assignment create --assignee-object-id "$SP_OBJECT_ID" \ --assignee-principal-type ServicePrincipal \ --role "Reservations Reader" --scope /providers/Microsoft.Capacity
az role assignment create --assignee-object-id "$SP_OBJECT_ID" \ --assignee-principal-type ServicePrincipal \ --role "Savings plan reader" --scope /providers/Microsoft.BillingBenefitsBilling roles for credits and invoices are assigned on the billing account, not in Azure RBAC:
- Enterprise Agreement: EA roles can be given to a service principal only through the Billing Role Assignments API, by an Enterprise Administrator. Follow Microsoft’s Assign Enterprise Agreement roles to service principals and choose EnrollmentReader. A service principal holds one EA role, and these assignments do not show in the portal.
- Microsoft Customer Agreement: assign Billing profile reader (and Billing account reader, if you have a MACC) in Cost Management + Billing → Billing profiles or the billing account → Access control (IAM), or through the Billing Role Assignments API. You need Billing profile owner or Billing account owner to do this.
Then enter the billing account ID (plus the billing profile ID for a Microsoft Customer Agreement) on the Credits page when you add a grant, and on the Invoices page for invoice ingestion.
Connecting several subscriptions
Section titled “Connecting several subscriptions”One service principal can serve many subscriptions. Assign it Reader on each
one (or once on a management group above them, which the subscriptions
inherit), then add each subscription in Xplorr as its own cloud account with
its own subscriptionId in the JSON.
Who can do what
Section titled “Who can do what”| Action | Who |
|---|---|
| Connect an account | Member or admin |
| Sync or delete an account | An organization admin, or whoever connected it |
| See an account’s data | Admins see everything; members and viewers see only accounts they have been granted |
Troubleshooting
Section titled “Troubleshooting”The account is Active but Inventory shows nothing for it The service principal has Cost Management Reader but not Reader. Add Reader on the subscription; the next sync fills the inventory.
The account shows Error after the first sync Read the reason under the status. A permission error on the cost query means the role cannot read cost data, or the agreement’s cost visibility policy (see Before you start) is off.
Costs arrived but no Advisor recommendations Advisor may simply have no cost advice for the subscription. Advisor failures are logged and skipped rather than failing the sync, so a missing role shows up as an empty list, not an error. Check the role assignment.
The connection stopped working after months The client secret has probably expired. Check App registrations → your app → Certificates & secrets. There is currently no way to replace the credentials of an existing account from the console, and deleting and reconnecting the account also deletes its stored cost history, budgets and recommendations (the new connection backfills 90 days). Pick a long secret expiry and keep a reminder.
The account row asks you to add the Reader or Monitoring Reader role The last sync was refused the subscription Activity Log, which Cost Management Reader alone cannot read. Add Reader; the warning clears after the next sync.
Recent days are missing Azure cost data lags 24 to 48 hours. Each daily sync re-pulls the last 30 days, so late data is picked up without you doing anything.
Setup checklist
Section titled “Setup checklist”- App registration created, secret Value copied
- Reader assigned on the subscription (or Cost Management Reader plus Reader)
- Optional, only for the features you use: Carbon Optimization Reader, Storage Blob Data Reader on a FOCUS export, Reservations Reader and Savings plan reader at tenant scope, a billing role for credits and invoices
- Cost visibility policy on (EA: account owners can view charges; MCA: Azure charges)
- Credentials JSON has
clientId,tenantId,clientSecretandsubscriptionId - Account shows Active in Infrastructure → Cloud Accounts
- Costs on the dashboard, and resources in Infrastructure → Inventory
Is the access read only? Yes. Reader and Cost Management Reader are both read-only roles, and Xplorr makes no write calls.
Which regions are covered? All of them. The queries run at subscription scope, so every region in the subscription is included.
How often does Xplorr sync?
Once a day at 02:00 UTC. You can also sync any account on demand with the
Sync button on its row, or through the trigger_sync MCP tool.
Do I need to set up a cost export? No. Xplorr queries Cost Management directly. If you already export FOCUS data to a storage account, you can point Xplorr at it from the FOCUS export settings on the account’s row, but it is optional. Reading it needs Storage Blob Data Reader on that storage account.
Set it up with code
Section titled “Set it up with code”The public Xplorrio/cloud-onboarding repository, release v0.0.1,
creates the app registration xplorr-reader, its service principal, and
Reader plus Cost Management Reader on each subscription, then prints the
Credentials (JSON) the Connect account form asks for. The console shows the
same quick starts under Set up with code on the Connect account form,
pre-filled with the subscription ID you typed.
provider "azurerm" { features {} subscription_id = "00000000-0000-0000-0000-000000000000"}
provider "azuread" {}
module "xplorr" { source = "git::https://github.com/Xplorrio/cloud-onboarding.git//azure/terraform?ref=v0.0.1"
subscription_ids = ["00000000-0000-0000-0000-000000000000"]}
output "xplorr_connect_form" { value = module.xplorr.xplorr_connect_form}terraform initterraform applyterraform output -json xplorr_connect_formsubscription_ids is the only required input. Set management_group_id to
assign the roles once on a management group instead, and see the
Azure README for the optional roles (carbon, reservations and
savings plans, FOCUS export) and the other inputs.
git clone --depth 1 --branch v0.0.1 https://github.com/Xplorrio/cloud-onboarding.git cloud-onboardingcd cloud-onboarding/azure/bicep
az loginaz account set --subscription 00000000-0000-0000-0000-000000000000az deployment sub create --name xplorr-onboarding --location westeurope \ --template-file main.bicep --parameters main.example.bicepparam
az deployment sub show --name xplorr-onboarding \ --query properties.outputs.credentialsJson.value -o tsv | jq .Run it from azure/bicep, where bicepconfig.json pins the Microsoft Graph
extension (Bicep CLI 0.36.1 or later). For many subscriptions under a
management group, deploy management-group.bicep. The Bicep templates assign
only the subscription or management group roles; the
Azure README has the az commands for the tenant scope and FOCUS
roles.
Neither option creates the client secret, so it never lands in Terraform state or a deployment’s history. Create it yourself; it is shown once:
az ad app credential reset --id <clientId> --append --display-name xplorr --years 2 --query password -o tsvThen, in Connect account:
| Xplorr field | Terraform output | Bicep output |
|---|---|---|
| Azure subscription ID | subscription_id in xplorr_connect_form | subscriptionId |
| Credentials (JSON) | credentials_json in xplorr_connect_form, which holds tenantId, clientId and subscriptionId | credentialsJson, the same keys |
Replace PASTE_THE_CLIENT_SECRET_VALUE in the JSON with the secret Value (not
the Secret ID) before you paste it. Every template also has an
xplorr_principal trust mode; it needs keyless onboarding, which is not
available yet, so keep the default.
Related guides
Section titled “Related guides”- Connect AWS
- Connect GCP
- Data Health: check that the first sync returned data
- Budget Alerts: set up spend notifications after connecting