Cost · MIT

Read a Lobstack receipt

Read the receipt Lobstack returns on every model call — cost_usd, savings_usd, baseline_reason — and report cost from it instead of estimating.

Installing a skill

Version 1.0.0 · 180 lines

When an agent loads it

What the file covers.

Use when a response carries x_lobstack or an x-lobstack-* header, when asked what a call or a run cost, or when a cost renders as $0.00.

  1. 01

    Where the number is

  2. 02

    Three rules

  3. 03

    Reading it

  4. 04

    Also worth keeping

  5. 05

    Do not

The file

The whole SKILL.md, and where it goes.

In Lobstack, open Skills › Add a skill and paste it. In Claude Code, make the folder and paste it in, then start a new session.

shell
mkdir -p .claude/skills/lobstack-receipts
# paste SKILL.md into .claude/skills/lobstack-receipts/SKILL.md
lobstack-receipts/SKILL.md180 lines
---name: lobstack-receiptsdescription: "Read the receipt Lobstack returns on every model call — cost_usd, savings_usd, baseline_reason — and report cost from it instead of estimating. Use when a response carries x_lobstack or an x-lobstack-* header, when asked what a call or a run cost, or when a cost renders as $0.00."license: MITmetadata:  title: "Read a Lobstack receipt"  version: 1.0.0  author: lobstack  category: cost  tags: [cost, receipts, metering, gateway]--- # Read a Lobstack receipt Every response from the Lobstack Gateway carries what the call cost. Read it.Never multiply token counts by a rate card you are holding in your head — thatis how a bundled price table drifts out of date and reports $0.00 next to areal charge, which is a mistake this product shipped for three months. ## Where the number is There are two paths and they carry the receipt differently. This is not adetail; a client that only handles one of them under-reports half its traffic. Buffered (stream: false) — HTTP response headers: ```x-lobstack-request-id: 8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41x-lobstack-model: gemini-3.8-flash        # what actually served itx-lobstack-tier: standardx-lobstack-complexity: 47x-lobstack-routed: truex-lobstack-mode: managed                  # managed | byok | directx-lobstack-cost-usd: 0.000017             # empty string when unpricedx-lobstack-savings-usd: 0.000352x-lobstack-priced: truex-lobstack-metered: truex-lobstack-baseline-reason: namedx-lobstack-baseline-model: claude-opus-5x-lobstack-baseline-usd: 0.000369``` Streamed (stream: true) — the final SSE frame, under x_lobstack: ```json{  "id": "chatcmpl-…", "object": "chat.completion.chunk", "choices": [],  "usage": { "prompt_tokens": 14, "completion_tokens": 9, "total_tokens": 23 },  "x_lobstack": {    "request_id": "2f1c…",    "served_model": "gpt-5.6-luna",    "requested_model": "claude-opus-5",    "routed": true,    "cost_usd": 0.000017,    "savings_usd": 0.000352,    "priced": true,    "baseline_model": "claude-opus-5",    "baseline_reason": "named",    "baseline_cost_usd": 0.000369  }}``` There is no x-lobstack-cost-usd header on a streamed response, and therecannot be: the headers are written before the provider has counted a singletoken. If you are streaming, the receipt is on the last frame or you do not haveit. usage keeps the exact OpenAI shape so a foreign SDK parses it unchanged;the money hangs off x_lobstack, which those SDKs ignore. ## Three rules ### 1. null is not zero cost_usd is null — never 0 — when the Gateway could not price thecall, and priced is then false. A model key outside the registry gets acompletion and no price. Render null as unknown. Never as $0.00, never as free, and neversummed into a total that you then present as exact. Writing off a real charge aszero is the single most expensive way to be wrong about money, and a UI willhappily do it for you if you let a null fall through a ?? 0. ```ts// wrong: turns "we do not know" into "it was free"total += receipt.cost_usd ?? 0; // right: a total, and a count of what it could not includeif (receipt.cost_usd === null) unpriced += 1;else total += receipt.cost_usd;// report both: "$4.21 across 1,190 calls; 3 calls unpriced"``` ### 2. savings_usd is meaningless without baseline_reason savings_usd is a subtraction, and a subtraction is only honest if the readercan see where the first operand came from. Two reasons exist: - `named` — the caller asked for baseline_model and the router served  something else. savings_usd is a measurement against a model somebody  actually chose. Report it as a saving.- `plan_ceiling` — the caller said "auto" and named nothing.  baseline_model is the most expensive model their plan would have allowed:  what they *would* have paid had they asked for the best one. Nobody requested  it. It is a real number and also the most flattering one available. Print the reason next to the figure, or do not print the figure. Label the secondcase as "vs plan ceiling", never as "saved" — presenting a plan-ceilingcomparison as a saving publishes a number the customer never chose to bemeasured against. baseline_reason is null, with savings_usd also null, when thereis nothing to compare: the requested model is the served model, or the modelcannot be priced. That is the correct third answer, not a gap to fill. ### 3. cost_usd is what the customer owes On mode: managed it is the provider's list price inside the plan's includedusage and on Free, and the list price times 1.5 past it(provider price + 50%); rate on the receipt says which, andprovider_cost_usd carries the list price beside it. It is pass-through onbyok and direct — because on BYOK the customer already paid their ownprovider and marking that up would be inventing a charge. So a BYOK receipt showing cost_usd is telling you what the tokenscost at the provider, not what will appear on a Lobstack invoice. ## Reading it ```tsimport OpenAI from "openai"; const client = new OpenAI({  apiKey: process.env.LOBSTACK_API_KEY,  baseURL: "https://www.lobstack.ai/api/gateway/v1",}); // Buffered: headers, via the raw response.const { data, response } = await client.chat.completions  .create({ model: "auto", messages: [{ role: "user", content: "…" }] })  .withResponse(); const costHeader = response.headers.get("x-lobstack-cost-usd");const cost = costHeader ? Number(costHeader) : null;   // "" means unpricedconst requestId = response.headers.get("x-lobstack-request-id");``` ```python# Streamed: the last frame with a usage block is the receipt.receipt = Nonefor chunk in client.chat.completions.create(model="auto", messages=msgs, stream=True):    raw = chunk.model_dump()    if raw.get("x_lobstack"):        receipt = raw["x_lobstack"]    for c in chunk.choices:        print(c.delta.content or "", end="") if receipt and receipt["priced"]:    print("\ncost $%.6f on %s" % (receipt["cost_usd"], receipt["served_model"]))elif receipt:    print("\ncost unknown - model not priced")``` ## Also worth keeping - request_id is the id of the trace row and is returned even on failures that  happen before authentication finishes. Quote it in a support thread and a  description becomes a lookup. Log it on every call; it costs nothing.- requested_model vs served_model is the routing decision, in the  response. When they differ, routed is true.- x-lobstack-dropped-params: temperature means a sampling parameter you sent  was not forwarded, because the target model rejects it with a hard 400. The  request succeeded. Nothing is wrong. Do not retry.- x-lobstack-metered: false means the completion happened but the ledger write  failed. You were served and the row is missing. Worth an alert. ## Do not - Do not compute cost from token counts and a local price table. Read it.- Do not sum null as zero.- Do not show a saving without its reason.- Do not assume the buffered and streamed shapes are interchangeable.
More for developers

More SKILL.md files, from Cost first.

Cost on its own shelf, or every developer skill.