How many times have you been blocked waiting for backend APIs to be ready? Or spent hours maintaining brittle mock data that becomes stale the moment requirements change?
Enter mostlylucid.mockllmapi - a production-ready ASP.NET Core mocking platform that uses Large Language Models to generate realistic, contextually aware API responses on the fly. Instead of maintaining JSON fixtures, you get intelligent mocks that adapt to your requests and remember state across calls.
What it supports: Every protocol you need - REST, GraphQL, gRPC, SignalR, Server-Sent Events, and OpenAPI. Unlike static fixtures, responses are generated dynamically based on your request context, making multi-step workflows and complex testing scenarios trivial.
You can use mostlylucid.mockllmapi in three ways, depending on how isolated you want your dev environment to be:
📖 Full Guide: API Contexts Documentation
Traditional mock APIs have a fatal flaw: each request is independent. Get a user with ID 42, then fetch their orders, and you'll get orders for user ID 99. No consistency.
API Contexts solve this with shared memory across related requests:
// Request 1: Get a user
// Note: 'context' is a simple query parameter - no cookies or sessions needed
fetch('/api/users/123?context=checkout-session')
// Response: { id: 42, name: "Alice Smith", email: "alice@example.com" }
// Request 2: Get orders (same context parameter)
fetch('/api/orders?userId=42&context=checkout-session')
// Response: { userId: 42, customerName: "Alice Smith", items: [...] }
// Perfect! Same user, consistent data
The LLM sees previous requests in the same context and generates consistent data. This is the game-changer for multi-step workflows.
Features:
Lifecycle:
Behaviour:
Safety:
Use Cases:
dotnet add package mostlylucid.mockllmapi
// Program.cs
builder.Services.AddLLMockApi(builder.Configuration);
app.MapLLMockApi("/api/mock");
# Download from https://github.com/scottgal/LLMApi/releases
llmock serve --port 5000
📖 Complete Guide: Docker Deployment Guide
git clone https://github.com/scottgal/LLMApi.git
cd LLMApi
docker compose up -d
You need one of: Ollama, OpenAI, or LM Studio:
# Recommended: Ollama with ministral-3:3b (ultra-fast, accurate JSON generation)
ollama pull ministral-3:3b
See Ollama Models Guide for all model recommendations and comparisons.
Once running, make your first request:
curl http://localhost:5000/api/mock/users
# Response: [{"id": 1, "name": "Alice Johnson", "email": "alice@example.com"}, ...]
That's it! You now have a working mock API that generates realistic data on demand.
Here's the actual search code from this blog - this is unchanged production frontend code, no adaptations needed for the mock:
// typeahead.js from mostlylucid.net
export function typeahead() {
return {
query: '',
results: [],
search() {
fetch(`/api/search/${encodeURIComponent(this.query)}`)
.then(response => response.json())
.then(data => { this.results = data; });
}
}
}
Mock it:
# Using CLI
llmock serve --port 5000
# Query returns contextual results
curl http://localhost:5000/api/search/markdown
# LLM generates blog posts about Markdown
curl http://localhost:5000/api/search/docker
# LLM generates blog posts about Docker
Each response is unique and realistic, adapting to the query.
Beyond just generating random data, you often need precise control over the JSON structure. Shape control lets you tell the LLM exactly what structure to generate - the most powerful feature for frontend development.
# Without shape - random structure
curl http://localhost:5000/api/mock/users
# Response: { "userId": 1, "fullName": "Alice" }
# With shape - you control it
curl "http://localhost:5000/api/mock/users" \
-H 'X-Response-Shape: {"id":0,"name":"string","email":"string"}'
# Response: { "id": 1, "name": "Alice", "email": "alice@example.com" }
Three ways to specify shape:
?shape={...}X-Response-Shape: {...} (recommended){"shape": {...}}const shape = {
company: {
id: 0,
name: "string",
employees: [{
id: 0,
firstName: "string",
department: { id: 0, name: "string" },
projects: [{ id: 0, title: "string" }]
}]
}
};
fetch('/api/mock/company', {
headers: { 'X-Response-Shape': JSON.stringify(shape) }
});
interface User {
id: number;
name: string;
email: string;
}
const USER_SHAPE: Partial<User> = { id: 0, name: "", email: "" };
// Shape becomes your type definition AND mock schema!
Now let's combine shape control with API contexts to handle complex, multi-step workflows. Remember the context memory feature from earlier? Here's how it shines in real-world asynchronous operations.
This example from mostlylucid.net's translation service shows how the LLM maintains state across a complete async workflow:
# Step 1: Start translation
curl -X POST http://localhost:5000/api/translate/start?context=translate-session \
-d '{"language": "es", "markdown": "# Hello World"}'
# Response: { "taskId": "abc-123", "status": "processing" }
# Step 2: Check status (LLM remembers the task)
curl http://localhost:5000/api/translate/status/abc-123?context=translate-session
# Response: { "taskId": "abc-123", "status": "complete" }
# Step 3: Get result (same taskId!)
curl http://localhost:5000/api/translate/result/abc-123?context=translate-session
# Response: { "taskId": "abc-123", "translatedText": "# Hola Mundo" }
Notice how taskId is consistent across all requests. Context makes this possible.
So far we've focused on REST, but modern applications need more. Whether you're building with GraphQL, implementing real-time features with SignalR, or working with gRPC services, mostlylucid.mockllmapi has you covered.
Supported protocols:
📖 Guide: GraphQL Section
curl -X POST http://localhost:5000/api/mock/graphql \
-d '{"query": "{ users { id name email } }"}'
The query IS the shape - no separate schema needed.
📖 Complete Guide: gRPC Support
# Upload .proto file
curl -X POST http://localhost:5116/api/grpc-protos \
--data-binary "@user_service.proto"
# Call via JSON or binary Protobuf
curl -X POST http://localhost:5116/api/grpc/userservice/UserService/GetUser \
-d '{"user_id": 123}'
📖 Guide: SignalR Demo Guide
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub/mock")
.build();
connection.on("DataUpdate", (message) => {
console.log(message.data); // Live generated data
});
await connection.start();
await connection.invoke("SubscribeToContext", "stock-prices");
Perfect for dashboard prototyping.
📖 Guide: SSE Streaming Modes
const eventSource = new EventSource('/api/mock/stream/users');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Token:', data.chunk); // Progressive generation
};
📖 Complete Guide: OpenAPI Features
# CLI: Load any OpenAPI spec
llmock serve --spec https://petstore3.swagger.io/api/v3/openapi.json
# All endpoints become live mocks automatically
curl http://localhost:5000/petstore/pet/123
📖 Complete Guide: Tools & Actions
Sometimes you need a hybrid approach - real data from production combined with generated mock data. The pluggable tools system lets you call actual APIs during mock generation, creating incredibly realistic test scenarios.
{
"Tools": [{
"Name": "getUserData",
"Type": "http",
"HttpConfig": {
"Endpoint": "https://api.production.com/users/{userId}",
"Headers": { "Authorization": "Bearer ${PROD_API_KEY}" }
}
}]
}
curl "http://localhost:5000/api/mock/orders?useTool=getUserData&userId=123"
The mock fetches REAL user data, then the LLM generates orders using it. Extremely useful for realistic testing with hybrid mock/real workflows.
If you're building with ASP.NET Core, integration is seamless. The beauty of this approach is zero code changes to your services - you simply configure HttpClient to point at the mock during development and at the real API in production.
// Real code from mostlylucid.net
builder.Services.AddHttpClient<IMarkdownTranslatorService, MarkdownTranslatorService>(
client => {
var baseUrl = builder.Configuration["TranslationService:BaseUrl"]
?? "http://localhost:5000"; // Mock during dev
client.BaseAddress = new Uri(baseUrl);
}
);
appsettings.Development.json:
{
"TranslationService": {
"BaseUrl": "http://localhost:5000" // Mock
}
}
appsettings.Production.json:
{
"TranslationService": {
"BaseUrl": "https://api.production.com" // Real
}
}
This pattern works for any HttpClient in your application - translation services, payment gateways, external APIs, you name it.
Before we dive into advanced features, let's be clear about when this tool makes sense for your workflow.
Perfect for:
Not ideal for:
Now that you know where it fits, let's explore the advanced capabilities.
These features are optional - you can get tremendous value from the basics alone. But when you need production-grade realism at scale, these tools are here.
📖 Guide: Multiple LLM Backends
# Fast for dev
curl http://localhost:5000/api/mock/users
# High quality for demos
curl "http://localhost:5000/api/mock/users?backend=quality"
# Cloud AI for production-like
curl "http://localhost:5000/api/mock/users?backend=openai"
📖 Guide: Rate Limiting & Batching
Test how your app handles rate limits:
{
"EnableRateLimiting": true,
"RateLimitDelayRange": "500-2000"
}
# Test 429 rate limiting
curl "http://localhost:5000/api/mock/users?error=429&errorMessage=Rate%20limit%20exceeded"
# Test 503 unavailable
curl "http://localhost:5000/api/mock/users?error=503"
Supports all 4xx and 5xx codes.
# Generate and cache 10 variants
curl "http://localhost:5000/api/mock/users?shape={\"$cache\":10,\"id\":0,\"name\":\"string\"}"
Subsequent requests get instant cached responses.
📖 Package: mostlylucid.mockllmapi.Testing
All the features above are great for development, but what about automated testing? The companion testing package provides a fluent API that makes integration tests a breeze - configure mock behavior declaratively and let HttpClient do the rest.
dotnet add package mostlylucid.mockllmapi.Testing
using mostlylucid.mockllmapi.Testing;
// Create a client with a single endpoint configuration
var client = HttpClientExtensions.CreateMockLlmClient(
baseAddress: "http://localhost:5116",
pathPattern: "/users",
configure: endpoint => endpoint
.WithShape(new { id = 0, name = "", email = "" })
.WithCache(5)
);
// Make requests - configuration is automatically applied
var response = await client.GetAsync("/users");
var users = await response.Content.ReadFromJsonAsync<User[]>();
var client = HttpClientExtensions.CreateMockLlmClient(
"http://localhost:5116",
configure: handler => handler
.ForEndpoint("/users", config => config
.WithShape(new { id = 0, name = "", email = "" })
.WithCache(10))
.ForEndpoint("/posts", config => config
.WithShape(new { id = 0, title = "", content = "", authorId = 0 })
.WithCache(20))
.ForEndpoint("/error", config => config
.WithError(404, "Resource not found"))
);
// Each endpoint automatically uses its configuration
var usersResponse = await client.GetAsync("/users");
var postsResponse = await client.GetAsync("/posts");
var errorResponse = await client.GetAsync("/error"); // Returns 404
Shape Configuration:
// Using anonymous objects
.WithShape(new { id = 0, name = "", active = true })
// Using JSON strings
.WithShape("{ \"id\": 0, \"name\": \"\", \"tags\": [] }")
// Complex nested structures
.WithShape(new
{
user = new { id = 0, name = "" },
posts = new[] { new { id = 0, title = "" } }
})
Error Simulation:
// Simple error
.WithError(404)
// With custom message
.WithError(404, "User not found")
// With details
.WithError(422, "Validation failed", "Email address is invalid")
Streaming:
// Enable streaming with token-by-token output
.WithStreaming()
.WithSseMode("LlmTokens")
// Stream complete objects
.WithStreaming()
.WithSseMode("CompleteObjects")
// Stream array items individually
.WithStreaming()
.WithSseMode("ArrayItems")
Typed Client:
services.AddMockLlmHttpClient<IUserApiClient>(
baseApiPath: "/api/mock",
configure: handler => handler
.ForEndpoint("/users", config => config
.WithShape(new { id = 0, name = "", email = "" }))
);
Named Client:
services.AddMockLlmHttpClient(
name: "MockApi",
baseApiPath: "/api/mock",
configure: handler => handler
.ForEndpoint("/data", config => config
.WithShape(new { value = 0 }))
);
// Usage
var client = httpClientFactory.CreateClient("MockApi");
[Fact]
public async Task Should_Handle_User_Creation()
{
// Arrange
var client = HttpClientExtensions.CreateMockLlmClient(
"http://localhost:5116",
"/users",
config => config
.WithMethod("POST")
.WithShape(new { id = 0, name = "", email = "", createdAt = "" })
);
// Act
var newUser = new { name = "John Doe", email = "john@example.com" };
var response = await client.PostAsJsonAsync("/users", newUser);
// Assert
response.EnsureSuccessStatusCode();
var created = await response.Content.ReadFromJsonAsync<User>();
Assert.NotNull(created);
Assert.NotEqual(0, created.Id);
}
[Fact]
public async Task Should_Handle_Not_Found_Error()
{
// Arrange
var client = HttpClientExtensions.CreateMockLlmClient(
"http://localhost:5116",
"/users/999",
config => config.WithError(404, "User not found")
);
// Act
var response = await client.GetAsync("/users/999");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
The MockLlmHttpHandler is a DelegatingHandler that:
This allows you to use a real HttpClient in your tests while easily controlling mock API behavior without modifying your application code.
After working with this tool across multiple projects, here are the patterns that work best:
ministral-3:3b (3B params, 32K context) - KILLER for JSON! Ultra-fast, highly accurate, minimal RAMllama3 (8B params, 8K context) - Best balance of quality and performancemistral-nemo (12B params, 128K context) - Complex schemas and massive datasetsgemma3:4b or phi3 - Lighter alternativesFrontend development doesn't have to wait for backend APIs. mostlylucid.mockllmapi gives you:
The difference between this and traditional mocking? Your frontend works against realistic, contextually aware data from day one. No more "it worked with mock data but failed with real data" surprises.
Whether you're building a simple blog or a complex enterprise application, you'll iterate faster, test more thoroughly, and ship with confidence.
Ready to get started?
docker compose up -d
That's it. No backend required.
© 2025 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.