skip to content
conifer
Receipts

CONIFER · DOCS

SDK

Receipts

Every buffered response carries its exact settled cost in integer nanodollars ($1 = 1e9), on the response you already have. No stats endpoint, no float drift.


The headers

All three doors — /v1/chat/completions, /v1/responses, /v1/messages — carry the same receipt headers:

terminal
curl -sD - -o /dev/null https://api.conifer.build/v1/chat/completions \
  -H "authorization: Bearer $CONIFER_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model":"claude-haiku-4-5","max_tokens":200,
       "messages":[{"role":"user","content":"three names for a build cache"}]}' \
  | grep -i x-conifer-

# x-conifer-requested-model: claude-haiku-4-5
# x-conifer-effective-model: claude-haiku-4-5
# x-conifer-receipt-reason: as_requested
# x-conifer-cost-nanousd: 568000
# x-conifer-cost-components-nanousd: fresh=13000,cache_write=0,cache_read=0,output=555000
# x-conifer-request-id: gw-…
HeaderMeaning
x-conifer-cost-nanousdSettled cost of this call. Integer. Absent on a stream (see below).
x-conifer-cost-components-nanousdItemization: fresh, cache_write, cache_read, output. Always sums to the total; omitted when that identity cannot be guaranteed.
x-conifer-requested-model, x-conifer-effective-modelWhat you asked for and what served. They differ only on an id re-spelling or a disclosed fallback, never a silent substitution.
x-conifer-counterfactual-nanousdWhat the turn would have cost unrouted. Omitted when not applicable.
x-conifer-request-idQuote this in support requests. Echoed on request-id and x-request-id.

The cost is mirrored into the body as usage.cost (where OpenRouter puts it, so existing pipelines keep working) and usage.cost_nanousd (the integer).

Streams

Cost headers are absent on a streamed turn: the response head is sent before the first token and the money settles after the last. The routing headers (models, request id) are present from the start. Reconcile from the terminal usage chunk:

stream.ts
const stream = await openai.chat.completions.create({
  model: "claude-haiku-4-5",
  messages,
  stream: true,
  stream_options: { include_usage: true },
});

for await (const chunk of stream) {
  if (chunk.usage) {
    chunk.usage.cost_nanousd;   // settled cost, in the terminal chunk
  }
}

ReceiptCollector: receipts in any client

The OpenAI and Anthropic packages, LangChain, LiteLLM and the Vercel AI SDK all discard response headers — but each takes an injected fetch (or http_client). Hand them one that reads the receipt on the way past:

receipts.ts
import OpenAI from "openai";
import { ReceiptCollector } from "conifer-sdk";

const receipts = new ReceiptCollector();
const openai = new OpenAI({
  baseURL: "https://api.conifer.build/v1",
  apiKey: process.env.CONIFER_API_KEY,
  fetch: receipts.fetch,        // the only line that changes
});

await openai.chat.completions.create({ model: "claude-haiku-4-5", messages });

receipts.last.costNanoUsd;      // 568000
receipts.last.effectiveModel;   // "claude-haiku-4-5"
receipts.total.costUsd;         // "0.001170000" — the whole session
receipts.total.turns;           // turns that disclosed a cost

The collector reads headers only, never the body — the body is a single-use stream that belongs to the caller. Two guarantees: the running total stays exact after the retention cap drops old receipts from all (summed on arrival, not recomputed), and a throwing onReceipt callback cannot break the request or corrupt the total.

SpendBudget: a session ceiling

budget.ts
import { SpendBudget } from "conifer-sdk";

const budget = new SpendBudget(50_000_000);   // $0.05 in integer nanodollars

const openai = new OpenAI({
  baseURL: "https://api.conifer.build/v1",
  apiKey: process.env.CONIFER_API_KEY,
  fetch: budget.fetch,
});

budget.spentNanoUsd;      // observed on real receipts
budget.remainingNanoUsd;  // never negative
budget.exhausted;         // once true, the NEXT call throws before any request

Integer nanodollars only; a fractional or negative budget is refused rather than rounded.