GitHubBook a demoStart routing
Browse docs

Migrate from Portkey

Map Portkey's x-portkey-* headers, virtual keys, and config IDs to TokenTriage — a base-URL swap plus a header diff, what stays byte-identical, and the honest measurement you get on the first request.

Last updated

On this page

Moving off Portkey is mostly a header diff. Portkey layers its features onto an OpenAI-compatible endpoint through the x-portkey-* header family; TokenTriage layers its features onto the same OpenAI-compatible endpoint through the x-tt-* family. So the migration is: point at a new base URL, drop the x-portkey-* headers, and — only if you want them — add the smaller set of x-tt-* headers you actually use.

This guide leads with the full header map (verified against Portkey’s current docs, as of 2026-07, and TokenTriage’s proxy source), then the minimal drop-in, what stays identical, and what you get for free the moment traffic flows through.

Header & concept map

Portkey is a hosted gateway: x-portkey-api-key authenticates you to Portkey’s SaaS, a virtual key references a provider credential Portkey stores for you, and a config ID names a saved routing/caching config in Portkey’s UI. TokenTriage is a proxy you run yourself, so several of those concepts move from a per-request header to server-side config — and one word (“virtual key”) means something genuinely different on each side. The map below is honest about all three: direct equivalents, config-side moves, and headers with no TokenTriage analogue.

Portkey (as of 2026-07) What it does on Portkey TokenTriage equivalent
Base URL https://api.portkey.ai/v1 Hosted gateway endpoint (OpenAI dialect) http://<your-tt-host>:8787/v1 for the OpenAI dialect; Anthropic SDK takes the proxy root and appends /v1/messages itself
x-portkey-api-key Authenticates to your Portkey SaaS account No equivalent — TokenTriage isn’t a SaaS account. You run the proxy; auth to it is either your upstream provider key (passed through in ledger mode) or a TokenTriage virtual key
Authorization: Bearer <provider token> The upstream provider credential Unchanged in ledger mode — pass the same provider bearer through. Swap to Bearer ttv_… once you turn governance on
x-portkey-virtual-key (legacy) References a provider credential Portkey stores Different concept — no drop-in map. A TokenTriage virtual key (ttv_) is a governance bearer bound to a tenancy node, not a stored provider secret. TokenTriage never holds your provider key; you keep it (BYO). See the callout below
x-portkey-config A saved config ID (caching / fallback / load-balance) resolved in Portkey’s UI Moves to server-side config on the host (routing tiers, cache). Per request you can override the tier with x-tt-tier and cache behavior with x-tt-cache; there is no per-request config-ID header
x-portkey-provider (openai, @openai-prod) Selects the upstream provider per request No per-request provider header. The upstream is bound by run mode / config (e.g. run --anthropic) and the model string. All x-tt-* headers are stripped before forwarding, so TokenTriage never passes a provider selector upstream
x-portkey-trace-id Correlates one or more requests x-tt-trace-id — opaque, ≤128 B; the one header that groups rows into a single agent trace (top --trace <ID>)
x-portkey-metadata (JSON, _user) Custom metadata for analytics filtering First-class dimension headers: x-tt-user, x-tt-tenant, x-tt-feature, plus free-form x-tt-tag-<k>. Require ledger.dimensions.enabled: true
x-portkey-cache-force-refresh: true Bypass the cache read, write a fresh entry x-tt-cache: refresh (bypass read, allow write). Also off (bypass read + write) and no-store (serve hits, skip write)
x-portkey-cache-namespace Partition cache storage per request No per-request namespace header — cache partitioning is config-side in v1
x-portkey-request-timeout (ms) Per-request timeout No per-request timeout header in v1
x-portkey-custom-host Point at a private/custom upstream deployment No equivalent — the upstream host is set in TokenTriage config, not per request

The minimal drop-in

Two edits carry the traffic: change the base URL, and drop the x-portkey-* default headers. In ledger mode the Authorization header stays exactly as it was — your provider key, passed straight through.

from openai import OpenAI

# Before (Portkey):
# client = OpenAI(
#     base_url="https://api.portkey.ai/v1",
#     default_headers={
#         "x-portkey-api-key": PORTKEY_API_KEY,
#         "x-portkey-virtual-key": "openai-prod",
#         "x-portkey-config": "cfg-abc123",
#     },
# )

# After (TokenTriage) — one base_url, no x-portkey-* headers:
client = OpenAI(
    base_url="http://127.0.0.1:8787/v1",   # the only required change
    api_key=OPENAI_API_KEY,                 # your provider key, unchanged in ledger mode
)

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

curl

$ # Before (Portkey):
$ #   curl https://api.portkey.ai/v1/chat/completions \
$ #     -H "x-portkey-api-key: $PORTKEY_API_KEY" \
$ #     -H "x-portkey-virtual-key: openai-prod" \
$ #     -H "x-portkey-config: cfg-abc123" \
$ #     -H "Authorization: Bearer $OPENAI_API_KEY" ...
$
$ # After (TokenTriage) — new base URL, drop every x-portkey-* header:
$ 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"}]}'

Optionally re-add just the x-tt-* headers you were actually using — most Portkey migrations map x-portkey-trace-idx-tt-trace-id and x-portkey-metadatax-tt-user / x-tt-tenant / x-tt-feature, and nothing else:

$ curl http://127.0.0.1:8787/v1/chat/completions \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -H "x-tt-trace-id: run-a1b2c3" \
    -H "x-tt-tenant: acme" \
    -d '{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}'

What stays identical

The drop-in is small because the request and response contract does not change:

  • Your SDK and model strings. The OpenAI and Anthropic SDKs, your model values, and your request/response shapes are untouched. The OpenAI dialect stays at /v1/chat/completions; the Anthropic dialect stays at /v1/messages.
  • The proxied bytes. In ledger mode TokenTriage observes and costs every request and mutates nothing — streaming included — and any internal error fails open to a verbatim passthrough. The response you get is byte-for-byte what going direct would return.
  • Your provider keys. TokenTriage is BYO-key: your OpenAI / Anthropic credentials stay yours and keep working as-is. You are not moving secrets into a new vault to switch.
  • Your provider accounts and rate limits. Because the upstream call is unchanged, your provider-side quotas, billing, and org settings are exactly as before.

What you get on the first request

The moment traffic flows through the proxy — no config, still in ledger mode — you gain TokenTriage’s measurement surface:

  • Every request costed with a typed dollar. cost_usd is {usd, estimated, unpriced}, not a bare float. A request the server can’t price renders as UNPRICED and is excluded from totals and counted in the open, never silently added as $0.00.
  • A join key on every response. x-tt-request-id is stamped on every proxied request and is the join into the costed ledger row and the SDK receipt.
  • Attribution built in. Roll spend up by model, agent, tool, trace, or step-class from the terminal (tokentriage top --by agent) — no dashboard required.
  • Honest errors. Governance and proxy errors are RFC 9457 application/problem+json with a registered type, a stable code, and a request_id. See Errors & problem+json.
  • A trust ladder, not a switch. You start in ledger (observe + cost only), climb to shadow (compute the would-route decision and its counterfactual saving without touching a response), and enforce in route only once the evidence says the cheaper path is genuinely as good.