Connect a source

Connect Azure

Give a service principal Cost Management Reader on the subscriptions you want imported, or a billing reader role on your Enterprise Agreement or Microsoft Customer Agreement billing account, and add it to Costfluent as a connection.

Costfluent reads Azure cost through a service principal you create. One connection is one service principal, and it imports either every subscription that principal can read cost for, or one billing scope: the whole bill of an Enterprise Agreement enrollment, or of a Microsoft Customer Agreement billing account or billing profile. Several subscriptions do not need several connections. A subscription is imported by one connection only, the one that imported it first.

How Costfluent connects

A connection collects one of two scopes. You choose it when you add the connection, and you can switch it later (Switching between subscriptions and a billing scope):

  • Subscriptions imports every subscription the principal holds an Azure role on. Charges billed to the billing account rather than to a subscription are missing (What is not imported).
  • Billing account imports one billing scope: every subscription billed there, in every directory, and the purchases, tax and credits billed at the billing account or billing profile. It needs a billing reader role on that scope.

To list your reservations and savings plans under Commitments, also assign the service principal Reservations Reader and Savings plan Reader. Both are optional and read only, and Microsoft grants them once for the whole Microsoft Entra tenant rather than on a subscription (Add the commitment roles).

Subscriptions

One built-in role is required, and two are optional:

  • Cost Management Reader is required. With it, Costfluent lists the subscriptions the principal is assigned to, reads the Cost Details report the import is built on, and reads Azure Advisor's cost recommendations into Savings once a day.
  • Carbon Optimization Reader is optional. With it, Costfluent reads the emissions Azure reports for the subscription into the carbon footprint report once a month. It reads emissions data and nothing else.
  • Reader is optional. Costfluent does not use it, and it grants read access to every resource in the subscription. Leave it out unless your own policy asks for it.

None of these roles can change anything in your subscription. Do not grant Contributor or Owner.

Assign the role per subscription, or once at a management group covering the subscriptions you want imported. A management-group assignment also covers subscriptions added to it later.

If a second service principal reaches subscriptions another connection already imports, it still connects and imports the rest; the shared ones stay with the first connection. A principal that reaches only such subscriptions is refused. To move subscriptions to a new principal, update the existing connection's credentials instead of adding a connection.

Billing scope

The connection's setting billing_scope names the scope, in one of three forms:

AgreementScopebilling_scopeRole for the service principal
Enterprise AgreementThe enrollmentbillingAccounts/<enrollment number>Enrollment reader on the billing account
Microsoft Customer AgreementThe billing accountbillingAccounts/<billing account ID>Billing account reader on the billing account
Microsoft Customer AgreementOne billing profilebillingAccounts/<billing account ID>/billingProfiles/<billing profile ID>Billing profile reader on the billing profile, or Billing account reader on the billing account

In the Azure portal, under Cost Management + Billing, a billing account's ID is on its Properties page; for an Enterprise Agreement it is the enrollment number. A billing profile's ID is on the billing profile's Properties page. Copy the IDs exactly as the portal shows them. Costfluent refuses an enrollment's departments and enrollment accounts, a billing profile's invoice sections, and management groups: the first three carry usage only, which a subscription connection already imports, and Azure's Cost Details report does not serve management groups. Billing through a Cloud Solution Provider (CSP) is not supported.

Microsoft documents these billing reader roles as reading the cost of every subscription billed under the scope, including subscriptions in other Microsoft Entra directories, and as able to create, change and delete budgets and exports in Cost Management as well. Costfluent uses the role only to read cost, and creates, changes and deletes nothing in Azure.

Recommendations under Savings and the carbon footprint report are read per subscription, and a billing role does not reach them. For a billing scope, Costfluent reads them for each subscription the scope covers, with the roles a subscription connection uses: Cost Management Reader for recommendations and Carbon Optimization Reader for emissions, on each subscription or at a management group above them. The subscriptions the principal cannot read are reported, as for a subscription connection.

