API Model Proxy
A lightweight, extensible proxy layer for OpenAI-compatible APIs. Drop it between your application and any OpenAI-compatible backend to intercept, log, modify, or filter requests and responses — without changing your existing client code.
Features
- OpenAI-compatible — works as a drop-in with any
openai.OpenAIclient by settingbase_url - Hook-based — override
_preprocess_requestand_postprocess_responseto customise behaviour - Full API coverage — proxies all inference endpoints (chat, completions, responses, embeddings, audio, images, moderations) with hooks; all other endpoints (models, files, fine-tuning, batches, vector stores, etc.) are forwarded transparently
- Error visibility —
_postprocess_responseis called on both success and error responses, allowing subclasses to inspect or modify errors - FastAPI + uvicorn — async, production-ready server with auto-generated docs at
/docs
Use Cases
| Use case | Approach |
|---|---|
| Request/response logging | Override both hooks to log to stdout, a file, or an external service |
| Model gating / filtering | Raise in _preprocess_request to block disallowed models or prompt patterns |
| Response caching | Cache response dicts in _postprocess_response and serve from _preprocess_request |
| Rate limiting / usage tracking | Track request counts or token usage per user in _postprocess_response |
| Prompt augmentation | Inject system messages or rewrite user messages in _preprocess_request |
| A/B testing | Route requests to different model versions conditionally in _preprocess_request |
Installation
pip install api-model-proxy
Quick Start
1. Deploy the proxy
from openai import OpenAI
from api_model_proxy import APIModelProxy
class LoggingProxy(APIModelProxy):
def _preprocess_request(self, request):
print(f"Request: {request}")
return request
def _postprocess_response(self, response):
print(f"Response: {response}")
return response
client = OpenAI(
api_key="your-api-key",
base_url="https://api.openai.com/v1", # or any OpenAI-compatible endpoint
)
proxy = LoggingProxy(client)
proxy.deploy(host="localhost", port=8000)
2. Point your client at the proxy
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000", api_key="EMPTY")
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello, world!"}]
)
print(response.choices[0].message.content)
No other changes needed — the proxy is fully transparent to the client.
Deployment
Docker
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -e .
CMD ["python", "-c", "
from openai import OpenAI
from api_model_proxy import APIModelProxy
class LoggingProxy(APIModelProxy):
def _preprocess_request(self, request):
print(f'Request: {request}')
return request
def _postprocess_response(self, response):
print(f'Response: {response}')
return response
client = OpenAI(api_key='your-api-key', base_url='https://api.openai.com/v1')
LoggingProxy(client).deploy(host='0.0.0.0', port=8000)
"]
docker-compose
version: "3.9"
services:
proxy:
build: .
ports:
- "8000:8000"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY:?required}
Run with:
OPENAI_API_KEY=sk-... docker compose up
Customisation
Subclass APIModelProxy and override either or both hooks:
| Method | Called on | Default behaviour |
|---|---|---|
_preprocess_request(request: dict) -> dict |
Every inference request, before forwarding | No-op (return as-is) |
_postprocess_response(response: dict) -> dict |
Every inference response (success and error) | No-op (return as-is) |
Example: request filtering
class FilterProxy(APIModelProxy):
_BLOCKED = {"gpt-4o", "gpt-4-turbo"}
def _preprocess_request(self, request):
if request.get("model") in self._BLOCKED:
raise ValueError(f"Model {request['model']} is not allowed.")
return request
Example: response caching
import hashlib, json
class CachingProxy(APIModelProxy):
def __init__(self, client):
super().__init__(client)
self._cache = {}
self._last_key = None
def _preprocess_request(self, request):
self._last_key = hashlib.sha256(json.dumps(request, sort_keys=True).encode()).hexdigest()
return request
def _postprocess_response(self, response):
if self._last_key:
self._cache[self._last_key] = response
return response
More examples
See the examples/ directory for ready-to-use proxy implementations:
persistant_logging_proxy.py— logs every request/response to daily JSONL or YAML filescaching_proxy.py— in-memory LRU response cache with configurable TTLfallback_proxy.py— multi-backend circuit-breaker with automatic fallbackrate_limiting_proxy.py— token-bucket rate limiter returning429 Too Many Requests
Endpoints
Inference (hooks fire)
| Method | Path |
|---|---|
| POST | /chat/completions |
| POST | /completions |
| POST | /responses |
| POST | /embeddings |
| POST | /audio/transcriptions |
| POST | /audio/translations |
| POST | /audio/speech |
| POST | /images/generations |
| POST | /images/edits |
| POST | /images/variations |
| POST | /moderations |
All paths are also available with a /v1/ prefix.
Passthrough (no hooks, forwarded verbatim)
All other endpoints — models, files, fine-tuning, batches, vector stores, evals, organisation admin, and any future OpenAI endpoints — are forwarded transparently using the upstream client's base URL and API key.
Streaming
Streaming (stream=True) is not yet supported. Requests with stream=True will receive a 501 Not Implemented response. Streaming support is planned for the next version.
Running Tests
The test suite uses pytest. Install the development dependencies and run:
pip install pytest pytest-mock
pytest tests/ -v
The tests use a mocked OpenAI client so no API key or network access is required.
Project Structure
src/api_model_proxy/
├── __init__.py # exports APIModelProxy
├── proxy.py # APIModelProxy base class
├── server.py # FastAPI app factory
└── routes/
├── chat.py # POST /chat/completions
├── completions.py # POST /completions
├── responses.py # POST /responses
├── embeddings.py # POST /embeddings
├── audio.py # POST /audio/*
├── images.py # POST /images/*
├── moderations.py # POST /moderations
└── passthrough.py # catch-all transparent proxy
License
MIT
Metadata
Release files for api-model-proxy 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| api_model_proxy-0.1.2.tar.gz | 16.4 kB | With provenance | Details for api_model_proxy-0.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| api_model_proxy-0.1.2-py3-none-any.whl | Python 3 | none | any | Details for api_model_proxy-0.1.2-py3-none-any.whl |
Total release size: 32.3 kB
Release files / api_model_proxy-0.1.2.tar.gz
| Download URL | api_model_proxy-0.1.2.tar.gz |
|---|---|
| Size | 16.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f2d0018d60416ea7f351b7c1ba8c4f4309f8b4a1607c1d0c866e416bd825ae6d
|
|
BLAKE2b-256 checksum How to use checksums |
f40e9c49c08472dfab61ce9c0f2ff955e3b8141e7862b304dd32809c91d80de3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 7, 2026.
Transparency logRelease files / api_model_proxy-0.1.2-py3-none-any.whl
| Download URL | api_model_proxy-0.1.2-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d0fd818790f70d2f52c38efba58a425c8996b9d8e6fbe81ac63716dd46a1d7f3
|
|
BLAKE2b-256 checksum How to use checksums |
9658a894ff9b9b47e00d700ab43f1c3909054270226de9d9675a7dffbaf8f311
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 7, 2026.
Transparency logSecurity for api-model-proxy 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for api_model_proxy-0.1.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 7, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.