Skip to content
Official · LobstackObservabilityv1.0.0MIT

Report spend from the usage API

lobstack-usage-reporting

Answer what an organisation spent, on which models and keys, and what failed, from the Lobstack usage API rather than by estimating. Use when producing a cost report, attributing spend to a model, key or agent, or investigating an hour of high latency or errors.

#observability#usage#reporting#cost

Install

Copy the file and save it at the path below, then start a new session. Any agent that reads a skills directory will pick it up; the path shown is Claude Code's. Delete the folder to uninstall.

mkdir -p .claude/skills/lobstack-usage-reporting
# paste SKILL.md into .claude/skills/lobstack-usage-reporting/SKILL.md

No package, no registry client, no telemetry. 134 lines of text, licensed MIT.


What the agent matches on

Frontmatter

An agent decides whether to load a skill from name and description alone — it does not read the body first. That is why the description says when to use this, not only what it is.

---
name: lobstack-usage-reporting
description: "Answer what an organisation spent, on which models and keys, and what failed, from the Lobstack usage API rather than by estimating. Use when producing a cost report, attributing spend to a model, key or agent, or investigating an hour of high latency or errors."
license: MIT
metadata:
  title: "Report spend from the usage API"
  version: 1.0.0
  author: lobstack
  category: observability
  tags: [observability, usage, reporting, cost]
---

134 lines · MIT

SKILL.md

.claude/skills/lobstack-usage-reporting/SKILL.md134 lines
---name: lobstack-usage-reportingdescription: "Answer what an organisation spent, on which models and keys, and what failed, from the Lobstack usage API rather than by estimating. Use when producing a cost report, attributing spend to a model, key or agent, or investigating an hour of high latency or errors."license: MITmetadata:  title: "Report spend from the usage API"  version: 1.0.0  author: lobstack  category: observability  tags: [observability, usage, reporting, cost]--- # Report spend from the usage API One endpoint answers every version of "what did we spend". Use it rather thansumming your own logs: the Console and the desktop client both read this, so areport built from it agrees with what the customer sees instead of quietlydisagreeing by a few percent. ```bashcurl -s "https://www.lobstack.ai/api/v1/usage?range=30d&group_by=model" \  -H "Authorization: Bearer $LOBSTACK_API_KEY" | jq``` The key needs the usage:read scope, or the response is a 403 saying exactlythat. A browser session also works, which is how the Console reads it. | param | values | default || --- | --- | --- || `range` | `7d` `14d` `30d` `90d` | `7d` || `group_by` | `day` `model` `key` `agent` | `day` || `agent_id` | restrict to one agent | — || `key_id` | restrict to one API key | — | ## The summary, and the two fields that decide whether you may quote it ```json{  "enabled": true,  "org_id": "…",  "authenticated_via": "api_key",  "range": "30d",  "group_by": "model",  "summary": {    "requests": 12480, "errors": 41, "error_rate": 0.0033,    "errors_by_class": { "provider": 33, "timeout": 6, "validation": 2 },    "prompt_tokens": 8104221, "completion_tokens": 1201884, "total_tokens": 9306105,    "cost_usd": 41.204118,    "unpriced_requests": 0,    "p50_latency_ms": 780, "p95_latency_ms": 2410, "p99_latency_ms": 5120,    "streamed": 9902  },  "groups": [ … ],  "truncated": false}``` `unpriced_requests` — rows the meter could not price. cost_usd sums anull as zero, which is the only arithmetic available and not the only truth: atotal built partly from unpriced rows is a floor, and a reader who is not toldhow many were unpriced will read it as exact. Non-zero on a recent window means anunknown model key is passing through uncosted right now — go find it withaudit-unmetered-model-calls. `truncated` — true when the row cap bound. Sums are then a floor as well.Narrow the range or the filters rather than publishing the number. Report both, always, in one sentence: *"$41.20 across 12,480 requests over 30days; 0 unpriced, not truncated."* That sentence is defensible. $41.20 on itsown is not. The latency figures are true percentiles over the raw values rather than bucketedapproximations, so p95 is the 95th percentile and can be compared acrossranges. ## The four questions, and the group_by that answers each ```bash# What is it costing us over time, and is that trending?curl -s ".../api/v1/usage?range=30d&group_by=day" -H "$AUTH" \  | jq -r '.groups[] | [.key, .requests, .cost_usd] | @tsv' # Which model is the bill? Almost always one, and almost always a surprise.curl -s ".../api/v1/usage?range=30d&group_by=model" -H "$AUTH" \  | jq -r '.groups | sort_by(-.cost_usd)[] | [.key, .requests, .cost_usd] | @tsv' # Which team or environment? One key per surface makes this answerable.curl -s ".../api/v1/usage?range=30d&group_by=key" -H "$AUTH" | jq '.groups' # Which agent? For a fleet, this is the per-unit economics.curl -s ".../api/v1/usage?range=7d&group_by=agent" -H "$AUTH" | jq '.groups'``` Mint one API key per surface — web, batch job, staging, each internal tool — andgroup_by=key becomes cost attribution for free. Retrofitting that after thefact is impossible: the rows are already written against one key. ## Reading a bad hour errors_by_class settles the only question that matters first: whose fault wasit. provider and timeout dominating means upstream. validationdominating means a deploy went out with a bad body — check what changed. authdominating means a key was rotated or revoked and something did not get the memo. Then pull the specific requests by id from the trace table, or reproduce with therequest id in hand. The class names and what each implies are inlobstack-gateway-errors. ## From a terminal ```bashnpx lobstack spend --days 7``` Same data, same arithmetic, no jq. Also needs usage:read.  ## Two responses that are not errors enabled: false with reason: "request_tracing_not_migrated" means the tracetable does not exist on that deployment yet. It is a 200 with a null summary,deliberately, so a dashboard renders "not available" rather than "$0.00". An empty groups array with a real summary means the range genuinely had notraffic. Do not conflate the two. ## Do not - Do not publish cost_usd without unpriced_requests and truncated.- Do not build a parallel cost table from your own logs. It will disagree with the  invoice, and the invoice will be right.- Do not compare this month's cost_usd with last month's without checking  whether the model mix moved. A cheaper month can be a worse month.

Also in Observability