SDK
TypeScript
The official openai package, pointed at Conifer.
npm install openaiimport OpenAI from "openai";
export function conifer() {
const apiKey = process.env.CONIFER_API_KEY;
if (!apiKey) {
throw new Error("CONIFER_API_KEY is missing. Set it before calling the gateway.");
}
return new OpenAI({
baseURL: process.env.OPENAI_BASE_URL ?? "https://api.conifer.build/v1",
apiKey,
});
}
export async function POST(req: Request) {
const res = await conifer().chat.completions.create({
model: "claude-haiku-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "three names for a build cache" }],
});
return Response.json({ content: res.choices[0].message.content });
}CONIFER_API_KEY is the name we document. The OpenAI constructor also reads OPENAI_API_KEY if you prefer the drop-in:
export OPENAI_BASE_URL=https://api.conifer.build/v1
export OPENAI_API_KEY=$CONIFER_API_KEYStreaming
stream: true returns well-formed SSE. On the chat wire most models currently deliver the completion as one burst. Do not build a UI on incremental token timing. The cost arrives as a terminal conifer_receipt event; the SDK does not parse that event for you.
const stream = await client.chat.completions.create({
model: "claude-haiku-4-5",
max_tokens: 1024,
stream: true,
messages: [{ role: "user", content: "three names for a build cache" }],
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) process.stdout.write(delta.content);
}Tools
Send OpenAI-shaped tools and handle tool_calls in the reply. Check caps on GET /v1/models first for the string tools. A model without that cap is a 400 that says so. That 400 is not a 402 and not a 429. Missing tool_calls on a successful reply means the model did not call a tool.
curl -s https://api.conifer.build/v1/models \
-H "Authorization: Bearer $CONIFER_API_KEY" \
| jq '.data[] | {id, caps, context_window, pricing}'const res = await client.chat.completions.create({
model: "claude-haiku-4-5",
max_tokens: 1024,
tools: [{
type: "function",
function: {
name: "list_files",
description: "List files in a directory",
parameters: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
}],
messages: [{ role: "user", content: "what is in /tmp?" }],
});
console.log(res.choices[0].message.tool_calls ?? []);Reasoning traces
Official OpenAI types do not declare these fields. Read them off the message anyway: choices[0].message.reasoning, and reasoning_content when upstream used DeepSeek’s name. Both also stream on choices[0].delta. Not every model emits a trace. An omitted field is the absence signal, not an SDK bug.
const res = await client.chat.completions.create({
model: "claude-haiku-4-5",
max_tokens: 1024,
reasoning: { effort: "medium" },
messages: [{ role: "user", content: "three names for a build cache" }],
});
const msg = res.choices[0].message;
const trace = msg.reasoning ?? msg.reasoning_content;
if (trace) console.log(trace);Cache metadata
On the chat wire, cache lands on usage.prompt_tokens_details: cached_tokens and cache_write_tokens (0 when the object is present and nothing cached). The details object itself may be omitted. Do not assume a thinking-token count on usage unless that field is present. Cache directives themselves ride /v1/messages.
const details = res.usage?.prompt_tokens_details;
console.log(details?.cached_tokens);
console.log(details?.cache_write_tokens);Azure BYOK
After you save a resource in Add Azure OpenAI, set model to the deployment name or azure/<deployment>. Your deployments are not in GET /v1/models.
const res = await client.chat.completions.create({
model: "azure/YOUR_DEPLOYMENT_NAME",
max_tokens: 1024,
messages: [{ role: "user", content: "three names for a build cache" }],
});Anthropic package
npm install @anthropic-ai/sdkimport Anthropic from "@anthropic-ai/sdk";
export function conifer() {
const apiKey = process.env.CONIFER_API_KEY;
if (!apiKey) {
throw new Error("CONIFER_API_KEY is missing. Set it before calling the gateway.");
}
return new Anthropic({
baseURL: process.env.ANTHROPIC_BASE_URL ?? "https://api.conifer.build",
apiKey,
});
}
export async function POST(req: Request) {
const res = await conifer().messages.create({
model: "claude-haiku-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "three names for a build cache" }],
});
return Response.json({ content: res.content });
}Base URL is https://api.conifer.build with no /v1. Anthropic model ids only.
Errors
import OpenAI, { APIError } from "openai";
try {
await client.chat.completions.create({ /* … */ });
} catch (err) {
if (err instanceof APIError) {
console.error(err.status, err.code, err.error);
}
throw err;
}Branch on err.status and err.code. OpenAI-compat names: invalid_api_key, insufficient_quota, rate_limit_exceeded, model_not_found, context_length_exceeded. 402 stays 402. The full table is on Errors.