SDK
Python
How to point the official openai and anthropic packages at Conifer.
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)We document CONIFER_API_KEY as the variable name. If you set the drop-in pair below, the OpenAI client 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 price the final usage chunk from the catalog when you need the figure.
stream = 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 chunk in stream:
delta = chunk.choices[0].delta
if delta and delta.content:
print(delta.content, end="", flush=True)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}'res = 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?"}],
)
print(getattr(res.choices[0].message, "tool_calls", None) or [])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. Pass reasoning through extra_body, because the typed client drops unknown kwargs. Not every model emits a trace. If the field is missing, the model did not produce one.
res = client.chat.completions.create(
model="claude-haiku-4-5",
max_tokens=8192,
extra_body={"reasoning": {"effort": "medium"}},
messages=[{"role": "user", "content": "three names for a build cache"}],
)
msg = res.choices[0].message
trace = getattr(msg, "reasoning", None) or getattr(msg, "reasoning_content", None)
if trace:
print(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 the Anthropic package below.
details = getattr(getattr(res, "usage", None), "prompt_tokens_details", None)
if details is not None:
print(getattr(details, "cached_tokens", None))
print(getattr(details, "cache_write_tokens", None))Azure BYOK
First save a resource in Add Azure OpenAI. Then set model to the deployment name or azure/<deployment>.
res = client.chat.completions.create(
model="azure/YOUR_DEPLOYMENT_NAME",
max_tokens=1024,
messages=[{"role": "user", "content": "three names for a build cache"}],
)Anthropic package
pip install anthropicexport ANTHROPIC_BASE_URL=https://api.conifer.build
export ANTHROPIC_API_KEY=$CONIFER_API_KEYfrom 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)The base URL is https://api.conifer.build, with no /v1 suffix. This route serves Anthropic model ids. Send cache_control breakpoints on this wire, and usage then reports cache_read_input_tokens and cache_creation_input_tokens.
Errors
from openai import APIError, AuthenticationError, NotFoundError, RateLimitError
try:
client.chat.completions.create(...)
except AuthenticationError as e:
print("401", e.code)
except NotFoundError as e:
print("404", e.code, e.body)
except RateLimitError as e:
print("429", e.code)
except APIError as e:
print(e.status_code, e.code, e.body)Branch on status and code. The code names are invalid_api_key, insufficient_quota, rate_limit_exceeded, model_not_found, and context_length_exceeded. A 402 stays a 402 and arrives as a plain APIStatusError, which the APIError branch catches. The full table is on Errors.