Setting up an Azure Cost Integration
The Azure Cost integration reads your Cost Management FOCUS export out of the storage account it is delivered to. It does not call a cost API per billing period, so it is not subject to the Consumption API rate limits.
CloudQuery resolves the storage account, container and folder from the export definition itself. You name the scope and the export, and nothing else.
This integration requires a FOCUS export delivered as Parquet. The ActualCost, AmortizedCost and PriceSheet metrics and CSV delivery are not supported. CloudQuery detects a mismatched export and names what it found.
Prerequisites
- A CloudQuery Platform account with admin access
- A storage account for the export to deliver to
- Permission to create app registrations in your Entra ID tenant
- Permission to create Cost Management exports at the scope you want to read
- Permission to assign roles on the export scope and on the destination storage account
- Azure CLI installed
Azure Cost does not need an Azure integration. With no connection to reuse, the form starts on Set up a new connection and its setup script creates everything. If a connection is already set up for cost exports, you can choose Reuse an existing connection instead, as described in Reuse a connection.
Set up a new connection
-
In CloudQuery Platform, go to Data Sources → Integrations, click Add new integration and choose Azure Cost.
-
Click Generate setup script.
-
Set the storage account the export delivers to, then run the script with the Azure CLI, signed in to the subscription you want cost data for:
export CQ_STORAGE_ACCOUNT="<STORAGE_ACCOUNT_NAME>" export CQ_BACKFILL=1Variable Required Default CQ_STORAGE_ACCOUNTYes None CQ_STORAGE_ACCOUNT_IDNo Looked up in the current subscription CQ_EXPORT_SCOPENo The current subscription CQ_EXPORT_NAMENo Generated for this connection CQ_CONTAINERNo cloudquery-focus-cost-exportsCQ_ROOT_FOLDER_PATHNo focusCQ_BACKFILLNo Unset, so the export waits for its first daily run If the storage account lives in another subscription, set
CQ_STORAGE_ACCOUNT_IDto its full resource ID. -
Paste the Tenant ID and Client ID the script prints, then click Complete setup.
-
Paste the Export scope and Export name the script prints into Scope and Export name.
-
Click Continue. CloudQuery reads the export back through the new connection before it lets you select tables.
-
Select tables, then click Test and Continue.
The script makes these changes:
| Change | Where |
|---|---|
| App registration with a federated identity credential | Your Entra ID tenant. No secret is created. |
Reader | The current subscription |
Microsoft.CostManagementExports registration | The subscription that hosts the storage account |
| FOCUS export, delivered daily as Parquet | The export scope |
Cost Management Reader | The export scope |
Storage Blob Data Reader | The export’s container |
A new export has no data until it runs. With CQ_BACKFILL=1 the script runs it once. Without it, Continue reports that the export has not delivered any data until the first daily run lands.
Reuse a connection
If an Azure connection is already set up for cost exports, choose Reuse an existing connection and select it. The connection your Azure integration uses is not offered: it is set up for asset inventory only, and CloudQuery reads a cost export only through a connection set up for cost.
To read an export that connection has no access to yet, follow Steps 1 to 4.
Step 1: Create the FOCUS export
az costmanagement export create cannot create this export. Its --type accepts only ActualCost, AmortizedCost and Usage, and it does not offer Parquet. Use the Azure Portal, or the Cost Management REST API at version 2023-07-01-preview with definition.type set to FocusCost.
- In the Azure Portal, go to Cost Management → Exports.
- Click Create, then set the metric to Cost and usage details (FOCUS).
- Set the file format to Parquet.
- Choose the storage account and container to deliver to, and note the export name.
- Save the export, then wait for the first delivery. Azure schedules the first run rather than delivering immediately.
An export is only visible at the scope it was created at. Note that scope: you enter it in Step 4 exactly as you created it, for example subscriptions/00000000-0000-0000-0000-000000000000 or providers/Microsoft.Billing/billingAccounts/1234567.
Step 2: Register the exports provider
Microsoft.CostManagementExports must be registered on the subscription that hosts the destination storage account, not on the export’s scope subscription. The two are often different, because an export may deliver outside its own scope.
Unregistered, Azure accepts the export and then delivers nothing. There is no error and no files, so this is worth checking before you wait on a delivery that will never arrive.
az provider register \
--namespace Microsoft.CostManagementExports \
--subscription "STORAGE_SUBSCRIPTION_ID"Confirm it took effect:
az provider show \
--namespace Microsoft.CostManagementExports \
--subscription "STORAGE_SUBSCRIPTION_ID" \
--query registrationState -o tsvThe command returns Registered once it completes. Registration can take a few minutes.
Step 3: Grant the two roles
The reused connection’s identity needs access on two different planes, and one role does not imply the other.
| Role | Scope | Why |
|---|---|---|
Cost Management Reader | The export scope | Reads the export definition |
Storage Blob Data Reader | The destination storage account | Reads the delivered Parquet files |
Owner and Contributor are control-plane roles and grant no blob data access. Storage Blob Data Reader is required even for an owner. Without it CloudQuery resolves the export and then fails to read it.
az role assignment create \
--assignee "CLIENT_ID" \
--role "Cost Management Reader" \
--scope "/subscriptions/SUBSCRIPTION_ID"
az role assignment create \
--assignee "CLIENT_ID" \
--role "Storage Blob Data Reader" \
--scope "/subscriptions/STORAGE_SUBSCRIPTION_ID/resourceGroups/RESOURCE_GROUP/providers/Microsoft.Storage/storageAccounts/STORAGE_ACCOUNT"Replace CLIENT_ID with the application ID of your Azure connection, and the scope placeholders with your own. The setup guide panel beside the form prints the first command with the client ID and export scope already filled in.
Step 4: Point CloudQuery at the export
- In CloudQuery Platform, go to Data Sources → Integrations, click Add new integration and choose Azure Cost.
- Choose Reuse an existing connection, then select the connection under Azure connection. CloudQuery preselects it when there is only one.
- Enter the Scope the export was created at, and the Export name on its own without a scope prefix.
- Click Continue. CloudQuery reads the export definition through your connection, resolves the storage destination from the export’s own delivery settings, and lists the blobs already delivered. It stops here with an explanation if any of that fails.
- Select tables, then click Test and Continue.
CloudQuery builds the integration configuration from what it read during that check, so the storage account, container and prefix are never taken from anything you typed.
What gets synced
| Table | Description |
|---|---|
azurecost_focus_costs | FOCUS export line items, read as Parquet from the storage account the export delivers to |
Each row is a line item from your export. See the Azure Cost table documentation for the full schema.
export_scope and export_name are part of each row’s identity, so two exports covering the same subscription each contribute their own copy of every charge. A subscription-scoped export and a billing-account-scoped export that both cover that subscription will double-count where they overlap. Group or filter by export_name when summing across exports.
Verify the integration
After your first sync completes, open the SQL Console and run these queries to confirm cost data arrived:
-- Count synced cost records
SELECT count(*) FROM azurecost_focus_costs-- Preview cost data
SELECT * FROM azurecost_focus_costs LIMIT 5Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| The export exists but no files are delivered | Microsoft.CostManagementExports is not registered on the storage subscription | Run the az provider register command in Step 2 against the subscription that hosts the storage account, then wait for the next delivery. |
| Listing the container fails | Missing Storage Blob Data Reader | Assign it on the storage account, as in Step 3. Owner and Contributor do not grant blob data access. |
| The export cannot be found at the scope | The scope does not match where the export was created | An export is only visible at its own scope. Enter the scope exactly as you created it. |
| CloudQuery reports an unsupported metric or format | The export is ActualCost, AmortizedCost, PriceSheet, or CSV | Recreate it as Cost and usage details (FOCUS) in Parquet. az costmanagement export create cannot do this; use the Portal or the REST API. |
| No Azure connection is offered | No connection is set up for cost exports yet. The Azure integration’s connection is set up for asset inventory only. | Follow Set up a new connection. |
| The export has not delivered any data | The export has not run yet | Re-run the setup script with CQ_BACKFILL=1, or wait for the first daily run, then click Continue again. |
The setup script stops and asks for CQ_STORAGE_ACCOUNT | The variable is not set | Set it to the storage account the export delivers to, then re-run the script. |
| The setup script cannot find the storage account | The storage account is in another subscription | Set CQ_STORAGE_ACCOUNT_ID to its full resource ID, then re-run the script. |
| Costs appear twice | Two overlapping exports are synced | Group or filter by export_name, or stop syncing one of them. |
Next steps
- Set up a sync to schedule when your cost data syncs
- Browse cost data in the SQL Console
- Set up the Azure integration to bring your Azure assets in alongside their costs
Related resources
- FOCUS cost and usage details
- Create and manage exported data
- Azure built-in roles
- Workload identity federation
- CloudQuery Azure Cost integration on Hub
Last updated on