SDK
The Conifer SDK
The open-source client: exact per-turn cost receipts, a hard spend ceiling, and errors you can branch on.
The Conifer SDK is developed in the open at ConiferKit/use-conifer. It is a thin client over the same public wire you could call with curl, so nothing it does is hidden from you — and if an integration is wrong, you can read exactly why and send a fix.
When to use it, and when not to
The official OpenAI and Anthropic packages work against the gateway and remain the right choice for a plain drop-in, or for an existing codebase you do not want to touch. Reach for this SDK when you want one of four things the OpenAI client structurally cannot give you:
| What you get | Why the OpenAI client cannot |
|---|---|
| The receipt | Every response carries the exact integer nanodollar cost of that call, itemized across fresh input, cache write, cache read, and output. No second stats request, no estimating from token counts. |
| A spend ceiling | maxCostNanoUsd is enforced by the gateway before any upstream call. It refuses rather than serves. |
| Named refusals | Out of credit and your-ceiling-refused are both HTTP 402 but have opposite remedies, so they are distinct error types. |
| Honest migration | The shims refuse a field Conifer cannot honor and name the replacement, instead of dropping it silently. |
Install
One call
Set CONIFER_API_KEY and construct the client with no arguments. Both languages take the same shape:
import { Conifer, textOf } from "@conifer/sdk";
const conifer = new Conifer(); // reads CONIFER_API_KEY
const answer = await conifer.chat({
model: "claude-haiku-4-5",
messages: [{ role: "user", content: "three names for a build cache" }],
maxTokens: 200,
maxCostNanoUsd: 5_000_000, // refuse this turn if it could cost over $0.005
});
console.log(textOf(answer));
console.log(answer.receipt.costUsd); // "0.001250000" — this exact callfrom conifer_sdk import Conifer, ChatRequest
conifer = Conifer() # reads CONIFER_API_KEY
answer = conifer.chat(ChatRequest(
model="claude-haiku-4-5",
messages=[{"role": "user", "content": "three names for a build cache"}],
max_tokens=200,
max_cost_nano_usd=5_000_000,
))
print(answer.text, answer.receipt.cost_usd)Streaming
On a streamed turn the cost headers are absent, in both languages. That is the wire being honest rather than a gap: the response head is sent before the first token, and the money settles after the last. Reconcile a stream from its terminal usage chunk, which the SDK always requests.
Errors you can branch on
import { Conifer, ConiferCostCeilingError, ConiferPaymentError } from "@conifer/sdk";
try {
await conifer.chat({ model: "claude-opus-5", messages, maxCostNanoUsd: 1_000 });
} catch (err) {
if (err instanceof ConiferCostCeilingError) {
// your own ceiling refused the turn — raise it, or pick a cheaper model
} else if (err instanceof ConiferPaymentError) {
// the account is out of credit — top up
}
}The wire-level names behind these types — insufficient_quota, invalid_api_key and the rest — are listed on Errors.
Fallbacks are client-side, and opt-in
Conifer admits exactly the model you name; there is no server-side fallback list. A fallback chain is therefore a series of separate billed requests, and the SDK makes you say so with allowClientFallback. Only a retryable failure advances the chain: a 402 or a bad request would answer the same way on every member.
Contribute
Integration bugs are the ones we most want reported: they are where a gateway quietly diverges from the SDKs people actually use. See Contributing.