Skip to main content

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.

Python License GitHub

Features

  • OpenAI-compatible — works as a drop-in with any openai.OpenAI client by setting base_url
  • Hook-based — override _preprocess_request and _postprocess_response to 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_response is 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:

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)

Source distribution for api-model-proxy 0.1.2
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)

Table of built distributions (wheels) for api-model-proxy 0.1.2
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page