Skip to content

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.

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).

Send the token as a bearer header:

Terminal window
curl -H "Authorization: Bearer leancosts_pat_xxxxxxxx…" \
https://app.leancosts.com/api/change-requests

The 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).

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:

Terminal window
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.ts

Reused 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 in Usd is a DisplayedMoney: amount in your display currency, plus currency, originalUsd, fxRate and fxSource, 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/kubernetes figures 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.

GET /api/costs/export returns cost line items as a CSV attachment: either in leancosts’ own columns or projected onto FOCUS 1.0:

Terminal window
curl -H "Authorization: Bearer leancosts_pat_xxxxxxxx…" \
"https://app.leancosts.com/api/costs/export?format=focus&periods=2026-06,2026-07" \
-o costs.csv

periods 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.

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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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.

Revoke your own tokens from Settings → Tokens; admins can list/revoke any user’s tokens with the governance.admin capability. Revocation is immediate.

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.

Terminal window
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.

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.

ToolAsk it for
get_cost_summarySpend per billing month, by service, tag and subscription, with commitment coverage.
get_cost_forecastThis month’s projected spend with its confidence band, plus your mirrored Azure budgets.
get_cost_varianceWhy the bill moved between two months: per service, or as the seven-bucket decomposition.
get_daily_costsDay-by-day spend inside a month, or one day’s biggest risers and fallers.
get_cost_evolutionThe month-over-month grid by service or by the values of one tag key.
get_tag_driversWhat drove one tag value’s swing in a given month.
get_cost_metersThe billed units under the money for one account, service or resource.
list_anomaliesDetected cost anomalies, with acknowledged and ended spikes withheld.
list_alert_rulesYour alert rules, delivery channels, and what recently fired.
update_alert_ruleTune an alert rule’s threshold, its channels, or whether it is active. Needs governance.admin.
get_impact_summaryRealized savings measured against the bill, net of subscription cost.
get_savings_pipelineThe savings funnel and the cost-avoidance counterfactual.
get_cost_readinessWhether the ingested data is complete enough to quote a month as final.
get_finops_scorecardPer-resource, program-maturity, and per-team scores. The per-resource view returns the worst rows first; narrow it with a score filter.
query_costsAny ad-hoc aggregation of raw cost lines with your own pivot and filters.
export_costsThe 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_requestsFindings, reservations and savings plans, inventory, and the record of what you acted on.
get_kubernetes_costsKubernetes cost per namespace for AKS, EKS and GKE, where the provider’s cost allocation is enabled.
get_tag_coverageHow much of the estate carries your required tags, plus the gaps ranked by what each untagged resource costs.
get_tag_catalogYour tag taxonomy: declared keys, allowed values and aliases, system-derived keys, and normalization suggestions.
list_allocation_rulesHow spend is attributed to teams and cost centres, plus the last materialized totals.
list_shared_cost_patternsThe rules that spread a shared cost pool across tag-value targets, with a preview of what each allocates.
get_allocation_proposalThe latest saved allocation-rule proposal.
list_staged_tagsTag changes staged in leancosts, optionally with the az / aws commands that would apply them.
list_virtual_tagsThe tags that exist only in leancosts, with the filter behind each one and how many resources it currently covers.
get_tag_reconciliationResources violating the required taxonomy, grouped for bulk remediation.
list_opportunitiesThe Savings Register: every triaged finding with its claim, plus the one canonical open-claim total and what your confidence floor is holding back.
get_opportunityOne register row with its decision history and, where the hunter produced one, the inputs and formula behind the claim.
list_hunter_findingsCurrent 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_rollupPer-category candidate counts and addressable spend, the realized savings beside them, per-hunter totals, and when the snapshot was computed.
describe_hunterHow a hunter works: what it reads, what it claims, and when it stays quiet. Reads no data of yours.
list_ledger_entries, get_ledger_entryClaimed savings and, once the bill has graded them, the measured amount.
get_change_requestOne change request with its approval status, its modelled before/after, and any child items.
get_change_request_execution_bundleThe runnable artifact for an approved change request, for you to run with your own credentials.
list_resource_notesThe 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_resourceOne resource in depth: identity, tags, monthly cost, what changed, commitment coverage, meters, metrics and sizing.
search_resourcesFind resources by provider, type, account, tag or free text, ranked by their latest closed month’s spend.
list_accountsYour connected subscriptions, accounts and projects, the vocabulary the other tools filter by.
get_commitment_recommendationThe spend-ranked resources behind one reservation or savings-plan recommendation.
list_spend_commitmentsAzure MACC, AWS EDP/PPA and GCP contract obligations with their burn-down.
list_variance_notesThe notes left on one pair of billing months, newest first with their author and time. Worth reading before explaining a variance.
get_hunter_readinessWhether the hunters can run yet, and which step is still blocking them.
list_saved_queriesThe cost questions your organization has named and shared, with the slug behind each /copilot?saved= link.
run_saved_queryRe-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.

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”.

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.