Reproduce the figure
You can check every saving LightHouse shows in three steps of increasing depth: read the figure with its method, recompute it from the per-lever read, and recompute it from your raw records.
All examples use a viewer key and your tenant’s service token, as in Authentication.
1. The API call that returns the figure
Section titled “1. The API call that returns the figure”GET /api/v1/savings/headline?period=YYYY-MM returns the month’s headline and every lever, each with its baseline, method, conservative choices, coverage and detail. Omit period for the current month.
curl "https://$AAL_TENANT.atlanticagenticlabs.com/api/v1/savings/headline?period=2026-10" \ -H "Authorization: Bearer $AAL_KEY" \ -H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \ -H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET"import os
import httpx
BASE = f"https://{os.environ['AAL_TENANT']}.atlanticagenticlabs.com/api/v1"HEADERS = { "Authorization": f"Bearer {os.environ['AAL_KEY']}", "CF-Access-Client-Id": os.environ["CF_ACCESS_CLIENT_ID"], "CF-Access-Client-Secret": os.environ["CF_ACCESS_CLIENT_SECRET"],}
doc = httpx.get(f"{BASE}/savings/headline", params={"period": "2026-10"}, headers=HEADERS).json()print(doc["headline"]["sentence"])for lever in doc["levers"].values(): print(f'{lever["name"]:<50} {lever["status"]:<20} {lever["usd"]} ({lever["coverage"]["sentence"]})')const BASE = `https://${process.env.AAL_TENANT}.atlanticagenticlabs.com/api/v1`;const HEADERS = { Authorization: `Bearer ${process.env.AAL_KEY}`, "CF-Access-Client-Id": process.env.CF_ACCESS_CLIENT_ID!, "CF-Access-Client-Secret": process.env.CF_ACCESS_CLIENT_SECRET!,};
const doc = await (await fetch(`${BASE}/savings/headline?period=2026-10`, { headers: HEADERS })).json();console.log(doc.headline.sentence);for (const lever of Object.values<any>(doc.levers)) { console.log(lever.name, lever.status, lever.usd, lever.coverage.sentence);}What to look at in each lever:
status:ok(counted),included(already inside another lever),partial_comparison(a figure, not counted), or why there is no number (no_data,no_price_card,no_comparison,no_cost_feed).coverage: how much of your estate reported what the lever needs. A saving on 40% coverage is a saving on 40% of your work.detail: the lever’s own working. For routing,detail.sourceisdecisionsorcounterfactual.
The same document is in LightHouse at Insights → Savings, and through MCP as the savings tool with view=headline. See the Savings Headline reference.
2. Recompute a lever from its own read
Section titled “2. Recompute a lever from its own read”Each lever has a read that carries the inputs to its formula.
| Lever | Read | What it carries |
|---|---|---|
| Retrieval | GET /api/v1/retrieval |
stuffing: stuffed_tokens, sent_tokens, tokens_avoided, usd_uncached, usd_cached, the baseline used, and the same by agent, pack, index, model and day |
| Model routing | GET /api/v1/routing |
Every decision with its candidates and their costs; totals with saving_usd, escalation_usd and off_saving_usd |
| Prompt caching | GET /api/v1/caching |
Tokens by kind and the price card, by model, agent, pack, deployment and provider |
| Infrastructure and ephemeral | GET /api/v1/deployments/profiles |
Each deployment’s profile and its estimated always-on cost |
Most take format=csv for a spreadsheet.
Example: the caching lever, by hand
Section titled “Example: the caching lever, by hand”This recomputes the caching saving for each model from GET /api/v1/caching and compares it with LightHouse’s own figure. Run it with the BASE and HEADERS from step 1.
doc = httpx.get(f"{BASE}/caching", params={"since": "2026-10-01", "until": "2026-10-31"}, headers=HEADERS).json()M = 1_000_000for row in doc["by_model"]: card = row["price"] if not card or card["cache_read_per_m"] is None: print(row["key"], "no cache-read price: saves nothing") continue inp = card["input_per_m"] read_rate = card["cache_read_per_m"] write_rate = card["cache_write_per_m"] if card["cache_write_per_m"] is not None else inp mine = (row["cache_read_tokens"] * (inp - read_rate) - row["cache_write_tokens"] * max(write_rate - inp, 0)) / M print(f'{row["key"]:<45} mine {mine:10.4f} LightHouse {row["saved_usd"]:10.4f}')const q = new URLSearchParams({ since: "2026-10-01", until: "2026-10-31" });const cache = await (await fetch(`${BASE}/caching?${q}`, { headers: HEADERS })).json();const M = 1_000_000;for (const row of cache.by_model) { const card = row.price; if (!card || card.cache_read_per_m === null) { console.log(row.key, "no cache-read price"); continue; } const inp = card.input_per_m; const write = card.cache_write_per_m ?? inp; const mine = (row.cache_read_tokens * (inp - card.cache_read_per_m) - row.cache_write_tokens * Math.max(write - inp, 0)) / M; console.log(row.key, mine.toFixed(4), row.saved_usd.toFixed(4));}A model that more than one deployment calls on different price cards can differ slightly here, because LightHouse prices each deployment on its own card. Group by deployment (format=csv&group_by=deployment) to compare exactly.
3. Recompute from your raw records
Section titled “3. Recompute from your raw records”For the deepest check, export your tenant (Setup and admin → Data export; see Leaving). records.jsonl holds every telemetry record LightHouse stores for you, one per line, with its type, deployment, ts and attrs. The manifest’s SHA-256 lets you show the file is the one LightHouse produced.
This recomputes retrieval’s tokens avoided for a month, by the formula on How a saving is calculated:
import jsonfrom collections import defaultdict
DEFAULT_WINDOW = 200_000records = [json.loads(line) for line in open("records.jsonl", encoding="utf-8")]month = [r for r in records if r["ts"].startswith("2026-10")]
# The smallest context window among the priced models each pack called.window_of = {}for r in records: if r["type"] == "models": limits = r["attrs"].get("limits") or {} w = limits.get("context_tokens") or limits.get("context_window") if w: window_of[(r["deployment"], r["attrs"]["model"])] = wcalled = defaultdict(set)for r in month: if r["type"] == "model_call" and r["attrs"].get("outcome") != "refused": called[(r["deployment"], r["attrs"]["pack"])].add(r["attrs"]["model"])
def window(deployment, pack): known = [window_of[(deployment, m)] for m in called[(deployment, pack)] if (deployment, m) in window_of] return min(known) if known else DEFAULT_WINDOW
avoided = measured = unmeasured = 0for r in month: if r["type"] != "retrieval": continue a = r["attrs"] corpus = a.get("corpus_tokens_in_scope") if corpus is None: unmeasured += 1 # a search that did not report its scope counts nothing continue measured += 1 stuffed = min(corpus, window(r["deployment"], a["pack"])) weight = 1 / (a.get("sample_rate") or 1) avoided += max(stuffed - a["context_tokens_sent"], 0) * weight
print(f"tokens avoided: {avoided:,.0f} over {measured} searches ({unmeasured} unmeasured)")Compare tokens avoided with stuffing.tokens_avoided from GET /api/v1/retrieval for the same dates. Multiply by your input price (or your cache-read price, if you cached that month) per million to get the dollar figure.
The script takes each model’s context window from any models record; LightHouse uses the deployment’s newest complete snapshot, which matters only if a window changed during the month.
Days are your tenant’s calendar days in its time zone in LightHouse’s reads; the script above uses UTC dates, so a search near midnight can fall in a different month. Set your tenant’s time zone to UTC, or filter by your own zone, for an exact match.
Comparing with LightHouse’s figure
Section titled “Comparing with LightHouse’s figure”If your figure and LightHouse’s differ:
- Check the period and time zone first: LightHouse buckets days in your tenant’s time zone.
- Check coverage: searches or calls that reported nothing are counted by LightHouse as unmeasured and given no saving.
- Check the price card: LightHouse uses each deployment’s newest complete snapshot, not today’s provider list price.
- If they still differ, tell your certified forward-deployed engineer, with the period and the lever. A disagreement between two calculations is a bug in one of them, and we want to know which.