FOCUS Specification 1.2: A Practical Guide to Unified Cloud Cost Data Across AWS, Azure, and GCP in 2026

FOCUS 1.2 is reshaping how FinOps teams normalize cloud billing data. This hands-on guide shows how to enable FOCUS exports on AWS Data Exports, Azure Cost Management, and GCP Billing Export, then query the unified schema with SQL to build true multi-cloud cost dashboards.

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 typesBilledCost and EffectiveCost mean the same thing across clouds.
  • Charge categories — every line item is classified as Usage, Purchase, Tax, Credit, or Adjustment.
  • Commitment discount handling — Reserved Instances, Savings Plans, and Committed Use Discounts all populate the same CommitmentDiscountId, CommitmentDiscountType, and CommitmentDiscountStatus columns.
  • Time grainBillingPeriodStart, ChargePeriodStart, and their End counterparts 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):

  • CapacityReservationId and CapacityReservationStatus — 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 PricingCurrencyContractedUnitPrice for any commitment, so you can compute realized savings without joining external rate cards.
  • Tightened nullability rules: BilledCost, EffectiveCost, ListCost, and ContractedCost are 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 SubAccountId with the subscription GUID and SubAccountName with the subscription display name — useful for filtering when you join across clouds.
  • The x_ prefix appears on Azure-specific extension columns. Anything starting with x_ 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.

  1. In the Cloud Console, go to Billing > Billing export.
  2. Under FOCUS export, click Edit settings, choose your project and dataset, and enable.
  3. 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:

  1. Each cloud writes its FOCUS export to its native object store (S3, Azure Blob, GCS).
  2. A scheduled job — typically Airflow, dbt Cloud, or a simple Lambda — copies all three exports into a single Iceberg or Delta table partitioned by BillingPeriodStart and ProviderName.
  3. 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 BillingCurrency per row. If you have multi-currency billing accounts, never SUM(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 on ResourceId — 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' and ChargeClass = '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.

About the Author Editorial Team

Our team of expert writers and editors.