A self-hosted mock of the OpenAI, Anthropic, Azure and Gemini APIs. Same endpoints, same response
shapes, same streaming — with deterministic replies and no real key required. Change the
baseURL, keep your code.
$docker run --rm -p 3000:3000 ghcr.io/axium-lab/llm-mock
Fastest path: zero install. Or self-host in one command.
No download, no signup, no key of your own to manage. Point your SDK's baseURL at the hosted mock and start testing right now:
baseURLhttps://api.llm-mock.dev/openai/v1
Anthropic lives at /anthropic, Gemini at /gemini, Gemini Enterprise at /gemini-enterprise and Azure at /azure/openai on the same host.
Prebuilt multi-arch image on GHCR, or run from source with Bun.
# Docker
docker run --rm -p 3000:3000 ghcr.io/axium-lab/llm-mock
# From source
git clone https://github.com/axium-lab/llm-mock.git
cd llm-mock
bun install
bun start
See the API reference for every endpoint, header and error shape — one page per provider: OpenAI, Anthropic, Gemini, Gemini Enterprise, Azure OpenAI.
The official openai, @anthropic-ai/sdk and @google/genai SDKs talk to llm-mock exactly as they talk to the real APIs. No mocking libraries, no request interception.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://localhost:3000/openai/v1", // the only change in your app
apiKey: "sk-mock-key-01", // any mock key, no real secret
});
const completion = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(completion.choices[0].message.content); // "Echo: Hello!"
Same idea for Anthropic — its client appends only the request path, so the version segment belongs in the baseURL:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "sk-mock-key-01",
baseURL: "http://localhost:3000/anthropic",
});
const message = await client.messages.create({
model: "claude-opus-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }],
});
console.log(message.content[0].text); // "Echo: Hello!"
And for Gemini — note that @google/genai appends the version segment itself, so its baseUrl stops at the prefix:
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({
apiKey: "sk-mock-key-01",
httpOptions: { baseUrl: "http://localhost:3000/gemini" },
});
const response = await ai.models.generateContent({
model: "gemini-3.6-flash",
contents: "Hello!",
});
console.log(response.text); // "Echo: Hello!"
Not a stub — a faithful contract, verified in CI against each provider's official SDK.
Responses match each provider's real shapes, validated in CI with the official openai, @anthropic-ai/sdk and @google/genai SDKs as the clients.
OpenAI, Anthropic, Gemini (AI Studio), Gemini Enterprise and Azure OpenAI, each under its own prefix with its own auth scheme and error envelope.
stream: true works everywhere, in all three conventions the five providers use — including Anthropic's named event: frames.
Same request, same bytes. Snapshot-friendly, and identical on a laptop, in CI, or behind a load balancer.
Real tool_calls with arguments synthesized from your JSON Schema, streaming included — so agent loops run end to end.
Multipart uploads and the chunked Uploads API work, yet nothing is persisted: the id itself carries the file's metadata.
Invalid keys, unknown models, validation errors and content filters return each provider's exact error envelope — test your error handling too.
Need a specific answer? Send it in a request header. Nothing to register, nothing to clean up.
One Docker command, or bun install && bun start. Mock keys ship in the repo.
Multi-provider by design: each provider mounts under its own URL prefix and implements its own API contract. Here's where each one stands today.
| Provider | Prefix | Status |
|---|---|---|
| OpenAI | /openai/v1 | ✅ Supported |
| Anthropic | /anthropic | ✅ Supported |
| Gemini (AI Studio) | /gemini | ✅ Supported |
| Gemini Enterprise (ex-Vertex AI) | /gemini-enterprise | ✅ Supported |
| Azure OpenAI | /azure/openai | ✅ Supported |