A subscription is still imported once. When a billing scope covers a subscription another Azure connection of your organization already imports, the billing connection drops that subscription's cost, and both connections' pages say so. A subscription connection added later lists the subscriptions a billing connection already covers as Unavailable. A bill is also imported through one connection only: a billing scope that bills part of another connection's billing scope, the same enrollment or an MCA billing account and one of its billing profiles, is refused, so its purchases, tax and credits are not counted twice. Two billing profiles of one billing account can be two connections.

What is not imported

A connection at subscription scope reads cost per subscription. Charges billed to the billing account or billing profile rather than to a subscription are not imported: tax, purchases under a Microsoft Customer Agreement (reservations, savings plans and Marketplace bought at the billing profile), and other billing-profile charges. Nor are credits and prepayment, such as Azure credit offers, sponsorships and prepayment balances: Microsoft applies them on the invoice, at the billing account or billing profile, so Include credits shows none of them. Your Costfluent totals for Azure can therefore be lower than your invoice by those charges, and higher by those credits.

A connection at billing scope imports the purchases, tax and credits billed at the billing account or billing profile, beside the cost of every subscription billed there.

Prerequisites

  • The Owner or Integration owner organization role in Costfluent.
  • In Azure, the right to register an application in your Microsoft Entra tenant.
  • For subscriptions, the right to assign roles on the subscriptions or the management group you want imported.
  • For a billing scope, the right to grant its billing role: the enrollment writer role on an Enterprise Agreement enrollment, Billing account owner on a Microsoft Customer Agreement billing account, or Billing profile owner on its billing profile.
  • Optional, for reservations and savings plans on Commitments: User Access Administrator at the root of your tenant, which a Global Administrator gets by elevating access (Add the commitment roles).
  • An Enterprise Agreement or a Microsoft Customer Agreement. Azure's Cost Details API, which Costfluent reads, does not serve pay-as-you-go subscriptions on the Microsoft Online Services Program at either scope, and the connection check says so.

Connect

Connect with the Azure CLI

Confirm which tenant and subscription you are working in:

TerminalCode
az account show --query '{tenantId:tenantId,subscriptionId:id,subscriptionName:name}' --output table

Create the service principal. The output contains appId, password and tenant; the password cannot be retrieved later, so store it in your password manager now.

TerminalCode
az ad sp create-for-rbac --name "Costfluent" --output json

Grant it Cost Management Reader, on each subscription you want imported or once at a management group. The second command is optional: it grants Carbon Optimization Reader at the same scope, for the carbon footprint report.

SUBSCRIPTION_ID="$(az account show --query id --output tsv)" APP_ID="<the appId from the previous step>" az role assignment create --assignee "$APP_ID" \ --role "Cost Management Reader" --scope "/subscriptions/$SUBSCRIPTION_ID" az role assignment create --assignee "$APP_ID" \ --role "Carbon Optimization Reader" --scope "/subscriptions/$SUBSCRIPTION_ID"

Role assignments take a moment to propagate. An authorization failure immediately after creating them is not proof that the credentials are wrong.

Then add the connection in Costfluent:

  1. Open Settings, then Integrations, and select Azure.

  2. Open the Connect tab and select Add service principal.

  3. Fill in the four fields:

    FieldValue
    Display nameFilled in with Microsoft Azure, or Microsoft Azure 2 and up when a connection already has that name; keep it or enter any name, such as Production
    Microsoft Entra tenant IDtenant from the service principal
    Service principal app IDappId from the service principal
    Service principal passwordpassword from the service principal
  4. Select Test connection to see each check, or Connect to save.

The Add service principal dialog with Display name filled in as Microsoft Azure, the empty Microsoft Entra tenant ID, Service principal app ID and Service principal password fields, each saying where its value comes from, Scope set to Subscriptions, and the Cancel, Test connection and Connect buttons

The Add service principal dialog, opened from the Connect tab.

There is no subscription field: which subscriptions are imported is decided by the role assignments you made, and Costfluent reads that from Azure. Costfluent checks the credentials against the Cost Details API before saving them, so a connection that saves can read your cost. If Azure does not answer during that check, the connection is saved anyway and checked again automatically.

Connect at billing scope

Create the service principal with az ad sp create-for-rbac as Connect with the Azure CLI shows. Its subscription role assignments are needed only for recommendations and carbon (Billing scope). Then grant the billing role.

