Skip to Content

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

  1. In CloudQuery Platform, go to Data SourcesIntegrations, click Add new integration and choose Azure Cost.

  2. Click Generate setup script.

  3. 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=1
    VariableRequiredDefault
    CQ_STORAGE_ACCOUNTYesNone
    CQ_STORAGE_ACCOUNT_IDNoLooked up in the current subscription
    CQ_EXPORT_SCOPENoThe current subscription
    CQ_EXPORT_NAMENoGenerated for this connection
    CQ_CONTAINERNocloudquery-focus-cost-exports
    CQ_ROOT_FOLDER_PATHNofocus
    CQ_BACKFILLNoUnset, so the export waits for its first daily run

    If the storage account lives in another subscription, set CQ_STORAGE_ACCOUNT_ID to its full resource ID.

  4. Paste the Tenant ID and Client ID the script prints, then click Complete setup.

  5. Paste the Export scope and Export name the script prints into Scope and Export name.

  6. Click Continue. CloudQuery reads the export back through the new connection before it lets you select tables.

  7. Select tables, then click Test and Continue.

The script makes these changes:

ChangeWhere
App registration with a federated identity credentialYour Entra ID tenant. No secret is created.
ReaderThe current subscription
Microsoft.CostManagementExports registrationThe subscription that hosts the storage account
FOCUS export, delivered daily as ParquetThe export scope
Cost Management ReaderThe export scope
Storage Blob Data ReaderThe 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.

  1. In the Azure Portal, go to Cost ManagementExports.
  2. Click Create, then set the metric to Cost and usage details (FOCUS).
  3. Set the file format to Parquet.
  4. Choose the storage account and container to deliver to, and note the export name.
  5. 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 tsv

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

RoleScopeWhy
Cost Management ReaderThe export scopeReads the export definition
Storage Blob Data ReaderThe destination storage accountReads 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

  1. In CloudQuery Platform, go to Data SourcesIntegrations, click Add new integration and choose Azure Cost.
  2. Choose Reuse an existing connection, then select the connection under Azure connection. CloudQuery preselects it when there is only one.
  3. Enter the Scope the export was created at, and the Export name on its own without a scope prefix.
  4. 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.
  5. 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

TableDescription
azurecost_focus_costsFOCUS 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 5

Troubleshooting

IssueCauseFix
The export exists but no files are deliveredMicrosoft.CostManagementExports is not registered on the storage subscriptionRun 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 failsMissing Storage Blob Data ReaderAssign 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 scopeThe scope does not match where the export was createdAn export is only visible at its own scope. Enter the scope exactly as you created it.
CloudQuery reports an unsupported metric or formatThe export is ActualCost, AmortizedCost, PriceSheet, or CSVRecreate 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 offeredNo 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 dataThe export has not run yetRe-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_ACCOUNTThe variable is not setSet it to the storage account the export delivers to, then re-run the script.
The setup script cannot find the storage accountThe storage account is in another subscriptionSet CQ_STORAGE_ACCOUNT_ID to its full resource ID, then re-run the script.
Costs appear twiceTwo overlapping exports are syncedGroup or filter by export_name, or stop syncing one of them.

Next steps

Was this page helpful?

Last updated on