FinOps

From 45.3% to 76.6%: One Fix Closed a Real AWS Attribution Gap

All Posts FinOps DevOps Cybersecurity Product Updates
Share

The number nobody publishes

Every cost tool that reads AWS Cost Explorer has a coverage gap: some fraction of the bill it can tie to a specific resource, and a remainder it cannot. This is not a defect in any particular product. It is a property of the API.

What is strange is that almost nobody measures it, and nobody publishes it. Screens are full of per-resource cost tables that look complete because there is no visible marker for the rows that are missing. Absence does not render.

So here is one — a real CLARITY customer's account, measured three times over six weeks against the same production AWS account each time. The trend across those three measurements is the actual point of this post.

On 2026-08-31 we measured 45.3% of a real customer's AWS bill attributed to individual resources using real billing data. On 2026-09-18, after a fix that shipped on 2026-09-06, the same measurement read 68.8%. Today, 2026-10-14, it reads 76.6%. Nothing new shipped between any of those three dates. One fix, measured three times, each measurement closer to capturing the fix's full effect than the last. This post is about what that fix was, why it was almost free, and what's honestly still missing on the other side of it.

Our number, measured three times

The first measurement's window was 30 settled days, 2026-07-31 through 2026-08-29. Settled matters: AWS finalises a UTC day 24–48 hours after it ends, so measuring through "today" would count a partial day as a real one and understate everything. The most recent day in that data at time of writing carried a small fraction of the account's typical daily spend — that is not a quiet Sunday, that is an unsettled day, and including it would have quietly moved every percentage below.

Measure — measured 2026-08-31AmountShare of bill
Total AWS spend (30 settled days)$10,000100%
Attributed to a resource by real billing data$4,53045.3%
Attributed by CLARITY's pricing models$9509.5%
Attributed by proportional split$1,50015.0%
Not attributed to any resource$3,02030.2%

We sat on that number for a few weeks rather than publish it, because a fix was already in flight for the single largest thing it was missing. On 2026-09-18 — twelve days after that fix had been running quietly in production — we re-ran the same measurement over the trailing 30 days:

Measure — measured 2026-09-18AmountShare of bill
Total AWS spend (trailing 30 days)$10,000100%
Attributed to a resource by real billing data$6,88068.8%
Everything else (modelled, proportionally split, or unattributed)$3,12031.2%

We are not re-publishing the internal three-way split of that remaining 31.2% in this post — the point here is what moved and why, not a fresh version of the August table. The same discipline from that table still applies to whatever is in it: modelled money, proportionally split money, and truly unattributed money are three different things, tracked in separate columns internally, and never blended into one number. That has not changed. What changed is how much no longer needs to be in any of those three buckets at all.

Both measurements exclude tax, support, credits and refunds at ingestion — those record types are filtered out before cost data enters our pipeline, because they are not resource costs and blending them into allocation produces nonsense. So both totals above are resource-shaped spend, and both percentages are honest against that base.

We left the post at 68.8% for a few weeks, because we did not want to publish a number we hadn't checked for durability. Today, 2026-10-14 — roughly four weeks further on — we ran the identical trailing-30-day query a third time:

Measure — measured 2026-10-14AmountShare of bill
Total AWS spend (trailing 30 days)$10,000100%
Attributed to a resource by real billing data$7,66076.6%
Everything else (modelled, proportionally split, or unattributed)$2,34023.4%

The dollar figures in all three tables above are illustrative, not this customer's real invoice — see the note below the fold for why, and what's real instead. The percentage is the number that matters here, and it is real, measured against the same production account and the same query as the other two.

And this third measurement is not a new improvement. It is the same September fix, continuing to compound. A trailing-30-day window measured on 2026-09-18 only had twelve of its thirty days sitting after the fix's 2026-09-06 ship date — the other eighteen days were still pre-fix, dragging the average down. By 2026-10-14, the entire trailing window is past the ship date. Nothing else attribution-related shipped between 2026-09-18 and today. The climb from 68.8% to 76.6% is just the same fix finishing the job of fully absorbing into the window it's measured against — not a second mechanism, not a coincidence, and not something we should get to claim credit for twice.

A note on the dollar figures above: the real customer's bill is not ours to publish, so every dollar amount in this post — in all three tables, and in every section below — is an illustrative stand-in scaled to a hypothetical mid-sized SaaS company we'll call Nexus Digital, running roughly $10,000/month of AWS spend with an Aurora-backed production database, the same situation the real account was in. The percentages throughout this post are real, measured figures from that real account. The dollars are Nexus Digital's, chosen to match the real percentages, not a re-measurement of anyone's real invoice.

What actually closed the gap

The gap did not close because we adopted a new data source. It closed because we found one field we were not reading in a response we were already fetching.

