GitHubBook a demoStart routing
Browse docs

Migrate from OpenAI or Anthropic SDKs

Point the official OpenAI or Anthropic SDK at TokenTriage with a single base-URL change — nothing else in your code moves, and you get a costed ledger plus optional routing for free.

Last updated

On this page

If your app already calls the model provider through the official OpenAI or Anthropic SDK, this is the smallest possible migration: change the SDK’s base URL to point at a TokenTriage proxy. You keep the same SDK, the same calls, the same API keys — the proxy speaks each provider’s native dialect, so the request and response bytes are unchanged.

The one change

TokenTriage’s data plane listens on 127.0.0.1:8787 by default. Each SDK gets a different base URL because each speaks a different dialect:

  • OpenAI SDK → the proxy’s /v1 prefix (http://127.0.0.1:8787/v1). The SDK appends /chat/completions, so requests land on /v1/chat/completions, which the OpenAI dialect owns.
  • Anthropic SDK → the proxy root (http://127.0.0.1:8787). The Anthropic SDK appends /v1/messages itself, and the Anthropic dialect owns /v1/messages — so you give it the root, not /v1.

Migrate the OpenAI SDK

First run a ledger proxy with an OpenAI upstream. Drop this into tokentriage.yaml:

version: 1
mode: ledger                    # observe and cost every request; mutate nothing
upstreams:
  openai:
    base_url: https://api.openai.com/v1
    dialect: openai
    api_key_env: OPENAI_API_KEY
$ tokentriage run -c tokentriage.yaml

Then change one line in your app.

from openai import OpenAI

# BEFORE: client = OpenAI()                                  # talks straight to api.openai.com
# AFTER  — the only change: base_url points at the proxy's /v1 dialect prefix.
client = OpenAI(base_url="http://127.0.0.1:8787/v1")

resp = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "Summarize this ticket."}],
)

curl

$ curl http://127.0.0.1:8787/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}'

Migrate the Anthropic SDK

For an Anthropic upstream (ideal for Claude Code), the zero-config quickstart is enough — no config file needed:

$ tokentriage run --anthropic

Then point the Anthropic SDK at the proxy root:

from anthropic import Anthropic

# BEFORE: client = Anthropic()                               # talks straight to api.anthropic.com
# AFTER  — the only change: base_url is the proxy ROOT, not /v1. The SDK appends /v1/messages.
client = Anthropic(base_url="http://127.0.0.1:8787")

resp = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    messages=[{"role": "user", "content": "Summarize this ticket."}],
)

curl

$ curl http://127.0.0.1:8787/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"claude-sonnet-4-6","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}'

What you get for the one-line change

Nothing about your responses changed — but now every request is on the ledger:

  • A costed row per request. TokenTriage stamps an x-tt-request-id response header on every proxied call; that’s the join key into a ledger row with the real dollar cost, computed from the compiled-in pricing snapshot. Roll it up from the terminal:

    $ tokentriage top --by model

    A model the server can’t price shows a - in the COST column, with a footnote stating how many rows were excluded because no price resolved — never a fabricated $0.

  • Optional trace grouping. Add x-tt-trace-id / x-tt-agent headers later to group a run or split spend by sub-agent. They’re optional — a request with no x-tt-* headers is still fully costed; it just lands as a standalone row.

  • A path to savings with zero further code change. Everything above is ledger mode: pure observation. When the evidence says a cheaper model is genuinely as good, you climb to shadow and then route — enforcing routing decisions in the proxy config, without touching your application again.