For an Enterprise Agreement, the role is Enrollment reader. A service principal receives it only through Azure's billing role assignment API: the Azure portal neither offers it for a service principal nor shows the assignment. Run this as a user with the enrollment writer role on the enrollment:

TerminalCode
ENROLLMENT_NUMBER="<your enrollment number>" APP_ID="<the appId of the service principal>" TENANT_ID="<the tenant of the service principal>" PRINCIPAL_ID="$(az ad sp show --id "$APP_ID" --query id --output tsv)" ASSIGNMENT_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')" az rest --method put \ --url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/$ENROLLMENT_NUMBER/billingRoleAssignments/$ASSIGNMENT_ID?api-version=2019-10-01-preview" \ --body "{\"properties\": { \"principalId\": \"$PRINCIPAL_ID\", \"principalTenantId\": \"$TENANT_ID\", \"roleDefinitionId\": \"/providers/Microsoft.Billing/billingAccounts/$ENROLLMENT_NUMBER/billingRoleDefinitions/24f8edb6-1668-4659-b5e2-40bb5f3a7d7e\" }}"

principalId is the service principal's object ID, the one Microsoft Entra ID lists under Enterprise applications, which az ad sp show returns. The application's object ID under App registrations makes the call fail. 24f8edb6-1668-4659-b5e2-40bb5f3a7d7e is the Enrollment reader role, and a 200 OK answer means it is assigned.

For a Microsoft Customer Agreement, grant the role in the Azure portal, as you would for a user:

  1. Open Cost Management + Billing and select the billing account, or, for a billing profile scope, the billing profile under Billing profiles.
  2. Select Access control (IAM), then Add.
  3. Choose Billing account reader on the billing account, or Billing profile reader on the billing profile.
  4. Search for the service principal by its name, Costfluent if you created it as above, select it, and select Add.

Then add the connection in Costfluent as Connect with the Azure CLI shows, choosing Billing account in the Add service principal dialog, then your agreement, and entering the IDs. Costfluent checks that the principal can read the Cost Details report at the scope before saving it. It finds no subscriptions at that point: they appear once their cost arrives (After connecting).

Add the commitment roles

Optional. With two more roles, Costfluent lists your Azure reservations and savings plans under Commitments. Microsoft grants them not on a subscription but once for the whole Microsoft Entra tenant, at a scope of its own:

RoleScopeWhat it reads
Reservations Reader/providers/Microsoft.CapacityEvery reservation in the tenant
Savings plan Reader/providers/Microsoft.BillingBenefitsEvery savings plan in the tenant

Both are read only: neither can buy, change or cancel a reservation or a savings plan. Assigning a role at these scopes needs User Access Administrator at the root of the tenant, which a Global Administrator gets by elevating access. In the Azure portal, only Global Administrators can assign them. Microsoft documents the assignment with Azure PowerShell (reservations, savings plans). Run it with that access:

Code
Connect-AzAccount -Tenant "<the tenant of the service principal>" $PrincipalId = (Get-AzADServicePrincipal -ApplicationId "<the appId of the service principal>").Id New-AzRoleAssignment -PrincipalId $PrincipalId -RoleDefinitionName "Reservations Reader" -Scope "/providers/Microsoft.Capacity" New-AzRoleAssignment -PrincipalId $PrincipalId -RoleDefinitionName "Savings plan Reader" -Scope "/providers/Microsoft.BillingBenefits"

-PrincipalId is the service principal's object ID, the one Microsoft Entra ID lists under Enterprise applications. The Azure portal does not show assignments made this way; Get-AzRoleAssignment -ObjectId $PrincipalId lists them. With Terraform, set commitment_roles = true on the module instead (Connect with Terraform) and apply it with the same access. Without the roles, cost and everything else still arrive as before.

Connect with Terraform

The costfluent/cost-access/azure module registers the application, creates its service principal and a client secret, and assigns Cost Management Reader and Carbon Optimization Reader on a subscription, on any further subscriptions and at any management groups you list, and the two commitment roles when you set commitment_roles. Its credentials output feeds costfluent_provider directly:

