Skip to content

Connect Microsoft Azure

CloudQuell reads your Azure spend through a Microsoft Entra ID service principal with read-only cost roles. Two connect paths are available: Cost Management exports (recommended — invoice-accurate FOCUS data delivered to your own storage account) or quick connect (API pull, no export setup). Nothing is written back to your Azure tenant, and once connected, Azure spend flows into the same ledger, dashboards, reports, and allocation views as your other providers. Subscriptions appear as their own sub-accounts — the Azure analog of AWS linked accounts.

  • A CloudQuell admin — creating integrations requires the super_admin or admin role. See roles.
  • The Azure integration enabled for your CloudQuell organization (see the note above).
  • An Azure administrator who can create app registrations in Microsoft Entra ID and assign roles at your billing scope — subscription, MCA billing account, or EA enrollment, depending on your account type.

The fastest path is the Azure CLI:

Terminal window
az ad sp create-for-rbac -n "cloudquell"

Record three values from the output — the wizard asks for all of them:

  • appId — the Application (client) ID.
  • password — the client secret value. It’s shown once; copy it now.
  • tenant — the Directory (tenant) ID.

Secrets created this way expire after one year by default (pass --years to change it). Note the expiry date — the wizard records it and CloudQuell warns you before it lapses.

Prefer the portal?

  1. Open Microsoft Entra ID. On the Overview page, click + Add → App registration (or use Manage → App registrations → New registration).
  2. Name it cloudquell, keep the defaults, and click Register.
  3. On the app’s Overview, copy the Application (client) ID and Directory (tenant) ID.
  4. In the left menu, expand Manage → Certificates & secrets → + New client secret. Set an expiry, click Add, then copy the secret’s Value column (not the Secret ID) — it’s shown only once.

What to grant depends on your billing account type — pick the one that matches.

Assign Cost Management Reader (or Reader) to the app on each subscription you want CloudQuell to read.

Portal:

  1. Open Subscriptions and click the subscription name to open it.
  2. Left menu → Access control (IAM) → + Add → Add role assignment.
  3. Role tab → search Cost Management Reader → select it → Next.
  4. Members tab → User, group, or service principal → + Select members → search cloudquellSelect.
  5. Review + assign (and confirm on the final screen — the assignment isn’t saved until then).

Repeat per subscription.

CLI:

Terminal window
az role assignment create \
--assignee <APP_ID> \
--role "Cost Management Reader" \
--scope "/subscriptions/<SUBSCRIPTION_ID>"

Grant access at the billing account (or billing profile) in the portal: Cost Management + Billing → select your billing account → Access control (IAM) → Add → “Billing account reader”, then select the app you registered in step 1.

The wizard asks for your billing account ID — copy it from Cost Management + Billing → Properties. For billing-profile scope, use the profile’s full resource path instead, from Billing profiles → your profile → Properties.

The EnrollmentReader billing role can only be assigned over REST — there’s no portal or CLI path. As an Enterprise Administrator, PUT a billing role assignment (role definition 24f8edb6-1668-4659-b5e2-40bb5f3a7d7e is EnrollmentReader):

Terminal window
curl -X PUT \
"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/<ENROLLMENT_ID>/billingRoleAssignments/<NEW_GUID>?api-version=2019-10-01-preview" \
-H "Authorization: Bearer <ARM_ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"properties": {
"principalId": "<APP_OBJECT_ID>",
"principalTenantId": "<TENANT_ID>",
"roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/<ENROLLMENT_ID>/billingRoleDefinitions/24f8edb6-1668-4659-b5e2-40bb5f3a7d7e"
}
}'

Placeholders:

  • <ENROLLMENT_ID> — your EA enrollment number (the billing account name), shown under Cost Management + Billing → Properties. The wizard asks for this same value.
  • <NEW_GUID> — any new GUID you generate (uuidgen) to name the assignment.
  • <APP_OBJECT_ID> — the app’s Enterprise Application object ID (Entra ID → Enterprise applications → your app → Object ID), not the Application (client) ID. CLI: az ad sp show --id <APP_ID> --query id -o tsv.
  • <TENANT_ID> — your Directory (tenant) ID.
  • <ARM_ACCESS_TOKEN> — an Enterprise Administrator’s token: az account get-access-token --query accessToken -o tsv.

The wizard’s first step asks which path you’re taking.

Invoice-accurate billed and amortized cost at any scale.

Create the export (portal):

  1. In Cost Management, open Reporting + analytics → Exports → + Create.
  2. Under Select a template, choose Cost and usage (FOCUS). Set an Export prefix, keep the Daily frequency, then Next.
  3. On Destination: Azure blob storage → Use existing, then pick the Subscription, Storage account, and Container you own, and set a Directory (e.g. cloudquell).
  4. Leave Format = Parquet and Compression = Snappy — CloudQuell reads Parquet (CSV and CSV.gz also work) — then Review + create.

Note the storage account, container, and Directory — the wizard asks for all three. The wizard’s Directory field is the export’s Destination → Directory value, not the Datasets-tab Export prefix.

For history, use Export selected dates to create one-time exports for past months — one calendar month per export (the 1st to the last day of the same month).

Grant the app read access to the container. The role must be Storage Blob Data Reader — the plain Reader role does not grant blob-data access.

