Back to list
    When Our Cost Dashboard Showed 2x What the Console Did — Tracing It to AWS Reseller Account Mechanics
    Dev Labo
    PRThis article contains advertisements

    When Our Cost Dashboard Showed 2x What the Console Did — Tracing It to AWS Reseller Account Mechanics

    (Updated: 5/8/2026)
    45 min read

    Introduction

    In an earlier post, I wrote about AWS cost forecasting using ce:GetCostForecast.

    We've been using that mechanism in-house — a Jenkins morning-check job aggregates monthly costs across several AWS accounts and posts a daily report to Teams. "Month-to-date actual," "month-end forecast," and "month-over-month" are all visible at a glance. A fairly ordinary setup.

    But the other day, when we added a new AWS account to the monitoring set, we hit an unexpected wall:

    "The values in our report don't match the AWS Console — they're off by about 2x."

    This article is the investigation log that started from that friction and eventually landed on a structural fact: "this account is a member under an AWS reseller."

    If you only want the conclusion, the path may feel a bit roundabout. But for problems like this, the order in which you eliminate your initial hypotheses is itself the reusable lesson, so I've left as much of the verification process visible as I could.


    Spotting the discrepancy

    The report immediately after we added the new AWS account (call it "Account A") looked like this:

    • Our report's "month-to-date actual": $404.60
    • AWS Console's "this month" display: $807.20

    Almost exactly 2x. A ratio that clean — clean enough that "is it a unit error?" was the first thought — actually made the friction stronger.

    The first hypothesis was simple:

    "Maybe we're missing costs from multiple regions."

    The target account does use multiple regions. But re-reading the code immediately ruled this out:

    response = ce_client.get_cost_and_usage(
        TimePeriod=time_period,
        Granularity="DAILY",
        Metrics=["UnblendedCost"],
    )

    Cost Explorer's GetCostAndUsage is fundamentally an account-level aggregation API, and unless you set a Filter, it sums across all regions. The boto3 session's region only selects the API endpoint — it has no effect on the values you get back.

    I paused here and wrote out the hypothesis list:

    The two hypotheses that could naturally explain "exactly 2x" came down to H2 (Savings Plans) and H3 (Linked Account roll-up).


    Verifying hypotheses — suspecting Savings Plans

    The first thing I suspected was how Savings Plans (SP) and Reserved Instances (RI) show up.

    Our report uses UnblendedCost, while the Console can return values that include AmortizedCost depending on the display mode. If you've bought SP/RI, this difference can become large.

    So I queried Cost Explorer directly with the same conditions:

    # Same as our report
    aws ce get-cost-and-usage \
      --time-period Start=2026-05-01,End=2026-05-08 \
      --granularity DAILY \
      --metrics UnblendedCost
    
    # AmortizedCost over the same window
    aws ce get-cost-and-usage \
      --time-period Start=2026-05-01,End=2026-05-08 \
      --granularity DAILY \
      --metrics AmortizedCost

    The result was unexpected: UnblendedCost and AmortizedCost matched perfectly.

    That kills the SP/RI hypothesis immediately. If the two metrics agree, there's no SP-prepay-amortization expression difference, so SP/RI cannot be the cause.

    Just to be safe, I broke it down by RECORD_TYPE:

    RECORD_TYPEUnblendedCostAmortizedCost
    Usage$243.48$243.48
    SavingsPlanCoveredUsage$113.60$95.88
    SavingsPlanNegation-$113.60$0.00
    SavingsPlanRecurringFee$112.73$16.85
    Tax$64.50$64.50
    Total$420.70$420.70

    SavingsPlanCoveredUsage (+113.60) and SavingsPlanNegation (-113.60) cancel out in UnblendedCost, and the amortized side sums to the same total post-amortization. Of course the totals match. With or without Savings Plans, the value visible to the CE API is $420.70 — it doesn't move.

    There was an important observation here too. The daily total ($420.70) lines up almost exactly with our report's $404.60 (the difference is just one day of aggregation offset). Our report value matches the raw CE data. In other words, our calculation isn't broken.


    Suspecting Linked Account roll-up

    Next I suspected Linked Accounts. With AWS Organizations, the "Billing Home" top page can sometimes show org-payer-side values where the target account ends up summed with other linked accounts.

    I queried CE again, this time grouped by LINKED_ACCOUNT:

    aws ce get-cost-and-usage \
      --time-period Start=2026-05-01,End=2026-05-08 \
      --granularity MONTHLY \
      --metrics UnblendedCost \
      --group-by Type=DIMENSION,Key=LINKED_ACCOUNT

    The result: Account A alone, $420.70, with no other linked accounts mixed in. There is no other account that could combine to produce the Console's $807.20.

    I had to pause here.

    "There's no mathematical interpretation that fits."

    Honestly, that's a moment that gets to you. After eliminating a few hypotheses, you start to feel your "this is probably it" inventory running out.

    Organizing what we had: while the Console showed "$807.20 this month," the API-side numbers were:

    ComparisonAPI valueConsole displayMatch
    Month-end forecast$1881.69 (April-baseline)$1881.69 (▼4%)
    Past months (Mar/Apr)$2014 / $1968bar chart ~$2000
    This month (5/1–5/7)$420.70$807.20❌ ~1.92x
    Same period in April (4/1–4/7)$578.90$578.90

    The interesting thing was: past months and the month-end forecast are perfectly consistent — only "month-to-date this month" is high on the Console side.

    If "the current-month display alone runs on a different logic," it can't be reproduced from the API. That's the moment when "the world the Console is looking at might not be visible from our side at all" first crossed my mind.


    The decisive move — looking at the Organization structure

    Just as I was about to hit a wall, I thought of aws organizations describe-organization. This API tells you "which Organization is this account under."

    aws organizations describe-organization

    The response changed the picture instantly:

    {
      "Organization": {
        "Id": "o-xxxxxxxxxx",
        "MasterAccountId": "<some other vendor's account ID>",
        "MasterAccountEmail": "<...@example-reseller.example>",
        ...
      }
    }

    This account isn't directly contracted with AWS by us. It's a "member account" issued through some AWS reseller's (resale partner's) member program.

    So the billing flow looks like this:

    The CE API on the member account side returns AWS's actual cost charge to this account — that's $420.70.

    Meanwhile, the AWS Console's "Cost and usage" widget is referencing a payer-side aggregate value, probably something close to the pre-discount list price, as the "this month" display — that's $807.20.

    Viewed as pre-discount-vs-post-discount, the 1.92x ratio is unsurprising as a reseller markup/discount structure.


    Confirming the hypothesis — corroborating with other accounts

    We had a strong "because it's a reseller member account" hypothesis, but with a single data point it's just "well, that can happen sometimes." We had several other production accounts internally, so I asked the team to check each:

    AccountConsole discrepancyMaster AccountSame Org?
    Account AYesReseller's payer (Account-X)Reseller
    Account BYesSame as aboveReseller
    Account CNoDifferent / direct-contract-equivDifferent

    "Discrepancy = under reseller payer" held cleanly. With that, the hypothesis was confirmed.

    [Update: 2026-05-08] This conclusion turned out to be only half right when later verified. Account C was actually under the same reseller, and many reseller-managed accounts showed no discrepancy at all, so the detection axis was redesigned around whether Savings Plans are applied. See the follow-up article for the full story: When "It's the Reseller Account" Was Only Half Right — Redesigning Cost-Discrepancy Detection Around Savings Plans.

    At this point, the suspicion on our code and configuration was finally cleared. Our monitoring code uses UnblendedCost to fetch per-account actual cost, which is the legitimate "real cost as seen by AWS."


    "Is there a path to get the Console value?"

    A mix of relief and a touch of melancholy hit when I confirmed "we weren't broken." The cause wasn't in our code — but with the cause now sitting where it sat, we'd entered territory that technology alone couldn't move.

    From here on, half of the discussion becomes more about etiquette than technology. From the receiving side of the report, the natural question is:

    "OK, so is there a way to get the Console value programmatically?"

    To be honest: I couldn't find a reliable way to obtain the Console's current-month value from the member-account-side APIs. Below is the list of paths I considered and why each was a dead end.

    Path A: Try other Cost Explorer API metrics

    Beyond UnblendedCost, there are AmortizedCost / BlendedCost / NetUnblendedCost / NetAmortizedCost. They all returned the same value ($420.70).

    Aggregating by RECORD_TYPE / USAGE_TYPE / SERVICE doesn't reach the Console value either. Payer-side aggregation and list-price conversion don't surface in CE API, so this direction can't reproduce the Console number.

    Path B: AWS Invoicing API

    I remembered that aws invoicing list-invoice-summaries, added in 2024, can fetch finalized invoices, and tried it.

    aws invoicing list-invoice-summaries \
      --selector ResourceType=ACCOUNT_ID,Value=<account-id> \
      --filter '{"BillingPeriod":{"Year":"2026","Month":"5"}}'

    Conclusion: for the current month, this isn't retrievable by spec.

    • AWS invoices are issued after the billing period ends
    • Monthly invoices are finalized in the first 1–3 days of the next month
    • Querying for the current month returns nothing (an empty array)

    So what the Console shows for the current month is not an invoice — it's a Console-only real-time computed value.

    Going further: for member accounts under a reseller, "AWS invoices visible from the member account" likely don't exist at all. The recipient of AWS invoices is the payer, not the member.

    Path C: Cost and Usage Report (CUR)

    You can recompute on-demand-equivalent from pricing/publicOnDemandRate × lineItem/UsageAmount, which can get you close to the Console display. Walls here too:

    • CUR enablement is a payer-side operation
    • Member accounts can't enable it
    • You'd have to ask the reseller to set up the export, which doesn't pay back the automation cost of a Jenkins job

    Path D: Reseller-provided management API

    The last option: the reseller's own management console / API. There's a chance an "invoice-equivalent value" can be fetched here, but it's outside standard AWS APIs and uses a separate auth system. Automating this from a Jenkins job depends on the reseller's integration availability and spec.


    Re-aligning what we mean by "cost monitoring"

    Given the investigation, the conversation shifted to "OK, so what should we be monitoring?"

    The point: Console values and API values aren't a "which is correct" question — they mean different things.

    ValueMeaningUse case
    CE API UnblendedCostActual cost AWS charges against this accountMonitoring, forecasts, MoM compare
    Console "this month" displayPayer-side aggregated reference value (possibly list-price)Hard to define what it means
    Reseller's monthly invoiceWhat the reseller bills us (incl. markup / discounts)Accounting, payment

    If the monitoring job's purpose is "detect anomalous spikes/drops" and "produce next-month forecasts," CE API values do the job. The month-end forecast and the past-month bar chart did line up perfectly with the Console anyway.

    On the other hand, "track exactly what we pay the reseller" sits outside CE API's reach, and that requires a separate invoice-integration path.

    When that landed, I finally accepted: this isn't a code bug — it's a conceptual-organization problem.


    Where to leave the knowledge — appending to troubleshooting

    The first thing on my mind once the investigation finished was: "where should the next person (including future me) look to land here?"

    So I added a new "AWS cost-check related" section to the operations doc troubleshooting.md with:

    • Symptom: Discrepancy between dashboard's current-month value and Console's "Cost and usage" current-month value
    • Cause: A spec-level effect that surfaces when the target account is under a reseller
    • How to identify: Check MasterAccountId from aws organizations describe-organization
    • Confirmed-account table: Which of our currently-managed accounts fall into this case
    • What to do: No code or config change needed. The CE API value is the actual cost
    • Triage queries: Commands comparing UnblendedCost and AmortizedCost to check SP/RI effects

    I also added a sub-section "Is there a way to get the Console value via API?" listing all four paths above (CE API / Invoicing / CUR / reseller-specific API) along with "why each is unobtainable."

    The point was: when future-me hits the same question, I'd rather it be "30 minutes to read to the conclusion" than "2 hours to re-eliminate the same hypotheses."


    Reflection — order of hypothesis elimination, and answers outside the code

    Looking back, a few lessons stuck:

    The value of eliminating "easier-to-eliminate" hypotheses first

    I went after multi-region and SP/RI first because each could be verified just by changing CE API metrics. No code or config changes needed, so the cost of being wrong was tiny. Scanning lighter hypotheses before heavier ones (architecture / contract structure) feels like a detour but ends up being a shortcut.

    The "answer is outside the code" pattern

    Same code, same config, working fine on existing accounts — only the new account exhibits the symptom. In that case, before suspecting code, suspect "is the account itself of a different breed?" That instinct is what I built up this time. Going forward, when problems of this shape appear, aws organizations describe-organization is now in my first few moves.

    The value of confirming "we weren't broken"

    About half the investigation went into verifying "our code isn't broken." Less glamorous than finding and fixing a bug, but being able to say with grounding "this is operating correctly" is itself an operational value. The faint melancholy I felt when I confirmed there was no bug is the flip side of that process.

    Documentation that doesn't end at "this is by design"

    A single paragraph in troubleshooting saying "differs from the Console — by design" leaves a dead end the next time someone asks "OK, then how do I get the Console value?" Recording, at every branch of the question, "how far we looked" and "why it can't be obtained" turns the doc from a "warehouse of conclusions" into a "thinking map." This time I wrote with that in mind.


    Closing

    The "off by 2x" friction smelled like a simple bug at first. By the time I'd chased it to the end, though, it was neither code nor config — it was the more upstream structural question of "who, exactly, holds the AWS contract for this account?"

    Technical problems sometimes only have answers outside the technical layer. Whether you can pause before opening the code and ask "is this premise really the same?" matters quite a lot for operational maturity, I think.

    Going forward, when adding a new AWS account to monitoring, "is it under a reseller, or directly contracted?" goes onto the checklist as the first item. The checklist items spawned by friction may, in the end, be the most useful byproduct of this investigation.


    Here are recommended books for those who want to learn more about AWS operations, monitoring dashboard design, and documentation practices like in this case.

    Was this article helpful?

    Coffee cup

    If this article helped you organize your thoughts

    a coffee-sized support would be much appreciated.

    ※ This is separate from tipping, but—

    If you'd like to organize similar themes in your own context, I also offer dialogue sessions as a form of thought organization.

    About Dialogue Sessions