skip to content
Any client

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.

terminal
export CONIFER_API_KEY='sk-conifer-…'
drop-in
export OPENAI_BASE_URL=https://api.conifer.build/v1
export OPENAI_API_KEY=$CONIFER_API_KEY
drop-in
export ANTHROPIC_BASE_URL=https://api.conifer.build
export ANTHROPIC_API_KEY=$CONIFER_API_KEY

The 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.

openai
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"}]
  }'
anthropic
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

terminal
npm install openai
app.ts
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 });
}
terminal
pip install openai
app.py
from 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

terminal
npm install @anthropic-ai/sdk
messages.ts
import 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 });
}
terminal
pip install anthropic
messages.py
from 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.

models
curl -s https://api.conifer.build/v1/models \
  -H "Authorization: Bearer $CONIFER_API_KEY"
langchain.py
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.

ai-sdk.ts
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 });
}
fetch.ts
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.

bolt.ts
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.

install wrapper dependencies
npm install @modelcontextprotocol/sdk@1 zod
mcp-complete.ts
import { 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.

ClientWhere the two facts go
Claude CodeANTHROPIC_BASE_URL + ANTHROPIC_API_KEY
Codex~/.codex/config.toml
CursorSettings → Models
Pi~/.pi/agent/models.json
opencode~/.config/opencode/opencode.json
AiderOPENAI_API_BASE + OPENAI_API_KEY
Zedsettings.json
Open WebUIOPENAI_API_BASE_URL