Use the API with a token
Call the leancosts HTTP API from a script, CI job, or scheduled refresh using a Personal Access Token (PAT) instead of a browser session.
1. Mint a token
Section titled “1. Mint a token”In the web app: User menu → Settings → Tokens (/settings/api-tokens) →
create token. At mint time you can:
- set a TTL (default 90 days, max 365),
- optionally narrow the scopes to a subset of your capabilities (empty = inherit everything you can already do).
The cleartext token (leancosts_pat_<64 hex>) is shown exactly once. Copy
it now; only its sha256 is stored server-side. If you lose it, revoke and
reissue.
The same dialog also shows a ready-to-run Connect an MCP client command
(claude mcp add …) with the token already in the Authorization header, so a
Claude Code connection needs no second trip (see
Connect Claude).
2. Call the API
Section titled “2. Call the API”Send the token as a bearer header:
curl -H "Authorization: Bearer leancosts_pat_xxxxxxxx…" \ https://app.leancosts.com/api/change-requestsThe same gate protects the docs endpoints, so a token also unlocks:
GET https://app.leancosts.com/api/openapi.json: the OpenAPI 3.1 document (every registered endpoint, method + path).GET https://app.leancosts.com/api/docs: the Scalar API reference UI (click Authorize, paste your PAT, and try requests in-browser).
Generate a typed client
Section titled “Generate a typed client”The cost (/costs), Savings Register (/opportunities) and hunter finding
(/hunters) routes carry full request, query and response schemas in the
document, so you can generate a client instead of hand-writing types:
curl -H "Authorization: Bearer leancosts_pat_xxxxxxxx…" \ https://app.leancosts.com/api/openapi.json -o leancosts.json
npx openapi-typescript leancosts.json -o leancosts.d.tsReused shapes (OpportunityEntry, HunterCandidate, ChangeRequest,
DisplayedMoney, ApiError) live under components.schemas, so they come out
as one named type each.
Two things to know before you read the generated types:
- Money is sometimes an object, not a number: read the schema, don’t assume.
On the
/hunters/*routes, every field whose name ends inUsdis aDisplayedMoney:amountin your display currency, pluscurrency,originalUsd,fxRateandfxSource, so you can always get back to the dollar figure and the rate used. On the cost and/opportunities*routes, money is a plain number today. The generated types are the authority here, which is the point of generating them. /costs/kubernetesfigures stay in the cloud’s own billing currency. They split money the bill already holds, so they are never converted, and never summed across currencies.- Endpoints outside those two groups document method and path only. They work exactly the same, you just won’t get response types for them yet.
Export raw cost lines
Section titled “Export raw cost lines”GET /api/costs/export returns cost line items as a CSV attachment: either in
leancosts’ own columns or projected onto FOCUS 1.0:
curl -H "Authorization: Bearer leancosts_pat_xxxxxxxx…" \ "https://app.leancosts.com/api/costs/export?format=focus&periods=2026-06,2026-07" \ -o costs.csvperiods is required (comma-separated YYYY-MM). Narrow with providers,
accounts, services and repeatable tags=key=value. Each call returns at most
50,000 rows: follow the x-next-offset response header into offset until it
stops coming back. A FOCUS column leancosts does not ingest is left empty rather
than filled from another field, so BilledCost, ResourceName,
SubAccountName and RegionId are blank. Months older than your plan’s
retention are dropped and named in x-retention-clamped-to.
Annotate a resource
Section titled “Annotate a resource”Three routes carry the operational notes shown on a resource in the app: the context a person already knows and the bill cannot show, like who owns the resource or why it must not be stopped. A note is inert. It never changes a finding, a cost or a tag.
The resource id goes into the path percent-encoded, so encode it once and reuse it:
TOKEN=leancosts_pat_xxxxxxxx…ID='/subscriptions/s/resourcegroups/rg/providers/microsoft.compute/virtualmachines/vm1'ENC=$(printf %s "$ID" | jq -sRr @uri)GET lists the notes, newest first, with the unbounded total beside them.
limit defaults to 100 and maxes at 500. Reading needs no extra permission:
curl -H "Authorization: Bearer $TOKEN" \ "https://app.leancosts.com/api/resources/$ENC/notes?limit=50"POST appends one note, attributed to the token’s owner. The body is plain
text, 1 to 2000 characters once trimmed; anything else is a 400. The reply is
201 and the created note, including its id:
curl -X POST -H "Authorization: Bearer $TOKEN" \ -H "content-type: application/json" \ -d '{"body":"Batch job runs 03:00-04:00 UTC. Do not stop before the Q4 migration."}' \ "https://app.leancosts.com/api/resources/$ENC/notes"DELETE removes one note by that id. Only its author can, unless you hold the
governance.admin capability: someone else’s note answers 403, an unknown id
404:
curl -X DELETE -H "Authorization: Bearer $TOKEN" \ "https://app.leancosts.com/api/resources/$ENC/notes/<note-id>"Both writes need a PAT carrying costs.notes.write.
The token’s prefix is intentionally greppable (GitHub/Stripe-style) so a leaked token is easy to spot in CI logs: treat it like a password and prefer a CI secret store.
3. Revoke when done
Section titled “3. Revoke when done”Revoke your own tokens from Settings → Tokens; admins can list/revoke any
user’s tokens with the governance.admin capability. Revocation is immediate.
Connect an AI agent (MCP)
Section titled “Connect an AI agent (MCP)”The same PAT also unlocks the MCP tenant server at POST /api/mcp, a
Model Context Protocol endpoint that lets an
AI agent (Claude Code or any MCP client) set up your cloud connections
conversationally: fetch the exact read-only grant commands for your
organization, run them with your own cloud CLI, create the connection, and
watch the first sync.
claude mcp add --transport http leancosts \ https://api.leancosts.com/api/mcp \ --header "Authorization: Bearer leancosts_pat_xxxxxxxx…"Then ask: “Using leancosts, connect my AWS payer account.” For least
privilege, mint the PAT scoped to costs.connections.manage.
What the agent can read
Section titled “What the agent can read”Beyond connector setup, the same endpoint exposes read-only tools over the data you have already ingested, so an agent answers from your bill rather than guessing. Every figure is canonical USD, and every tool takes structured parameters, never a free-text question.
| Tool | Ask it for |
|---|---|
get_cost_summary | Spend per billing month, by service, tag and subscription, with commitment coverage. |
get_cost_forecast | This month’s projected spend with its confidence band, plus your mirrored Azure budgets. |
get_cost_variance | Why the bill moved between two months: per service, or as the seven-bucket decomposition. |
get_daily_costs | Day-by-day spend inside a month, or one day’s biggest risers and fallers. |
get_cost_evolution | The month-over-month grid by service or by the values of one tag key. |
get_tag_drivers | What drove one tag value’s swing in a given month. |
get_cost_meters | The billed units under the money for one account, service or resource. |
list_anomalies | Detected cost anomalies, with acknowledged and ended spikes withheld. |
list_alert_rules | Your alert rules, delivery channels, and what recently fired. |
update_alert_rule | Tune an alert rule’s threshold, its channels, or whether it is active. Needs governance.admin. |
get_impact_summary | Realized savings measured against the bill, net of subscription cost. |
get_savings_pipeline | The savings funnel and the cost-avoidance counterfactual. |
get_cost_readiness | Whether the ingested data is complete enough to quote a month as final. |
get_finops_scorecard | Per-resource, program-maturity, and per-team scores. The per-resource view returns the worst rows first; narrow it with a score filter. |
query_costs | Any ad-hoc aggregation of raw cost lines with your own pivot and filters. |
export_costs | The raw cost line items for a set of billing months, as JSON rows, FOCUS 1.0 columns or CSV. Page it with nextOffset; the rows are the same dollars get_cost_summary totals, so never add them to a summary. |
list_insights, list_commitments, list_resources, list_change_requests | Findings, reservations and savings plans, inventory, and the record of what you acted on. |
get_kubernetes_costs | Kubernetes cost per namespace for AKS, EKS and GKE, where the provider’s cost allocation is enabled. |
get_tag_coverage | How much of the estate carries your required tags, plus the gaps ranked by what each untagged resource costs. |
get_tag_catalog | Your tag taxonomy: declared keys, allowed values and aliases, system-derived keys, and normalization suggestions. |
list_allocation_rules | How spend is attributed to teams and cost centres, plus the last materialized totals. |
list_shared_cost_patterns | The rules that spread a shared cost pool across tag-value targets, with a preview of what each allocates. |
get_allocation_proposal | The latest saved allocation-rule proposal. |
list_staged_tags | Tag changes staged in leancosts, optionally with the az / aws commands that would apply them. |
list_virtual_tags | The tags that exist only in leancosts, with the filter behind each one and how many resources it currently covers. |
get_tag_reconciliation | Resources violating the required taxonomy, grouped for bulk remediation. |
list_opportunities | The Savings Register: every triaged finding with its claim, plus the one canonical open-claim total and what your confidence floor is holding back. |
get_opportunity | One register row with its decision history and, where the hunter produced one, the inputs and formula behind the claim. |
list_hunter_findings | Current findings from the last hunter snapshot, filtered by category, cloud, hunter or resource, with what was withheld and what could not be looked at. |
get_hunter_rollup | Per-category candidate counts and addressable spend, the realized savings beside them, per-hunter totals, and when the snapshot was computed. |
describe_hunter | How a hunter works: what it reads, what it claims, and when it stays quiet. Reads no data of yours. |
list_ledger_entries, get_ledger_entry | Claimed savings and, once the bill has graded them, the measured amount. |
get_change_request | One change request with its approval status, its modelled before/after, and any child items. |
get_change_request_execution_bundle | The runnable artifact for an approved change request, for you to run with your own credentials. |
list_resource_notes | The operational notes left on one resource, newest first with their author and time. Worth reading before acting on a resource: a note may explain why the obvious fix is wrong. |
get_resource | One resource in depth: identity, tags, monthly cost, what changed, commitment coverage, meters, metrics and sizing. |
search_resources | Find resources by provider, type, account, tag or free text, ranked by their latest closed month’s spend. |
list_accounts | Your connected subscriptions, accounts and projects, the vocabulary the other tools filter by. |
get_commitment_recommendation | The spend-ranked resources behind one reservation or savings-plan recommendation. |
list_spend_commitments | Azure MACC, AWS EDP/PPA and GCP contract obligations with their burn-down. |
list_variance_notes | The notes left on one pair of billing months, newest first with their author and time. Worth reading before explaining a variance. |
get_hunter_readiness | Whether the hunters can run yet, and which step is still blocking them. |
list_saved_queries | The cost questions your organization has named and shared, with the slug behind each /copilot?saved= link. |
run_saved_query | Re-run one of them by id or slug. It is the stored question, not a stored number, so it answers from the data ingested today. |
Lists take a limit (1-500) and answer with returned and total, so a slice
is never mistaken for the whole estate.
Every one of these is read-only. Six tools write, and none of them touches
your cloud. stage_tags records a tag change inside leancosts so it shows
up under the planned lens and in the CLI readback you run yourself.
upsert_virtual_tag goes one step further and declares a tag that never leaves
leancosts at all: you describe the resources once as a filter, and every match
carries the tag under both lenses, re-resolved after each sync.
draft_change_request turns an accepted register row into a draft change
request that still needs a human approval; leancosts holds no write credential
and never applies it. save_query names a cost question so your whole
organization can re-ask it, from the Costs page or from an agent; it stores the
question, never the figures. add_resource_note appends one note to a resource,
and add_variance_note one to a pair of billing months, each attributed to you;
neither changes a finding, cost or tag. All six require the same permission the matching page does,
as does get_change_request_execution_bundle. An unsubscribed trial outside its
free-scan window is refused all of them, exactly as it is refused the matching
pages.
How many calls your plan includes
Section titled “How many calls your plan includes”MCP tool calls are metered per calendar month, per organization, against your plan band: 500 on Free, 5,000 on Starter, 20,000 on the next band, 100,000 on mid, and 1,000,000 on Enterprise. Connecting a client, negotiating the protocol and listing the tools are all free: only running a tool counts. Past the cap a call returns HTTP 429 with a calm message telling you the cap, the month, and that calls reset on the 1st; the tool itself never runs. Your usage for the current month is shown on Admin → Subscription as “MCP calls this month”.
Discovering endpoints
Section titled “Discovering endpoints”The OpenAPI document describes the method + path for every endpoint, grouped by
first path segment (/costs/* → tag costs, etc.). Use the Scalar reference
UI (/api/docs) to browse and try them in-browser.