Back to blog
FILE 0xDF·DOWNLOADING YOUR AWS COST HISTORY AS A CSV

Downloading your AWS cost history as a CSV

June 9, 2026 · costwatch, aws, saas, side-project, python

The cost chart shows a sparkline and the top 5 services by spend. That's useful for spotting anomalies at a glance. But the finance team doesn't want a sparkline — they want a spreadsheet.

CostWatch now has a CSV export endpoint.


The endpoint

GET /account/{account_id}/export?days=90
Authorization: session cookie

Returns a CSV file with Content-Disposition: attachment:

date,total,Amazon EC2,Amazon RDS,Amazon S3,AWS Lambda,...
2026-05-10,47.23,31.20,9.80,4.12,1.11,...
2026-05-11,52.18,35.40,9.80,4.15,1.83,...
...

Every service that appeared in the window gets its own column, sorted alphabetically. A service that was zero on a particular day gets 0.0000. The total column is the sum of all services for that day.


Why not top-5

The cost chart caps at top 5 services to keep the chart readable. The export has no cap. If you have 47 services you actually spent money on last month (yes, this happens), all 47 get columns.

The intent is different: the chart answers "what's unusual?", the CSV answers "give me everything so I can pivot it".


The implementation

Cost Explorer's DAILY granularity returns one result per day, with groups per service:

resp = ce.get_cost_and_usage(
    TimePeriod={"Start": start, "End": end},
    Granularity="DAILY",
    Metrics=["UnblendedCost"],
    GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
)

Building the CSV from that is straightforward:

days_data: list[tuple[str, dict[str, float]]] = []
all_services: set[str] = set()

for day_data in resp.get("ResultsByTime", []):
    day_str = day_data["TimePeriod"]["Start"]
    day_services: dict[str, float] = {}
    for group in day_data.get("Groups", []):
        svc = group["Keys"][0]
        amt = float(group["Metrics"]["UnblendedCost"]["Amount"])
        day_services[svc] = round(amt, 4)
        all_services.add(svc)
    days_data.append((day_str, day_services))

services_sorted = sorted(all_services)

import io, csv
buf = io.StringIO()
writer = csv.writer(buf)
writer.writerow(["date", "total"] + services_sorted)
for day_str, svc_map in days_data:
    total = round(sum(svc_map.values()), 4)
    row = [day_str, total] + [round(svc_map.get(s, 0.0), 4) for s in services_sorted]
    writer.writerow(row)

The response uses Cache-Control: no-store since it contains billing data. Same-account cross-account role assumption as the cost chart — the Lambda assumes a reader role in the user's account to call Cost Explorer.


Caveats


CostWatch is at osspulse.io — wait, wrong product. CostWatch doesn't have a live URL yet. Working on it.