SDK
TypeScript
How to point the official openai package at Conifer.
npm install openaiInstall the package, then paste this handler into an App Router route such as app/api/chat/route.ts. Outside Next.js, keep conifer() in a shared module and call it from any server code.
import 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 });
}We document CONIFER_API_KEY as the variable name. If you prefer a drop-in setup, the OpenAI constructor also reads OPENAI_API_KEY.
export OPENAI_BASE_URL=https://api.conifer.build/v1
export OPENAI_API_KEY=$CONIFER_API_KEYStreaming
stream: true returns SSE, and tokens arrive as the model produces them. A few providers hand back the whole answer at the end instead, in the same chunk shape. The cost header is absent on a stream, so send stream_options: { include_usage: true } to get a final chunk with usage and price the token counts from the catalog.
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. Before you do, check that the model’s caps field on GET /v1/models includes tools. If it does not, Conifer returns a 400 that names the missing capability. That 400 is not a 402 and not a 429. If a successful reply has no tool_calls, 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
The official OpenAI types do not declare these fields, but you can still read them off the message. The trace is at choices[0].message.reasoning. Some models use DeepSeek’s name instead, so also check reasoning_content. Both fields also stream on choices[0].delta. Not every model emits a trace. If the field is missing, the model did not produce one.
const res = await client.chat.completions.create({
model: "claude-haiku-4-5",
max_tokens: 8192,
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 counts appear under usage.prompt_tokens_details as cached_tokens and cache_write_tokens. Both are 0 when nothing was cached, and the details object itself may be absent. Read only the usage fields that exist, and do not assume a thinking-token count unless that field is on the body. To send cache directives, use /v1/messages.
const details = res.usage?.prompt_tokens_details;
console.log(details?.cached_tokens);
console.log(details?.cache_write_tokens);Azure BYOK
First save a resource in Add Azure OpenAI. Then set model to the deployment name or azure/<deployment>. Your deployments do not appear 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 });
}The base URL is https://api.conifer.build, with no /v1 suffix. This route serves Anthropic model ids.
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. The OpenAI-compat code names are invalid_api_key, insufficient_quota, rate_limit_exceeded, model_not_found, and context_length_exceeded. A 402 stays a 402. The full table is on Errors.