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:
| Agreement | Scope | billing_scope | Role for the service principal |
|---|---|---|---|
| Enterprise Agreement | The enrollment | billingAccounts/<enrollment number> | Enrollment reader on the billing account |
| Microsoft Customer Agreement | The billing account | billingAccounts/<billing account ID> | Billing account reader on the billing account |
| Microsoft Customer Agreement | One billing profile | billingAccounts/<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:
Code
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.
Code
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.
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:
-
Open Settings, then Integrations, and select Azure.
-
Open the Connect tab and select Add service principal.
-
Fill in the four fields:
Field Value Display name Filled 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 ID tenantfrom the service principalService principal app ID appIdfrom the service principalService principal password passwordfrom the service principal -
Select Test connection to see each check, or Connect to save.

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:
Code
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:
- Open Cost Management + Billing and select the billing account, or, for a billing profile scope, the billing profile under Billing profiles.
- Select Access control (IAM), then Add.
- Choose Billing account reader on the billing account, or Billing profile reader on the billing profile.
- Search for the service principal by its name,
Costfluentif 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:
| Role | Scope | What it reads |
|---|---|---|
| Reservations Reader | /providers/Microsoft.Capacity | Every reservation in the tenant |
| Savings plan Reader | /providers/Microsoft.BillingBenefits | Every 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
-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
- Roles.
role_definition_namesdefaults 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_idsassigns the same roles on more subscriptions in the tenant, andmanagement_group_idsat 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'sassignment_scopesoutput lists every scope it assigned the roles on. - Commitment roles.
commitment_roles = truealso 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>" }tocostfluent_provider, in the form Billing scope gives for your agreement. Removingsettingsswitches the connection back to its subscriptions. - Propagation.
costfluent_providerdoes not wait for Azure role assignments to propagate. If the first apply fails because the principal cannot read cost yet, runterraform applyagain a minute later. - Secret rotation. The client secret lasts
secret_expiry_days, 365 by default, until the instant the module'ssecret_expires_atoutput shows. A module version with thesecret_rotates_atoutput replaces it from that instant,secret_rotation_days_before_expirydays 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, runterraform 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.
| Dimension | For Azure | From |
|---|---|---|
| Provider | azure | The connection |
| Provider name | Microsoft Azure; for a Marketplace charge, the publisher | PublisherType, PublisherName |
| Sub-account | The subscription's ID; at billing scope, the billing account's ID for a charge billed without a subscription | SubscriptionId, else BillingAccountId |
| Service | The service, or the meter category when the row names none | ServiceName, else MeterCategory |
| Region | The resource location's name, such as West Europe; Global for a charge without one | ResourceLocation |
| Resource | The resource's name, or the last segment of its ID | ResourceName, else ResourceId |
| Charge category | Usage (unused reservations and savings plans included), Purchase (refunds included), Tax or Credit; any other charge type is Adjustment | ChargeType |
| Service category | The FOCUS category of the meter category; Cognitive Services, Foundry Models and Azure Machine Learning are AI and Machine Learning | MeterCategory, MeterSubCategory |
| SKU | The meter's name, such as gpt-4o-Input-global Tokens | MeterName |
| SKU ID | The meter's ID | MeterId |
| Resource type | The resource's type, as Cost Details states it | ResourceType |
| Product family | The service family, such as Compute or Storage | ServiceFamily |
| Resource group | The resource group, read from the resource's ID in lower case, so RG-App and rg-app are one group | ResourceId |
| Invoice ID | The invoice the line is billed on | InvoiceId |
| Invoice issuer | The partner or reseller that invoices the line; Microsoft where none does | PartnerName, else ResellerName |
| Billing period | The month invoiced, such as 2026-03 | BillingPeriodStartDate |
| Operation, Charge description | Never filled; Cost Details states neither | |
| Tag | Every key of the row's tags | Tags |
| Segment | Your allocation rules | Segments and allocation rules |
| Cluster, Namespace | Never filled; they hold Kubernetes cost (Connect Kubernetes) | |
| Virtual tag | Your virtual tag rules | Virtual 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).