Code
provider "azurerm" { subscription_id = "<subscription to import>" features {} } provider "azuread" {} module "costfluent_azure" { source = "costfluent/cost-access/azure" version = "~> 0.4" subscription_id = "<subscription to import>" # Optional: more subscriptions in the same tenant, and management groups whose subscriptions # inherit the roles, all imported through this one connection. additional_subscription_ids = [] management_group_ids = [] # Optional: true also assigns the commitment roles for the tenant, which needs elevated access. commitment_roles = false } resource "costfluent_provider" "azure" { key = "azure" display_name = "Azure" credentials = module.costfluent_azure.credentials }
  • Roles. role_definition_names defaults to Cost Management Reader and Carbon Optimization Reader. Remove Carbon Optimization Reader from it to keep the subscription out of the carbon footprint report. Add "Reader" to it only if your policy asks for it.
  • Several subscriptions. additional_subscription_ids assigns the same roles on more subscriptions in the tenant, and management_group_ids at management groups, whose subscriptions inherit them, including ones added later; each is the group's ID, and the tenant root group's ID is the tenant ID. Assigning at a management group needs Owner or User Access Administrator on it. Connecting checks the subscriptions the principal can read within 90 seconds; any it does not reach in that time appear after the next daily check, without a new connection. The module's assignment_scopes output lists every scope it assigned the roles on.
  • Commitment roles. commitment_roles = true also assigns Reservations Reader and Savings plan Reader at their tenant scopes. Only a User Access Administrator at the tenant root can apply it (Add the commitment roles); without that access the apply fails on those two assignments.
  • Billing scope. The module assigns no billing role. To collect a billing scope, grant the role as Connect at billing scope shows, and add settings = { billing_scope = "billingAccounts/<enrollment number>" } to costfluent_provider, in the form Billing scope gives for your agreement. Removing settings switches the connection back to its subscriptions.
  • Propagation. costfluent_provider does not wait for Azure role assignments to propagate. If the first apply fails because the principal cannot read cost yet, run terraform apply again a minute later.
  • Secret rotation. The client secret lasts secret_expiry_days, 365 by default, until the instant the module's secret_expires_at output shows. A module version with the secret_rotates_at output replaces it from that instant, secret_rotation_days_before_expiry days before expiry (30 by default), so an apply inside that window creates a new secret and passes it to the connection while the old one still works. An earlier version replaces it only at or after expiry, and Costfluent cannot collect from the expiry until that apply runs. An apply outside the window leaves the secret alone. To rotate at any other time, run terraform apply -replace='module.costfluent_azure.time_rotating.secret': it deletes the old secret, creates a new one and passes it to the connection in the same apply.

Data and freshness

Connecting imports each subscription's cost as far back as your plan's backfill reaches, and at most six months (Manage connections). A role granted on another subscription later needs no new connection: the subscription appears after the next check and its first collection reaches back the same way, counted from the day it appears.

A billing scope is imported as far back in the same way. A subscription added to the enrollment or billing profile later needs no new role: its cost arrives with the rest of the bill.

One day of one subscription, or of the billing scope a connection collects, its actual and amortized cost reports together, may hold at most 2,560,000 rows and 4 GiB of cost data. A billing scope puts the whole enrollment's or billing profile's day into that one bound. A larger day stops the connection with an error that names the day, rather than importing part of it; contact Costfluent support through the contact form.

Reservations and savings plans

With the commitment roles, Costfluent lists the tenant's reservations and savings plans under Commitments held (Commitments). Azure lists every commitment in the tenant, so a connection lists the ones applied to a shared scope or a management group, and the ones applied to a single subscription only when the connection imports that subscription.

Carbon emissions

Azure publishes each month's emissions by the 19th of the next month and serves the last twelve months. Costfluent reads them on the 22nd (Carbon footprint), for every subscription the connection imports. Every one of them needs Carbon Optimization Reader: when one lacks it, Azure refuses that subscription, and the connection's month is refused as a whole rather than shown incomplete. The carbon footprint report then names the subscriptions that need the role. Azure states no accounting method for these figures and no service category, so the report shows them as Method not stated and by resource type.

For a billing scope, these are the subscriptions the scope covers, and the billing role does not reach their emissions: grant Carbon Optimization Reader on them, or at a management group above them.

Reporting dimensions

What each group-by dimension of a cost report holds for an Azure connection, and the Cost Details column it comes from.

DimensionFor AzureFrom
ProviderazureThe connection
Provider nameMicrosoft Azure; for a Marketplace charge, the publisherPublisherType, PublisherName
Sub-accountThe subscription's ID; at billing scope, the billing account's ID for a charge billed without a subscriptionSubscriptionId, else BillingAccountId
ServiceThe service, or the meter category when the row names noneServiceName, else MeterCategory
RegionThe resource location's name, such as West Europe; Global for a charge without oneResourceLocation
ResourceThe resource's name, or the last segment of its IDResourceName, else ResourceId
Charge categoryUsage (unused reservations and savings plans included), Purchase (refunds included), Tax or Credit; any other charge type is AdjustmentChargeType
Service categoryThe FOCUS category of the meter category; Cognitive Services, Foundry Models and Azure Machine Learning are AI and Machine LearningMeterCategory, MeterSubCategory
SKUThe meter's name, such as gpt-4o-Input-global TokensMeterName
SKU IDThe meter's IDMeterId
Resource typeThe resource's type, as Cost Details states itResourceType
Product familyThe service family, such as Compute or StorageServiceFamily
Resource groupThe resource group, read from the resource's ID in lower case, so RG-App and rg-app are one groupResourceId
Invoice IDThe invoice the line is billed onInvoiceId
Invoice issuerThe partner or reseller that invoices the line; Microsoft where none doesPartnerName, else ResellerName
Billing periodThe month invoiced, such as 2026-03BillingPeriodStartDate
Operation, Charge descriptionNever filled; Cost Details states neither
TagEvery key of the row's tagsTags
SegmentYour allocation rulesSegments and allocation rules
Cluster, NamespaceNever filled; they hold Kubernetes cost (Connect Kubernetes)
Virtual tagYour virtual tag rulesVirtual tags

An Azure meter billed per thousand or million tokens, unit 1K or 1M on a meter named … Tokens, has its usage stored as Tokens, one by one, so the Usage measure sums it with other providers' tokens.

Update or disconnect

After connecting

The Manage tab lists the connection by its display name, with its tenant ID under Tenant ID and its App ID. Open the gear icon beside it to see whether the credentials still work, which workspaces the connection feeds, which billing months have been imported, and a Subscriptions card listing every subscription the principal reaches with what the connection does with each.

A subscription the principal can see but cannot read cost for, or one another connection already imports, is listed as Unavailable with the reason, rather than appearing healthy and never producing data.

For a billing scope, the page shows the scope the connection collects, and the Subscriptions card lists the subscriptions the scope's cost has named so far as Covered. A subscription whose cost another Azure connection already imports is named on both connections' pages, and its cost stays with that connection.

Run checks on the connection page runs the checks again on request and reports each one separately, so a failure names the step that failed: signing in, or reading cost data. The run happens in the background, and the page updates itself when it finishes.

Switching between subscriptions and a billing scope

A connection switches scope in place, and its credentials stay. To switch to a billing scope, grant the billing role first (Connect at billing scope), then use Update credentials on the connection page, choose Billing account, and enter the IDs. Costfluent checks the scope with the stored credentials before storing it, and a refused check changes nothing. From then on the connection collects the billing scope and lists each subscription as Covered once the scope's cost names it. A former subscription the scope does not bill keeps the cost already imported for it and is free for another connection. Cost already imported per subscription is not counted twice: the billing scope's rows replace the subscriptions' rows of the same charges.

To switch back, choose Subscriptions the same way, remove settings in Terraform, or send clearSettings to the Public API's PUT /v1/providers/{providerId}. The connection then collects its subscriptions again and imports their past cost anew, which replaces the billing scope's rows of the same charges; the purchases and tax it imported at the billing account stay.

Rotating or removing the credentials

Reset the service principal's credential in Azure, then use Update credentials on the connection page. With Terraform, a plain apply rotates the secret only from secret_rotates_at (at or after expiry on a module version without that output); to rotate it at another time, replace the module's timer as Connect with Terraform shows, which updates the connection in the same apply. To retire the connection, remove it in Costfluent and delete the role assignments and the service principal in Azure, or run terraform destroy. Removing it erases the cost it imported (Remove a connection).

Troubleshooting

One heading per message, as the connection page shows it: under the connection checks, or as the reason beside a subscription on the Subscriptions card, where Azure's own words end the reason. Run checks runs the checks again.

Costfluent could not authenticate with the Azure credentials.

The tenant ID, app ID or client secret is wrong, or the secret has expired (with Terraform it lasts secret_expiry_days, 365 by default). Reset the secret in Azure and use Update credentials on the connection page (Rotating or removing the credentials).

Costfluent could not read savings recommendations from Azure.

Cost still arrives, and the connection shows as degraded until this is fixed. Azure Advisor refused the service principal on the subscription the message beside the check names. Cost Management Reader, the role the connection already needs, includes Microsoft.Advisor/recommendations/read; assign it on that subscription again, or at a management group covering it. Advisor's cost recommendations then appear under Savings after the next daily read. A billing role does not reach Advisor, so a connection at billing scope needs Cost Management Reader on the subscriptions the scope covers (Billing scope).

Costfluent could not list the commitments Azure holds.

Cost still arrives, and the connection stays Healthy. Azure refused the list of reservations, of savings plans, or both: the service principal lacks Reservations Reader at /providers/Microsoft.Capacity or Savings plan Reader at /providers/Microsoft.BillingBenefits, and the message beside the check names which. Both roles are optional; assign them as Add the commitment roles shows, then Run checks. Until then Commitments names the connection above Commitments held.

Costfluent could not read cost data from Azure.

Signing in worked, but the Cost Details report could not be read. The reason beside each subscription on the Subscriptions card names the cause, and for a billing scope the reason under the check does; the headings below are the usual ones.

The service principal cannot read Cost Management at this subscription

The principal has no Cost Management Reader assignment on the subscription or on a management group above it, or the assignment is not in effect yet: role assignments take a moment to propagate, and a failure right after creating them is not proof that the credentials are wrong. Assign the role (How Costfluent connects), then Run checks a minute later; with Terraform, run terraform apply again.

The subscription was not found, or the service principal cannot see it

The subscription ID is wrong, the principal has no assignment on it at all, or the subscription is not under an Enterprise Agreement or a Microsoft Customer Agreement: Azure's Cost Details API, which Costfluent reads, does not serve pay-as-you-go subscriptions (Prerequisites). Azure's own answer ends the reason.

Azure rejected the Cost Details request for this subscription

Azure accepted the sign-in and knows the subscription, but refuses the Cost Details report for it. That is what a pay-as-you-go subscription answers; Azure's own words at the end of the reason tell it apart from a mistyped ID.

The service principal cannot read Cost Management at this enrollment

The connection collects an Enterprise Agreement enrollment, and the principal has no Enrollment reader assignment on its billing account. Assign it with the billing role assignment call (Connect at billing scope); the Azure portal does not show this assignment, so it is no place to check for it. Then Run checks.

The service principal cannot read Cost Management at this billing scope

The connection collects a Microsoft Customer Agreement billing account or billing profile, and the principal holds neither Billing account reader on the billing account nor Billing profile reader on the billing profile. Grant one in the Azure portal (Connect at billing scope), then Run checks.

The billing account or billing profile was not found, or the service principal cannot see it

The ID in the scope is wrong, or names a billing account or billing profile the principal holds no role on. Compare it with the IDs under Cost Management + Billing in the Azure portal (Billing scope), and correct it on the connection (Switching between subscriptions and a billing scope).

Azure rejected the Cost Details request for this billing scope

Azure accepted the sign-in and knows the scope, but refuses the Cost Details report for it. That is what a pay-as-you-go billing account on the Microsoft Online Services Program answers, and Costfluent imports such an account at neither scope (Prerequisites).

Azure subscription … is already collected by another Azure connection in this organization

Another connection's principal already imports the subscription, so this one lists it as Unavailable and imports the rest; a principal that reaches only such subscriptions is refused. Remove the role assignment from one of the two principals, or move the subscriptions by updating the existing connection's credentials instead of adding a connection (How Costfluent connects).