skip to content
Python

SDK

Python

The official openai and anthropic packages, pointed at Conifer.


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)

CONIFER_API_KEY is the name we document. The OpenAI client also reads OPENAI_API_KEY if you set the drop-in pair:

terminal
export OPENAI_BASE_URL=https://api.conifer.build/v1
export OPENAI_API_KEY=$CONIFER_API_KEY

Streaming

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.

stream.py
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. 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.

terminal
curl -s https://api.conifer.build/v1/models \
  -H "Authorization: Bearer $CONIFER_API_KEY" \
  | jq '.data[] | {id, caps, context_window, pricing}'
tools.py
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

Official OpenAI types do not declare these fields. Read choices[0].message.reasoning, and reasoning_content when upstream used DeepSeek’s name. Pass reasoning through extra_body; the typed client drops unknown kwargs. Not every model emits a trace. An omitted field is the absence signal, not an SDK bug.

reasoning.py
res = client.chat.completions.create(
    model="claude-haiku-4-5",
    max_tokens=1024,
    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 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 the Anthropic package below.

cache.py
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

After you save a resource in Add Azure OpenAI, set model to the deployment name or azure/<deployment>.

azure.py
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

terminal
pip install anthropic
terminal
export ANTHROPIC_BASE_URL=https://api.conifer.build
export ANTHROPIC_API_KEY=$CONIFER_API_KEY
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)

Base URL is https://api.conifer.build with no /v1. ANTHROPIC_AUTH_TOKEN also works and takes precedence when both are set. Anthropic model ids only. Send cache_control breakpoints on this wire; usage reports cache_read_input_tokens and cache_creation_input_tokens.

Errors

errors.py
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: invalid_api_key, insufficient_quota, rate_limit_exceeded, model_not_found, context_length_exceeded. 402 stays 402 and arrives as a generic APIError. The full table is on Errors.