Skip to content

Connect Azure to Xplorr: Service Principal Setup for Cost Data

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.

  • 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.

Assign Reader on the subscription. It is read only, and it is the one built-in role that covers everything Xplorr calls:

What Xplorr readsAzure APIReaderCost Management Reader
Daily costs, network costs, per-resource costsCost Management query (Microsoft.CostManagement)YesYes
Cost recommendationsAzure Advisor (Microsoft.Advisor/recommendations/read)YesYes
Reservation and savings plan purchase recommendationsMicrosoft.Consumption/reservationRecommendations/read, Microsoft.CostManagement/benefitRecommendations/readYesYes
The connect checkSubscription read (Microsoft.Resources/subscriptions/read)YesYes
Resource inventoryResource list (Microsoft.Resources/subscriptions/resources/read)YesNo
Running or stopped state of VMs, App Service apps and AKS clustersMicrosoft.Compute, Microsoft.Web, Microsoft.ContainerService readsYesNo
Activity Log: every sync checks it is readable, and anomaly root cause reads itActivity Log events (Microsoft.Insights/eventtypes/values/read)YesNo

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.

  1. 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.

  2. 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.

  3. 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.

  4. Copy the subscription ID

    It is on the subscription’s overview page.

Terminal window
az login
SUBSCRIPTION_ID="<your-subscription-id>"
# Creates the app registration, its service principal and a client secret,
# and assigns Reader on the subscription in one step
az 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).

  1. Open the form

    In Xplorr go to Infrastructure → Cloud Accounts and click Connect account. Set Provider to Microsoft Azure.

  2. 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 subscriptionId inside the JSON is the one Xplorr actually queries, so make it match the Azure subscription ID field. The uppercase names AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET and AZURE_SUBSCRIPTION_ID are accepted too.

  3. Test, then connect

    Test connection runs the check below without saving anything. Then click Connect account.

  1. 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.
  2. 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.
  3. 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 subscriptionId or tenantId is 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.
  4. If the subscription is already connected to your organization, you get a message saying so rather than a duplicate.
  5. 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.

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.

FeatureRoleScope
Carbon emissionsCarbon Optimization ReaderEach subscription
FOCUS export ingestion, if you point Xplorr at oneStorage Blob Data ReaderThe storage account (or container) the export writes to
Commitments: each reservation’s term, price and end dateReservations ReaderTenant, /providers/Microsoft.Capacity
Commitments: each savings plan’s term, price and end dateSavings plan readerTenant, /providers/Microsoft.BillingBenefits
Credits, Enterprise Agreement (Azure Prepayment balance and commitment lots)EnrollmentReaderThe enrollment (billing account)
Credits, Microsoft Customer Agreement (credit balance and credit lots)Billing profile readerThe billing profile
Credits, Microsoft Customer Agreement consumption commitment (MACC) lotsBilling account readerThe billing account
InvoicesBilling 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):

Terminal window
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.BillingBenefits

Billing 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.

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.

ActionWho
Connect an accountMember or admin
Sync or delete an accountAn organization admin, or whoever connected it
See an account’s dataAdmins see everything; members and viewers see only accounts they have been granted

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.

  • 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, clientSecret and subscriptionId
  • 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.

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
}
Terminal window
terraform init
terraform apply
terraform output -json xplorr_connect_form

subscription_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.

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:

Terminal window
az ad app credential reset --id <clientId> --append --display-name xplorr --years 2 --query password -o tsv

Then, in Connect account:

Xplorr fieldTerraform outputBicep output
Azure subscription IDsubscription_id in xplorr_connect_formsubscriptionId
Credentials (JSON)credentials_json in xplorr_connect_form, which holds tenantId, clientId and subscriptionIdcredentialsJson, 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.