Your tools
Any client
Two facts: https://api.conifer.build and a bearer key. Every other recipe is that pair in a different dialect.
These are configuration examples. Verify the model against your authenticated catalog before sending a paid request. Client UI behavior depends on the installed version.
Mint a key at conifer.build/console#/keys. New keys start with sk-conifer-. Store it as CONIFER_API_KEY. That is the whole secret.
export CONIFER_API_KEY='sk-conifer-…'export OPENAI_BASE_URL=https://api.conifer.build/v1
export OPENAI_API_KEY=$CONIFER_API_KEYexport ANTHROPIC_BASE_URL=https://api.conifer.build
export ANTHROPIC_API_KEY=$CONIFER_API_KEYThe Anthropic base URL has no /v1 suffix. The OpenAI one does. Pick a model id from GET /v1/models with that key. Public list prices (no key) are GET /v1/catalog.
Plain HTTP
The key goes in Authorization: Bearer or x-api-key. Authorization wins if both are sent.
curl 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": 1024,
"messages": [{"role": "user", "content": "three names for a build cache"}]
}'curl https://api.conifer.build/v1/messages \
-H "x-api-key: $CONIFER_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-haiku-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "three names for a build cache"}]
}'max_tokens is required on /v1/messages. On the chat wire the server applies 4096 when it is absent.
OpenAI SDK
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 });
}pip install openaifrom openai import OpenAI
import os
client = OpenAI(
base_url=os.environ.get("OPENAI_BASE_URL", "https://api.conifer.build/v1"),
api_key=os.environ["CONIFER_API_KEY"],
)
res = client.chat.completions.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "three names for a build cache"}],
)
print(res.choices[0].message.content)Anthropic SDK
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 });
}pip install anthropicfrom anthropic import Anthropic
import os
client = Anthropic(
base_url=os.environ.get("ANTHROPIC_BASE_URL", "https://api.conifer.build"),
api_key=os.environ["CONIFER_API_KEY"],
)
res = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "three names for a build cache"}],
)
print(res.content)Any OpenAI-compatible client
Slack bots, MCP hosts, LangChain, the Vercel AI SDK, internal tools: set the base URL, set the key, pick a model id.
curl -s https://api.conifer.build/v1/models \
-H "Authorization: Bearer $CONIFER_API_KEY"from langchain_openai import ChatOpenAI
import os
llm = ChatOpenAI(
model="claude-haiku-4-5",
base_url=os.environ.get("OPENAI_BASE_URL", "https://api.conifer.build/v1"),
api_key=os.environ["CONIFER_API_KEY"],
)
print(llm.invoke("three names for a build cache").content)The Vercel snippet uses @ai-sdk/openai-compatible chatModel, which posts /v1/chat/completions. Same wire as the curl above. Official @ai-sdk/openai defaults to /v1/responses.
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText } from "ai";
export async function POST() {
const apiKey = process.env.CONIFER_API_KEY;
if (!apiKey) throw new Error("CONIFER_API_KEY is missing");
const conifer = createOpenAICompatible({
name: "conifer",
baseURL: process.env.OPENAI_BASE_URL ?? "https://api.conifer.build/v1",
apiKey,
});
const { text } = await generateText({
model: conifer.chatModel("claude-haiku-4-5"),
prompt: "three names for a build cache",
});
return Response.json({ text });
}export async function POST() {
const apiKey = process.env.CONIFER_API_KEY;
if (!apiKey) throw new Error("CONIFER_API_KEY is missing");
const res = await fetch("https://api.conifer.build/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-haiku-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "three names for a build cache" }],
}),
});
const data = await res.json();
return Response.json(data, { status: res.status });
}Slack bot
There is no Conifer Slack app. Hold the key on your server. Bolt (or any HTTP handler) posts the chat wire.
import { App } from "@slack/bolt";
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
app.message(async ({ message, say }) => {
if (message.subtype || !("text" in message) || !message.text) return;
const res = await fetch("https://api.conifer.build/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CONIFER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-haiku-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: message.text }],
}),
});
const data = await res.json();
await say(data.choices?.[0]?.message?.content ?? "no reply");
});
await app.start(Number(process.env.PORT) || 3000);MCP wrapper
There is no hosted Conifer MCP endpoint. Wrap the same HTTP wire as a tool named complete.
npm install @modelcontextprotocol/sdk@1 zodimport { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "conifer-complete", version: "1.0.0" });
server.registerTool(
"complete",
{
description: "Complete a prompt via Conifer chat completions",
inputSchema: { prompt: z.string() },
},
async ({ prompt }) => {
const res = await fetch("https://api.conifer.build/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CONIFER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-haiku-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
});
const data = await res.json();
const text = data.choices?.[0]?.message?.content ?? JSON.stringify(data);
return { content: [{ type: "text", text }] };
},
);
await server.connect(new StdioServerTransport());Run this standalone process with a TypeScript runner such as tsx, then point your MCP host at it. The published Conifer MCP serveris also available through conifer-sdk. Keep CONIFER_API_KEY in the process environment.
Named harnesses
Each page is the same two values plus that client’s own config file. Claude Code is one of them.
| Client | Where the two facts go |
|---|---|
| Claude Code | ANTHROPIC_BASE_URL + ANTHROPIC_API_KEY |
| Codex | ~/.codex/config.toml |
| Cursor | Settings → Models |
| Pi | ~/.pi/agent/models.json |
| opencode | ~/.config/opencode/opencode.json |
| Aider | OPENAI_API_BASE + OPENAI_API_KEY |
| Zed | settings.json |
| Open WebUI | OPENAI_API_BASE_URL |