The Bridge Between Worlds
If you've ever wondered how to make two systems speak to each other when they were never designed to communicate, you're not alone. This is the story of a translator—a local proxy that lets Claude Code interface with OpenAI models.
At its heart, this project solves a simple problem: Claude Code speaks Anthropic's language, but sometimes you need it to work with OpenAI's infrastructure. Enter LiteLLM and nginx, working in concert to bridge that gap.
The Architecture of Translation
Picture a request leaving Claude Code. It doesn't know where it's really going—it thinks it's talking to Anthropic. But nginx intercepts that request on port 3999, examines it, and makes a decision.
Most requests flow straight to LiteLLM on port 4000, where the real magic happens. LiteLLM takes the Anthropic-formatted request, translates it to OpenAI's format, sends it off, then translates the response back. Claude Code never knows the difference.
But there's a subtle complexity here. Claude Code also sends telemetry—logging events, usage data. These requests get routed to a separate interceptor service. It's a flask application that quietly logs everything, keeping the main translation pipeline clean.
Speaking in Tongues
The model mapping is perhaps the most elegant part of this system. When you ask for Claude Sonnet, you get GPT-4.1. Request Claude Opus, and GPT-5.2 answers. Claude Haiku becomes GPT-5.
This isn't just string replacement—it's a complete format translation. Request structures differ, response formats vary, even the way errors are reported changes between APIs. LiteLLM handles all of this invisibly.
The Three Pillars
Three services work together inside a single Docker container. nginx sits at the edge, the first thing to touch incoming requests. It's fast, it's reliable, and it's been routing web traffic since 2004.
Behind nginx, LiteLLM does the heavy lifting. It's purpose-built for exactly this kind of API translation work. And tucked away in a corner, the interceptor quietly logs telemetry events to a local file.
Each component has a single responsibility. nginx routes. LiteLLM translates. The interceptor logs. This separation keeps the system maintainable and debuggable.
Getting Started
The setup is deliberately simple. Pull the container from GitHub's registry, pass in your OpenAI API key as an environment variable, and map port 3999 to your host. That's it.
For Claude Code to cooperate, you'll need to point it at localhost:3999 and give it a dummy API key. The word 'dummy' is literal here—the proxy expects it. Your real OpenAI credentials live safely in the container's environment.
When Things Go Wrong
Debugging distributed systems is an art. When the container won't start, check the logs. When the port is busy, find the culprit with lsof. When connections refuse, verify the container is actually running.
The most common issue? Model not found errors. These happen when the model name in your request doesn't exactly match what's defined in config.yaml. Spelling matters. Case matters. The hyphen versus underscore distinction matters.