If your backend calls chat.completions.create(...) from the OpenAI SDK, or client.messages.create(...) from the Anthropic SDK, migrating to SchneeAI is deliberately small. The client-side change is one base URL and one auth header. The request body — model, messages, tools, temperature — is unchanged. So is the response shape.
This is by design. SchneeAI’s Gateway is OpenAI-compatible at the wire level: same endpoint shape (POST /v1/chat/completions), same field names, same streaming SSE format. Your SDK does not know SchneeAI exists.
This post walks the actual diff, language by language, with the gotchas that show up in real migrations. If you are still weighing whether to migrate at all, read SchneeAI vs LiteLLM first; this post assumes you have decided to.
Design-partner phase. The public
api.schneeai.comendpoint is activated at general availability. To run the examples below today, request design-partner access at [email protected] — we will issue a partner-specific endpoint and key. Replacehttps://api.schneeai.comin the examples with your partner endpoint.
1. What actually changes
Two things, and an optional third:
- Base URL:
https://api.openai.com/v1→https://api.schneeai.com/v1 - Auth header:
Authorization: Bearer sk-...→Authorization: Bearer <schneeai-key> - Model (optional):
"gpt-4o"→"auto"(or any alias in the Model Directory)
The request body is unchanged. Messages, tools, temperature, top_p, max_tokens, stream, stop, response_format — every field OpenAI’s API accepts, SchneeAI accepts at the same path with the same semantics.
This is the design philosophy: your SDK is the integration surface, not SchneeAI. We expose OpenAI’s wire shape, so the entire ecosystem of OpenAI-compatible clients — the official Python and TypeScript SDKs, LangChain’s OpenAI adapter, AutoGen, Continue, Aider, OpenWebUI — works against SchneeAI without source changes.
2. Before and after: the diff
Python — OpenAI SDK
# before — direct OpenAI call
=
=
# after — through SchneeAI
=
=
# response shape is unchanged
# SchneeAI extensions live alongside
# "auto→gpt-4o-mini" — what the router picked
# trace handle for support
Same import. Same constructor. Same method. Two arguments change. The migration review is a 30-second diff.
TypeScript — openai-node
// before
;
;
;
// after
;
;
;
curl
# before — direct OpenAI call
# after — through SchneeAI
Response shape
The response is OpenAI-shaped. SchneeAI extensions live alongside, not in place of:
Existing code that reads resp.choices[0].message.content works unchanged. Code that reads resp.model will see auto→... instead of the literal gpt-4o-mini — this is a feature (you learn what the router picked), but be aware if you have assertions that compare model strings exactly.
3. Auth: Bearer header, SchneeAI credential
The header shape is identical to direct OpenAI calls: Authorization: Bearer .... What changes is what goes in the header — instead of an OpenAI-issued sk-... key, you send a SchneeAI-issued credential scoped to your service.
During the design-partner phase, that credential is a partner API key we issue when you register. At general availability it becomes a short-lived JWT minted from your service credentials via Ory Hydra — same Bearer header, signed token instead of long-lived key. The full token-acquisition flow is in the Tutorial.
The migration-relevant point: the header shape does not change. If your existing code stores the OpenAI key in OPENAI_API_KEY, the cleanest migration is to add SCHNEEAI_API_KEY alongside, switch the SDK init to read it, and remove the OpenAI env var once traffic has moved.
4. Model aliases: auto, or pin one
The "model" field accepts three kinds of values:
"auto"— SchneeAI’s router picks based on your routing policy (latency target, budget remaining, feature tag).- A SchneeAI alias —
"gpt-4o","claude-sonnet-4-5","gemini-2-5-flash","deepseek-v3", etc. (full list in the Model Directory). - A provider-specific name — SchneeAI maps and forwards it.
The pragmatic migration path: start with your existing model pinned during the migration window, then switch to "auto" for new traffic once you trust the router.
The reason to prefer "auto" over pinning: providers ship new models monthly, pricing shifts, and a hardcoded "gpt-4o" will outlive its cost-effectiveness. Routing policy is one config edit; code changes are a deploy.
5. What you get for free after the switch
Once traffic flows through SchneeAI, you inherit the eight-hop Gateway path without writing any of it:
- Pre-call PII scanning — 17 categories; a leaked API key in a prompt never reaches the provider.
- Budget enforcement — per-service, per-tenant, per-feature caps; over-budget calls return 429 with
code: budget_exceededbefore any provider spend. - Encrypted raw retention — every prompt and response lands in the Vault (AES-256 at rest), queryable for incident response.
- Structured usage — service / tenant / user / model / tokens / cost in micro-USD, queryable via
GET /v1/usage. - Audit trail — append-only record of every request, policy change, and operational action.
- Provider abstraction — if OpenAI has an outage, your routing policy can fail over to Anthropic or DeepSeek without a code change.
These are the layers most teams build in their second or third quarter of operating AI in production. The point of SchneeAI is that you inherit them on day one — without changing your call sites.
6. Pitfalls
Migrations go wrong in predictable ways. The five we see most:
Hardcoded provider URLs
The OpenAI SDK lets you skip base_url because it defaults to https://api.openai.com/v1. Code that hardcodes the URL — bypassing the SDK — is easy to miss:
# broken — bypasses the SDK, hits OpenAI directly
=
# fixed — through SchneeAI
=
Audit for api.openai.com, api.anthropic.com, and generativelanguage.googleapis.com across your codebase before declaring the migration done.
Streaming SSE clients that buffer
The OpenAI SDK’s streaming mode works against SchneeAI unchanged. Hand-rolled SSE parsers sometimes assume the upstream buffers full events — SchneeAI streams the provider’s SSE through as it arrives, so partial-event buffering matters. If you see truncation, check your reader’s buffer handling.
Tool-calling shape parity
SchneeAI forwards the tools array to the provider as-is. OpenAI’s tool-call shape (choices[0].message.tool_calls) is preserved on the way back. If your code reads tool calls from the response, it works unchanged — but cross-check if you depend on provider-specific quirks (Anthropic’s tool-use differs in edge cases).
SDK version pinning
The OpenAI Python SDK at v1.x and the TypeScript SDK at v4.x are the versions we test against. Older SDK versions (Python 0.x, TypeScript 3.x) use a different shape and will not work without code changes. If you are on a legacy SDK, upgrade as part of the migration — it is a one-time cost.
Anthropic SDK is not OpenAI-shaped
The Anthropic SDK (@anthropic-ai/sdk in TypeScript, anthropic in Python) uses a different request shape — messages.create with max_tokens required, different system handling, different tool format. SchneeAI does not change the Anthropic SDK’s shape, so you cannot point it at SchneeAI.
If you are migrating from the Anthropic SDK specifically, you have two paths:
- Switch to the OpenAI SDK against SchneeAI — recommended. You get access to every provider SchneeAI routes to (including Anthropic models), with the OpenAI-shaped ergonomics.
- Use raw
requests.post/fetchagainstPOST /v1/chat/completions— if you want zero SDK dependency.
The Anthropic models (claude-sonnet-4-5, claude-haiku-4-5) are available through SchneeAI under those aliases. You do not lose model access by switching to the OpenAI shape.
7. Migration checklist
A practical pre-flight for cutting traffic over:
SCHNEEAI_API_KEYadded to your secret manager;OPENAI_API_KEY(orANTHROPIC_API_KEY) still in place for rollback.- All SDK initializations updated to read
base_url/baseURLfrom the SchneeAI endpoint. - Codebase grep for
api.openai.com,api.anthropic.com,generativelanguage.googleapis.com— only test fixtures or documentation should match. - Smoke test: one production-shaped request through SchneeAI, verify
resp.choices[0].message.contentparses correctly. - Verify
resp.modelshowsauto→...(or the pinned alias) — confirms the router is in the path. - Verify
resp.request_idis non-empty — your trace handle for support. - Streaming: confirm a
stream: truerequest returns SSE chunks within expected latency. - Retries: add an
Idempotency-Keyheader (UUIDv4) on retried requests so duplicates do not double-charge. See Error Handling. - Cut a low-stakes feature over first (internal tooling, batch job). Watch
GET /v1/usagefor a day. - Cut remaining features.
- Remove
OPENAI_API_KEY/ANTHROPIC_API_KEYfrom the secret manager once the rollback window closes.
For the full error model — retry rules, idempotency, status code semantics — see Error Handling.
What this buys you
The promise of an OpenAI-compatible API is that migration is a configuration change, not a rewrite. That is genuinely true for the call site: two arguments in the SDK init, one env var.
What you get on the other side is the part that is hard to build yourself: governance, billing, audit, PII scanning, encrypted retention, and a routing layer that lets you treat “which model” as a policy decision instead of a code change. The Gateway architecture walk-through covers what each hop does in detail.
If you want to try the migration on a real workload, the Tutorial has runnable examples in curl, Python, and TypeScript — and email us to request design-partner access.