This customer's largest managed-database service is Amazon Aurora, billed through RDS. Cost Explorer's resource-level view bills an Aurora cluster under its DbClusterResourceId — an ARN built on an opaque identifier like cluster-7f3k9pz2mxq1b0wrzbhy4c (illustrative, not the real customer's actual id). The AWS API call we use to discover clusters, DescribeDBClusters, instead hands back a DBClusterArn built on the human-readable DBClusterIdentifier — something like nexus-prod-cluster. Same cluster, two spellings of its ARN. Our resource normalizer resolves a billed ARN to a discovered resource by exact match only, deliberately — substring or first-match resolution is how two different resources silently share one cost figure — so a billed cluster-7f3k9… ARN and a discovered nexus-prod-cluster ARN never matched. Not one dollar, ever.

Before the fix, the measured gap on Aurora cluster ARNs was 83.4% of RDS spend unattributed on one AWS account and 85.2% on the other. Zero dollars had ever been attributed to an RDS cluster by billing data, on either account, at any point we could measure.

The fix is three commits, shipped 2026-09-06: capture DbClusterResourceId — a field already present in the DescribeDBClusters response we were already calling, at no extra permission and no extra API cost — and feed it through the same exact-match normalizer as a second, billed-side alias for the cluster. No new data source. No new IAM grant. No new scheduled job.

Verified read-only against the live database on 2026-09-18, with no paid sync required to check: all five discovered Aurora clusters across both accounts now carry real, Cost-Explorer-billed cost rows, accumulating without gaps since 2026-08-25.

ClusterDays of billed dataTotal attributed
nexus-prod-cluster25$1,370
nexus-staging-cluster22$322
nexus-qa-cluster25$11
nexus-lab-cluster25$8
nexus-legacy-cluster25$9

$1,720 newly attributed (Nexus Digital scale) — above our original estimate of the gap, which had put it at roughly $1,490 over a 14-day window. The production cluster carries nearly all of it; the staging, QA, and lab clusters are small and mostly idle, which is itself a fact we could not previously see.

Why 100% was never the target

Even with the Aurora fix, full attribution was never the goal, and chasing it would be a category error. The August measurement illustrates why.

In that window, 6.2% of the bill — $620 on Nexus Digital's illustrative scale — was Savings Plans charges. A Savings Plan is not a resource. It is a financial commitment: you promise a dollar-per-hour spend level for one or three years, and AWS discounts usage that falls under it. There is no server you can point at. There is no instance you can terminate to make the charge go away.

You can allocate that $558 — spread it across the workloads that consumed the commitment — and that is a legitimate and useful thing to do for chargeback. But allocating is not attributing. Attribution says "this resource caused this charge." Allocation says "we have chosen a rule for dividing this charge up." Conflating them is how a chargeback report ends up with a precision it has not earned.

Subtract the commitment from the August bill and the ceiling for that window was:

Total bill (Aug window)       $10,000.00  100.0%
Savings Plans (not a resource)   $620.00     6.2%
                               ----------  ------
Attributable in principle      $9,380.00    93.8%

93.8% was the number to beat that month. Not 100%. Any tool claiming complete per-resource attribution of an AWS bill containing commitments is either allocating and calling it attributing, or has not looked closely.

What's left, and why it isn't a CUR problem

There is a well-known answer to an attribution gap like this: ingest the Cost and Usage Report (CUR), AWS's full line-item billing export. It has no 14-day resource-level cap, and it includes charge detail Cost Explorer's live API will not key to a resource at all. We looked at this directly rather than assume it, by measuring what kind of money was actually unattributed, the same week the Aurora fix shipped.

Every unattributed dollar splits into exactly two causes, and only one of them is even a candidate for a different data source:

AccountUndiscovered resource (AWS gave us the ID)No resource identity at all (NoResourceId)
Account A$900 · 64.3% · ~600 distinct resource ids$500 · 35.7% · 2 rows, 1 key
Account B$850 · 99.2% · ~54,000 distinct resource ids$7 · 0.8% · 14 rows, 1 key

The left column is not a CUR problem at all: AWS already told us the resource's identity. We simply had not matched it to something in our own resource hierarchy yet. A different billing source returns the identical id and changes nothing — the fix has to happen on our side, in discovery, exactly like it did for Aurora. In fact Aurora was the single largest entry in this column on both accounts before the fix above landed, which is why this measurement doubles as evidence for the decision rather than a separate argument for it.

The right column — the literal string NoResourceId, where AWS returns no identity to match against — is the only honest CUR candidate, and it is small: well under $500 combined, concentrated almost entirely on one account. On that account it is overwhelmingly one charge category: Savings Plan recurring fees, 14 rows, roughly $260. A Savings Plan has no resource by construction, the same point made above about the 93.8% ceiling — and CUR reports those same fees with an empty resource id too. Switching data sources would not give this money a resource to attach to; it would just report the same absence on a different schedule.

What is real about that bucket is a cost-basis question, not a data-source one: under amortized cost (spreading a commitment's cost evenly over its term, rather than booking it the day it is billed) most of that money is already re-expressed against resources we do hold. We measured the two bases directly: unblended and amortized cost diverge by 5.2% on the account with the larger commitment exposure, and by 0.0% on the other. That is a real, separate question about which cost basis to present — and an honest answer requires naming it as basis, not coverage.

Net: at most 22.3% of what was unattributed in that measurement could even theoretically be touched by CUR, it is 98.6% concentrated in a single account, and there is no evidence it would recover any of it — the one category CUR could reach is a fee with no resource to attach to, regardless of where the data comes from. That measurement predates the Aurora fix fully flowing through a sync, and Aurora was the biggest thing sitting in the undiscovered-resource column on both accounts, so the true picture today is smaller than the table above. We have not re-run this specific breakdown since; we are reporting the honest, dated measurement rather than a guess at what it looks like now.

The export, and the trade we still haven't made

So the decision stands, and it was re-checked rather than merely restated. Adopting CUR would cost us freshness. The export is a file-delivery pipeline: AWS assembles it and drops it in your bucket on its own schedule, and consuming it means waiting for the current period's file, ingesting potentially millions of rows, and reconciling them against what you already have. The industry-standard "your data will be ready in 24–48 hours" onboarding message comes from exactly this.

Our product is built the other way round: live billing APIs, queried continuously, so a number appears minutes after you connect an account rather than a day or two later. Those two architectures pull in genuinely opposite directions. The export gives you completeness at the cost of latency; the live API gives you latency at the cost of completeness. We picked latency — and the Aurora fix is proof that a chunk of the completeness we were missing was recoverable without giving it up.

What we do not do today

To be unambiguous, because this is the kind of paragraph that gets skipped and then quoted back at us:

What we do instead is narrower and worth naming: we model the charges the API will not attribute, we label modelled money as modelled everywhere it appears, and when we find a dollar that should have matched a resource and did not, we fix the matching — which is what closed 31.3 points of this gap, in two measured steps, without a new pipeline.

That is a smaller claim than "complete attribution." It is one we can defend line by line, and one that just got measurably better in six weeks.

Measure Your Own Coverage

Connect an AWS account with read-only credentials and CLARITY shows per-resource costs with their provenance attached — billed money and modelled money kept apart, not blended into one confident-looking number.

Start Free Trial

Three questions for whatever you use

You do not need to switch tools to get value out of this. You need to know your own coverage number, and you need to check it more than once. Ask your current platform — or your own pipeline — these three:

  1. What percentage of my bill is attributed to a specific resource by provider billing data, as opposed to by your model? If the answer is a single blended figure, ask for the split. If there is no split, the tool is not tracking provenance, which means neither are you.
  2. What is my largest service, and what share of it is attributed? Run this on your biggest line, not your total. We did, on this account, in August: RDS was 38.9% of the bill and attributed at 13.3% — far worse than the 45.3% overall figure suggested. That gap turned out to be a single unread field. Aggregate coverage would never have told us where to look; the service-level number did.
  3. Where does my commitment spend appear, and what fraction of "unattributed" is actually "undiscovered"? Savings Plans and Reserved Instances have to land somewhere, and some of what looks unattributable is really just unmatched — the provider already told your tool the resource id. Ask which one you're looking at before you conclude a different data source would fix it.

If you cannot get answers, that is an answer. And if you can, publish the number internally, and re-check it — a coverage figure that finance and engineering both know is worth more than a per-resource table that quietly omits a third of the bill, and a number you only measured once is a number you don't actually know is still true.

The Bottom Line

45.3% attributed in August. 68.8% in September. 76.6% today, in October. Three measurements, six weeks, one additional field — already present in a response we were already fetching, wired through code that already existed to prevent exactly the wrong kind of match. No new data source, no new pipeline, no CUR. The climb from the second measurement to the third isn't a second fix; it's the first one's effect finishing the job of soaking into the window we measure against.

The honest remainder is smaller than it was, and we can say precisely what kind of money is in it: mostly resources AWS already identified that we have not yet matched — the same category Aurora was in — and a sliver of financial-commitment fees that have no resource to attach to under any data source. Neither of those is solved by switching to CUR. The first is solved by exactly the kind of work that just moved 31.3 points over two measured steps; the second is a cost-basis question we've already measured separately.

The failure mode this post argues against is not incomplete attribution. It is invisible incomplete attribution: a screen full of per-resource costs that gives no indication which rows are billing data, which are estimates, and how much of the invoice never made it onto the screen at all — and a vendor who measures that gap once, publishes a comfortable number, and never checks it again. We checked twice more. It moved both times. We're telling you by how much.

Measure your coverage. Then measure it again next quarter.

For why the export-based architecture makes you wait in the first place, see Why Your FinOps Tool Makes You Wait 48 Hours. For what happens to costs that no team claims, read Orphan Spend: The Hidden 79% of Your Cloud Bill Nobody Owns. And for how accuracy problems compound across providers, see Multi-Cloud Cost Management: Why Cost Accuracy Matters.

Stop guessing. Start validating.

CLARITY keeps billed money and modelled money in separate columns, so you always know which half of a number you can take to finance. Free for 5 days, no credit card.

Start Free Trial Or talk to us about a free cloud cost audit

Did you find this article useful?