Portal:

  1. Open the storage account (or the specific container) → Access Control (IAM) → + Add → Add role assignment.
  2. Role tab → search Storage Blob Data Reader → select it → Next.
  3. Members tab → User, group, or service principal → + Select memberscloudquellSelectReview + assign.

CLI:

Terminal window
az role assignment create \
--assignee <APP_ID> \
--role "Storage Blob Data Reader" \
--scope "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Storage/storageAccounts/<STORAGE_ACCOUNT>/blobServices/default/containers/<CONTAINER>"

No export setup — you just list the subscriptions to read, and CloudQuell pulls cost data from Azure’s API. Up to 13 months of history backfills on connect. One caveat: on this path billed cost equals amortized cost (the API pull carries the amortized view only). You can connect exports later on the same integration for invoice-accurate billed cost — CloudQuell automatically prefers export data for any month where both exist, so nothing double-counts.

For Enterprise Agreement (EA) accounts specifically, the API dataset omits a list-cost column, so CloudQuell derives list cost from the pay-as-you-go unit price × quantity. Azure leaves that unit price blank on reservation-covered and Marketplace rows, so commitment coverage and realized-savings figures are partial on the EA quick-connect path — connect a FOCUS export for complete savings/coverage. (MCA accounts carry list cost directly and aren’t affected.)

To see your Azure reservations and savings plans on the Commitments page, grant two tenant-level read roles.

Portal:

  • Reservations Reader — search ReservationsRole assignments (top toolbar) → AddReservations Reader → select cloudquell.
  • Savings Plan Reader — search Savings plansRole assignmentsAddSavings Plan Reader → select cloudquell.

CLI:

Terminal window
az role assignment create \
--assignee <APP_ID> \
--role "Reservations Reader" \
--scope "/providers/Microsoft.Capacity"
az role assignment create \
--assignee <APP_ID> \
--role "Savings Plan Reader" \
--scope "/providers/Microsoft.BillingBenefits"

These are optional and never block the connection — the wizard flags a missing role as a warning, not a failure. You can grant them any time later; they’re picked up automatically on the next daily sync.

In the CloudQuell admin, go to Integrations, open the Microsoft Azure tile, and click Connect. The wizard walks you through the path choice, the credentials from step 1 (plus your billing scope), and the export details or subscription list — then runs live checks against Azure: credentials, billing-scope access, storage or per-subscription cost access, and the commitments roles. Nothing is saved until the checks pass, so a missing role fails immediately with the exact fix — not on the first sync.

  • Azure reports usage with an 8–24 hour lag, and restates month-to-date daily — so the most recent day or two may shift slightly before settling.
  • On the export path, data appears after your export completes its first run (check Exports in Cost Management for run status). Historical months appear as their one-time exports complete.
  • On the quick connect path, the backfill (up to 13 months) starts the moment you connect.
  • After that, Azure spend syncs daily at 07:30 UTC.

Each subscription appears as its own sub-account, so you can filter, report, and allocate per subscription. Resource groups and invoice sections arrive as tags, filterable anywhere tags are.

Client secrets expire — it’s the most common way an Azure connection breaks. CloudQuell shows a warning on the integration card when the secret has less than 30 days left.

  1. In Entra ID → App registrations → your app → Certificates & secrets, create a new client secret and copy its value.
  2. In CloudQuell, open the integration’s Manage dialog and update the client secret. It’s validated live against Azure before it’s swapped in.

Syncs continue on the old secret until the rotation succeeds, so rotating before expiry means no gap in data. Rotation never resets or interrupts your ingested data.

Symptom Likely cause Fix
Sync fails with AADSTS7000222 The client secret has expired Create a new secret in Entra ID and rotate it from Manage — see Rotate the client secret.
Billing-scope check fails (403) Role grant doesn’t match your scope — often assigned on the billing account instead of the subscription For subscription scope, grant Cost Management Reader on the subscription (Subscriptions → the subscription → Access control (IAM)), not on the billing account. MCA needs Billing account reader on the billing account; EA needs the EnrollmentReader REST assignment — see step 2.
Storage check fails (403) The app can’t read the export container, or you granted plain Reader Grant Storage Blob Data Readernot the generic Reader role — on the container or storage account (step 3), then re-run the checks.
A check still fails right after you granted the role RBAC hasn’t propagated yet Wait a few minutes and Run checks again. Data-plane roles like Storage Blob Data Reader can take 5–10 min — longer than subscription-scope roles.
Connected, but no data yet The first export run hasn’t completed, or you’re inside Azure’s 8–24 h reporting lag Wait for the export’s first run, then the next daily sync picks it up. On quick connect, data appears after the first sync.
A backfill month never shows up The one-time export covered multiple months, so ingestion skipped it Recreate the historical export as one month per export, then re-sync.

Deleting the Azure integration is a permanent hard delete: it removes all ingested Azure cost data and the stored client secret from Secrets Manager. This can’t be undone — see manage your integrations. The app registration and role assignments in Azure are yours to remove; deleting the app registration in Entra ID revokes all access.

Azure spend lands in the same normalized ledger as everything else, so a connected agent can query it in plain language alongside your AWS costs — by subscription, service, resource-group tag, or day.