If you've ever tried to merge an AWS Cost and Usage Report with an Azure Cost Management export and a GCP BigQuery billing dataset, you already know the pain. Three vendors, three column conventions, three commitment models, three different ways of reporting tax and credits. Honestly, I've watched senior data engineers stare at three side-by-side schemas and just sigh.
The FOCUS specification — the FinOps Open Cost and Usage Specification — was created by the FinOps Foundation to fix exactly this. With version 1.2 finalized in early 2026 and now natively supported by all three hyperscalers, FOCUS is the first time multi-cloud FinOps actually works without a translation layer wedged in the middle.
So, this guide walks through what FOCUS 1.2 changes, how to enable native FOCUS exports on AWS, Azure, and GCP, and how to query the unified schema to answer the questions your CFO actually asks (instead of the ones that are easy to answer).
What FOCUS Solves (and Why You Should Care in 2026)
Before FOCUS, every FinOps platform — Vantage, CloudHealth, Apptio Cloudability, Spot.io — had to maintain a custom ETL layer for each cloud provider. When AWS introduced split cost allocation data, or Azure renamed MeterSubCategory, or GCP changed how reservation discounts were attributed, the platform broke and your dashboards lied for a week. Sometimes longer.
FOCUS standardizes:
- Column names and data types —
BilledCostandEffectiveCostmean the same thing across clouds. - Charge categories — every line item is classified as
Usage,Purchase,Tax,Credit, orAdjustment. - Commitment discount handling — Reserved Instances, Savings Plans, and Committed Use Discounts all populate the same
CommitmentDiscountId,CommitmentDiscountType, andCommitmentDiscountStatuscolumns. - Time grain —
BillingPeriodStart,ChargePeriodStart, and theirEndcounterparts replace a confusing mix of usage dates that nobody could ever quite agree on.
What's New in FOCUS 1.2
FOCUS 1.0 shipped in mid-2024 with the basics. The 1.1 release added SKU pricing columns. FOCUS 1.2 — the version you should be targeting in 2026 — added the columns that production FinOps teams kept asking for (loudly, in every working group meeting):
CapacityReservationIdandCapacityReservationStatus— finally lets you separate capacity reservations from billing discounts.ServiceSubcategory— a second-level categorization (e.g.Compute > Containers,Storage > Object Storage) that makes cross-cloud rollups trivial.- Mandatory
PricingCurrencyContractedUnitPricefor any commitment, so you can compute realized savings without joining external rate cards. - Tightened nullability rules:
BilledCost,EffectiveCost,ListCost, andContractedCostare now required on every row. No more null-coalescing gymnastics.
Enabling FOCUS 1.2 Exports on AWS
AWS exposes FOCUS through the Data Exports service (the successor to legacy CUR). You can either use the console or Terraform. Here's the Terraform path, which is what you want for production — clicking through the console is fine for a one-off proof of concept, but it's a nightmare to audit later.
resource "aws_cur_report_definition" "focus_export" {
report_name = "focus-1-2-daily"
time_unit = "DAILY"
format = "Parquet"
compression = "PARQUET"
s3_bucket = aws_s3_bucket.billing.id
s3_prefix = "focus/"
s3_region = "us-east-1"
refresh_closed_reports = true
report_versioning = "OVERWRITE_REPORT"
additional_artifacts = ["ATHENA"]
additional_schema_elements = ["RESOURCES", "SPLIT_COST_ALLOCATION_DATA"]
}
# FOCUS 1.2 export via the new Data Exports API
resource "awscc_bcmdataexports_export" "focus" {
export = {
name = "focus-1-2-daily"
data_query = {
query_statement = "SELECT * FROM FOCUS_1_0_AWS"
table_configurations = {
FOCUS_1_0_AWS = {
INCLUDE_MANUAL_DISCOUNT_COMPATIBILITY = "FALSE"
TIME_GRANULARITY = "DAILY"
}
}
}
destination_configurations = {
s3_destination = {
s3_bucket = aws_s3_bucket.billing.id
s3_prefix = "focus-1-2/"
s3_region = "us-east-1"
s3_output_configurations = {
overwrite = "OVERWRITE_REPORT"
format = "PARQUET"
compression = "PARQUET"
output_type = "CUSTOM"
}
}
}
refresh_cadence = { frequency = "SYNCHRONOUS" }
}
}
Heads up: the query name FOCUS_1_0_AWS is misleading — it actually emits the latest supported FOCUS schema (1.2 in 2026). AWS keeps that table identifier stable so existing pipelines don't break when the spec ticks forward. I tripped over this the first time and spent an embarrassing 20 minutes convinced I was getting old data.
Once data lands in S3, register the table in Glue and you can query it from Athena directly:
SELECT
BillingPeriodStart,
ServiceCategory,
SUM(EffectiveCost) AS effective_cost_usd
FROM focus_1_2_daily
WHERE BillingPeriodStart >= date '2026-04-01'
AND ChargeCategory = 'Usage'
GROUP BY 1, 2
ORDER BY 1, 3 DESC;
Enabling FOCUS 1.2 Exports on Azure
Azure delivers FOCUS through Cost Management Exports. As of 2026, FOCUS is the default recommended export type for any new subscription or billing account. The schema is identical to AWS at the column level — the differences are mostly in how you scope the export.
az costmanagement export create \
--name focus-1-2-daily \
--type FocusCost \
--scope "providers/Microsoft.Billing/billingAccounts/<billing-account-id>" \
--storage-account-id "/subscriptions/<sub>/resourceGroups/finops/providers/Microsoft.Storage/storageAccounts/finopsexports" \
--storage-container "focus" \
--storage-directory "daily" \
--recurrence Daily \
--recurrence-period from=2026-05-01T00:00:00Z to=2030-12-31T00:00:00Z \
--schedule-status Active \
--timeframe MonthToDate \
--format Parquet \
--data-version "1.2"
A few gotchas specific to Azure:
- Exports must be created at the billing account scope if you want commitment data (Reservations, Savings Plans). Subscription-scoped exports quietly omit purchase line items, which can make your numbers look great until you reconcile against the invoice.
- Azure populates
SubAccountIdwith the subscription GUID andSubAccountNamewith the subscription display name — useful for filtering when you join across clouds. - The
x_prefix appears on Azure-specific extension columns. Anything starting withx_is not portable; treat it as a debug aid, not a join key.
Enabling FOCUS 1.2 Exports on GCP
GCP delivers FOCUS via BigQuery Billing Export. You enable a second export alongside the standard detailed billing export — they don't replace each other, which is a little annoying but at least it means you can run them side-by-side during migration.
- In the Cloud Console, go to Billing > Billing export.
- Under FOCUS export, click Edit settings, choose your project and dataset, and enable.
- GCP creates a table named
focus_v1_<billing_account_id_with_underscores>within 24 hours.
Or in Terraform:
resource "google_billing_account_iam_member" "billing_export" {
billing_account_id = var.billing_account_id
role = "roles/billing.viewer"
member = "serviceAccount:billing-export@${var.project_id}.iam.gserviceaccount.com"
}
resource "google_bigquery_dataset" "focus" {
dataset_id = "focus_billing"
location = "US"
}
# Note: as of 2026 the FOCUS export itself is enabled via the
# google_billing_budget API or the console — there is no first-class
# Terraform resource yet. Use a null_resource with gcloud:
resource "null_resource" "enable_focus_export" {
provisioner "local-exec" {
command = <<EOT
gcloud billing export focus update \
--billing-account=${var.billing_account_id} \
--dataset=${google_bigquery_dataset.focus.dataset_id} \
--project=${var.project_id}
EOT
}
}
Querying the Unified FOCUS Schema
This is where FOCUS earns its keep. Once all three clouds are exporting to the same schema, you can write one SQL query that answers a multi-cloud question. Here are three patterns that come up constantly in real engagements.
1. Effective spend by provider and service category
SELECT
ProviderName,
ServiceCategory,
DATE_TRUNC('month', BillingPeriodStart) AS month,
SUM(EffectiveCost) AS effective_cost_usd,
SUM(BilledCost) AS billed_cost_usd,
SUM(ListCost) - SUM(EffectiveCost) AS savings_usd
FROM focus_unified
WHERE BillingPeriodStart >= DATE '2026-01-01'
AND ChargeCategory IN ('Usage', 'Purchase')
GROUP BY 1, 2, 3
ORDER BY month, effective_cost_usd DESC;
Because EffectiveCost, BilledCost, and ListCost have identical semantics in FOCUS, the savings calculation is now a one-liner. Pre-FOCUS, this query needed a different formula per cloud — and the AWS variant alone was a small essay.
2. Commitment discount utilization
SELECT
ProviderName,
CommitmentDiscountType, -- 'Reserved', 'SavingsPlan', 'Committed Use'
CommitmentDiscountStatus, -- 'Used' or 'Unused'
SUM(EffectiveCost) AS cost_usd,
SUM(CASE WHEN CommitmentDiscountStatus = 'Unused'
THEN EffectiveCost ELSE 0 END) AS waste_usd
FROM focus_unified
WHERE CommitmentDiscountId IS NOT NULL
AND BillingPeriodStart >= DATE '2026-04-01'
GROUP BY 1, 2, 3
ORDER BY waste_usd DESC;
Unused commitment cost is, in my experience, the single highest-leverage waste signal in any FinOps program. With FOCUS, you can finally compute it the same way across AWS Savings Plans, Azure Reservations, and GCP CUDs without three different code paths. One query, one number, one uncomfortable conversation with engineering leadership.
3. Tag-driven cost allocation
FOCUS exposes tags through the Tags column as a JSON-encoded map. The same query works on all three clouds:
SELECT
JSON_EXTRACT_SCALAR(Tags, '$.team') AS team,
JSON_EXTRACT_SCALAR(Tags, '$.env') AS environment,
SUM(EffectiveCost) AS cost_usd
FROM focus_unified
WHERE BillingPeriodStart >= DATE '2026-04-01'
AND ChargeCategory = 'Usage'
GROUP BY 1, 2
HAVING cost_usd > 100
ORDER BY cost_usd DESC;
Building a Cross-Cloud Lakehouse on FOCUS
The reference architecture most teams converge on in 2026 looks like this:
- Each cloud writes its FOCUS export to its native object store (S3, Azure Blob, GCS).
- A scheduled job — typically Airflow, dbt Cloud, or a simple Lambda — copies all three exports into a single Iceberg or Delta table partitioned by
BillingPeriodStartandProviderName. - BI tools (Looker, Metabase, Superset) point at that one unified table. No more per-provider dashboards, no more reconciling totals across three different tabs.
The dbt model below is what the merge step usually looks like. It's essentially a three-way UNION ALL with no transformations, because FOCUS already did the hard work for you:
{{ config(
materialized = 'incremental',
unique_key = ['ProviderName', 'ChargePeriodStart', 'ResourceId', 'SkuId'],
partition_by = {'field': 'BillingPeriodStart', 'data_type': 'date'},
cluster_by = ['ProviderName', 'ServiceCategory']
) }}
SELECT * FROM {{ source('aws_focus', 'focus_1_2_daily') }}
{% if is_incremental() %}
WHERE BillingPeriodStart >= (SELECT MAX(BillingPeriodStart) FROM {{ this }})
{% endif %}
UNION ALL
SELECT * FROM {{ source('azure_focus', 'focus_1_2_daily') }}
{% if is_incremental() %}
WHERE BillingPeriodStart >= (SELECT MAX(BillingPeriodStart) FROM {{ this }})
{% endif %}
UNION ALL
SELECT * FROM {{ source('gcp_focus', 'focus_v1_billing') }}
{% if is_incremental() %}
WHERE BillingPeriodStart >= (SELECT MAX(BillingPeriodStart) FROM {{ this }})
{% endif %}
Common Pitfalls When Migrating to FOCUS
- Don't delete your legacy exports yet. Run FOCUS in parallel with CUR / Azure usage details / GCP detailed billing for at least one full billing cycle. Reconcile
SUM(BilledCost)against your invoice before cutting over. I've seen teams skip this and lose a month tracking down a 0.4% discrepancy. - Watch for currency mismatches. FOCUS uses
BillingCurrencyper row. If you have multi-currency billing accounts, neverSUM(BilledCost)without grouping by currency or converting first. - Tags are case-sensitive. AWS lower-cases tag keys on export, Azure preserves case, GCP lower-cases. Normalize in your transform layer or you'll end up with three rows for "Team", "team", and "TEAM".
- Resource IDs differ in shape. FOCUS standardizes the column name (
ResourceId) but not the value format. Don't try to join across clouds onResourceId— use tags instead. - FOCUS 1.2 split cost allocation. AWS includes EKS split cost allocation rows in FOCUS exports if you opted in. They appear with
ChargeCategory = 'Usage'andChargeClass = 'Correction'. Filter or include them deliberately — accidentally double-counting them is a classic week-one mistake.
FAQ
What's the difference between FOCUS and AWS CUR 2.0?
CUR 2.0 is AWS's native, AWS-specific billing export with their original column names (lineItem/UnblendedCost, product/ProductName, etc.). FOCUS is the cross-cloud standard. AWS now offers a FOCUS-formatted export option through the same Data Exports service that produces CUR 2.0 — under the hood it's the same data, transformed to the FOCUS column names. Use FOCUS if you also operate on Azure or GCP; use CUR 2.0 if you're AWS-only and need AWS-specific columns like lineItem/LegalEntity.
Is FOCUS free to use?
Yes. The specification itself is open and maintained by the FinOps Foundation under a permissive license. The exports are free from all three clouds — you only pay for the underlying object storage (S3, Blob, GCS) and any query engine costs (Athena, Synapse, BigQuery) when you analyze the data.
Does FOCUS support Kubernetes cost allocation?
Partially. FOCUS 1.2 captures the underlying compute, storage, and network costs of your nodes, but it doesn't break those down by namespace or pod on its own. You still need a layer like OpenCost, Kubecost, or AWS split cost allocation to attribute costs to Kubernetes workloads — but those tools can now emit their output in FOCUS-compatible format, which makes joining cluster-level allocation back to FOCUS a lot less painful than it used to be.
How often is the FOCUS specification updated?
The FinOps Foundation targets a major release every 9–12 months. FOCUS 1.0 shipped in June 2024, 1.1 in early 2025, and 1.2 in early 2026. Minor releases are backward-compatible — new columns are added, existing columns are not renamed or removed within a major version line.
Can I use FOCUS with FinOps platforms like Vantage or CloudHealth?
Yes — and honestly, this is one of the strongest reasons to adopt it. Vantage, CloudHealth, Cloudability, and Spot.io all natively ingest FOCUS exports as of 2026. Pointing them at your FOCUS data lake instead of giving them direct billing-account access reduces the blast radius of credentials and gives you a single source of truth that survives if you ever decide to switch platforms.