This is the full developer documentation for mockd
# mockd
> Create realistic mock APIs in seconds. No external dependencies. Works offline.
## Why mockd?
[Section titled “Why mockd?”](#why-mockd)
Zero Dependencies
Single binary written in Go. No runtime dependencies, no configuration servers, no Docker required for basic usage.
Instant Setup
Define mocks in JSON and start serving immediately. Your first mock API runs in under 5 minutes.
Stateful Mocking
Simulate real CRUD APIs with in-memory state. Create, update, delete resources and see changes reflected across requests. Resource definitions persist, but runtime data resets to seed data on restart.
Proxy Recording
Record real API traffic through the MITM proxy and replay responses as mocks. Perfect for capturing production behavior.
Flexible Matching
Match requests by path, method, headers, query params, and body content. Use exact matches or regex patterns.
Web Dashboard
Manage mocks visually with the built-in web dashboard served from the admin port. No separate install required.
Developer First
Works offline, runs locally, integrates with any HTTP client. Use for development, testing, or CI/CD pipelines.
***
## Quick Install
[Section titled “Quick Install”](#quick-install)
* Binary (Linux/macOS)
```bash
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m); [ "$ARCH" = "x86_64" ] && ARCH="amd64"; [ "$ARCH" = "aarch64" ] && ARCH="arm64"
curl -sSL "https://github.com/getmockd/mockd/releases/latest/download/mockd-${OS}-${ARCH}" -o mockd
chmod +x mockd
./mockd --version
```
* Go Install
```bash
go install github.com/getmockd/mockd/cmd/mockd@latest
```
* Docker
```bash
docker run -p 4280:4280 -v $(pwd)/mocks:/mocks ghcr.io/getmockd/mockd
```
***
## Your First Mock
[Section titled “Your First Mock”](#your-first-mock)
Create a file called `mocks.json`:
```json
{
"mocks": [
{
"matcher": {
"method": "GET",
"path": "/api/users"
},
"response": {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
}
}
]
}
```
Start the server:
```bash
mockd start --config mocks.json
```
Test it:
```bash
curl http://localhost:4280/api/users
```
***
## Use Cases
[Section titled “Use Cases”](#use-cases)
### Development
[Section titled “Development”](#development)
Mock external APIs during frontend development. No need to wait for backend teams or deal with rate limits.
### Testing
[Section titled “Testing”](#testing)
Create predictable test fixtures for integration and end-to-end tests. Control every response.
### CI/CD
[Section titled “CI/CD”](#cicd)
Run fast, isolated tests in CI pipelines without external dependencies.
### Prototyping
[Section titled “Prototyping”](#prototyping)
Quickly prototype API designs before implementing the real backend.
***
## Learn More
[Section titled “Learn More”](#learn-more)
* **[Installation](/getting-started/installation/)** - Download and install mockd
* **[Quickstart](/getting-started/quickstart/)** - Run your first mock server
* **[Core Concepts](/getting-started/concepts/)** - Understand how mockd works
* **[Request Matching](/guides/request-matching/)** - Configure matching rules
* **[Stateful Mocking](/guides/stateful-mocking/)** - Simulate CRUD APIs
# Basic Mocks Examples
> Simple examples to get started with mockd request/response mocking.
Simple examples to get started with mockd request/response mocking. Each config below can be saved as a YAML file and loaded with `mockd serve --config mocks.yaml`.
## Hello World
[Section titled “Hello World”](#hello-world)
The simplest possible mock:
```yaml
version: "1.0"
mocks:
- id: hello
type: http
http:
matcher:
method: GET
path: /hello
response:
statusCode: 200
body: "Hello, World!"
```
Test:
```bash
curl http://localhost:4280/hello
# Hello, World!
```
## JSON Response
[Section titled “JSON Response”](#json-response)
Return JSON data:
```yaml
version: "1.0"
mocks:
- id: get-user
type: http
http:
matcher:
method: GET
path: /api/user
response:
statusCode: 200
headers:
Content-Type: application/json
body: '{"id": 1, "name": "Alice", "email": "alice@example.com", "roles": ["user", "admin"]}'
```
## Multiple Endpoints
[Section titled “Multiple Endpoints”](#multiple-endpoints)
Mock a simple API:
```yaml
version: "1.0"
mocks:
- id: list-products
name: List products
type: http
http:
matcher:
method: GET
path: /api/products
response:
statusCode: 200
body: '{"products": [{"id": 1, "name": "Widget", "price": 9.99}, {"id": 2, "name": "Gadget", "price": 19.99}]}'
- id: get-product
name: Get product
type: http
http:
matcher:
method: GET
path: /api/products/1
response:
statusCode: 200
body: '{"id": 1, "name": "Widget", "price": 9.99, "description": "A useful widget"}'
- id: product-not-found
name: Product not found
type: http
http:
matcher:
method: GET
path: /api/products/999
response:
statusCode: 404
body: '{"error": "Product not found"}'
```
## Path Parameters
[Section titled “Path Parameters”](#path-parameters)
Match dynamic paths:
```yaml
version: "1.0"
mocks:
- id: get-user-by-id
type: http
http:
matcher:
method: GET
path: /api/users/{id}
response:
statusCode: 200
body: '{"id": "{{request.pathParam.id}}", "name": "User {{request.pathParam.id}}"}'
```
Test:
```bash
curl http://localhost:4280/api/users/42
# {"id": "42", "name": "User 42"}
curl http://localhost:4280/api/users/abc
# {"id": "abc", "name": "User abc"}
```
## Query Parameters
[Section titled “Query Parameters”](#query-parameters)
Match and use query params:
```yaml
version: "1.0"
mocks:
- id: search
type: http
http:
matcher:
method: GET
path: /api/search
response:
statusCode: 200
body: '{"query": "{{request.query.q}}", "results": []}'
```
This mock matches any GET to `/api/search` regardless of query parameters. The template `{{request.query.q}}` extracts the `q` parameter from the request.
Test:
```bash
curl "http://localhost:4280/api/search?q=hello"
# {"query": "hello", "results": []}
```
## Header Matching
[Section titled “Header Matching”](#header-matching)
Require specific headers:
```yaml
version: "1.0"
mocks:
- id: authenticated
name: Authenticated request
type: http
http:
priority: 10
matcher:
method: GET
path: /api/protected
headers:
Authorization: "Bearer valid-token"
response:
statusCode: 200
body: '{"message": "Access granted"}'
- id: unauthorized
name: Unauthorized
type: http
http:
matcher:
method: GET
path: /api/protected
response:
statusCode: 401
body: '{"error": "Unauthorized"}'
```
Test:
```bash
curl http://localhost:4280/api/protected
# {"error": "Unauthorized"}
curl -H "Authorization: Bearer valid-token" http://localhost:4280/api/protected
# {"message": "Access granted"}
```
## POST with Body
[Section titled “POST with Body”](#post-with-body)
Handle POST requests:
```yaml
version: "1.0"
mocks:
- id: create-user
type: http
http:
matcher:
method: POST
path: /api/users
response:
statusCode: 201
headers:
Location: "/api/users/{{uuid}}"
body: '{"id": "{{uuid}}", "name": "{{request.body.name}}", "email": "{{request.body.email}}", "createdAt": "{{now}}"}'
```
Test:
```bash
curl -X POST http://localhost:4280/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Bob", "email": "bob@example.com"}'
# {"id": "x7k9m2", "name": "Bob", "email": "bob@example.com", "createdAt": "2024-01-15T10:30:00Z"}
```
## Simulated Delay
[Section titled “Simulated Delay”](#simulated-delay)
Add latency to responses:
```yaml
version: "1.0"
mocks:
- id: slow-endpoint
type: http
http:
matcher:
method: GET
path: /api/slow
response:
statusCode: 200
delayMs: 2000
body: '{"message": "Finally!"}'
```
## Error Responses
[Section titled “Error Responses”](#error-responses)
Mock various error scenarios:
```yaml
version: "1.0"
mocks:
- id: error-400
type: http
http:
matcher:
method: GET
path: /api/error/400
response:
statusCode: 400
body: '{"error": "Bad Request", "message": "Invalid parameters"}'
- id: error-500
type: http
http:
matcher:
method: GET
path: /api/error/500
response:
statusCode: 500
body: '{"error": "Internal Server Error", "message": "Something went wrong"}'
- id: error-503
type: http
http:
matcher:
method: GET
path: /api/error/503
response:
statusCode: 503
headers:
Retry-After: "30"
body: '{"error": "Service Unavailable"}'
```
## File-Based Response
[Section titled “File-Based Response”](#file-based-response)
Load response body from file:
```yaml
version: "1.0"
mocks:
- id: large-data
type: http
http:
matcher:
method: GET
path: /api/large-data
response:
statusCode: 200
headers:
Content-Type: application/json
bodyFile: ./responses/large-data.json
```
## Complete Example
[Section titled “Complete Example”](#complete-example)
A realistic API mock:
```yaml
version: "1.0"
mocks:
- id: health-check
name: Health check
type: http
http:
matcher:
method: GET
path: /health
response:
statusCode: 200
body: '{"status": "ok"}'
- id: list-users
name: List users
type: http
http:
matcher:
method: GET
path: /api/v1/users
response:
statusCode: 200
body: '{"data": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}], "meta": {"total": 2, "page": 1}}'
- id: get-user-by-id
name: Get user by ID
type: http
http:
matcher:
method: GET
path: /api/v1/users/{id}
response:
statusCode: 200
body: '{"id": "{{request.pathParam.id}}", "name": "User {{request.pathParam.id}}", "email": "user{{request.pathParam.id}}@example.com"}'
- id: create-user
name: Create user
type: http
http:
matcher:
method: POST
path: /api/v1/users
response:
statusCode: 201
body: '{"id": "{{uuid}}", "name": "{{request.body.name}}", "createdAt": "{{now}}"}'
- id: delete-user
name: Delete user
type: http
http:
matcher:
method: DELETE
path: /api/v1/users/{id}
response:
statusCode: 204
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [CRUD API Example](/examples/crud-api) - Stateful CRUD simulation
* [Integration Testing](/examples/integration-testing) - Using mocks in tests
* [Request Matching](/guides/request-matching) - Advanced matching
# CRUD API Example
> Demonstrates mockd's stateful mocking feature to simulate a complete CRUD (Create, Read, Update, Delete) API.
This example demonstrates mockd’s stateful mocking feature to simulate a complete CRUD (Create, Read, Update, Delete) API.
## Overview
[Section titled “Overview”](#overview)
We’ll create a mock API for a task management system with:
* Tasks (main resource)
* Users (for assignment)
* In-memory state across requests (resets to seed data on restart)
## Configuration
[Section titled “Configuration”](#configuration)
Create `tasks-api.yaml`:
```yaml
version: "1.0"
serverConfig:
httpPort: 4280
adminPort: 4290
tables:
- name: users
seedData:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
- name: tasks
seedData:
- id: "1"
title: "Setup project"
description: "Initialize the project structure"
status: "done"
assigneeId: 1
createdAt: "2024-01-10T09:00:00Z"
- id: "2"
title: "Write documentation"
description: "Create user documentation"
status: "in_progress"
assigneeId: 2
createdAt: "2024-01-11T10:00:00Z"
- id: "3"
title: "Add tests"
description: "Write unit tests"
status: "todo"
assigneeId: null
createdAt: "2024-01-12T11:00:00Z"
mocks:
- id: health-check
type: http
name: Health check
http:
matcher: { method: GET, path: /health }
response:
statusCode: 200
body: '{"status": "ok", "timestamp": "{{now}}"}'
# Users CRUD
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
- id: create-user
type: http
http:
matcher: { method: POST, path: /api/users }
response: { statusCode: 201 }
- id: get-user
type: http
http:
matcher: { method: GET, path: /api/users/{id} }
response: { statusCode: 200 }
- id: update-user
type: http
http:
matcher: { method: PUT, path: /api/users/{id} }
response: { statusCode: 200 }
- id: delete-user
type: http
http:
matcher: { method: DELETE, path: /api/users/{id} }
response: { statusCode: 200 }
# Tasks CRUD
- id: list-tasks
type: http
http:
matcher: { method: GET, path: /api/tasks }
response: { statusCode: 200 }
- id: create-task
type: http
http:
matcher: { method: POST, path: /api/tasks }
response: { statusCode: 201 }
- id: get-task
type: http
http:
matcher: { method: GET, path: /api/tasks/{id} }
response: { statusCode: 200 }
- id: update-task
type: http
http:
matcher: { method: PUT, path: /api/tasks/{id} }
response: { statusCode: 200 }
- id: patch-task
type: http
http:
matcher: { method: PATCH, path: /api/tasks/{id} }
response: { statusCode: 200 }
- id: delete-task
type: http
http:
matcher: { method: DELETE, path: /api/tasks/{id} }
response: { statusCode: 200 }
extend:
# Users
- { mock: list-users, table: users, action: list }
- { mock: create-user, table: users, action: create }
- { mock: get-user, table: users, action: get }
- { mock: update-user, table: users, action: update }
- { mock: delete-user, table: users, action: delete }
# Tasks
- { mock: list-tasks, table: tasks, action: list }
- { mock: create-task, table: tasks, action: create }
- { mock: get-task, table: tasks, action: get }
- { mock: update-task, table: tasks, action: update }
- { mock: patch-task, table: tasks, action: patch }
- { mock: delete-task, table: tasks, action: delete }
```
## Start the Server
[Section titled “Start the Server”](#start-the-server)
```bash
mockd start --config tasks-api.yaml
```
## API Operations
[Section titled “API Operations”](#api-operations)
### List All Tasks
[Section titled “List All Tasks”](#list-all-tasks)
```bash
curl http://localhost:4280/api/tasks
```
Response (paginated envelope):
```json
{
"data": [
{
"id": "1",
"title": "Setup project",
"description": "Initialize the project structure",
"status": "done",
"assigneeId": 1,
"createdAt": "2024-01-10T09:00:00Z"
},
{
"id": "2",
"title": "Write documentation",
"description": "Create user documentation",
"status": "in_progress",
"assigneeId": 2,
"createdAt": "2024-01-11T10:00:00Z"
},
{
"id": "3",
"title": "Add tests",
"description": "Write unit tests",
"status": "todo",
"assigneeId": null,
"createdAt": "2024-01-12T11:00:00Z"
}
],
"meta": {
"total": 3,
"limit": 100,
"offset": 0,
"count": 3
}
}
```
### Filter Tasks
[Section titled “Filter Tasks”](#filter-tasks)
```bash
# By status
curl "http://localhost:4280/api/tasks?status=todo"
# By assignee
curl "http://localhost:4280/api/tasks?assigneeId=1"
```
### Get Single Task
[Section titled “Get Single Task”](#get-single-task)
```bash
curl http://localhost:4280/api/tasks/2
```
Response:
```json
{
"id": "2",
"title": "Write documentation",
"description": "Create user documentation",
"status": "in_progress",
"assigneeId": 2,
"createdAt": "2024-01-11T10:00:00Z"
}
```
### Create Task
[Section titled “Create Task”](#create-task)
```bash
curl -X POST http://localhost:4280/api/tasks \
-H "Content-Type: application/json" \
-d '{
"title": "Review PR",
"description": "Review pull request #42",
"status": "todo",
"assigneeId": 1
}'
```
Response:
```json
{
"id": "4",
"title": "Review PR",
"description": "Review pull request #42",
"status": "todo",
"assigneeId": 1
}
```
### Update Task
[Section titled “Update Task”](#update-task)
```bash
curl -X PUT http://localhost:4280/api/tasks/4 \
-H "Content-Type: application/json" \
-d '{
"title": "Review PR",
"description": "Review pull request #42",
"status": "in_progress",
"assigneeId": 1
}'
```
### Partial Update (PATCH)
[Section titled “Partial Update (PATCH)”](#partial-update-patch)
```bash
curl -X PATCH http://localhost:4280/api/tasks/4 \
-H "Content-Type: application/json" \
-d '{"status": "done"}'
```
### Delete Task
[Section titled “Delete Task”](#delete-task)
```bash
curl -X DELETE http://localhost:4280/api/tasks/4
```
Response: `204 No Content`
Verify deletion:
```bash
curl http://localhost:4280/api/tasks/4
```
Response: `404 Not Found`
## User Operations
[Section titled “User Operations”](#user-operations)
### List Users
[Section titled “List Users”](#list-users)
```bash
curl http://localhost:4280/api/users
```
### Create User
[Section titled “Create User”](#create-user)
```bash
curl -X POST http://localhost:4280/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Charlie", "email": "charlie@example.com"}'
```
## State Management
[Section titled “State Management”](#state-management)
### View Registered Resources
[Section titled “View Registered Resources”](#view-registered-resources)
```bash
curl http://localhost:4290/state/resources
```
### List Items in a Resource
[Section titled “List Items in a Resource”](#list-items-in-a-resource)
```bash
curl http://localhost:4290/state/resources/tasks/items
```
### Reset State
[Section titled “Reset State”](#reset-state)
```bash
# Reset a specific resource to its seed data
curl -X POST http://localhost:4290/state/resources/tasks/reset
# Clear all items from a resource (does NOT restore seed data)
curl -X DELETE http://localhost:4290/state/resources/tasks
```
### Import Stateful Resources
[Section titled “Import Stateful Resources”](#import-stateful-resources)
Register new stateful resources via config import:
```bash
curl -X POST http://localhost:4290/config \
-H "Content-Type: application/json" \
-d '{
"config": {
"statefulResources": [{
"name": "tasks",
"idField": "id",
"seedData": [
{"id": "1", "title": "Fresh task", "status": "todo"}
]
}]
}
}'
```
Note
For new projects, the recommended approach is to use `tables` and `extend` bindings in your config file instead of `statefulResources`. Tables provide a cleaner separation between data and routing. See [Tables and Extend Bindings](/reference/configuration/#tables) for details.
## State Lifecycle
[Section titled “State Lifecycle”](#state-lifecycle)
Stateful resource **definitions** (name, seedData) are persisted to the admin file store and survive restarts. However, **runtime data** (items created, updated, or deleted via CRUD operations) is held in memory only. When the server restarts, runtime data resets to the seed data.
## Workflow Example
[Section titled “Workflow Example”](#workflow-example)
Simulate a complete workflow:
```bash
# 1. Create a new task
TASK=$(curl -s -X POST http://localhost:4280/api/tasks \
-H "Content-Type: application/json" \
-d '{"title": "New feature", "status": "todo"}')
TASK_ID=$(echo $TASK | jq -r '.id')
# 2. Assign to user
curl -X PATCH http://localhost:4280/api/tasks/$TASK_ID \
-H "Content-Type: application/json" \
-d '{"assigneeId": 1}'
# 3. Start work
curl -X PATCH http://localhost:4280/api/tasks/$TASK_ID \
-H "Content-Type: application/json" \
-d '{"status": "in_progress"}'
# 4. Complete task
curl -X PATCH http://localhost:4280/api/tasks/$TASK_ID \
-H "Content-Type: application/json" \
-d '{"status": "done"}'
# 5. Verify
curl http://localhost:4280/api/tasks/$TASK_ID
```
## Integration with Tests
[Section titled “Integration with Tests”](#integration-with-tests)
### JavaScript/Node.js
[Section titled “JavaScript/Node.js”](#javascriptnodejs)
```javascript
const API = 'http://localhost:4280/api';
describe('Tasks API', () => {
beforeEach(async () => {
// Reset all resources to seed data before each test
await fetch('http://localhost:4290/state/reset', {
method: 'POST'
});
});
test('create and list tasks', async () => {
// Create task
const createRes = await fetch(`${API}/tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Test task', status: 'todo' })
});
const task = await createRes.json();
expect(task.id).toBeDefined();
// List tasks — response is a paginated envelope with data + meta
const listRes = await fetch(`${API}/tasks`);
const result = await listRes.json();
expect(result.data).toHaveLength(4); // 3 seed + 1 created
expect(result.meta.total).toBe(4);
expect(result.data.find(t => t.title === 'Test task')).toBeDefined();
});
test('update task status', async () => {
// Create
const createRes = await fetch(`${API}/tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Task', status: 'todo' })
});
const { id } = await createRes.json();
// Update
await fetch(`${API}/tasks/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'done' })
});
// Verify
const getRes = await fetch(`${API}/tasks/${id}`);
const task = await getRes.json();
expect(task.status).toBe('done');
});
});
```
### Python
[Section titled “Python”](#python)
```python
import requests
API = 'http://localhost:4280/api'
ADMIN = 'http://localhost:4290'
def test_task_crud():
# Reset all resources to seed data
requests.post(f'{ADMIN}/state/reset')
# Create
task = requests.post(f'{API}/tasks', json={
'title': 'Test task',
'status': 'todo'
}).json()
assert 'id' in task
# Read
fetched = requests.get(f'{API}/tasks/{task["id"]}').json()
assert fetched['title'] == 'Test task'
# Update
requests.patch(f'{API}/tasks/{task["id"]}', json={
'status': 'done'
})
updated = requests.get(f'{API}/tasks/{task["id"]}').json()
assert updated['status'] == 'done'
# Delete
resp = requests.delete(f'{API}/tasks/{task["id"]}')
assert resp.status_code == 204
# Verify deleted
resp = requests.get(f'{API}/tasks/{task["id"]}')
assert resp.status_code == 404
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Integration Testing](/examples/integration-testing) - More testing patterns
* [Stateful Mocking Guide](/guides/stateful-mocking) - Full reference
* [Admin API](/reference/admin-api) - State management API
# Integration Testing Example
> Learn how to use mockd in integration tests for various languages and frameworks.
This guide shows how to use mockd in integration tests for various languages and frameworks.
## Overview
[Section titled “Overview”](#overview)
mockd is ideal for integration testing because:
* **Isolation**: Tests don’t depend on external services
* **Speed**: No network latency to real APIs
* **Predictability**: Responses are always consistent
* **Control**: Easy to simulate errors and edge cases
## Test Setup Pattern
[Section titled “Test Setup Pattern”](#test-setup-pattern)
### 1. Start mockd Before Tests
[Section titled “1. Start mockd Before Tests”](#1-start-mockd-before-tests)
```bash
# Start in background
mockd start --config test-mocks.json &
MOCKD_PID=$!
# Run tests
npm test
# Cleanup
kill $MOCKD_PID
```
### 2. Reset State Between Tests
[Section titled “2. Reset State Between Tests”](#2-reset-state-between-tests)
```bash
# Reset a specific resource to its seed data
curl -X POST http://localhost:4290/state/resources/users/reset
# Or clear a resource (remove all items, no seed data restored)
curl -X DELETE http://localhost:4290/state/resources/users
```
### 3. Point Application to mockd
[Section titled “3. Point Application to mockd”](#3-point-application-to-mockd)
```bash
API_BASE_URL=http://localhost:4280 npm test
```
## JavaScript / Node.js
[Section titled “JavaScript / Node.js”](#javascript--nodejs)
### Jest Setup
[Section titled “Jest Setup”](#jest-setup)
`jest.setup.js`:
```javascript
const { spawn } = require('child_process');
let mockdProcess;
beforeAll(async () => {
// Start mockd
mockdProcess = spawn('mockd', ['start', '--config', 'test-mocks.json'], {
stdio: 'pipe'
});
// Wait for server to be ready
await waitForServer('http://localhost:4280/health');
});
afterAll(() => {
if (mockdProcess) {
mockdProcess.kill();
}
});
beforeEach(async () => {
// Reset stateful resources to seed data
await fetch('http://localhost:4290/state/resources/users/reset', { method: 'POST' });
});
async function waitForServer(url, timeout = 5000) {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
await fetch(url);
return;
} catch {
await new Promise(r => setTimeout(r, 100));
}
}
throw new Error('Server did not start');
}
```
### Example Tests
[Section titled “Example Tests”](#example-tests)
```javascript
const API = process.env.API_BASE_URL || 'http://localhost:4280';
describe('User Service', () => {
test('fetches user by ID', async () => {
const response = await fetch(`${API}/api/users/1`);
const user = await response.json();
expect(response.status).toBe(200);
expect(user).toEqual({
id: 1,
name: 'Alice',
email: 'alice@example.com'
});
});
test('handles user not found', async () => {
const response = await fetch(`${API}/api/users/999`);
expect(response.status).toBe(404);
});
test('creates new user', async () => {
const response = await fetch(`${API}/api/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Charlie',
email: 'charlie@example.com'
})
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user.id).toBeDefined();
expect(user.name).toBe('Charlie');
});
});
```
### Testing Error Scenarios
[Section titled “Testing Error Scenarios”](#testing-error-scenarios)
```javascript
describe('Error Handling', () => {
test('handles server errors gracefully', async () => {
// Add temporary mock for error
await fetch('http://localhost:4290/mocks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'http',
http: {
matcher: { method: 'GET', path: '/api/flaky' },
response: { statusCode: 500, body: '{"error": "Internal error"}' }
}
})
});
const response = await fetch(`${API}/api/flaky`);
expect(response.status).toBe(500);
// Test your app's error handling
});
test('handles timeout', async () => {
await fetch('http://localhost:4290/mocks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'http',
http: {
matcher: { method: 'GET', path: '/api/slow' },
response: { statusCode: 200, delayMs: 10000, body: '{}' }
}
})
});
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 1000);
await expect(
fetch(`${API}/api/slow`, { signal: controller.signal })
).rejects.toThrow();
clearTimeout(timeout);
});
});
```
## Python
[Section titled “Python”](#python)
### pytest Setup
[Section titled “pytest Setup”](#pytest-setup)
`conftest.py`:
```python
import subprocess
import time
import requests
import pytest
@pytest.fixture(scope="session")
def mockd_server():
"""Start mockd server for the test session."""
proc = subprocess.Popen(
["mockd", "start", "--config", "test-mocks.json"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# Wait for server
for _ in range(50):
try:
requests.get("http://localhost:4280/health")
break
except requests.ConnectionError:
time.sleep(0.1)
else:
raise RuntimeError("mockd failed to start")
yield "http://localhost:4280"
proc.terminate()
proc.wait()
@pytest.fixture(autouse=True)
def reset_state():
"""Reset mockd stateful resources before each test."""
requests.post("http://localhost:4290/state/resources/users/reset")
requests.post("http://localhost:4290/state/resources/tasks/reset")
```
### Example Tests
[Section titled “Example Tests”](#example-tests-1)
```python
import requests
import pytest
def test_get_users(mockd_server):
response = requests.get(f"{mockd_server}/api/users")
assert response.status_code == 200
users = response.json()
assert len(users) >= 1
def test_create_user(mockd_server):
response = requests.post(
f"{mockd_server}/api/users",
json={"name": "Test User", "email": "test@example.com"}
)
assert response.status_code == 201
user = response.json()
assert "id" in user
assert user["name"] == "Test User"
def test_user_not_found(mockd_server):
response = requests.get(f"{mockd_server}/api/users/99999")
assert response.status_code == 404
class TestStatefulOperations:
def test_crud_workflow(self, mockd_server):
# Create
create_resp = requests.post(
f"{mockd_server}/api/tasks",
json={"title": "Test task", "status": "todo"}
)
assert create_resp.status_code == 201
task_id = create_resp.json()["id"]
# Read
get_resp = requests.get(f"{mockd_server}/api/tasks/{task_id}")
assert get_resp.json()["title"] == "Test task"
# Update
requests.patch(
f"{mockd_server}/api/tasks/{task_id}",
json={"status": "done"}
)
get_resp = requests.get(f"{mockd_server}/api/tasks/{task_id}")
assert get_resp.json()["status"] == "done"
# Delete
delete_resp = requests.delete(f"{mockd_server}/api/tasks/{task_id}")
assert delete_resp.status_code == 204
```
## Go
[Section titled “Go”](#go)
### Testing Setup
[Section titled “Testing Setup”](#testing-setup)
```go
package integration_test
import (
"encoding/json"
"net/http"
"os"
"os/exec"
"strings"
"testing"
"time"
)
var baseURL = "http://localhost:4280"
var adminURL = "http://localhost:4290"
func TestMain(m *testing.M) {
// Start mockd
cmd := exec.Command("mockd", "start", "--config", "test-mocks.json")
if err := cmd.Start(); err != nil {
panic(err)
}
// Wait for ready
waitForServer(baseURL + "/health")
// Run tests
code := m.Run()
// Cleanup
cmd.Process.Kill()
os.Exit(code)
}
func waitForServer(url string) {
for i := 0; i < 50; i++ {
if _, err := http.Get(url); err == nil {
return
}
time.Sleep(100 * time.Millisecond)
}
panic("server did not start")
}
func resetState(t *testing.T) {
t.Helper()
req, _ := http.NewRequest("POST", adminURL+"/state/resources/users/reset", nil)
http.DefaultClient.Do(req)
req, _ = http.NewRequest("POST", adminURL+"/state/resources/tasks/reset", nil)
http.DefaultClient.Do(req)
}
```
### Example Tests
[Section titled “Example Tests”](#example-tests-2)
```go
func TestGetUsers(t *testing.T) {
resetState(t)
resp, err := http.Get(baseURL + "/api/users")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("expected 200, got %d", resp.StatusCode)
}
var users []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&users)
if len(users) == 0 {
t.Error("expected users")
}
}
func TestCreateTask(t *testing.T) {
resetState(t)
body := strings.NewReader(`{"title": "Test", "status": "todo"}`)
resp, err := http.Post(baseURL+"/api/tasks", "application/json", body)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 201 {
t.Errorf("expected 201, got %d", resp.StatusCode)
}
var task map[string]interface{}
json.NewDecoder(resp.Body).Decode(&task)
if _, ok := task["id"]; !ok {
t.Error("expected id field")
}
}
```
## Headless Engine for CI (`mockd engine`)
[Section titled “Headless Engine for CI (mockd engine)”](#headless-engine-for-ci-mockd-engine)
For CI/CD pipelines where you don’t need an admin API, use `mockd engine` — it’s lighter, has no dependencies, and can auto-assign ports to avoid conflicts in parallel jobs.
```bash
# Auto-assign a port and capture it
mockd engine --config test-mocks.yaml --port 0 --print-url &
sleep 1
MOCKD_URL=$(curl -s http://localhost:4280 2>/dev/null && echo "http://localhost:4280")
# Run tests against the engine
API_BASE_URL=$MOCKD_URL pytest tests/
kill %1
```
**Why `mockd engine` over `mockd start`?**
* No admin API → smaller attack surface, fewer ports
* No PID files or disk persistence → clean ephemeral containers
* `--port 0` auto-assigns → no port conflicts in parallel CI jobs
* `--print-url` outputs the URL for easy programmatic capture
## Seeded Responses for Deterministic Tests
[Section titled “Seeded Responses for Deterministic Tests”](#seeded-responses-for-deterministic-tests)
Use `?_mockd_seed=` to make faker functions and random values deterministic:
```javascript
test('returns consistent user data across runs', async () => {
// Same seed = same faker output every time
const resp1 = await fetch(`${API}/api/random-user?_mockd_seed=42`);
const resp2 = await fetch(`${API}/api/random-user?_mockd_seed=42`);
const user1 = await resp1.json();
const user2 = await resp2.json();
// Identical — same seed produces same faker.name, faker.email, uuid, etc.
expect(user1).toEqual(user2);
});
```
Or set `seed` in the config for always-deterministic responses:
```yaml
mocks:
- id: test-user
type: http
http:
matcher: { method: GET, path: /api/test-user }
response:
statusCode: 200
seed: 42
body: '{"name": "{{faker.name}}", "email": "{{faker.email}}"}'
```
This eliminates flaky tests caused by random data while still using realistic faker output.
## Docker Compose
[Section titled “Docker Compose”](#docker-compose)
For CI/CD environments:
```yaml
version: '3.8'
services:
mockd:
image: ghcr.io/getmockd/mockd:latest
ports:
- "4280:4280"
- "4290:4290"
volumes:
- ./test-mocks.json:/mocks/config.json
command: start --config /mocks/config.json
app-tests:
build: .
depends_on:
- mockd
environment:
- API_BASE_URL=http://mockd:4280
command: npm test
```
## GitHub Actions
[Section titled “GitHub Actions”](#github-actions)
`.github/workflows/test.yml`:
```yaml
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mockd
run: |
curl -sSL https://github.com/getmockd/mockd/releases/latest/download/mockd-linux-amd64 -o mockd
chmod +x mockd
sudo mv mockd /usr/local/bin/
- name: Start mockd
run: |
# Option A: Full server with admin API
mockd start --config test-mocks.json &
sleep 2
# Option B: Headless engine (lighter, no admin API)
# mockd engine --config test-mocks.json --port 0 --print-url > mockd-url.txt &
# sleep 1
- name: Run tests
run: npm test
env:
API_BASE_URL: http://localhost:4280
```
## Tips
[Section titled “Tips”](#tips)
### 1. Seed Data for Tests
[Section titled “1. Seed Data for Tests”](#1-seed-data-for-tests)
```yaml
tables:
- name: users
seedData:
- id: "1"
name: "Test User"
email: "test@example.com"
mocks:
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
- id: create-user
type: http
http:
matcher: { method: POST, path: /api/users }
response: { statusCode: 201 }
- id: get-user
type: http
http:
matcher: { method: GET, path: /api/users/{id} }
response: { statusCode: 200 }
extend:
- { mock: list-users, table: users, action: list }
- { mock: create-user, table: users, action: create }
- { mock: get-user, table: users, action: get }
```
### 2. Test Different Scenarios
[Section titled “2. Test Different Scenarios”](#2-test-different-scenarios)
Create multiple config files:
* `mocks-success.json` - Happy path
* `mocks-errors.json` - Error scenarios
* `mocks-slow.json` - Timeout testing
### 3. Parallel Test Safety
[Section titled “3. Parallel Test Safety”](#3-parallel-test-safety)
Reset state in each test to ensure isolation:
```javascript
beforeEach(async () => {
await fetch('http://localhost:4290/state/resources/users/reset', { method: 'POST' });
});
```
### 4. Dynamic Mocks for Edge Cases
[Section titled “4. Dynamic Mocks for Edge Cases”](#4-dynamic-mocks-for-edge-cases)
Add mocks at runtime for specific test scenarios:
```javascript
test('handles rate limiting', async () => {
await fetch('http://localhost:4290/mocks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'http',
http: {
matcher: { method: 'GET', path: '/api/limited' },
response: {
statusCode: 429,
headers: { 'Retry-After': '60' },
body: '{"error": "Rate limited"}'
}
}
})
});
// Test your rate limit handling
});
```
## Mock Verification
[Section titled “Mock Verification”](#mock-verification)
After running your tests, verify that your code made the expected API calls. mockd tracks every request matched to a mock, so you can assert call counts and inspect invocation details.
### Assert Call Counts
[Section titled “Assert Call Counts”](#assert-call-counts)
```javascript
test('payment endpoint is called exactly once', async () => {
// Create mock
const res = await fetch('http://localhost:4290/mocks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'http',
http: {
matcher: { method: 'POST', path: '/api/payments' },
response: { statusCode: 201, body: '{"id": "pay_123"}' }
}
})
});
const { id: mockId } = await res.json();
// Run your application code...
await myApp.processOrder({ amount: 49.99 });
// Verify: payment endpoint called exactly once
const verify = await fetch(`http://localhost:4290/mocks/${mockId}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ exactly: 1 })
});
const result = await verify.json();
expect(result.passed).toBe(true);
});
```
### Inspect Invocations
[Section titled “Inspect Invocations”](#inspect-invocations)
```bash
# View every request that hit a specific mock
curl http://localhost:4290/mocks/http_a1b2c3d4/invocations
```
Returns timestamps, request headers, bodies, and matched response details for each call.
### Reset Between Tests
[Section titled “Reset Between Tests”](#reset-between-tests)
```javascript
beforeEach(async () => {
// Reset all verification data (call counts + invocation history)
await fetch('http://localhost:4290/verify', { method: 'DELETE' });
});
```
### Verification API Reference
[Section titled “Verification API Reference”](#verification-api-reference)
| Endpoint | Method | Description |
| ------------------------- | ------ | ----------------------------------------------------------- |
| `/mocks/{id}/verify` | GET | Get call count and last-called timestamp |
| `/mocks/{id}/verify` | POST | Assert call count (`exactly`, `atLeast`, `atMost`, `never`) |
| `/mocks/{id}/invocations` | GET | List all request/response pairs |
| `/mocks/{id}/invocations` | DELETE | Reset invocations for one mock |
| `/verify` | DELETE | Reset all verification data |
For full details, see the [Mock Verification guide](/guides/mock-verification/).
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Basic Mocks](/examples/basic-mocks) - Simple mock examples
* [CRUD API](/examples/crud-api) - Stateful API example
* [Mock Verification](/guides/mock-verification/) - Full verification guide
* [Admin API](/reference/admin-api) - Runtime management
# Core Concepts
> Understanding the fundamental concepts of mockd
Understanding the fundamental concepts of mockd will help you create effective mocks for any scenario.
## Multi-Protocol Architecture
[Section titled “Multi-Protocol Architecture”](#multi-protocol-architecture)
mockd is not just an HTTP mock server. It supports **seven protocols** from a single binary and configuration file:
| Protocol | Use Case | Default Port |
| -------------- | ----------------------------------------- | ----------------- |
| **HTTP/HTTPS** | REST APIs, webhooks, file downloads | 4280 |
| **GraphQL** | GraphQL queries, mutations, subscriptions | 4280 (path-based) |
| **gRPC** | Protobuf-based RPC services | 50051 |
| **WebSocket** | Real-time bidirectional communication | 4280 (path-based) |
| **MQTT** | IoT devices, sensor networks, pub/sub | 1883 |
| **SOAP/WSDL** | Enterprise XML web services | 4280 (path-based) |
| **SSE** | Server-Sent Events, AI streaming | 4280 (path-based) |
HTTP, GraphQL, WebSocket, SOAP, and SSE share the HTTP port (4280) and are differentiated by path or content type. gRPC and MQTT run on their own ports.
All protocols are configured in the same YAML/JSON file using the `type` field:
```yaml
mocks:
- id: rest-api
type: http # HTTP mock
http: { ... }
- id: graphql-api
type: graphql # GraphQL mock
graphql: { ... }
- id: grpc-service
type: grpc # gRPC mock
grpc: { ... }
- id: ws-endpoint
type: websocket # WebSocket mock
websocket: { ... }
- id: mqtt-broker
type: mqtt # MQTT mock
mqtt: { ... }
- id: soap-service
type: soap # SOAP mock
soap: { ... }
```
## What is a Mock?
[Section titled “What is a Mock?”](#what-is-a-mock)
A **mock** is a rule that defines:
1. **Request Matcher** - Which incoming requests to match
2. **Response** - What to send back when matched
```yaml
- type: http
http:
matcher: { ... } # Which requests to match
response: { ... } # What to send back
```
Each mock has a `type` (http, graphql, grpc, websocket, mqtt, soap, oauth) and a protocol-specific block. For HTTP mocks, when a request arrives, mockd checks each mock’s matcher. The first match wins and its response is returned. Other protocols use protocol-specific matching (GraphQL operations, gRPC methods, MQTT topics, etc.).
## Request Matching
[Section titled “Request Matching”](#request-matching)
The request matcher defines criteria for matching incoming requests:
```json
{
"matcher": {
"method": "GET",
"path": "/api/users",
"headers": {
"Authorization": "Bearer*"
},
"queryParams": {
"page": "1"
}
}
}
```
### Matching Fields
[Section titled “Matching Fields”](#matching-fields)
| Field | Description | Matching Type |
| -------------- | ----------------------------- | -------------------------- |
| `method` | HTTP method (GET, POST, etc.) | Exact match |
| `path` | URL path | Exact or `{param}` pattern |
| `pathPattern` | URL path regex | Full regex |
| `headers` | HTTP headers | Exact or glob (`*`) |
| `queryParams` | Query string parameters | Exact match |
| `bodyContains` | Request body substring | Substring match |
### Path Patterns
[Section titled “Path Patterns”](#path-patterns)
Paths can include dynamic segments:
```json
"/api/users/{id}" // Matches /api/users/1, /api/users/abc
"/api/{resource}/{id}" // Matches /api/posts/123
```
For full regex matching, use `pathPattern` instead of `path`:
```json
{
"pathPattern": "/api/v[0-9]+/users/.*"
}
```
### Glob Matching
[Section titled “Glob Matching”](#glob-matching)
Headers support glob patterns with `*`:
```json
{
"headers": {
"Authorization": "Bearer*",
"Content-Type": "*json*"
}
}
```
Patterns: `prefix*` (starts with), `*suffix` (ends with), `*middle*` (contains).
See [Request Matching Guide](/guides/request-matching/) for complete details.
## Response Definition
[Section titled “Response Definition”](#response-definition)
The response defines what mockd sends back:
```json
{
"response": {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"message": "Success"
},
"delayMs": 100
}
}
```
### Response Fields
[Section titled “Response Fields”](#response-fields)
| Field | Description | Default |
| ------------ | ------------------------------------------ | ------------ |
| `statusCode` | HTTP status code | 200 |
| `headers` | Response headers | `{}` |
| `body` | Response body (string or JSON) | `""` |
| `delayMs` | Simulated latency (milliseconds) | `0` |
| `seed` | Deterministic seed for faker/random output | `0` (random) |
### Body Types
[Section titled “Body Types”](#body-types)
The body can be:
* **JSON object/array** - Automatically serialized
* **String** - Sent as-is
* **File reference** - Load from file
```json
// JSON body
"body": {"users": []}
// String body
"body": "Hello"
// File reference
"body": "@./responses/users.json"
```
## Response Templating
[Section titled “Response Templating”](#response-templating)
Responses can use templates to include request data:
```json
{
"response": {
"body": {
"received_id": "{{request.pathParam.id}}",
"timestamp": "{{now}}"
}
}
}
```
### Available Variables
[Section titled “Available Variables”](#available-variables)
| Variable | Description |
| -------------------------- | --------------------- |
| `request.method` | HTTP method |
| `request.path` | Request path |
| `request.pathParam.{name}` | Path parameter value |
| `request.query.{name}` | Query parameter value |
| `request.header.{name}` | Header value |
| `request.body` | Parsed request body |
| `now` | Current timestamp |
| `uuid` | Random UUID |
See [Response Templating Guide](/guides/response-templating/) for more.
## Mock Priority
[Section titled “Mock Priority”](#mock-priority)
When multiple mocks could match a request, mockd uses this priority:
1. **More specific paths win** - `/api/users/1` beats `/api/users/{id}`
2. **More matchers win** - Path + headers beats path only
3. **Order in config** - Earlier mocks win if priority is equal
## Configuration File
[Section titled “Configuration File”](#configuration-file)
A complete configuration file:
```yaml
version: "1.0"
mocks:
- id: list-users
name: List users
type: http
http:
matcher:
method: GET
path: /api/users
response:
statusCode: 200
body: '{"users": []}'
```
### Top-Level Fields
[Section titled “Top-Level Fields”](#top-level-fields)
| Field | Description | Required |
| ------------------- | ----------------------------------------------------- | -------- |
| `version` | Config version (`"1.0"`) | Yes |
| `mocks` | Array of mock definitions | Yes |
| `tables` | Named data stores (pure data, no routing) | No |
| `extend` | Bindings from mocks to tables | No |
| `imports` | External spec imports with namespacing | No |
| `statefulResources` | Low-level CRUD resources (prefer `tables` + `extend`) | No |
## Stateful Mocking
[Section titled “Stateful Mocking”](#stateful-mocking)
mockd can simulate stateful CRUD APIs where:
* POST creates resources
* GET retrieves resources
* PUT/PATCH updates resources
* DELETE removes resources
State persists across requests during the server session.
The stateful architecture uses two concepts:
* **Tables** — Pure data stores that hold seed data. No routing is attached.
* **Extend bindings** — Wire mock endpoints to tables with a specific action (list, get, create, update, delete, custom).
```yaml
tables:
- name: users
idField: id
seedData: []
mocks:
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
extend:
- mock: list-users
table: users
action: list
```
For quick prototyping, the CLI shortcut `mockd http add --path /api/users --stateful` creates a table + mocks + extend bindings in one step.
See [Stateful Mocking Guide](/guides/stateful-mocking/).
## Workspaces
[Section titled “Workspaces”](#workspaces)
Workspaces provide isolated environments within a single mockd instance. Each workspace has its own:
* **Mocks** — route definitions scoped to the workspace
* **Stateful resources** — independent data stores per workspace
* **Request logs** — traffic filtered by workspace
The default workspace (empty string) contains everything not assigned to a specific workspace. Use `mockd workspace create` to create isolated environments, or pass `--workspace ` to scope any CLI command.
Workspaces are useful for:
* Running multiple API environments side-by-side (e.g., Stripe + Twilio)
* Test isolation (each test suite gets its own workspace)
* Team separation on shared instances
## Proxy Recording
[Section titled “Proxy Recording”](#proxy-recording)
mockd can act as a proxy to record real API traffic:
```bash
mockd proxy start
```
Recorded requests become mocks automatically.
See [Proxy Recording Guide](/guides/proxy-recording/).
## Ways to Interact with mockd
[Section titled “Ways to Interact with mockd”](#ways-to-interact-with-mockd)
mockd provides several interfaces for managing mocks:
* **CLI** — The `mockd` command-line tool for creating, listing, and managing mocks from your terminal. See the [CLI Reference](/reference/cli/).
* **Admin API** — A RESTful HTTP API on port 4290 for runtime mock management, state control, and proxy operations. See the [Admin API Reference](/reference/admin-api/).
* **MCP Server** — A Model Context Protocol server that lets AI-powered editors (Claude Code, Cursor, Windsurf) create and manage mocks directly. See the [MCP Server Guide](/guides/mcp-server/).
* **Web Dashboard** — A built-in UI served from the admin port () for visual mock management. Available in release builds. See the [Dashboard Guide](/guides/dashboard/).
## Next Steps
[Section titled “Next Steps”](#next-steps)
* **[Request Matching](/guides/request-matching/)** - Advanced matching techniques
* **[Response Templating](/guides/response-templating/)** - Dynamic responses
* **[Protocol Guides](/protocols/graphql/)** - GraphQL, gRPC, WebSocket, MQTT, SOAP, SSE
* **[CLI Reference](/reference/cli/)** - Command-line options
* **[Configuration Reference](/reference/configuration/)** - Full config schema
# Installation
> Install mockd on Linux, macOS, Windows, or via Docker
mockd is distributed as a single binary with no external dependencies. Choose the installation method that works best for your environment.
## Quick Install (Recommended)
[Section titled “Quick Install (Recommended)”](#quick-install-recommended)
The fastest way to install mockd on Linux or macOS:
```bash
curl -sSL https://get.mockd.io | sh
```
This detects your OS and architecture automatically and installs the latest release.
## Homebrew (macOS / Linux)
[Section titled “Homebrew (macOS / Linux)”](#homebrew-macos--linux)
```bash
brew install getmockd/tap/mockd
```
## Binary Download
[Section titled “Binary Download”](#binary-download)
Download the latest release for your platform:
* Linux (x86\_64)
```bash
curl -sSL https://github.com/getmockd/mockd/releases/latest/download/mockd-linux-amd64 -o mockd
chmod +x mockd
sudo mv mockd /usr/local/bin/
```
* Linux (ARM64)
```bash
curl -sSL https://github.com/getmockd/mockd/releases/latest/download/mockd-linux-arm64 -o mockd
chmod +x mockd
sudo mv mockd /usr/local/bin/
```
* macOS (Intel)
```bash
curl -sSL https://github.com/getmockd/mockd/releases/latest/download/mockd-darwin-amd64 -o mockd
chmod +x mockd
sudo mv mockd /usr/local/bin/
```
* macOS (Apple Silicon)
```bash
curl -sSL https://github.com/getmockd/mockd/releases/latest/download/mockd-darwin-arm64 -o mockd
chmod +x mockd
sudo mv mockd /usr/local/bin/
```
* Windows
```powershell
# Download from GitHub releases
Invoke-WebRequest -Uri "https://github.com/getmockd/mockd/releases/latest/download/mockd-windows-amd64.exe" -OutFile "mockd.exe"
# Add to PATH or move to a directory in your PATH
```
Verify the installation:
```bash
mockd --version
```
## Windows
[Section titled “Windows”](#windows)
### Using Go Install
[Section titled “Using Go Install”](#using-go-install)
If you have Go 1.25+ installed:
```powershell
go install github.com/getmockd/mockd/cmd/mockd@latest
```
This installs mockd to your `$GOPATH/bin` directory. Make sure it’s in your `PATH`.
### Download Binary
[Section titled “Download Binary”](#download-binary)
```powershell
# Download latest release
Invoke-WebRequest -Uri "https://github.com/getmockd/mockd/releases/latest/download/mockd_windows_amd64.zip" -OutFile "mockd.zip"
# Extract
Expand-Archive -Path "mockd.zip" -DestinationPath "."
# Add to PATH (current session)
$env:PATH += ";$PWD"
# Or move to a directory in your PATH
Move-Item mockd.exe C:\Users\YourName\bin\
```
### Using Scoop
[Section titled “Using Scoop”](#using-scoop)
If you have [Scoop](https://scoop.sh/) installed:
```powershell
scoop install mockd
```
### Verify Installation
[Section titled “Verify Installation”](#verify-installation)
```powershell
mockd --version
```
## Go Install (Linux/macOS)
[Section titled “Go Install (Linux/macOS)”](#go-install-linuxmacos)
If you have Go 1.25+ installed:
```bash
go install github.com/getmockd/mockd/cmd/mockd@latest
```
This installs mockd to your `$GOPATH/bin` directory. Make sure it’s in your `PATH`.
## Docker
[Section titled “Docker”](#docker)
Pull and run the official Docker image:
Port **4280** serves mock traffic. Port **4290** is the Admin API for managing mocks at runtime.
```bash
# Pull the latest image
docker pull ghcr.io/getmockd/mockd:latest
# Run with both mock and admin ports exposed
docker run -p 4280:4280 -p 4290:4290 ghcr.io/getmockd/mockd
# Run with a local config file
docker run -p 4280:4280 -p 4290:4290 -v $(pwd)/mockd.yaml:/mockd.yaml ghcr.io/getmockd/mockd serve --config /mockd.yaml
```
### Docker Compose
[Section titled “Docker Compose”](#docker-compose)
```yaml
services:
mockd:
image: ghcr.io/getmockd/mockd:latest
ports:
- "4280:4280" # Mock server
- "4290:4290" # Admin API
volumes:
- ./mockd.yaml:/mockd.yaml
command: serve --config /mockd.yaml
```
## Build from Source
[Section titled “Build from Source”](#build-from-source)
Clone and build the project:
```bash
git clone https://github.com/getmockd/mockd.git
cd mockd
go build -o mockd ./cmd/mockd
```
## Verify Installation
[Section titled “Verify Installation”](#verify-installation-1)
After installation, verify mockd is working:
```bash
# Check version
mockd --version
# Show help
mockd --help
# Start a simple mock server (creates default config if none exists)
mockd start
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Quickstart](/getting-started/quickstart/) - Create your first mock API
* [Core Concepts](/getting-started/concepts/) - Learn how mockd works
# Quickstart
> Get your first mock API running in under 5 minutes
Get your first mock API running in under 5 minutes.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* mockd installed ([Installation Guide](/getting-started/installation/))
* A terminal
* curl or any HTTP client
## Option A: CLI-First (No Config File)
[Section titled “Option A: CLI-First (No Config File)”](#option-a-cli-first-no-config-file)
The fastest way to start — add mocks directly from the command line.
### Start an Empty Server
[Section titled “Start an Empty Server”](#start-an-empty-server)
```bash
mockd start -d
```
This starts mockd in the background on port 4280 (mock server) and 4290 (admin API).
### Add a Mock
[Section titled “Add a Mock”](#add-a-mock)
```bash
mockd add http --path /api/hello --body '{"message": "Hello, World!"}'
```
Output:
```plaintext
Created mock: http_abc123
Type: http
Method: GET
Path: /api/hello
Status: 200
```
### Test It
[Section titled “Test It”](#test-it)
```bash
curl http://localhost:4280/api/hello
```
Response:
```json
{"message": "Hello, World!"}
```
### Add More Mocks
[Section titled “Add More Mocks”](#add-more-mocks)
```bash
# POST endpoint
mockd add http -m POST --path /api/users --status 201 \
--body '{"id": 3, "message": "User created"}'
# Endpoint with delay
mockd add http --path /api/slow --delay 500 \
--body '{"message": "This took a while"}'
# List what you've created
mockd list
```
***
## Option B: YAML Config File
[Section titled “Option B: YAML Config File”](#option-b-yaml-config-file)
For version-controlled, reproducible mock setups.
### Create a Config File
[Section titled “Create a Config File”](#create-a-config-file)
Create `mockd.yaml`:
```yaml
version: "1.0"
mocks:
- id: hello-world
name: Hello World Endpoint
type: http
enabled: true
http:
matcher:
method: GET
path: /api/hello
response:
statusCode: 200
headers:
Content-Type: application/json
body: '{"message": "Hello, World!"}'
```
### Start the Server
[Section titled “Start the Server”](#start-the-server)
```bash
mockd serve --config mockd.yaml
```
You should see output like:
```plaintext
mockd server starting...
Listening on http://localhost:4280
Admin API on http://localhost:4290
Loaded 1 mock(s) from mockd.yaml
```
### Test Your Mock
[Section titled “Test Your Mock”](#test-your-mock)
```bash
curl http://localhost:4280/api/hello
```
Response:
```json
{"message": "Hello, World!"}
```
***
## Option C: Initialize a Project
[Section titled “Option C: Initialize a Project”](#option-c-initialize-a-project)
Use `mockd init` to scaffold a starter configuration:
```bash
mockd init
```
This creates a `mockd.yaml` with example mocks you can customize. Then start with:
```bash
mockd serve
```
***
## Adding More Mocks
[Section titled “Adding More Mocks”](#adding-more-mocks)
Expand your YAML config with a realistic REST API:
```yaml
version: "1.0"
mocks:
- id: get-users
name: Get Users List
type: http
enabled: true
http:
matcher:
method: GET
path: /api/users
response:
statusCode: 200
body: |
{
"users": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
]
}
- id: get-user-by-id
name: Get User by ID
type: http
enabled: true
http:
matcher:
method: GET
path: /api/users/{id}
response:
statusCode: 200
body: |
{"id": "{{request.pathParam.id}}", "name": "Dynamic User"}
- id: create-user
name: Create New User
type: http
enabled: true
http:
matcher:
method: POST
path: /api/users
response:
statusCode: 201
body: '{"id": 3, "message": "User created"}'
```
Restart the server (Ctrl+C to stop, then start again):
```bash
mockd serve --config mockd.yaml
```
Test the endpoints:
```bash
# List users
curl http://localhost:4280/api/users
# Get single user (dynamic path parameter)
curl http://localhost:4280/api/users/42
# Create user
curl -X POST http://localhost:4280/api/users
```
***
## Using Path Parameters
[Section titled “Using Path Parameters”](#using-path-parameters)
Match dynamic path segments:
```yaml
http:
matcher:
method: GET
path: /api/users/{id}
response:
statusCode: 200
body: '{"id": "{{request.pathParam.id}}", "name": "User {{request.pathParam.id}}"}'
```
This matches `/api/users/1`, `/api/users/abc`, etc.
***
## Adding Delays
[Section titled “Adding Delays”](#adding-delays)
Simulate network latency:
```yaml
http:
matcher:
method: GET
path: /api/slow
response:
statusCode: 200
delayMs: 500
body: '{"message": "This took a while"}'
```
***
## Changing the Port
[Section titled “Changing the Port”](#changing-the-port)
Use a different port:
```bash
mockd serve --config mockd.yaml --port 3000
```
***
## Beyond HTTP
[Section titled “Beyond HTTP”](#beyond-http)
mockd isn’t just for HTTP. Add other protocol mocks to the same config:
```yaml
version: "1.0"
mocks:
# HTTP mock
- id: api-hello
type: http
http:
matcher: { method: GET, path: /api/hello }
response: { statusCode: 200, body: '{"msg": "hello"}' }
# WebSocket mock
- id: ws-echo
type: websocket
websocket:
path: /ws
echoMode: true
# GraphQL mock
- id: graphql-api
type: graphql
graphql:
path: /graphql
schema: |
type Query { hello: String }
resolvers:
Query.hello:
response: "world"
```
```bash
# Start everything
mockd serve --config mockd.yaml
# Test HTTP
curl http://localhost:4280/api/hello
# Test GraphQL
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ hello }"}'
```
Web Dashboard
If you’re using a release build, open in your browser to manage mocks visually. See the [Dashboard Guide](/guides/dashboard/) for details.
Workspace Isolation
By default, all mocks live in the default workspace. To isolate mocks per project or team, use the `--workspace` flag:
```bash
mockd workspace create -n "Payment API" --use
mockd http add --path /api/charges --status 200 --body '{"id": "ch_1"}'
# These mocks are only visible in the "Payment API" workspace
```
## What’s Next?
[Section titled “What’s Next?”](#whats-next)
* **[Core Concepts](/getting-started/concepts/)** - Understand mocks, matching, and responses
* **[Request Matching](/guides/request-matching/)** - Advanced matching patterns
* **[Stateful Mocking](/guides/stateful-mocking/)** - Simulate CRUD APIs
* **[Protocol Guides](/protocols/graphql/)** - GraphQL, gRPC, WebSocket, MQTT, SOAP, SSE
* **[CLI Reference](/reference/cli/)** - All command-line options
# AI Agent Setup
> Pre-built configuration templates for Claude Code, Cursor, GitHub Copilot, and other AI assistants
mockd ships with agent configuration templates that teach AI assistants how to use mockd effectively. These templates provide your AI editor with the correct ports, commands, config syntax, and patterns — so you can say “create an API mock” and it just works.
## Why Agent Config Templates?
[Section titled “Why Agent Config Templates?”](#why-agent-config-templates)
AI assistants like Claude Code, Cursor, and GitHub Copilot read project-level instruction files to understand your tools and conventions. Without a mockd config, the AI might:
* Use port 8080 instead of 4280
* Generate invalid mock configs
* Miss useful features like `--stateful`, chaos profiles, or verification
With a config file, the AI knows mockd’s full capabilities and generates correct commands on the first try.
## Quick Setup
[Section titled “Quick Setup”](#quick-setup)
### Claude Code
[Section titled “Claude Code”](#claude-code)
Copy `CLAUDE.md` to your project:
```bash
mkdir -p .claude
curl -sSL https://raw.githubusercontent.com/getmockd/mockd/main/contrib/agent-configs/CLAUDE.md \
-o .claude/mockd.md
```
Claude Code reads all `.md` files in `.claude/` automatically.
### Cursor
[Section titled “Cursor”](#cursor)
Copy `cursor-rules.md` to your project:
```bash
mkdir -p .cursor/rules
curl -sSL https://raw.githubusercontent.com/getmockd/mockd/main/contrib/agent-configs/cursor-rules.md \
-o .cursor/rules/mockd.md
```
Cursor reads all `.md` files in `.cursor/rules/` automatically.
### GitHub Copilot
[Section titled “GitHub Copilot”](#github-copilot)
Append the mockd instructions to your Copilot config:
```bash
curl -sSL https://raw.githubusercontent.com/getmockd/mockd/main/contrib/agent-configs/copilot-instructions.md \
>> .github/copilot-instructions.md
```
## Combining with MCP
[Section titled “Combining with MCP”](#combining-with-mcp)
For the best experience, combine an agent config template with the [MCP server](/guides/mcp-server/). The config template teaches the AI about mockd concepts and syntax, while MCP gives it direct tool access to create and manage mocks without running CLI commands.
**Claude Code** — Add MCP + config:
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp"]
}
}
}
```
Plus `.claude/mockd.md` in your project root.
**Cursor** — Add MCP + rules:
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp"]
}
}
}
```
Plus `.cursor/rules/mockd.md` in your project root.
## What’s In the Templates
[Section titled “What’s In the Templates”](#whats-in-the-templates)
Each template includes:
| Section | Purpose |
| ---------------------- | -------------------------------------------------------------------------------- |
| **Ports** | Correct defaults: 4280 (mock), 4290 (admin) |
| **CLI Reference** | Create, list, delete, import, export, verify, chaos commands |
| **Config Format** | Valid YAML structure with `type` + protocol wrapper |
| **Template Functions** | 35 faker types (case-insensitive), UUID, timestamps, request echo, random values |
| **Matching Rules** | Path patterns, header globs, body matchers |
| **MCP Tools** | All 18 tools with action parameters |
## Customizing
[Section titled “Customizing”](#customizing)
The templates are starting points. You can customize them for your project:
```markdown
## Project-Specific Mocks
This project mocks the Payment API (Stripe-like):
- Base path: /api/v1/payments
- Auth: Bearer token in Authorization header
- Always return idempotency-key in response headers
When creating payment mocks, use:
- faker.creditCard for card numbers (Luhn-valid)
- faker.currencyCode for currencies (ISO 4217)
- faker.price for amounts
```
## Templates Location
[Section titled “Templates Location”](#templates-location)
All templates live in the mockd repository:
```plaintext
mockd/contrib/agent-configs/
CLAUDE.md # Claude Code / Claude Desktop
cursor-rules.md # Cursor
copilot-instructions.md # GitHub Copilot
```
Browse them on GitHub: [github.com/getmockd/mockd/tree/main/contrib/agent-configs](https://github.com/getmockd/mockd/tree/main/contrib/agent-configs)
# Chaos Engineering
> Inject latency, errors, and failures to test your application's resilience
Chaos engineering lets you simulate real-world failure conditions — slow APIs, intermittent errors, service outages — so you can verify your application handles them gracefully.
## Quick Start
[Section titled “Quick Start”](#quick-start)
```bash
# Start mockd with a mock endpoint
mockd serve &
mockd http add --path /api/users --body '[{"id":1,"name":"Alice"}]'
# Enable chaos: 200ms latency + 10% error rate
mockd chaos enable --latency 200ms --error-rate 0.1 --error-code 503
# Test it — some requests will be slow, some will fail
curl http://localhost:4280/api/users
curl http://localhost:4280/api/users
curl http://localhost:4280/api/users
# Check current chaos settings
mockd chaos status
# Disable when done
mockd chaos disable
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
### Enable Chaos
[Section titled “Enable Chaos”](#enable-chaos)
```bash
mockd chaos enable [flags]
```
| Flag | Type | Default | Description |
| --------------- | ------ | ------- | --------------------------------------------------- |
| `--latency` | string | — | Random latency range (e.g., `200ms`, `100ms-500ms`) |
| `--error-rate` | float | 0 | Fraction of requests that return errors (0.0–1.0) |
| `--error-code` | int | 500 | HTTP status code for error responses |
| `--path` | string | — | Regex pattern to scope chaos to specific paths |
| `--probability` | float | 1.0 | Probability of applying chaos at all (0.0–1.0) |
### Check Status
[Section titled “Check Status”](#check-status)
```bash
mockd chaos status
```
Returns the current chaos configuration (latency, error rate, affected paths).
### Disable Chaos
[Section titled “Disable Chaos”](#disable-chaos)
```bash
mockd chaos disable
```
Immediately removes all chaos injection. Requests return to normal behavior.
## Chaos Profiles
[Section titled “Chaos Profiles”](#chaos-profiles)
Instead of manually configuring latency and error rates, use one of 10 built-in chaos profiles that simulate common failure scenarios:
```bash
# Apply a profile at startup
mockd serve --chaos-profile flaky
# Or apply at runtime
mockd chaos apply flaky
# List available profiles
mockd chaos profiles
# Disable when done
mockd chaos disable
```
### Available Profiles
[Section titled “Available Profiles”](#available-profiles)
| Profile | Description | Latency | Error Rate | Bandwidth |
| -------------- | ------------------------------------ | ----------- | --------------------- | --------- |
| `slow-api` | Slow upstream API | 500ms-2s | — | — |
| `degraded` | Partially degraded service | 200ms-800ms | 5% (503) | — |
| `flaky` | Unreliable with random errors | 0-100ms | 20% (500/502/503) | — |
| `offline` | Service completely down | — | 100% (503) | — |
| `timeout` | Connection timeout simulation | 30s fixed | — | — |
| `rate-limited` | Rate-limited API | 50ms-200ms | 30% (429) | — |
| `mobile-3g` | Mobile 3G network conditions | 300ms-800ms | 2% (503) | 50 KB/s |
| `satellite` | Satellite internet simulation | 600ms-2s | 5% (503) | 20 KB/s |
| `dns-flaky` | Intermittent DNS resolution failures | — | 10% (503) | — |
| `overloaded` | Overloaded server under heavy load | 1s-5s | 15% (500/502/503/504) | 100 KB/s |
### Admin API for Profiles
[Section titled “Admin API for Profiles”](#admin-api-for-profiles)
```bash
# List all profiles
curl http://localhost:4290/chaos/profiles
# Get a specific profile
curl http://localhost:4290/chaos/profiles/flaky
# Apply a profile
curl -X POST http://localhost:4290/chaos/profiles/flaky/apply
```
## Examples
[Section titled “Examples”](#examples)
### Fixed Latency
[Section titled “Fixed Latency”](#fixed-latency)
Add a flat 200ms delay to every response:
```bash
mockd chaos enable --latency 200ms
```
### Random Latency Range
[Section titled “Random Latency Range”](#random-latency-range)
Responses take between 100ms and 500ms (uniformly random):
```bash
mockd chaos enable --latency 100ms-500ms
```
### Error Injection
[Section titled “Error Injection”](#error-injection)
10% of requests return HTTP 503:
```bash
mockd chaos enable --error-rate 0.1 --error-code 503
```
### Combined Latency + Errors
[Section titled “Combined Latency + Errors”](#combined-latency--errors)
Simulate a degraded upstream service — slow responses with occasional failures:
```bash
mockd chaos enable --latency 200ms-800ms --error-rate 0.05 --error-code 502
```
### Path-Scoped Chaos
[Section titled “Path-Scoped Chaos”](#path-scoped-chaos)
Only affect specific endpoints:
```bash
# Chaos only on /api/payments/* routes
mockd chaos enable --latency 500ms --error-rate 0.2 --error-code 500 --path "/api/payments/.*"
```
Other endpoints continue responding normally.
### Partial Application
[Section titled “Partial Application”](#partial-application)
Apply chaos to only 50% of matching requests:
```bash
mockd chaos enable --latency 1s --probability 0.5
```
## Admin API
[Section titled “Admin API”](#admin-api)
You can also manage chaos via the Admin API (port 4290):
### Get Current Settings
[Section titled “Get Current Settings”](#get-current-settings)
```bash
curl http://localhost:4290/chaos
```
### Enable Chaos
[Section titled “Enable Chaos”](#enable-chaos-1)
```bash
curl -X PUT http://localhost:4290/chaos -H 'Content-Type: application/json' -d '{
"enabled": true,
"latency": {"min": "100ms", "max": "500ms", "probability": 1.0},
"errorRate": {"probability": 0.1, "defaultCode": 503}
}'
```
### Disable Chaos
[Section titled “Disable Chaos”](#disable-chaos-1)
```bash
curl -X PUT http://localhost:4290/chaos -H 'Content-Type: application/json' -d '{
"enabled": false
}'
```
## Use Cases
[Section titled “Use Cases”](#use-cases)
### Timeout Testing
[Section titled “Timeout Testing”](#timeout-testing)
Verify your HTTP client’s timeout handling:
```bash
# Set latency higher than your client's timeout
mockd chaos enable --latency 10s
# Your app should timeout and handle it gracefully
curl --max-time 3 http://localhost:4280/api/users
# curl: (28) Operation timed out after 3000 milliseconds
```
### Circuit Breaker Testing
[Section titled “Circuit Breaker Testing”](#circuit-breaker-testing)
Verify your circuit breaker trips after enough failures:
```bash
# High error rate to trigger circuit breaker
mockd chaos enable --error-rate 0.8 --error-code 503
# Run your app and verify the circuit opens
# Then disable chaos and verify it closes
mockd chaos disable
```
### Retry Logic Testing
[Section titled “Retry Logic Testing”](#retry-logic-testing)
Verify your retry logic with intermittent failures:
```bash
# Low error rate — retries should succeed
mockd chaos enable --error-rate 0.3 --error-code 500
```
### CI/CD Resilience Tests
[Section titled “CI/CD Resilience Tests”](#cicd-resilience-tests)
Run chaos in your test pipeline to catch resilience regressions:
```bash
#!/bin/bash
# Start mockd with your API mocks
mockd serve --config mocks.yaml &
sleep 2
# Run happy-path tests first
pytest tests/integration/ || exit 1
# Enable chaos and run resilience tests
mockd chaos enable --latency 500ms --error-rate 0.1 --error-code 503
pytest tests/resilience/ || exit 1
# Clean up
mockd chaos disable
mockd stop
```
### Gradual Degradation
[Section titled “Gradual Degradation”](#gradual-degradation)
Simulate a service getting progressively worse:
```bash
# Start mild
mockd chaos enable --latency 50ms --error-rate 0.01
# Get worse
mockd chaos enable --latency 200ms --error-rate 0.05
# Service is struggling
mockd chaos enable --latency 1s --error-rate 0.2 --error-code 503
# Full outage
mockd chaos enable --error-rate 1.0 --error-code 503
# Recovery
mockd chaos disable
```
## Using —json
[Section titled “Using —json”](#using-json)
All chaos commands support `--json` for scripting:
```bash
mockd chaos status --json
```
```json
{
"enabled": true,
"latency": "200ms",
"errorRate": 0.1,
"errorCode": 503
}
```
## Stateful Fault Types
[Section titled “Stateful Fault Types”](#stateful-fault-types)
In addition to the 8 basic fault types (latency, error, timeout, corrupt body, empty response, slow body, connection reset, partial response), mockd supports 4 **stateful** fault types that maintain state across requests — simulating real-world failure patterns that evolve over time.
### Circuit Breaker
[Section titled “Circuit Breaker”](#circuit-breaker)
Simulates a circuit breaker pattern with three states: **closed** (normal), **open** (failing), and **half-open** (testing recovery).
```bash
# Configure via Admin API with rules
curl -X PUT http://localhost:4290/chaos -H 'Content-Type: application/json' -d '{
"enabled": true,
"rules": [{
"pathPattern": "/api/payments/.*",
"faults": [{
"type": "circuit_breaker",
"probability": 1.0,
"circuitBreaker": {
"failureThreshold": 5,
"recoveryTimeout": "30s",
"halfOpenRequests": 2,
"tripStatusCode": 503
}
}]
}]
}'
```
After `failureThreshold` failures, the circuit opens and all requests get `503`. After `recoveryTimeout`, it enters half-open state and allows `halfOpenRequests` test requests through. If those succeed, it closes; if they fail, it re-opens.
```bash
# Monitor circuit breaker state
mockd chaos faults
# Manually trip or reset
mockd chaos circuit-breaker trip 0:0
mockd chaos circuit-breaker reset 0:0
```
### Retry-After
[Section titled “Retry-After”](#retry-after)
Returns `429 Too Many Requests` or `503 Service Unavailable` with a `Retry-After` header. After the specified duration, requests pass through normally.
```bash
curl -X PUT http://localhost:4290/chaos -H 'Content-Type: application/json' -d '{
"enabled": true,
"rules": [{
"pathPattern": "/api/.*",
"faults": [{
"type": "retry_after",
"probability": 1.0,
"retryAfter": {
"statusCode": 429,
"retryAfterSeconds": 30
}
}]
}]
}'
```
### Progressive Degradation
[Section titled “Progressive Degradation”](#progressive-degradation)
Latency increases with each request, simulating a service that gets slower under load. Optionally starts returning errors after enough requests.
```bash
curl -X PUT http://localhost:4290/chaos -H 'Content-Type: application/json' -d '{
"enabled": true,
"rules": [{
"pathPattern": "/api/.*",
"faults": [{
"type": "progressive_degradation",
"probability": 1.0,
"progressiveDegradation": {
"initialDelay": "10ms",
"delayIncrement": "50ms",
"maxDelay": "5s",
"errorAfterRequests": 100,
"errorStatusCode": 503
}
}]
}]
}'
```
### Chunked Dribble
[Section titled “Chunked Dribble”](#chunked-dribble)
Delivers the response body in timed chunks instead of all at once, simulating slow or unstable network transfers.
```bash
curl -X PUT http://localhost:4290/chaos -H 'Content-Type: application/json' -d '{
"enabled": true,
"rules": [{
"pathPattern": "/api/.*",
"faults": [{
"type": "chunked_dribble",
"probability": 1.0,
"chunkedDribble": {
"chunkCount": 5,
"totalDuration": "2s"
}
}]
}]
}'
```
### Monitoring Stateful Faults
[Section titled “Monitoring Stateful Faults”](#monitoring-stateful-faults)
Use the CLI or MCP tools to inspect stateful fault state:
```bash
# View all stateful fault instances
mockd chaos faults
# Via MCP tool
# get_stateful_faults — returns circuit breaker states, retry-after counters, degradation progress
# manage_circuit_breaker — trip or reset circuit breakers by key
```
### Fault Type Reference
[Section titled “Fault Type Reference”](#fault-type-reference)
| Fault Type | Category | Description |
| ------------------------- | -------- | ---------------------------------------------- |
| `latency` | Basic | Adds random latency to responses |
| `error` | Basic | Returns error status codes |
| `timeout` | Basic | Simulates connection timeout |
| `corrupt_body` | Basic | Corrupts response body data |
| `empty_response` | Basic | Returns empty body |
| `slow_body` | Basic | Drip-feeds response data slowly |
| `connection_reset` | Basic | Simulates TCP connection reset |
| `partial_response` | Basic | Truncates response at random point |
| `circuit_breaker` | Stateful | Closed → open → half-open state machine |
| `retry_after` | Stateful | 429/503 with Retry-After header, auto-recovers |
| `progressive_degradation` | Stateful | Latency increases over time, optional errors |
| `chunked_dribble` | Stateful | Delivers body in timed chunks |
## Notes
[Section titled “Notes”](#notes)
* Chaos applies to **all protocols** that run over HTTP (HTTP mocks, GraphQL, SOAP, SSE). gRPC and MQTT have their own transports and are not affected by HTTP chaos.
* Latency is added **on top of** any `delayMs` configured on individual mocks.
* When both latency and error rate are enabled, the error check happens first — if a request is selected for an error, it returns immediately with the error code (no latency added).
* Chaos settings are runtime-only — they reset when mockd restarts. They are not persisted in config files.
* Stateful faults use a **rules-based** configuration with `pathPattern` matching, allowing different fault types on different routes.
* Use `get_stateful_faults` (MCP) or `mockd chaos faults` (CLI) to monitor stateful fault state machines.
# Web Dashboard
> Built-in web dashboard for managing mocks visually
mockd includes an embedded web dashboard — a full-featured Svelte UI for creating, editing, and managing mocks across all supported protocols. No separate install required. If your binary includes the dashboard, open your browser and start working.
## Accessing the Dashboard
[Section titled “Accessing the Dashboard”](#accessing-the-dashboard)
When mockd is running, open the admin port in your browser:
```plaintext
http://localhost:4290
```
No extra flags or configuration needed. If the binary was built with the dashboard included, it’s served automatically from the admin API port (default **4290**).
Release builds, Docker images, and packages installed via Homebrew, apt, or rpm all include the dashboard out of the box.
## Features
[Section titled “Features”](#features)
### Mock Management
[Section titled “Mock Management”](#mock-management)
Create, edit, and delete mocks for all 7 protocols from a single interface:
* **HTTP** — REST endpoints with method, path, headers, status, and body
* **WebSocket** — Connection handlers and message patterns
* **GraphQL** — Query and mutation mocks with operation matching
* **gRPC** — Service and RPC method mocks
* **SOAP** — WSDL-based operation mocks
* **MQTT** — Topic subscriptions and message payloads
* **OAuth** — Token endpoints and authorization flows
### Tabbed Editor
[Section titled “Tabbed Editor”](#tabbed-editor)
A VS Code-style editing experience:
* **Tabs** — Open multiple mocks simultaneously, switch between them
* **Command palette** — Press `Ctrl+K` to search commands, mocks, and actions
* **Keyboard shortcuts** — Navigate and edit without touching the mouse
### Mock Tree
[Section titled “Mock Tree”](#mock-tree)
The left sidebar organizes your mocks in a tree view:
* Folder grouping by protocol or custom structure
* Search and sort across all mocks
* Context menus with rename, duplicate, copy ID, copy JSON, and delete
### Request Log Viewer
[Section titled “Request Log Viewer”](#request-log-viewer)
Inspect incoming traffic in real time:
* Sortable columns for method, path, status, latency, and timestamp
* Near-miss debugging — see which mocks almost matched a request and why they didn’t
* Filter and search across logged requests
### Additional Views
[Section titled “Additional Views”](#additional-views)
* **Recording sessions** — Manage proxy recording sessions and captured traffic
* **Stateful resources** — View and edit CRUD resources created by stateful mocks
* **Custom operations** — Manage custom operation handlers
* **Engine status** — Monitor the running engine, active mocks, and resource usage
* **Settings** — Configure theme (light/dark) and connection settings
* **Workspaces** — Create, switch, and delete workspaces to organize mock sets
* **Import/Export** — Import from OpenAPI, Postman, HAR, WireMock, and other formats; export your mocks as YAML or JSON
## Architecture
[Section titled “Architecture”](#architecture)
The dashboard is a Svelte single-page application embedded directly into the mockd binary. It’s served as static assets from the admin API — no separate backend or additional process.
The dashboard makes API calls to `http://localhost:4290/...` — the same admin API used by the CLI and MCP server. Everything the dashboard can do, the CLI and API can do too. The dashboard is a convenience layer, not a separate system.
## Running with Docker
[Section titled “Running with Docker”](#running-with-docker)
The official Docker image includes the dashboard:
```bash
docker run -p 4280:4280 -p 4290:4290 ghcr.io/getmockd/mockd:latest
```
* Mock server: `http://localhost:4280`
* Dashboard: `http://localhost:4290`
## Building from Source
[Section titled “Building from Source”](#building-from-source)
If you build mockd from source with `go build ./cmd/mockd`, the binary works fully — CLI, admin API, MCP server, and all protocols — but does not include the dashboard UI. When you visit the admin port in a browser, you’ll see a plain-text fallback explaining that the dashboard is available in release builds.
To get the dashboard, use one of the pre-built options:
* **Homebrew:** `brew install getmockd/tap/mockd`
* **Docker:** `docker run ghcr.io/getmockd/mockd:latest`
* **Release binary:** Download from [GitHub Releases](https://github.com/getmockd/mockd/releases)
* **Install script:** `curl -fsSL https://get.mockd.io | sh`
The dashboard is free but not open source. Release binaries include the compiled dashboard assets embedded at build time.
# Building a Digital Twin
> Step-by-step guide to building a fully stateful, SDK-compatible mock of any third-party API.
## What is a Digital Twin?
[Section titled “What is a Digital Twin?”](#what-is-a-digital-twin)
A digital twin is a local mock server that mimics a third-party API with full stateful behavior — creates persist, updates modify state, deletes remove items. Unlike static mocks that return canned responses, digital twins let you run your real SDK test suites against localhost. mockd can twin any API that has an OpenAPI spec, turning hundreds of endpoints into a working local replica in minutes.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* **mockd installed** — via `go install github.com/getmockd/mockd/cmd/mockd@latest` or [binary download](https://github.com/getmockd/mockd/releases)
* **An OpenAPI spec** for your target API (Stripe: [github.com/stripe/openapi](https://github.com/stripe/openapi), Twilio: [github.com/twilio/twilio-oai](https://github.com/twilio/twilio-oai))
* **Familiarity with YAML**
## Step 1: Get the API Spec
[Section titled “Step 1: Get the API Spec”](#step-1-get-the-api-spec)
Download the OpenAPI spec for your target API. We’ll use Stripe as our running example.
```bash
# Stripe publishes their spec publicly
curl -o stripe.yaml https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.yaml
# Or if you already have a local copy
ls stripe.yaml
```
Any valid OpenAPI 3.x spec works. If your target API doesn’t publish one, you can record traffic with `mockd record` — see the [Import & Export guide](/guides/import-export/).
## Step 2: Import the Spec
[Section titled “Step 2: Import the Spec”](#step-2-import-the-spec)
Create a `mockd.yaml` configuration file. Start with just the import:
```yaml
version: "1.0"
imports:
- path: stripe.yaml
as: stripe
```
* **`path`** — relative path to the OpenAPI spec file
* **`as`** — namespace prefix; all operationIds get prefixed as `stripe.{operationId}`, preventing collisions if you import multiple APIs
mockd now serves all 587 endpoints with auto-generated schema-based responses. Everything works, but stateless. Try it:
```bash
mockd start -c mockd.yaml --no-auth -d
mockd list | head -20
# You'll see: stripe.PostCustomers, stripe.GetCustomersId, etc.
curl http://localhost:4280/v1/customers
mockd stop
```
You now have a working mock of the entire Stripe API. Let’s make the important parts stateful.
## Step 3: Identify Endpoints to Make Stateful
[Section titled “Step 3: Identify Endpoints to Make Stateful”](#step-3-identify-endpoints-to-make-stateful)
Most endpoints can stay as static mocks — you only need to bind the ones your code actually calls. Use `extend` to override specific imported endpoints with stateful behavior while leaving the rest untouched. Find the operationIds:
```bash
# List all imported mocks
mockd list | grep "stripe\."
# Common pattern: operationIds follow {Method}{Resource}
# POST /v1/customers → PostCustomers
# GET /v1/customers → GetCustomers
# GET /v1/customers/{customer} → GetCustomersCustomer
# POST /v1/customers/{customer} → PostCustomersCustomer (update!)
# DELETE /v1/customers/{customer} → DeleteCustomersCustomer
```
For a typical integration, you might only need 5-10 stateful endpoints out of hundreds.
## Step 4: Design Your Tables
[Section titled “Step 4: Design Your Tables”](#step-4-design-your-tables)
Tables are pure data stores — each one holds rows for a single resource type:
```yaml
tables:
- name: customers
idField: id
idStrategy: prefix
idPrefix: "cus_"
seedData:
- id: "cus_test"
name: "Test Customer"
email: "test@example.com"
created: 1705312200
```
Key decisions:
* **`idStrategy`** — Stripe uses prefixed IDs (`cus_`, `pi_`, `sub_`), so use `prefix`. Other options: `uuid`, `sequential`, `nanoid`
* **`idPrefix`** — Match the real API’s ID format so SDK validations pass
* **`seedData`** — Pre-populate with test fixtures. IDs in seed data are used as-is
## Step 5: Write Extend Bindings
[Section titled “Step 5: Write Extend Bindings”](#step-5-write-extend-bindings)
Connect imported endpoints to your tables. Each `extend` entry overrides one imported mock with a table-backed action:
```yaml
extend:
# List customers
- mock: stripe.GetCustomers
table: customers
action: list
# Create a customer
- mock: stripe.PostCustomers
table: customers
action: create
# Get a single customer
- mock: stripe.GetCustomersCustomer
table: customers
action: get
# Update a customer (Stripe uses POST, not PUT!)
- mock: stripe.PostCustomersCustomer
table: customers
action: patch
# Delete a customer
- mock: stripe.DeleteCustomersCustomer
table: customers
action: delete
```
POST for Updates
Stripe uses POST for both creates and updates. When the endpoint updates an existing resource (POST to a URL with an ID), use `action: patch` — this merges only the sent fields into the existing item. Using `action: update` would replace the entire item (PUT semantics), wiping out fields not included in the request.
Actions: `list` (all rows), `create` (insert + auto-ID), `get` (fetch by ID), `patch` (partial merge), `update` (full replace), `delete` (remove by ID).
## Step 6: Add Response Transforms
[Section titled “Step 6: Add Response Transforms”](#step-6-add-response-transforms)
Without transforms, mockd returns its default format. Response transforms let you match the target API’s conventions exactly — timestamps, envelopes, error shapes, and more:
```yaml
tables:
- name: customers
idField: id
idStrategy: prefix
idPrefix: "cus_"
seedData:
- id: "cus_test"
name: "Test Customer"
email: "test@example.com"
response:
# Timestamps as unix epoch, renamed to match Stripe's field names
timestamps:
format: unix
fields:
createdAt: created
updatedAt: updated
# Add object type field, hide internal tracking fields
fields:
inject:
object: customer
livemode: false
hide:
- updatedAt
# Stripe wraps lists in an envelope with metadata
list:
dataField: data
extraFields:
object: list
url: /v1/customers
has_more: false
hideMeta: true
# Stripe returns 200 for creates (not 201)
create:
status: 200
# Stripe soft-delete returns 200 with a confirmation body
delete:
status: 200
preserve: true
body:
id: "{{item.id}}"
object: customer
deleted: true
# Match Stripe's error envelope and type system
errors:
wrap: error
fields:
message: message
type: type
code: code
typeMap:
NOT_FOUND: invalid_request_error
CONFLICT: invalid_request_error
VALIDATION_ERROR: invalid_request_error
codeMap:
NOT_FOUND: resource_missing
CONFLICT: resource_already_exists
```
Each section targets a specific convention: **timestamps** (unix epoch + renamed fields), **fields** (inject `object`/`livemode`, hide internals), **list** (Stripe’s `{object, data, has_more, url}` envelope), **create** (200 instead of 201), **delete** (soft-delete with confirmation body), and **errors** (Stripe’s error envelope so SDKs parse them correctly).
YAML Anchors
When you have multiple tables, use YAML anchors to define shared transforms once. Keys starting with `x-` are ignored by mockd and work as anchor hosts:
```yaml
x-stripe-defaults: &stripe-defaults
timestamps:
format: unix
fields: { createdAt: created, updatedAt: updated }
errors:
wrap: error
# ...
tables:
- name: customers
response:
<<: *stripe-defaults
fields: { inject: { object: customer } }
- name: products
response:
<<: *stripe-defaults
fields: { inject: { object: product } }
```
See the [complete Stripe sample](https://github.com/getmockd/mockd-samples/tree/main/third-party-apis/stripe-api) for this pattern across 9 tables.
## Step 7: Test It
[Section titled “Step 7: Test It”](#step-7-test-it)
Start the server and exercise the full CRUD lifecycle:
```bash
mockd start -c mockd.yaml --no-auth -d
# Create
curl -X POST http://localhost:4280/v1/customers \
-d "name=Jenny Rosen" -d "email=jenny@example.com"
# → {"id":"cus_a1b2c3...","object":"customer","created":1705312200,...}
# List
curl http://localhost:4280/v1/customers
# → {"object":"list","data":[...],"has_more":false,"url":"/v1/customers"}
# Get
curl http://localhost:4280/v1/customers/cus_test
# Update (partial merge)
curl -X POST http://localhost:4280/v1/customers/cus_test -d "name=Updated Name"
# Verify update persisted
curl http://localhost:4280/v1/customers/cus_test
# → {"id":"cus_test","name":"Updated Name","email":"test@example.com",...}
# Delete
curl -X DELETE http://localhost:4280/v1/customers/cus_test
# → {"id":"cus_test","object":"customer","deleted":true}
# Confirm gone
curl http://localhost:4280/v1/customers/cus_test
# → {"error":{"type":"invalid_request_error","code":"resource_missing",...}}
mockd stop
```
## Step 8: Test with Your SDK
[Section titled “Step 8: Test with Your SDK”](#step-8-test-with-your-sdk)
The real payoff — point your SDK at the digital twin and run your actual test suite. No mocking libraries, no interface swapping. Your production code talks HTTP to what looks like Stripe.
### Stripe SDK (Go)
[Section titled “Stripe SDK (Go)”](#stripe-sdk-go)
```go
import (
"strings"
"testing"
"github.com/stripe/stripe-go/v82"
"github.com/stripe/stripe-go/v82/customer"
)
func init() {
stripe.Key = "sk_test_fake"
stripe.SetBackend(stripe.APIBackend, stripe.GetBackendWithConfig(
stripe.APIBackend,
&stripe.BackendConfig{
URL: stripe.String("http://localhost:4280"),
},
))
}
func TestCreateCustomer(t *testing.T) {
c, err := customer.New(&stripe.CustomerParams{
Name: stripe.String("Test User"),
Email: stripe.String("test@example.com"),
})
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(c.ID, "cus_") {
t.Errorf("expected cus_ prefix, got %s", c.ID)
}
}
```
### Stripe SDK (Python)
[Section titled “Stripe SDK (Python)”](#stripe-sdk-python)
```python
import stripe
stripe.api_key = "sk_test_fake"
stripe.api_base = "http://localhost:4280"
customer = stripe.Customer.create(name="Test User", email="test@example.com")
assert customer.id.startswith("cus_")
updated = stripe.Customer.modify(customer.id, name="New Name")
assert updated.name == "New Name"
```
### Stripe SDK (Node.js)
[Section titled “Stripe SDK (Node.js)”](#stripe-sdk-nodejs)
```javascript
const stripe = require('stripe')('sk_test_fake', {
apiVersion: '2024-04-10',
host: 'localhost',
port: 4280,
protocol: 'http',
});
const customer = await stripe.customers.create({
name: 'Test User',
email: 'test@example.com',
});
console.log(customer.id); // cus_a1b2c3d4...
```
No Auth
Use `--no-auth` when starting mockd to skip API key validation. The SDK still sends its key, but mockd won’t reject it.
Form Encoding
Stripe SDKs send requests as `application/x-www-form-urlencoded`, not JSON. mockd handles form-encoded bodies automatically — no extra configuration needed.
## Adding Custom Operations
[Section titled “Adding Custom Operations”](#adding-custom-operations)
For endpoints that aren’t simple CRUD — like confirming a payment intent or capturing a charge — use custom operations:
```yaml
customOperations:
- name: ConfirmPaymentIntent
steps:
- type: read
resource: payment_intents
id: "input.intent"
as: pi
- type: update
resource: payment_intents
id: "input.intent"
set:
status: '"succeeded"'
amount_received: "pi.amount"
response:
id: "pi.id"
object: '"payment_intent"'
status: '"succeeded"'
amount: "pi.amount"
amount_received: "pi.amount"
extend:
- mock: stripe.PostPaymentIntentsIntentConfirm
table: payment_intents
action: custom
operation: ConfirmPaymentIntent
```
Note the quoting: `'"succeeded"'` is a string literal (outer quotes are YAML, inner quotes mark it as a literal value). `"pi.amount"` without inner quotes is a reference to a field on the `pi` variable from the `read` step. Custom operations can chain reads and writes across multiple tables for complex workflows.
## Complete Working Example
[Section titled “Complete Working Example”](#complete-working-example)
```bash
# Clone the samples repository
git clone https://github.com/getmockd/mockd-samples.git
# Run the Stripe digital twin (9 tables, 44 bindings, 8 custom operations)
mockd start -c mockd-samples/third-party-apis/stripe-api/mockd.yaml --no-auth
# Run the Twilio digital twin (7 tables, 30 bindings)
mockd start -c mockd-samples/third-party-apis/twilio-api/mockd.yaml --no-auth
```
The Stripe sample passes 49/49 tests from the official `stripe-go` SDK. The Twilio sample passes 13/13 from `twilio-go`. These prove the pattern generalizes across different API styles.
## Next Steps
[Section titled “Next Steps”](#next-steps)
* **[Stateful Mocking Guide](/guides/stateful-mocking/)** — Complete reference for tables, extend, response transforms, and custom operations
* **[Configuration Reference](/reference/configuration/)** — Full schema documentation for `mockd.yaml`
* **[Import & Export](/guides/import-export/)** — Importing from OpenAPI, WSDL, Postman collections, and more
# Import & Export
> Import mocks from OpenAPI, Postman, WireMock, Mockoon, HAR, and cURL. Export to share or migrate.
mockd can import mock definitions from formats you probably already have — OpenAPI specs, Postman collections, WireMock stubs, Mockoon environments, HAR files from your browser, and even cURL commands.
## Supported Import Formats
[Section titled “Supported Import Formats”](#supported-import-formats)
| Format | File Extension | Auto-Detected | Description |
| -------- | ---------------- | ------------- | --------------------------------------------------- |
| OpenAPI | `.yaml`, `.json` | Yes | OpenAPI 3.x and Swagger 2.0 specs |
| Postman | `.json` | Yes | Postman Collection v2.0 and v2.1 |
| WireMock | directory | Yes | WireMock mapping JSON files |
| Mockoon | `.json` | Yes | Mockoon environment exports |
| HAR | `.har` | Yes | HTTP Archive files (from browser DevTools) |
| cURL | — | No | cURL command strings |
| WSDL | `.wsdl`, `.xml` | Yes | WSDL 1.1 service definitions (generates SOAP mocks) |
| mockd | `.yaml`, `.json` | Yes | mockd’s own config format |
## Importing
[Section titled “Importing”](#importing)
### From OpenAPI Specs
[Section titled “From OpenAPI Specs”](#from-openapi-specs)
Import an OpenAPI 3.x or Swagger 2.0 specification. mockd creates one mock per path+method combination, using example values from the spec:
```bash
mockd import openapi.yaml
```
```plaintext
Parsed 12 mocks from openapi.yaml (format: openapi)
Imported 12 mocks to server
```
Verify what was created:
```bash
mockd list
```
If your spec doesn’t have example values, mockd generates placeholder responses based on the schema types.
### From Postman Collections
[Section titled “From Postman Collections”](#from-postman-collections)
Import a Postman Collection (v2.0 or v2.1 format). Export your collection from Postman first (Collection → Export → Collection v2.1):
```bash
mockd import my-api.postman_collection.json
```
Postman environment variables in requests are preserved as literal strings (e.g., `{{baseUrl}}`). You may need to adjust paths after import.
### From WireMock
[Section titled “From WireMock”](#from-wiremock)
If you’re migrating from WireMock, point mockd at a directory containing WireMock mapping files:
```bash
mockd import ./wiremock-mappings/
```
mockd reads all `.json` files in the directory and converts WireMock’s request matching and response definitions to mockd format.
### From Mockoon Environments
[Section titled “From Mockoon Environments”](#from-mockoon-environments)
Import a Mockoon environment JSON export:
```bash
mockd import environment.json
```
mockd converts Mockoon routes (including CRUD resources), response templates, path parameters (`:id` → `{id}`), and Handlebars helpers (`{{faker 'person.firstName'}}` → `{{faker.firstName}}`). Disabled routes are skipped, and per-response + global latency is preserved.
### From HAR Files
[Section titled “From HAR Files”](#from-har-files)
Record API traffic in your browser (DevTools → Network → Export HAR), then import it:
```bash
mockd import recorded-traffic.har
```
This creates mocks for every request/response pair captured in the HAR file. Useful for quickly creating mocks that match real API behavior.
### From WSDL Files
[Section titled “From WSDL Files”](#from-wsdl-files)
Import WSDL service definitions to generate SOAP mocks:
```bash
mockd import service.wsdl
```
mockd parses the WSDL operations and generates SOAP mock endpoints. Use `--format wsdl` if auto-detection doesn’t work, or use the dedicated `mockd soap import` command for more control (e.g., `--stateful` to auto-detect CRUD patterns):
```bash
# Dedicated SOAP import with stateful CRUD heuristics
mockd soap import service.wsdl --stateful
```
### Using `imports` in Config Files
[Section titled “Using imports in Config Files”](#using-imports-in-config-files)
For declarative spec loading, use the `imports` field in your config file instead of the CLI. This loads specs at startup with namespace prefixes, allowing you to wire the generated mocks to tables via `extend`:
```yaml
version: "1.0"
imports:
- path: ./stripe-openapi.yaml
as: stripe
format: openapi
tables:
- name: customers
seedData:
- id: "cus_001"
name: "Alice"
extend:
- mock: stripe.ListCustomers
table: customers
action: list
```
See the [Stateful Mocking guide](/guides/stateful-mocking/#importing-specs-and-binding-to-tables) for more details.
### From cURL Commands
[Section titled “From cURL Commands”](#from-curl-commands)
Convert a cURL command directly into a mock:
```bash
mockd import --format curl 'curl -X POST https://api.example.com/orders -H "Content-Type: application/json" -d {"item":"widget","qty":5}'
```
```plaintext
Parsed 1 mocks from curl command (format: curl)
Imported 1 mocks to server
```
The `--format curl` flag is required since cURL commands can’t be auto-detected from file content.
### Dry Run
[Section titled “Dry Run”](#dry-run)
Preview what would be imported without actually applying it:
```bash
mockd import --dry-run openapi.yaml
```
This parses and validates the file, showing you what mocks would be created, without changing anything on the server.
### Merge vs Replace
[Section titled “Merge vs Replace”](#merge-vs-replace)
By default, imported mocks are **merged** with existing mocks. To replace all existing mocks with the imported ones:
```bash
mockd import --replace openapi.yaml
```
## Exporting
[Section titled “Exporting”](#exporting)
### To mockd YAML
[Section titled “To mockd YAML”](#to-mockd-yaml)
Export your current mock configuration:
```bash
mockd export --format yaml > mocks-backup.yaml
```
### To mockd JSON
[Section titled “To mockd JSON”](#to-mockd-json)
```bash
mockd export --format json > mocks-backup.json
```
### To OpenAPI
[Section titled “To OpenAPI”](#to-openapi)
Generate an OpenAPI 3.0 spec from your current mocks:
```bash
mockd export --format openapi > api-spec.yaml
```
This is useful for documenting the API your mocks represent, or for sharing the spec with frontend teams.
## Common Workflows
[Section titled “Common Workflows”](#common-workflows)
### Migrate from WireMock
[Section titled “Migrate from WireMock”](#migrate-from-wiremock)
```bash
# Import WireMock stubs
mockd import ./wiremock-mappings/
# Verify everything looks right
mockd list
# Export as mockd config for future use
mockd export --format yaml > mockd.yaml
```
### Migrate from Mockoon
[Section titled “Migrate from Mockoon”](#migrate-from-mockoon)
```bash
# Export your Mockoon environment (File → Export → Current Environment)
# Then import into mockd
mockd import mockoon-environment.json
# Verify the import
mockd list
# Export as mockd config for future use
mockd export --format yaml > mockd.yaml
```
### Capture Real Traffic → Mock
[Section titled “Capture Real Traffic → Mock”](#capture-real-traffic--mock)
```bash
# Start the MITM proxy (records traffic to disk)
mockd proxy start --port 8888
# Configure your app to use the proxy, then run it
http_proxy=http://localhost:8888 npm test
# Stop recording with Ctrl+C, then convert to mocks
mockd convert -o mocks.yaml
```
### Import from Browser
[Section titled “Import from Browser”](#import-from-browser)
1. Open your browser’s DevTools → Network tab
2. Use your application normally
3. Right-click in the Network tab → **Save all as HAR**
4. Import into mockd:
```bash
mockd import network-traffic.har
```
### CI/CD Pipeline
[Section titled “CI/CD Pipeline”](#cicd-pipeline)
Use import in your test pipeline to load mocks from version-controlled specs:
```bash
# In your CI script
mockd serve &
sleep 2
mockd import ./test-fixtures/api-spec.yaml
pytest tests/
```
### Round-Trip: Export → Edit → Import
[Section titled “Round-Trip: Export → Edit → Import”](#round-trip-export--edit--import)
```bash
# Export current state
mockd export --format yaml > mocks.yaml
# Edit the file (add mocks, change responses, etc.)
vim mocks.yaml
# Re-import (replace mode to get a clean state)
mockd import --replace mocks.yaml
```
## Format Detection
[Section titled “Format Detection”](#format-detection)
mockd auto-detects the format of imported files based on content:
* Files with `openapi` or `swagger` keys → OpenAPI
* Files with `info.schema` matching Postman patterns → Postman Collection
* Files with `log.entries` → HAR
* Files with `request.url` + `response` at top level → WireMock
* Files with `routes` array + `endpointPrefix` → Mockoon environment
* Files with `` or `` root elements → WSDL
* Files with `mocks` array or `version: "1.0"` → mockd native format
* Directories → scanned for WireMock JSON mappings
You can override auto-detection with `--format`:
```bash
mockd import --format openapi ambiguous-file.json
```
## Using —json
[Section titled “Using —json”](#using-json)
Import and export commands support `--json` for scripting:
```bash
mockd import --json openapi.yaml
```
```json
{
"imported": 12,
"format": "openapi",
"source": "openapi.yaml"
}
```
# MCP Server
> Use mockd from AI-powered editors like Cursor, Windsurf, and Claude Code via the Model Context Protocol
mockd includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server with 18 tools for creating, managing, and debugging mocks directly from AI-powered editors.
## What is MCP?
[Section titled “What is MCP?”](#what-is-mcp)
The Model Context Protocol (MCP) is an open standard from [Anthropic](https://anthropic.com) that lets AI assistants interact with external tools and data sources. Instead of copy-pasting curl commands or switching between your editor and terminal, your AI assistant talks to mockd directly — creating mocks, inspecting traffic, injecting chaos, and verifying behavior in a single conversation.
mockd’s MCP server is built on the official [mcp-go SDK](https://github.com/mark3labs/mcp-go) and exposes mockd’s full capabilities as structured tools that any MCP-compatible client can discover and invoke.
Note
mockd is the **only** API mocking tool with a built-in MCP server. No competitor (WireMock, Postman, Mockoon, MSW, Microcks) offers this.
## Quick Start
[Section titled “Quick Start”](#quick-start)
```bash
# Start mockd with MCP support (stdio transport)
# Auto-starts a background daemon if no server is running — zero setup needed.
mockd mcp
# Or enable MCP alongside the mock server (HTTP transport)
mockd serve --mcp
```
## Zero-Setup: Auto-Start Daemon
[Section titled “Zero-Setup: Auto-Start Daemon”](#zero-setup-auto-start-daemon)
When you run `mockd mcp`, it automatically handles server lifecycle:
1. **Already running?** Connects to the existing mockd server (via PID file or default URL)
2. **Nothing running?** Auto-starts a background daemon (`mockd start --detach --no-auth`)
3. **Daemon is shared** — it survives the MCP session, so multiple AI assistants (e.g., two Claude windows) share the same server and mocks persist across sessions
Stop the daemon with `mockd stop` when you’re done.
### Project-Scoped Isolation
[Section titled “Project-Scoped Isolation”](#project-scoped-isolation)
Use `--data-dir` to start a separate daemon per project, avoiding conflicts when working across multiple codebases:
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp", "--data-dir", "./mockd-data"]
}
}
}
```
Project daemons run on different ports (14280/14290 by default) and store their PID file inside the data directory. Multiple sessions in the same project share the same daemon.
### `mockd mcp` Flags
[Section titled “mockd mcp Flags”](#mockd-mcp-flags)
| Flag | Description | Default |
| -------------- | ------------------------------------------------------ | ------------------------------------- |
| `--admin-url` | Connect to a specific admin API URL (skips auto-start) | |
| `--data-dir` | Project-scoped data directory (starts separate daemon) | |
| `--config` | Config file to load on daemon startup | |
| `--port` | Mock server port for project daemon | `4280` (or `14280` with `--data-dir`) |
| `--admin-port` | Admin API port for project daemon | `4290` (or `14290` with `--data-dir`) |
| `--log-level` | Log level for stderr output | `warn` |
## Editor Setup
[Section titled “Editor Setup”](#editor-setup)
### Claude Code / Claude Desktop
[Section titled “Claude Code / Claude Desktop”](#claude-code--claude-desktop)
Add to your MCP config (`~/.claude/claude_code_config.json` or Claude Desktop settings):
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp"]
}
}
}
```
### Cursor
[Section titled “Cursor”](#cursor)
Add to `.cursor/mcp.json` in your project root:
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp"]
}
}
}
```
### Windsurf
[Section titled “Windsurf”](#windsurf)
Add to `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp"]
}
}
}
```
Prerequisites
mockd must be installed and in your `PATH`. Verify with `mockd version`. If you installed via Docker, MCP stdio transport won’t work — use the binary install (`brew install getmockd/tap/mockd` or `curl -sSL https://get.mockd.io | sh`).
## Available Tools (18)
[Section titled “Available Tools (18)”](#available-tools-18)
mockd’s MCP server exposes 18 tools organized by function:
### Mock Management
[Section titled “Mock Management”](#mock-management)
| Tool | Actions | Description |
| ------------- | ----------------------------------------- | --------------------------------------------------- |
| `manage_mock` | list, get, create, update, delete, toggle | Full CRUD for mock endpoints across all 7 protocols |
### Import & Export
[Section titled “Import & Export”](#import--export)
| Tool | Description |
| -------------- | ------------------------------------------------------------------------------------ |
| `import_mocks` | Import from OpenAPI, Postman, HAR, WireMock, Mockoon, cURL, WSDL, or mockd YAML/JSON |
| `export_mocks` | Export all mocks as YAML or JSON |
### Observability
[Section titled “Observability”](#observability)
| Tool | Description |
| -------------------- | ----------------------------------------------------------- |
| `get_server_status` | Server health, ports, uptime, and statistics |
| `get_request_logs` | View captured request/response logs with protocol filtering |
| `clear_request_logs` | Clear all logs for test isolation |
### Chaos Engineering
[Section titled “Chaos Engineering”](#chaos-engineering)
| Tool | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `get_chaos_config` | View current chaos fault injection settings |
| `set_chaos_config` | Configure latency, error rates, bandwidth throttling, apply named profiles, or define rules with stateful faults |
| `reset_chaos_stats` | Reset injection statistics counters |
| `get_stateful_faults` | View status of all stateful chaos fault instances (circuit breakers, retry-after trackers, progressive degradation) |
| `manage_circuit_breaker` | Manually trip or reset a chaos circuit breaker by its state key |
### Mock Verification
[Section titled “Mock Verification”](#mock-verification)
| Tool | Description |
| ---------------------- | -------------------------------------------------------- |
| `verify_mock` | Assert a mock was called the expected number of times |
| `get_mock_invocations` | View detailed request/response pairs for a specific mock |
| `reset_verification` | Clear verification data for test isolation |
### Stateful Resources
[Section titled “Stateful Resources”](#stateful-resources)
| Tool | Actions | Description |
| -------------- | -------------------------------------------------------------------- | --------------------------------------------------------- |
| `manage_state` | overview, add\_resource, list\_items, get\_item, create\_item, reset | Manage CRUD collections that persist data across requests |
### Custom Operations
[Section titled “Custom Operations”](#custom-operations)
| Tool | Actions | Description |
| ------------------------- | ------------------------------------ | ------------------------------------------- |
| `manage_custom_operation` | list, get, register, delete, execute | Multi-step operations on stateful resources |
### Session Management
[Section titled “Session Management”](#session-management)
| Tool | Actions | Description |
| ------------------ | -------------------- | -------------------------------------------------------- |
| `manage_context` | get, switch | Switch between mockd server contexts (multi-environment) |
| `manage_workspace` | list, switch, create | Manage isolated workspace configurations |
## Key Tool Parameters
[Section titled “Key Tool Parameters”](#key-tool-parameters)
### `manage_mock` — Stateful Bindings with `extend`
[Section titled “manage\_mock — Stateful Bindings with extend”](#manage_mock--stateful-bindings-with-extend)
The `extend` parameter on `manage_mock` binds a mock to a stateful resource table for automatic CRUD. Create the table first with `manage_state` (action: `add_resource`), then create mocks with `extend` to wire them up:
```json
// Step 1: Create the table
{ "action": "add_resource", "resource": "users" }
// Step 2: Create mocks bound to the table
{ "action": "create", "type": "http",
"http": { "matcher": { "method": "GET", "path": "/api/users" } },
"extend": { "table": "users", "action": "list" } }
{ "action": "create", "type": "http",
"http": { "matcher": { "method": "POST", "path": "/api/users" } },
"extend": { "table": "users", "action": "create" } }
{ "action": "create", "type": "http",
"http": { "matcher": { "method": "GET", "path": "/api/users/{id}" } },
"extend": { "table": "users", "action": "get" } }
{ "action": "create", "type": "http",
"http": { "matcher": { "method": "PUT", "path": "/api/users/{id}" } },
"extend": { "table": "users", "action": "update" } }
{ "action": "create", "type": "http",
"http": { "matcher": { "method": "DELETE", "path": "/api/users/{id}" } },
"extend": { "table": "users", "action": "delete" } }
```
For custom operations, use `action: "custom"` with an `operation` name:
```json
{ "action": "create", "type": "http",
"http": { "matcher": { "method": "POST", "path": "/api/users/{id}/verify" } },
"extend": { "table": "users", "action": "custom", "operation": "VerifyUser" } }
```
The `extend` parameter works with both HTTP and SOAP mocks. Available actions: `list`, `get`, `create`, `update`, `delete`, `custom`.
### `import_mocks` — File-Based Import with `file`
[Section titled “import\_mocks — File-Based Import with file”](#import_mocks--file-based-import-with-file)
For large API specs (OpenAPI, Postman, etc.) that exceed inline content limits, use the `file` parameter to import from the mockd server’s filesystem:
```json
{ "file": "./openapi.yaml" }
{ "file": "/absolute/path/to/postman-collection.json", "format": "postman" }
{ "file": "./api-spec.yaml", "dryRun": true }
```
The `file` and `content` parameters are mutually exclusive — use one or the other. Format is auto-detected when omitted.
## Example: Creating a Mock via MCP
[Section titled “Example: Creating a Mock via MCP”](#example-creating-a-mock-via-mcp)
When you ask your AI editor “Create an endpoint that returns a list of users,” the AI calls the `manage_mock` tool behind the scenes:
**Tool call** (`manage_mock` with action `create`):
```json
{
"action": "create",
"type": "http",
"http": {
"matcher": { "method": "GET", "path": "/api/users" },
"response": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": "[{\"id\":1,\"name\":\"{{faker.name}}\",\"email\":\"{{faker.email}}\"}]"
}
}
}
```
**Tool response:**
```json
{
"action": "created",
"id": "http_a1b2c3d4",
"message": "Created http mock"
}
```
The AI can then verify it works by calling `verify_mock` after sending test traffic, or inject chaos with `set_chaos_config` to test your app’s error handling — all without leaving the editor.
## Typical Workflow
[Section titled “Typical Workflow”](#typical-workflow)
Here’s what a full AI-assisted development session looks like:
1. **Create mocks** — “Create a REST API for a todo app with GET, POST, PUT, DELETE”
2. **Send test traffic** — The AI calls `get_request_logs` to verify traffic is flowing
3. **Verify behavior** — `verify_mock` asserts the right endpoints were called the right number of times
4. **Inject chaos** — Apply the `flaky` profile with `set_chaos_config` to test resilience
5. **Manage state** — `manage_state` creates CRUD collections, seeds data, resets between tests
6. **Export** — `export_mocks` saves the full configuration for version control
## MCP Resources
[Section titled “MCP Resources”](#mcp-resources)
The MCP server exposes 5 static resources for AI context, plus dynamic per-mock resources:
### Static Resources
[Section titled “Static Resources”](#static-resources)
| Resource URI | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------------- |
| `mock://chaos` | Current chaos configuration, including stateful fault state (circuit breaker states, retry-after counters) |
| `mock://verification` | Mock verification summary (template — use `mock://verification/{mockId}` for specific mocks) |
| `mock://logs` | Recent request logs |
| `mock://config` | Current server configuration |
| `mock://context` | Current context and workspace info |
### Dynamic Resources (per-mock)
[Section titled “Dynamic Resources (per-mock)”](#dynamic-resources-per-mock)
The server also generates resources for each configured mock, using URI patterns like `mock:///api/users#GET` (HTTP), `mock://websocket/ws/chat` (WebSocket), `mock://graphql/graphql` (GraphQL), `mock://grpc/{id}` (gRPC), `mock://mqtt/{id}` (MQTT), `mock://soap/soap` (SOAP), and `mock://stateful/{name}` (stateful resources).
## Transports
[Section titled “Transports”](#transports)
mockd supports two MCP transports:
| Transport | Command | Use Case |
| --------- | ------------------- | -------------------------------- |
| **stdio** | `mockd mcp` | Editor integration (recommended) |
| **HTTP** | `mockd serve --mcp` | Remote access, shared server |
The stdio transport is recommended for local editor integration. The HTTP transport runs alongside the mock server and is useful when the mockd server is running on a remote machine or in a container.
## CLI Equivalents
[Section titled “CLI Equivalents”](#cli-equivalents)
Every MCP tool has a corresponding CLI command. Use these interchangeably:
| MCP Tool | CLI Equivalent |
| ---------------------------- | ----------------------------------------------- |
| `manage_mock` (create) | `mockd add http --path /api/users --status 200` |
| `verify_mock` | `mockd verify check --exactly 3` |
| `get_mock_invocations` | `mockd verify invocations ` |
| `set_chaos_config` (profile) | `mockd chaos apply flaky` |
| `get_request_logs` | `mockd logs --requests` |
| `import_mocks` | `mockd import openapi.yaml` |
| `export_mocks` | `mockd export --format yaml` |
See the [CLI Reference](/reference/cli/) for the full command list, including [`mockd mcp`](/reference/cli/#mockd-mcp) and [`mockd verify`](/reference/cli/#mockd-verify).
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [AI Agent Setup](/guides/ai-agent-setup/) — Pre-built config templates for Claude Code, Cursor, and Copilot
* [Chaos Engineering](/guides/chaos-engineering/) — Fault injection and chaos profiles
* [Mock Verification](/guides/mock-verification/) — Verify mock call counts and invocations
* [Stateful Mocking](/guides/stateful-mocking/) — CRUD simulation and custom operations
* [Import & Export](/guides/import-export/) — Bring existing API definitions
# Mock Verification
> Verify that your mocks were called correctly during tests with call counts, invocation history, and expectations.
Mock verification lets you assert that your mocks were called the expected number of times and with the expected parameters. This is essential for integration testing where you need to verify that your code is making the correct API calls.
## Overview
[Section titled “Overview”](#overview)
mockd tracks every request that matches a mock:
* **Call count** - How many times the mock was called
* **Invocation history** - Details of each call (method, path, headers, body)
* **Timestamps** - When the mock was first and last called
## Quick Start
[Section titled “Quick Start”](#quick-start)
```bash
# 1. Start mockd with a mock
mockd serve --config mocks.json
# 2. Run your tests (which call the mock)
npm test
# 3. Verify the mock was called
curl http://localhost:4290/mocks/my-mock-id/verify
# 4. Reset for the next test
curl -X DELETE http://localhost:4290/verify
```
## Verification Endpoints
[Section titled “Verification Endpoints”](#verification-endpoints)
### Check Call Count
[Section titled “Check Call Count”](#check-call-count)
```bash
GET /mocks/{id}/verify
```
**Response:**
```json
{
"mockId": "get-users",
"callCount": 5,
"lastCalledAt": "2024-01-15T10:35:00Z"
}
```
### Assert Call Count
[Section titled “Assert Call Count”](#assert-call-count)
```bash
POST /mocks/{id}/verify
Content-Type: application/json
{
"atLeast": 1,
"atMost": 10
}
```
| Field | Description |
| --------- | ---------------------- |
| `atLeast` | Minimum expected calls |
| `atMost` | Maximum expected calls |
| `exactly` | Exact expected calls |
**Success Response (200):**
```json
{
"passed": true,
"actual": 5,
"expected": "at least 1 time(s)",
"message": "Mock was called 5 time(s), matching expectations"
}
```
**Failure Response (409):**
```json
{
"passed": false,
"actual": 0,
"expected": "at least 1 time(s)",
"message": "Mock was called 0 time(s), not matching expectations"
}
```
### Get Invocation History
[Section titled “Get Invocation History”](#get-invocation-history)
```bash
GET /mocks/{id}/invocations
```
**Response:**
```json
{
"invocations": [
{
"id": "req-1",
"timestamp": "2024-01-15T10:30:00Z",
"method": "GET",
"path": "/api/users",
"headers": {
"Authorization": "Bearer token123",
"User-Agent": "my-app/1.0"
},
"body": ""
},
{
"id": "req-2",
"timestamp": "2024-01-15T10:31:00Z",
"method": "POST",
"path": "/api/users",
"headers": {
"Authorization": "Bearer token123",
"Content-Type": "application/json"
},
"body": "{\"name\": \"Alice\"}"
}
],
"count": 2,
"total": 2
}
```
### Reset Verification Data
[Section titled “Reset Verification Data”](#reset-verification-data)
```bash
# Reset specific mock
DELETE /mocks/{id}/invocations
# Reset all mocks
DELETE /verify
```
## Testing Patterns
[Section titled “Testing Patterns”](#testing-patterns)
### Before Each Test
[Section titled “Before Each Test”](#before-each-test)
Reset verification state before each test to ensure isolation:
```javascript
beforeEach(async () => {
await fetch('http://localhost:4290/verify', { method: 'DELETE' });
});
```
### Verify After Test
[Section titled “Verify After Test”](#verify-after-test)
```javascript
test('fetches users on load', async () => {
// Run your code
await loadUsers();
// Verify the mock was called
const res = await fetch('http://localhost:4290/mocks/get-users/verify');
const data = await res.json();
expect(data.callCount).toBe(1);
});
```
### Verify Call Count Assertion
[Section titled “Verify Call Count Assertion”](#verify-call-count-assertion)
```javascript
test('retries on failure', async () => {
// Run code that should retry 3 times
await fetchWithRetry('/api/users');
// Assert exactly 3 calls
const res = await fetch('http://localhost:4290/mocks/get-users/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ exactly: 3 })
});
const data = await res.json();
expect(data.passed).toBe(true);
});
```
### Verify Request Details
[Section titled “Verify Request Details”](#verify-request-details)
```javascript
test('sends correct authorization header', async () => {
await fetchUsers('my-token');
const res = await fetch('http://localhost:4290/mocks/get-users/invocations');
const data = await res.json();
expect(data.invocations[0].headers['Authorization']).toBe('Bearer my-token');
});
```
### Verify Request Body
[Section titled “Verify Request Body”](#verify-request-body)
```javascript
test('sends correct payload', async () => {
await createUser({ name: 'Alice', email: 'alice@example.com' });
const res = await fetch('http://localhost:4290/mocks/create-user/invocations');
const data = await res.json();
const body = JSON.parse(data.invocations[0].body);
expect(body.name).toBe('Alice');
expect(body.email).toBe('alice@example.com');
});
```
## Framework Examples
[Section titled “Framework Examples”](#framework-examples)
### Jest (JavaScript)
[Section titled “Jest (JavaScript)”](#jest-javascript)
```javascript
const ADMIN_URL = 'http://localhost:4290';
describe('User API', () => {
beforeEach(async () => {
await fetch(`${ADMIN_URL}/verify`, { method: 'DELETE' });
});
test('fetches users with pagination', async () => {
const users = await fetchUsers({ page: 2, limit: 20 });
// Verify mock was called
const verify = await fetch(`${ADMIN_URL}/mocks/get-users/verify`);
const { callCount } = await verify.json();
expect(callCount).toBe(1);
// Verify query parameters
const invocations = await fetch(`${ADMIN_URL}/mocks/get-users/invocations`);
const { invocations: calls } = await invocations.json();
expect(calls[0].query.page).toBe('2');
expect(calls[0].query.limit).toBe('20');
});
});
```
### pytest (Python)
[Section titled “pytest (Python)”](#pytest-python)
```python
import requests
import pytest
ADMIN_URL = 'http://localhost:4290'
@pytest.fixture(autouse=True)
def reset_verification():
requests.delete(f'{ADMIN_URL}/verify')
yield
def test_fetches_users_with_auth():
# Run code under test
fetch_users(token='secret123')
# Verify mock was called
res = requests.get(f'{ADMIN_URL}/mocks/get-users/verify')
assert res.json()['callCount'] == 1
# Verify authorization header
res = requests.get(f'{ADMIN_URL}/mocks/get-users/invocations')
invocations = res.json()['invocations']
assert invocations[0]['headers']['Authorization'] == 'Bearer secret123'
def test_creates_user_with_correct_payload():
# Run code under test
create_user(name='Bob', email='bob@example.com')
# Verify request body
res = requests.get(f'{ADMIN_URL}/mocks/create-user/invocations')
body = json.loads(res.json()['invocations'][0]['body'])
assert body['name'] == 'Bob'
assert body['email'] == 'bob@example.com'
```
### Go
[Section titled “Go”](#go)
```go
func TestFetchUsers(t *testing.T) {
adminURL := "http://localhost:4290"
// Reset before test
req, _ := http.NewRequest("DELETE", adminURL+"/verify", nil)
http.DefaultClient.Do(req)
// Run code under test
FetchUsers()
// Verify
resp, _ := http.Get(adminURL + "/mocks/get-users/verify")
var result struct {
CallCount int `json:"callCount"`
}
json.NewDecoder(resp.Body).Decode(&result)
if result.CallCount != 1 {
t.Errorf("expected 1 call, got %d", result.CallCount)
}
}
```
## Best Practices
[Section titled “Best Practices”](#best-practices)
### 1. Always Reset Before Tests
[Section titled “1. Always Reset Before Tests”](#1-always-reset-before-tests)
```javascript
beforeEach(() => fetch('http://localhost:4290/verify', { method: 'DELETE' }));
```
### 2. Use Descriptive Mock IDs
[Section titled “2. Use Descriptive Mock IDs”](#2-use-descriptive-mock-ids)
```yaml
mocks:
- id: get-users-paginated # Not just "mock-1"
name: Get Users with Pagination
```
### 3. Verify Both Count and Content
[Section titled “3. Verify Both Count and Content”](#3-verify-both-count-and-content)
```javascript
// Verify it was called
expect(callCount).toBe(1);
// Verify it was called correctly
expect(invocations[0].headers['Authorization']).toBeDefined();
```
### 4. Use `exactly` for Strict Tests
[Section titled “4. Use exactly for Strict Tests”](#4-use-exactly-for-strict-tests)
```javascript
// Strict: fail if called more or less
{ exactly: 1 }
// Flexible: just ensure it was called
{ atLeast: 1 }
```
### 5. Consider Test Parallelization
[Section titled “5. Consider Test Parallelization”](#5-consider-test-parallelization)
If running tests in parallel, use unique mock IDs or separate mockd instances to avoid verification conflicts.
## Comparison with Other Tools
[Section titled “Comparison with Other Tools”](#comparison-with-other-tools)
| Feature | mockd | WireMock | Mockoon |
| -------------------- | ------ | -------- | ------- |
| Call count tracking | ✅ Free | ✅ Free | ❌ |
| Invocation history | ✅ Free | ✅ Free | ❌ |
| Assertion API | ✅ Free | ✅ Free | ❌ |
| Request body capture | ✅ Free | ✅ Paid | ❌ |
## See Also
[Section titled “See Also”](#see-also)
* [Admin API Reference](/reference/admin-api#mock-verification) - Full API details
* [Integration Testing](/examples/integration-testing) - Testing patterns
* [Stateful Mocking](/guides/stateful-mocking) - State management
# Observability
> Monitor mockd with Prometheus metrics, Loki log aggregation, and OpenTelemetry distributed tracing.
mockd provides comprehensive observability features for monitoring, debugging, and integrating with your existing observability stack.
## Prometheus Metrics
[Section titled “Prometheus Metrics”](#prometheus-metrics)
The admin API exposes Prometheus-compatible metrics at `/metrics`.
### Enabling Metrics
[Section titled “Enabling Metrics”](#enabling-metrics)
Metrics are available by default on the admin port:
4290/metrics
```bash
mockd serve --admin-port 4290
```
### Available Metrics
[Section titled “Available Metrics”](#available-metrics)
```plaintext
# Server uptime
mockd_uptime_seconds 3600
# Request counters (all protocols: HTTP, GraphQL, gRPC, WebSocket, MQTT, SSE, SOAP)
mockd_requests_total{method="GET",path="/api/users",status="200"} 42
mockd_requests_total{method="grpc",path="/helloworld.Greeter/SayHello",status="ok"} 10
mockd_requests_total{method="mqtt",path="sensors/temperature",status="ok"} 5
# Request latency histogram
mockd_request_duration_seconds_bucket{le="0.001",method="GET",path="/api/users"} 100
mockd_request_duration_seconds_bucket{le="0.01",method="GET",path="/api/users"} 150
mockd_request_duration_seconds_bucket{le="+Inf",method="GET",path="/api/users"} 155
# Active connections (WebSocket, etc.)
mockd_active_connections{protocol="websocket"} 3
# Mock matching counters
mockd_match_hits_total{mock_id="http_abc123"} 42
mockd_match_misses_total 5
# Go runtime metrics
go_goroutines 12
go_memstats_heap_alloc_bytes 4194304
```
### Prometheus Configuration
[Section titled “Prometheus Configuration”](#prometheus-configuration)
prometheus.yml
```yaml
scrape_configs:
- job_name: 'mockd'
static_configs:
- targets: ['localhost:4290']
metrics_path: /metrics
scrape_interval: 15s
```
### Grafana Dashboard
[Section titled “Grafana Dashboard”](#grafana-dashboard)
Example Grafana queries:
```promql
# Request rate
rate(mockd_requests_total[5m])
# Error rate
sum(rate(mockd_requests_total{status=~"5.."}[5m]))
/ sum(rate(mockd_requests_total[5m]))
# P95 latency
histogram_quantile(0.95, rate(mockd_request_duration_seconds_bucket[5m]))
# Mock match rate
rate(mockd_match_hits_total[5m])
```
***
## Loki Log Aggregation
[Section titled “Loki Log Aggregation”](#loki-log-aggregation)
Send mockd logs to Grafana Loki for centralized log aggregation.
### Enabling Loki
[Section titled “Enabling Loki”](#enabling-loki)
```bash
mockd serve --loki-endpoint http://localhost:3100/loki/api/v1/push
```
### Log Format
[Section titled “Log Format”](#log-format)
Logs are sent with the following labels:
| Label | Description |
| ----------- | -------------------------------------- |
| `job` | Always `mockd` |
| `level` | Log level (debug, info, warn, error) |
| `component` | Component name (server, admin, engine) |
### Loki Configuration
[Section titled “Loki Configuration”](#loki-configuration)
Ensure Loki is running and accessible:
docker-compose.yml
```yaml
services:
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
```
### Querying Logs in Grafana
[Section titled “Querying Logs in Grafana”](#querying-logs-in-grafana)
```logql
# All mockd logs
{job="mockd"}
# Errors only
{job="mockd", level="error"}
# Request logs
{job="mockd"} |= "request"
# Filter by path
{job="mockd"} | json | path="/api/users"
```
### Log Batching
[Section titled “Log Batching”](#log-batching)
Logs are batched for efficiency:
* Batch size: 100 entries or 5 seconds (whichever comes first)
* Graceful shutdown flushes pending logs
***
## OpenTelemetry Tracing
[Section titled “OpenTelemetry Tracing”](#opentelemetry-tracing)
Send distributed traces to any OpenTelemetry-compatible backend (Jaeger, Zipkin, Tempo, etc.).
### Enabling Tracing
[Section titled “Enabling Tracing”](#enabling-tracing)
```bash
mockd serve --otlp-endpoint http://localhost:4318/v1/traces
```
### Trace Sampling
[Section titled “Trace Sampling”](#trace-sampling)
Control the sampling rate (default: 100%):
```bash
# Sample 10% of traces
mockd serve --otlp-endpoint http://localhost:4318/v1/traces --trace-sampler 0.1
```
### Trace Attributes
[Section titled “Trace Attributes”](#trace-attributes)
Each span includes:
| Attribute | Description |
| ------------------ | ---------------------- |
| `http.method` | HTTP method |
| `http.url` | Request URL |
| `http.status_code` | Response status code |
| `mockd.mock_id` | Matched mock ID |
| `mockd.matched` | Whether a mock matched |
### Jaeger Setup
[Section titled “Jaeger Setup”](#jaeger-setup)
docker-compose.yml
```yaml
services:
jaeger:
image: jaegertracing/all-in-one:1.50
ports:
- "16686:16686" # UI
- "4318:4318" # OTLP HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
```
```bash
mockd serve --otlp-endpoint http://localhost:4318/v1/traces
# View traces at: http://localhost:16686
```
### Grafana Tempo Setup
[Section titled “Grafana Tempo Setup”](#grafana-tempo-setup)
docker-compose.yml
```yaml
services:
tempo:
image: grafana/tempo:latest
command: ["-config.file=/etc/tempo.yaml"]
ports:
- "4318:4318"
```
***
## Combined Setup
[Section titled “Combined Setup”](#combined-setup)
Run mockd with full observability:
```bash
mockd serve \
--log-level debug \
--log-format json \
--loki-endpoint http://localhost:3100/loki/api/v1/push \
--otlp-endpoint http://localhost:4318/v1/traces \
--trace-sampler 1.0
```
### Docker Compose Example
[Section titled “Docker Compose Example”](#docker-compose-example)
```yaml
version: '3.8'
services:
mockd:
image: ghcr.io/getmockd/mockd:latest
ports:
- "4280:4280"
- "4290:4290"
command: >
serve
--loki-endpoint http://loki:3100/loki/api/v1/push
--otlp-endpoint http://tempo:4318/v1/traces
depends_on:
- loki
- tempo
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
tempo:
image: grafana/tempo:latest
ports:
- "4318:4318"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
```
***
## Request Streaming
[Section titled “Request Streaming”](#request-streaming)
For real-time request monitoring, use the SSE endpoint:
```bash
curl -N http://localhost:4290/requests/stream
```
See [Admin API Reference](/reference/admin-api#get-requestsstream) for details.
***
## See Also
[Section titled “See Also”](#see-also)
* [Admin API Reference](/reference/admin-api) - Metrics and streaming endpoints
* [CLI Reference](/reference/cli) - Logging and tracing flags
* [Troubleshooting](/guides/troubleshooting) - Debugging issues
# Proxy Recording
> Use mockd as a MITM proxy to record real API traffic and convert recordings to mock definitions.
mockd includes a **MITM (Man-in-the-Middle) forward proxy** that records real API traffic. Configure your HTTP client to route through the proxy, and mockd captures every request/response pair to disk. You can then convert recordings into mock definitions with `mockd convert`.
## Overview
[Section titled “Overview”](#overview)
Proxy recording is useful for:
* **Capturing real API behavior** without writing mocks by hand
* **Recording integration test fixtures** from live services
* **Creating realistic mocks** from actual API responses
* **Debugging API interactions** by inspecting traffic
## Quick Start
[Section titled “Quick Start”](#quick-start)
Start the proxy in the foreground:
```bash
mockd proxy start
```
This starts a forward proxy on port **8888** in `record` mode. Configure your HTTP client to use it:
```bash
# cURL with proxy
curl -x http://localhost:8888 http://api.example.com/users
# Or set environment variables
export http_proxy=http://localhost:8888
export https_proxy=http://localhost:8888
curl http://api.example.com/users
```
The request and response are recorded to disk. Press **Ctrl+C** to stop the proxy.
View what was captured:
```bash
mockd recordings list
```
Convert recordings to mock definitions:
```bash
mockd convert -o mocks.yaml
```
## Starting the Proxy
[Section titled “Starting the Proxy”](#starting-the-proxy)
The proxy runs in the foreground and stops with Ctrl+C:
```bash
# Default: port 8888, record mode
mockd proxy start
# Custom port
mockd proxy start --port 9090
# Named session (for organizing recordings)
mockd proxy start --session my-api-test
# Passthrough mode (no recording, just forwarding)
mockd proxy start --mode passthrough
```
### Flags
[Section titled “Flags”](#flags)
| Flag | Short | Default | Description |
| ------------------ | ----- | ------------------ | ----------------------------------------------------- |
| `--port` | `-p` | `8888` | Proxy server port |
| `--mode` | `-m` | `record` | Proxy mode: `record` or `passthrough` |
| `--session` | `-s` | `default` | Recording session name |
| `--recordings-dir` | | (platform default) | Base directory for recordings |
| `--ca-path` | | | CA certificate directory (enables HTTPS interception) |
| `--include` | | | Comma-separated path patterns to include (glob) |
| `--exclude` | | | Comma-separated path patterns to exclude (glob) |
| `--include-hosts` | | | Comma-separated host patterns to include |
| `--exclude-hosts` | | | Comma-separated host patterns to exclude |
## Proxy Modes
[Section titled “Proxy Modes”](#proxy-modes)
### Record Mode (default)
[Section titled “Record Mode (default)”](#record-mode-default)
Records all traffic passing through:
```bash
mockd proxy start --mode record
```
Every HTTP request/response pair is persisted to disk as it flows through the proxy.
### Passthrough Mode
[Section titled “Passthrough Mode”](#passthrough-mode)
Forwards traffic without recording:
```bash
mockd proxy start --mode passthrough
```
Useful for debugging or when you only need the proxy behavior without capturing data.
## HTTPS Interception
[Section titled “HTTPS Interception”](#https-interception)
By default, HTTPS requests are tunneled (TCP pass-through) and **not recorded** because the traffic is encrypted.
To record HTTPS traffic, generate a CA certificate and configure your system to trust it:
### Generate a CA Certificate
[Section titled “Generate a CA Certificate”](#generate-a-ca-certificate)
```bash
# Generate CA cert and key
mockd proxy ca generate --ca-path ./certs
# Start proxy with HTTPS interception
mockd proxy start --ca-path ./certs
```
The proxy dynamically generates per-host TLS certificates signed by your CA, enabling it to decrypt and record HTTPS traffic.
### Trust the CA Certificate
[Section titled “Trust the CA Certificate”](#trust-the-ca-certificate)
Export the certificate for installation:
```bash
# Export to file
mockd proxy ca export --ca-path ./certs -o mockd-ca.crt
# macOS: Add to system keychain
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain mockd-ca.crt
# Linux (Debian/Ubuntu): Add to system certificates
sudo cp mockd-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
```
## Filtering
[Section titled “Filtering”](#filtering)
Control what gets recorded using include/exclude patterns with glob matching (`*` wildcard):
### Filter by Path
[Section titled “Filter by Path”](#filter-by-path)
```bash
# Only record API paths
mockd proxy start --include "/api/*"
# Exclude health checks
mockd proxy start --exclude "/health,/metrics,/ping"
```
### Filter by Host
[Section titled “Filter by Host”](#filter-by-host)
```bash
# Only record traffic to specific hosts
mockd proxy start --include-hosts "api.example.com,auth.example.com"
# Exclude noisy hosts
mockd proxy start --exclude-hosts "analytics.example.com,cdn.example.com"
```
### Combine Filters
[Section titled “Combine Filters”](#combine-filters)
```bash
mockd proxy start \
--include-hosts "api.example.com" \
--include "/api/*" \
--exclude "/api/health"
```
**Filter precedence:**
1. If the request matches **any** exclude pattern → not recorded
2. If include patterns exist and the request matches **none** → not recorded
3. Otherwise → recorded
## Recording Storage
[Section titled “Recording Storage”](#recording-storage)
Recordings are organized by session and host:
```plaintext
~/.local/share/mockd/recordings/
├── default-20260224-143000/
│ ├── meta.json
│ ├── api.example.com/
│ │ ├── rec_a1b2c3d4.json
│ │ └── rec_e5f6a7b8.json
│ └── auth.example.com/
│ └── rec_c9d0e1f2.json
├── my-session-20260225-091500/
│ ├── meta.json
│ └── ...
└── latest -> default-20260224-143000/
```
The `latest` symlink always points to the most recent session. The default storage location is platform-specific:
| Platform | Default Path |
| -------- | ------------------------------------------------- |
| macOS | `~/Library/Application Support/mockd/recordings/` |
| Linux | `~/.local/share/mockd/recordings/` |
| Windows | `%LOCALAPPDATA%/mockd/recordings/` |
Override with `--recordings-dir` or the `XDG_DATA_HOME` environment variable.
## Managing Recordings
[Section titled “Managing Recordings”](#managing-recordings)
### List Sessions
[Section titled “List Sessions”](#list-sessions)
```bash
mockd recordings sessions
```
### List Recordings
[Section titled “List Recordings”](#list-recordings)
```bash
# From the latest session
mockd recordings list
# From a specific session
mockd recordings list --session my-api-test
# Filter by method or host
mockd recordings list --method GET
mockd recordings list --host api.example.com
```
### Export Recordings
[Section titled “Export Recordings”](#export-recordings)
```bash
# Export to JSON
mockd recordings export -o recordings.json
# From a specific session
mockd recordings export --session my-api-test -o recordings.json
```
### Import Recordings
[Section titled “Import Recordings”](#import-recordings)
```bash
mockd recordings import --input recordings.json --session imported
```
### Clear Recordings
[Section titled “Clear Recordings”](#clear-recordings)
```bash
# Clear a specific session
mockd recordings clear --session my-api-test --force
# Clear all sessions
mockd recordings clear --force
```
## Converting to Mocks
[Section titled “Converting to Mocks”](#converting-to-mocks)
The `mockd convert` command transforms recorded traffic into mock definitions:
```bash
# Convert latest session, output to stdout
mockd convert
# Save to file
mockd convert -o mocks.yaml
# Convert a specific session
mockd convert --session my-api-test -o mocks.yaml
```
### Smart Matching
[Section titled “Smart Matching”](#smart-matching)
Detect dynamic path segments and convert them to path parameters:
```bash
# Turns /users/123 into /users/{id}
mockd convert --smart-match -o mocks.yaml
```
### Filtering During Conversion
[Section titled “Filtering During Conversion”](#filtering-during-conversion)
```bash
# Only GET and POST requests
mockd convert --method GET,POST
# Only successful responses
mockd convert --status 2xx
# Only specific hosts
mockd convert --include-hosts "api.example.com"
# Only specific paths
mockd convert --path-filter "/api/*"
```
### Duplicate Handling
[Section titled “Duplicate Handling”](#duplicate-handling)
When multiple recordings match the same endpoint:
```bash
# Keep first occurrence (default)
mockd convert --duplicates first
# Keep last occurrence
mockd convert --duplicates last
# Keep all occurrences
mockd convert --duplicates all
```
## Example Workflow
[Section titled “Example Workflow”](#example-workflow)
### 1. Record Real API Traffic
[Section titled “1. Record Real API Traffic”](#1-record-real-api-traffic)
```bash
# Start the proxy
mockd proxy start --session api-capture --port 8888
# In another terminal, run your app through the proxy
http_proxy=http://localhost:8888 npm test
# Stop the proxy with Ctrl+C
```
### 2. Review Recordings
[Section titled “2. Review Recordings”](#2-review-recordings)
```bash
mockd recordings list --session api-capture
# ID METHOD HOST PATH STATUS DURATION
# a1b2c3d4 GET api.example.com /api/users 200 150ms
# e5f6a7b8 POST api.example.com /api/users 201 89ms
# c9d0e1f2 GET api.example.com /api/users/1 200 45ms
```
### 3. Convert to Mocks
[Section titled “3. Convert to Mocks”](#3-convert-to-mocks)
```bash
mockd convert --session api-capture --smart-match -o mocks.yaml
```
### 4. Use the Mocks
[Section titled “4. Use the Mocks”](#4-use-the-mocks)
```bash
mockd serve --config mocks.yaml
# Your tests now run against captured responses — no external dependency needed
```
## Proxy vs Mock Server
[Section titled “Proxy vs Mock Server”](#proxy-vs-mock-server)
| Feature | Proxy Recording | Mock Server |
| ------------------- | ------------------------ | ----------------------- |
| Upstream dependency | Required (for recording) | Not needed |
| Real responses | Yes | No (mocked) |
| Recording | Built-in | N/A |
| Offline operation | No | Yes |
| Best for | Capturing real behavior | Development and testing |
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [TLS/HTTPS Configuration](/guides/tls-https/) — Certificate management for mock serving
* [CLI Reference](/reference/cli/) — All available commands
* [Configuration Reference](/reference/configuration/) — Full config schema
# Replay Modes
> Understand mockd's three replay modes for stream recordings - Pure, Synchronized, and Triggered - and when to use each.
mockd supports three replay modes for stream recordings, each suited to different testing scenarios. Understanding when to use each mode helps you create more effective and reliable tests.
## Overview
[Section titled “Overview”](#overview)
| Mode | Timing | Client Input | Best For |
| ---------------- | ------------------------- | ------------------- | ------------------------------------------- |
| **Pure** | Original delays preserved | Ignored | Demo, playback, timing verification |
| **Synchronized** | Waits for client messages | Required to proceed | Protocol compliance, request-response flows |
| **Triggered** | Manual/API control | Optional | Step debugging, integration tests |
## Pure Mode
[Section titled “Pure Mode”](#pure-mode)
Replays server messages with original timing, ignoring any client input.
### How It Works
[Section titled “How It Works”](#how-it-works)
1. Recording starts playing immediately on connection
2. Delays between frames match the original recording (scaled by `timingScale`)
3. Client messages are ignored - playback continues regardless
4. Completes when all server frames have been sent
### Use Cases
[Section titled “Use Cases”](#use-cases)
* **Demos**: Show realistic streaming behavior
* **Timing verification**: Ensure client handles message timing correctly
* **Load testing**: Generate predictable server traffic patterns
* **UI development**: Develop against a predictable stream
### Configuration
[Section titled “Configuration”](#configuration)
```bash
# Via Admin API
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/replay \
-H "Content-Type: application/json" \
-d '{
"mode": "pure",
"timingScale": 1.0
}'
```
```json
{
"mode": "pure",
"timingScale": 1.0
}
```
### Options
[Section titled “Options”](#options)
| Option | Type | Default | Description |
| ------------- | ----- | ------- | -------------------------------------------------- |
| `timingScale` | float | 1.0 | Speed multiplier. 0.5 = 2x speed, 2.0 = half speed |
### Example: 2x Speed Playback
[Section titled “Example: 2x Speed Playback”](#example-2x-speed-playback)
```bash
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/replay \
-d '{"mode": "pure", "timingScale": 0.5}'
```
### Pause/Resume
[Section titled “Pause/Resume”](#pauseresume)
Pure mode supports pause and resume:
```bash
# Pause
curl -X POST http://localhost:4290/replay/SESSION_ID/pause
# Resume
curl -X POST http://localhost:4290/replay/SESSION_ID/resume
```
## Synchronized Mode
[Section titled “Synchronized Mode”](#synchronized-mode)
Waits for expected client messages before sending server responses. Ensures the client follows the recorded conversation flow.
### How It Works
[Section titled “How It Works”](#how-it-works-1)
1. Recording analyzes client-to-server and server-to-client frame order
2. When a client message is expected, replay pauses and waits
3. Client must send a message (optionally matching expected content)
4. After receiving client message, server responses are sent
5. Timeout occurs if client doesn’t respond within limit
### Use Cases
[Section titled “Use Cases”](#use-cases-1)
* **Protocol compliance testing**: Verify client sends correct messages
* **Integration testing**: Test full request-response flows
* **Conversation flow testing**: Ensure proper message sequencing
* **State machine validation**: Test stateful WebSocket protocols
### Configuration
[Section titled “Configuration”](#configuration-1)
```bash
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/replay \
-H "Content-Type: application/json" \
-d '{
"mode": "synchronized",
"strictMatching": false,
"timeout": 30000
}'
```
```json
{
"mode": "synchronized",
"strictMatching": false,
"timeout": 30000
}
```
### Options
[Section titled “Options”](#options-1)
| Option | Type | Default | Description |
| ---------------- | ---- | ------- | ----------------------------------- |
| `strictMatching` | bool | false | Require exact message content match |
| `timeout` | int | 30000 | Max ms to wait for client message |
### Strict vs Loose Matching
[Section titled “Strict vs Loose Matching”](#strict-vs-loose-matching)
**Loose matching** (default): Any client message advances the replay. Useful when message content varies but sequence matters.
**Strict matching**: Client message must exactly match recorded content. Useful for protocol compliance verification.
```bash
# Strict matching - client must send exact messages
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/replay \
-d '{"mode": "synchronized", "strictMatching": true}'
```
### Timeout Handling
[Section titled “Timeout Handling”](#timeout-handling)
If the client doesn’t send a message within the timeout:
* Replay session enters error state
* `ErrMatchTimeout` error is returned
* Session can be stopped and restarted
### Example Flow
[Section titled “Example Flow”](#example-flow)
Given a recording with this sequence:
```plaintext
1. [s2c] Welcome message
2. [c2s] Client login
3. [s2c] Login success
4. [c2s] Client request data
5. [s2c] Data response
```
Replay behavior:
1. Server sends “Welcome message” immediately
2. Replay waits for client to send any message
3. Client sends login -> Server sends “Login success”
4. Replay waits for client
5. Client sends request -> Server sends “Data response”
6. Replay completes
## Triggered Mode
[Section titled “Triggered Mode”](#triggered-mode)
Manual control over frame advancement via API. Each frame or batch of frames is sent only when explicitly triggered.
### How It Works
[Section titled “How It Works”](#how-it-works-2)
1. Recording loads but doesn’t start sending
2. Optionally sends first frame on connect (`autoAdvanceOnConnect`)
3. Waits for `/replay/{id}/advance` API calls
4. Each advance sends one or more frames
5. Completes when all frames have been sent
### Use Cases
[Section titled “Use Cases”](#use-cases-2)
* **Step debugging**: Examine state after each message
* **Integration test assertions**: Assert between each message
* **Controlled scenarios**: Precise control over message timing
* **Interactive demos**: Manual control for presentations
### Configuration
[Section titled “Configuration”](#configuration-2)
```bash
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/replay \
-H "Content-Type: application/json" \
-d '{
"mode": "triggered",
"autoAdvanceOnConnect": true
}'
```
```json
{
"mode": "triggered",
"autoAdvanceOnConnect": true
}
```
### Options
[Section titled “Options”](#options-2)
| Option | Type | Default | Description |
| ---------------------- | ---- | ------- | --------------------------------------- |
| `autoAdvanceOnConnect` | bool | false | Send first frame immediately on connect |
### Advancing Playback
[Section titled “Advancing Playback”](#advancing-playback)
```bash
# Advance 1 frame (default)
curl -X POST http://localhost:4290/replay/SESSION_ID/advance
# Advance N frames
curl -X POST http://localhost:4290/replay/SESSION_ID/advance \
-d '{"count": 5}'
# Advance until specific content
curl -X POST http://localhost:4290/replay/SESSION_ID/advance \
-d '{"until": "{\"type\":\"complete\"}"}'
```
### Advance Response
[Section titled “Advance Response”](#advance-response)
```json
{
"framesSent": 1,
"currentFrame": 5,
"totalFrames": 42,
"status": "waiting",
"complete": false
}
```
### Example: Integration Test
[Section titled “Example: Integration Test”](#example-integration-test)
```javascript
// Start triggered replay
const { sessionId } = await fetch('/stream-recordings/REC_ID/replay', {
method: 'POST',
body: JSON.stringify({ mode: 'triggered' })
}).then(r => r.json());
// Connect WebSocket
const ws = new WebSocket('ws://localhost:4280/ws/chat');
// Advance and assert
await fetch(`/replay/${sessionId}/advance`, { method: 'POST' });
const msg1 = await nextMessage(ws);
expect(msg1.type).toBe('welcome');
await fetch(`/replay/${sessionId}/advance`, { method: 'POST' });
const msg2 = await nextMessage(ws);
expect(msg2.type).toBe('ready');
// Clean up
await fetch(`/replay/${sessionId}`, { method: 'DELETE' });
```
## Replay Status
[Section titled “Replay Status”](#replay-status)
Check replay session status:
```bash
curl http://localhost:4290/replay/SESSION_ID
```
```json
{
"id": "01REPLAY123456",
"recordingId": "01HXYZ123456",
"status": "playing",
"mode": "pure",
"currentFrame": 15,
"totalFrames": 42,
"framesSent": 15,
"elapsedMs": 5432
}
```
### Status Values
[Section titled “Status Values”](#status-values)
| Status | Description |
| ---------- | -------------------------------------------------------------- |
| `pending` | Session created, not yet started |
| `playing` | Actively sending frames |
| `waiting` | Waiting for client input (synchronized) or trigger (triggered) |
| `paused` | Paused (pure mode only) |
| `complete` | All frames sent |
| `aborted` | Session stopped early |
## Managing Replay Sessions
[Section titled “Managing Replay Sessions”](#managing-replay-sessions)
### List Active Sessions
[Section titled “List Active Sessions”](#list-active-sessions)
```bash
curl http://localhost:4290/replay
```
### Stop Replay
[Section titled “Stop Replay”](#stop-replay)
```bash
curl -X DELETE http://localhost:4290/replay/SESSION_ID
```
## Replay API Reference
[Section titled “Replay API Reference”](#replay-api-reference)
| Method | Endpoint | Description |
| ------ | -------------------------------- | --------------------------- |
| POST | `/stream-recordings/{id}/replay` | Start replay for recording |
| GET | `/replay` | List active replay sessions |
| GET | `/replay/{id}` | Get replay session status |
| DELETE | `/replay/{id}` | Stop replay session |
| POST | `/replay/{id}/advance` | Advance triggered replay |
| POST | `/replay/{id}/pause` | Pause pure mode replay |
| POST | `/replay/{id}/resume` | Resume paused replay |
## Mode Comparison
[Section titled “Mode Comparison”](#mode-comparison)
### When to Use Each Mode
[Section titled “When to Use Each Mode”](#when-to-use-each-mode)
```plaintext
Need original timing?
├── Yes → Pure Mode
│ └── Want to control speed? → Use timingScale
└── No
├── Need to verify client messages? → Synchronized Mode
│ └── Need exact content match? → strictMatching: true
└── Need step-by-step control? → Triggered Mode
└── Want first frame auto-sent? → autoAdvanceOnConnect: true
```
### Performance Characteristics
[Section titled “Performance Characteristics”](#performance-characteristics)
| Mode | CPU Usage | Memory | Network |
| ------------ | ------------- | ------ | ------------- |
| Pure | Low | Low | Steady stream |
| Synchronized | Low (waiting) | Low | Bursty |
| Triggered | Minimal | Low | On-demand |
## Common Patterns
[Section titled “Common Patterns”](#common-patterns)
### Speed Testing
[Section titled “Speed Testing”](#speed-testing)
Test how client handles fast message streams:
```bash
# 10x speed
curl -X POST http://localhost:4290/stream-recordings/REC_ID/replay \
-d '{"mode": "pure", "timingScale": 0.1}'
```
### Protocol Verification
[Section titled “Protocol Verification”](#protocol-verification)
Ensure client follows correct message sequence:
```bash
curl -X POST http://localhost:4290/stream-recordings/REC_ID/replay \
-d '{"mode": "synchronized", "strictMatching": true, "timeout": 5000}'
```
### Debugging Session
[Section titled “Debugging Session”](#debugging-session)
Step through messages one at a time:
```bash
# Start triggered replay
REPLAY_ID=$(curl -s -X POST http://localhost:4290/stream-recordings/REC_ID/replay \
-d '{"mode": "triggered"}' | jq -r '.sessionId')
# Step through
while true; do
result=$(curl -s -X POST "http://localhost:4290/replay/$REPLAY_ID/advance")
echo "$result" | jq
if [ "$(echo $result | jq -r '.complete')" = "true" ]; then
break
fi
read -p "Press enter to advance..."
done
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Stream Recording](/guides/stream-recording/) - Recording WebSocket and SSE streams
* [SSE Streaming](/guides/sse-streaming/) - SSE mock configuration
* [Admin API](/reference/admin-api/) - Stream recording API endpoints
# Request Matching
> Learn how mockd evaluates matchers to determine which mock responds to incoming HTTP requests.
Request matching determines which mock responds to an incoming HTTP request. mockd evaluates matchers in order and returns the first matching response.
Note
Examples below show the contents of the `http` block within a mock definition. In a config file, wrap each example in the full mock structure:
```yaml
mocks:
- id: my-mock
type: http
http:
matcher: { ... }
response: { ... }
```
## Basic Matching
[Section titled “Basic Matching”](#basic-matching)
### Method Matching
[Section titled “Method Matching”](#method-matching)
Match specific HTTP methods:
```json
{
"matcher": {
"method": "GET"
}
}
```
Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
### Path Matching
[Section titled “Path Matching”](#path-matching)
Exact path match:
```json
{
"matcher": {
"path": "/api/users"
}
}
```
## Path Parameters
[Section titled “Path Parameters”](#path-parameters)
Capture dynamic path segments using curly braces:
```json
{
"matcher": {
"path": "/api/users/{id}"
}
}
```
This matches:
* `/api/users/1`
* `/api/users/abc`
* `/api/users/123-456`
Access captured values in responses:
```json
{
"response": {
"body": {
"userId": "{{request.pathParam.id}}"
}
}
}
```
### Multiple Path Parameters
[Section titled “Multiple Path Parameters”](#multiple-path-parameters)
```json
{
"matcher": {
"path": "/api/{resource}/{id}/comments/{commentId}"
}
}
```
Matches `/api/posts/5/comments/12` with:
* `resource = "posts"`
* `id = "5"`
* `commentId = "12"`
### Greedy Path Matching
[Section titled “Greedy Path Matching”](#greedy-path-matching)
Match remaining path segments with `.*`:
```json
{
"matcher": {
"path": "/api/files/{filepath:.*}"
}
}
```
Matches `/api/files/documents/2024/report.pdf`
### Regex Path Matching (pathPattern)
[Section titled “Regex Path Matching (pathPattern)”](#regex-path-matching-pathpattern)
Use full regex patterns for advanced path matching with `pathPattern`:
```json
{
"matcher": {
"pathPattern": "^/api/users/\\d+$"
}
}
```
This matches `/api/users/123` but not `/api/users/abc`.
#### Named Capture Groups
[Section titled “Named Capture Groups”](#named-capture-groups)
Extract path segments into named variables:
```json
{
"matcher": {
"pathPattern": "^/api/(?P\\w+)/(?P\\d+)$"
}
}
```
Matches `/api/users/456` with captures:
* `resource = "users"`
* `id = "456"`
#### Common Regex Patterns
[Section titled “Common Regex Patterns”](#common-regex-patterns)
| Pattern | Description |
| ------------------------------- | ---------------------------------- |
| `^/api/users/\\d+$` | Numeric ID only |
| `^/api/(users\|products)/\\d+$` | Multiple resource types |
| `^/api/orders/[0-9a-f-]{36}$` | UUID format |
| `^/api/items/[\\w-]+$` | Slugs with alphanumeric and dashes |
## Query Parameter Matching
[Section titled “Query Parameter Matching”](#query-parameter-matching)
Match requests with specific query parameters:
```json
{
"matcher": {
"path": "/api/users",
"queryParams": {
"page": "1",
"limit": "10"
}
}
}
```
### Optional Query Parameters
[Section titled “Optional Query Parameters”](#optional-query-parameters)
Only specified parameters are required. Additional parameters are ignored:
```json
{
"matcher": {
"path": "/api/search",
"queryParams": {
"q": "test"
}
}
}
```
Matches both:
* `/api/search?q=test`
* `/api/search?q=test&page=1&extra=value`
## Header Matching
[Section titled “Header Matching”](#header-matching)
Match requests with specific headers:
```json
{
"matcher": {
"headers": {
"Content-Type": "application/json",
"X-API-Key": "secret123"
}
}
}
```
### Wildcard Header Matching
[Section titled “Wildcard Header Matching”](#wildcard-header-matching)
Use `*` wildcards for flexible header matching:
```json
{
"matcher": {
"headers": {
"Authorization": "Bearer *",
"Accept": "application/*"
}
}
}
```
Supported patterns:
* `prefix*` - matches values starting with prefix
* `*suffix` - matches values ending with suffix
* `*contains*` - matches values containing the substring
### Case Sensitivity
[Section titled “Case Sensitivity”](#case-sensitivity)
Header names are case-insensitive (per HTTP spec), but values are case-sensitive:
```json
{
"matcher": {
"headers": {
"content-type": "application/json"
}
}
}
```
Matches `Content-Type: application/json` and `CONTENT-TYPE: application/json`
## Body Matching
[Section titled “Body Matching”](#body-matching)
Match requests with specific body content.
### Substring Matching (bodyContains)
[Section titled “Substring Matching (bodyContains)”](#substring-matching-bodycontains)
Use `bodyContains` to match requests whose body contains a specific substring:
```json
{
"matcher": {
"bodyContains": "username"
}
}
```
Matches any request body that contains the string `"username"`. The value is a plain string, not a regex or JSON object.
### Exact Body Matching (bodyEquals)
[Section titled “Exact Body Matching (bodyEquals)”](#exact-body-matching-bodyequals)
Use `bodyEquals` for exact string comparison:
```json
{
"matcher": {
"bodyEquals": "{\"action\":\"login\"}"
}
}
```
The entire request body must match this string exactly.
### Regex Body Matching (bodyPattern)
[Section titled “Regex Body Matching (bodyPattern)”](#regex-body-matching-bodypattern)
Use full regex patterns for body matching with `bodyPattern`:
```json
{
"matcher": {
"bodyPattern": "\"email\":\\s*\"[^\"]+@example\\.com\""
}
}
```
This matches any JSON body containing an email field ending with `@example.com`.
#### Useful Body Patterns
[Section titled “Useful Body Patterns”](#useful-body-patterns)
| Pattern | Description |
| ------------------------------------ | ----------------------------------- |
| `"status":\\s*"(pending\|approved)"` | Match status values |
| `[0-9a-f]{8}-[0-9a-f]{4}-` | Contains UUID |
| `(?i)error` | Contains “error” (case-insensitive) |
| `(?s)start.*end` | Multiline matching |
### JSONPath Body Matching (bodyJsonPath)
[Section titled “JSONPath Body Matching (bodyJsonPath)”](#jsonpath-body-matching-bodyjsonpath)
Match specific JSON fields using JSONPath expressions:
```json
{
"matcher": {
"bodyJsonPath": {
"$.user.name": "John",
"$.items[0].quantity": 5,
"$.status": "active"
}
}
}
```
This matches requests where:
* `$.user.name` equals “John”
* `$.items[0].quantity` equals 5
* `$.status` equals “active”
#### JSONPath Syntax
[Section titled “JSONPath Syntax”](#jsonpath-syntax)
| Expression | Description |
| --------------- | ----------------- |
| `$.field` | Root-level field |
| `$.user.name` | Nested field |
| `$.items[0]` | Array index |
| `$.items[*].id` | Any array element |
| `$..name` | Recursive descent |
#### Existence Checks
[Section titled “Existence Checks”](#existence-checks)
Check if a field exists (or doesn’t exist):
```json
{
"matcher": {
"bodyJsonPath": {
"$.token": {"exists": true},
"$.deleted": {"exists": false}
}
}
}
```
#### Type Support
[Section titled “Type Support”](#type-support)
JSONPath matching supports:
* Strings: `"$.name": "John"`
* Numbers: `"$.age": 30`
* Booleans: `"$.active": true`
* Null: `"$.deleted": null`
## Combining Matchers
[Section titled “Combining Matchers”](#combining-matchers)
Combine multiple matchers for precise matching:
```json
{
"matcher": {
"method": "POST",
"path": "/api/users/{id}/comments",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer*"
},
"queryParams": {
"notify": "true"
},
"bodyContains": "comment text"
}
}
```
All conditions must match for the mock to respond.
## Priority and Ordering
[Section titled “Priority and Ordering”](#priority-and-ordering)
When multiple mocks could match, mockd uses this priority:
### 1. Specificity
[Section titled “1. Specificity”](#1-specificity)
More specific matches win:
```json
// This wins for /api/users/1
{ "path": "/api/users/1" }
// This wins for /api/users/2, /api/users/abc, etc.
{ "path": "/api/users/{id}" }
```
### 2. Number of Matchers
[Section titled “2. Number of Matchers”](#2-number-of-matchers)
Mocks with more conditions win:
```json
// Less specific (matches any GET /api/users)
{ "method": "GET", "path": "/api/users" }
// More specific (matches only with Authorization header)
{ "method": "GET", "path": "/api/users", "headers": { "Authorization": "*" } }
```
### 3. Configuration Order
[Section titled “3. Configuration Order”](#3-configuration-order)
When priority is equal, earlier mocks in the config file win.
## Matching Examples
[Section titled “Matching Examples”](#matching-examples)
### API Key Required
[Section titled “API Key Required”](#api-key-required)
```yaml
mocks:
- id: api-key-required
type: http
http:
matcher:
pathPattern: "^/api/.*"
headers:
X-API-Key: "valid-key-123"
response:
statusCode: 200
body: '{"access": "granted"}'
```
### Content Negotiation
[Section titled “Content Negotiation”](#content-negotiation)
```yaml
mocks:
- id: data-xml
type: http
http:
matcher:
path: /api/data
headers:
Accept: "application/xml"
response:
statusCode: 200
headers:
Content-Type: "application/xml"
body: "..."
- id: data-json
type: http
http:
matcher:
path: /api/data
headers:
Accept: "application/json"
response:
statusCode: 200
headers:
Content-Type: "application/json"
body: '{"data": "..."}'
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Response Templating](/guides/response-templating/) - Dynamic responses
* [Stateful Mocking](/guides/stateful-mocking/) - CRUD simulation
* [Configuration Reference](/reference/configuration/) - Full schema
# Response Templating
> Create dynamic responses that include data from the incoming request, generate random values, or compute values at response time.
Response templating allows you to create dynamic responses that include data from the incoming request, generate random values, or compute values at response time.
Note
Examples below show the contents of the `http` block within a mock definition. In a config file, wrap each example in the full mock structure:
```yaml
mocks:
- id: my-mock
type: http
http:
matcher: { method: POST, path: /api/example }
response: { ... }
```
## Template Syntax
[Section titled “Template Syntax”](#template-syntax)
Templates use double curly braces: `{{expression}}`
```json
{
"response": {
"body": {
"message": "Hello, {{request.query.name}}"
}
}
}
```
Request: `GET /api/greet?name=Alice` Response: `{"message": "Hello, Alice"}`
## Request Data
[Section titled “Request Data”](#request-data)
Access various parts of the incoming request.
### Path Parameters
[Section titled “Path Parameters”](#path-parameters)
```json
{
"matcher": {
"path": "/api/users/{id}"
},
"response": {
"body": {
"id": "{{request.pathParam.id}}",
"url": "/api/users/{{request.pathParam.id}}"
}
}
}
```
### Query Parameters
[Section titled “Query Parameters”](#query-parameters)
```json
{
"response": {
"body": {
"page": "{{request.query.page}}",
"limit": "{{request.query.limit}}"
}
}
}
```
### Headers
[Section titled “Headers”](#headers)
```json
{
"response": {
"body": {
"userAgent": "{{request.header.User-Agent}}",
"correlationId": "{{request.header.X-Correlation-ID}}"
}
}
}
```
### Request Body
[Section titled “Request Body”](#request-body)
Access parsed request body (JSON):
```json
{
"response": {
"body": {
"received": {
"username": "{{request.body.username}}",
"email": "{{request.body.email}}"
},
"status": "created"
}
}
}
```
Nested access:
```json
{
"response": {
"body": {
"city": "{{request.body.address.city}}",
"firstItem": "{{request.body.items[0].name}}"
}
}
}
```
### Request Metadata
[Section titled “Request Metadata”](#request-metadata)
```json
{
"response": {
"body": {
"method": "{{request.method}}",
"path": "{{request.path}}",
"fullUrl": "{{request.url}}"
}
}
}
```
## Built-in Functions
[Section titled “Built-in Functions”](#built-in-functions)
### Timestamps
[Section titled “Timestamps”](#timestamps)
```json
{
"response": {
"body": {
"timestamp": "{{now}}",
"isoTimestamp": "{{timestamp.iso}}",
"unixTimestamp": "{{timestamp}}",
"unixMs": "{{timestamp.unix_ms}}"
}
}
}
```
Output:
```json
{
"timestamp": "2024-01-15T10:30:00-06:00",
"isoTimestamp": "2024-01-15T16:30:00.123456789Z",
"unixTimestamp": "1705315800",
"unixMs": "1705315800123"
}
```
### Random Values
[Section titled “Random Values”](#random-values)
```json
{
"response": {
"body": {
"id": "{{uuid}}",
"shortId": "{{uuid.short}}",
"randomInt": "{{random.int(1, 100)}}",
"randomFloat": "{{random.float(0, 1)}}",
"randomString": "{{random.string(8)}}"
}
}
}
```
### String Functions
[Section titled “String Functions”](#string-functions)
```json
{
"response": {
"body": {
"upper": "{{upper request.body.name}}",
"lower": "{{lower request.body.email}}",
"fallback": "{{default request.query.name \"Anonymous\"}}"
}
}
}
```
## Default Values
[Section titled “Default Values”](#default-values)
Provide fallback values when a field is missing:
```json
{
"response": {
"body": {
"page": "{{default request.query.page \"1\"}}",
"limit": "{{default request.query.limit \"10\"}}"
}
}
}
```
Both space-separated and parenthesized syntax work:
```json
{
"response": {
"body": {
"name": "{{default(request.query.name, \"Anonymous\")}}"
}
}
}
```
## Response Headers
[Section titled “Response Headers”](#response-headers)
Templates work in headers too:
```json
{
"response": {
"headers": {
"X-Request-ID": "{{uuid}}",
"X-Correlation-ID": "{{request.header.X-Correlation-ID}}",
"Location": "/api/users/{{request.body.id}}"
}
}
}
```
## Faker Functions
[Section titled “Faker Functions”](#faker-functions)
Generate realistic sample data with 35 built-in faker types. Faker type names are **case-insensitive** — `faker.Name`, `faker.name`, and `faker.NAME` all work. Faker functions work in **all protocols** — HTTP, GraphQL, gRPC, SOAP, WebSocket, SSE, and MQTT response bodies.
```json
{
"response": {
"body": {
"name": "{{faker.name}}",
"email": "{{faker.email}}",
"phone": "{{faker.phone}}",
"company": "{{faker.company}}",
"card": "{{faker.creditCard}}",
"ip": "{{faker.ipv4}}",
"job": "{{faker.jobTitle}}",
"price": "{{faker.price}}"
}
}
}
```
### Basic
[Section titled “Basic”](#basic)
| Faker Type | Example Output |
| ----------------- | -------------------------------------- |
| `faker.name` | John Smith |
| `faker.firstName` | Alice |
| `faker.lastName` | Williams |
| `faker.email` | |
| `faker.phone` | +1-555-123-4567 |
| `faker.company` | Acme Corp |
| `faker.address` | 123 Main St, New York, NY 10001 |
| `faker.word` | delta |
| `faker.sentence` | Lorem ipsum dolor sit amet. |
| `faker.words` | alpha gamma epsilon (3-5 random words) |
| `faker.words(n)` | alpha beta gamma (exactly n words) |
| `faker.boolean` | true |
| `faker.uuid` | a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
### Internet
[Section titled “Internet”](#internet)
| Faker Type | Example Output |
| ------------------ | --------------------------------------- |
| `faker.ipv4` | 192.168.1.42 |
| `faker.ipv6` | 2001:0db8:85a3:0000:0000:8a2e:0370:7334 |
| `faker.macAddress` | 00:1A:2B:3C:4D:5E |
| `faker.userAgent` | Mozilla/5.0 (Windows NT 10.0; …) |
| `faker.url` | |
### Finance
[Section titled “Finance”](#finance)
| Faker Type | Example Output |
| --------------------- | ----------------------------- |
| `faker.creditCard` | 4532015112830366 (Luhn-valid) |
| `faker.creditCardExp` | 08/28 (MM/YY, always future) |
| `faker.cvv` | 847 |
| `faker.currencyCode` | USD (ISO 4217) |
| `faker.currency` | US Dollar |
| `faker.iban` | GB29NWBK60161331926819 |
| `faker.price` | 49.99 |
### Commerce & Identity
[Section titled “Commerce & Identity”](#commerce--identity)
| Faker Type | Example Output |
| ------------------- | ------------------------ |
| `faker.productName` | Ergonomic Steel Chair |
| `faker.color` | indigo |
| `faker.hexColor` | #4A90D9 |
| `faker.ssn` | 123-45-6789 |
| `faker.passport` | X12345678 |
| `faker.jobTitle` | Senior Software Engineer |
### Geo & Data
[Section titled “Geo & Data”](#geo--data)
| Faker Type | Example Output |
| --------------------- | --------------------- |
| `faker.latitude` | 37.774929 |
| `faker.longitude` | -122.419416 |
| `faker.slug` | ergonomic-steel-chair |
| `faker.mimeType` | application/json |
| `faker.fileExtension` | pdf |
Tip
`faker.words(n)` is the only parameterized faker type. Use it to generate exactly *n* space-separated words: `{{faker.words(5)}}` → `alpha beta gamma delta epsilon`.
Faker type names are case-insensitive: `{{faker.firstName}}`, `{{faker.firstname}}`, and `{{faker.FIRSTNAME}}` all produce the same result.
## Seeded (Deterministic) Responses
[Section titled “Seeded (Deterministic) Responses”](#seeded-deterministic-responses)
By default, faker functions and random values produce different output on every request. For deterministic testing, you can **seed** the random number generator so that the same request always produces the same response.
### Query Parameter Seeding
[Section titled “Query Parameter Seeding”](#query-parameter-seeding)
Add `?_mockd_seed=` to any request:
```bash
# These two requests return identical faker output
curl "http://localhost:4280/api/users?_mockd_seed=42"
curl "http://localhost:4280/api/users?_mockd_seed=42"
# Different seed = different (but deterministic) output
curl "http://localhost:4280/api/users?_mockd_seed=99"
```
### Config-Level Seeding
[Section titled “Config-Level Seeding”](#config-level-seeding)
Set the `seed` field on a response to make it always deterministic without query parameters:
```yaml
mocks:
- id: deterministic-user
type: http
http:
matcher:
method: GET
path: /api/test-user
response:
statusCode: 200
seed: 42
body: |
{
"name": "{{faker.name}}",
"email": "{{faker.email}}",
"id": "{{uuid}}"
}
```
Every request to `/api/test-user` returns the same name, email, and UUID.
### Use Cases
[Section titled “Use Cases”](#use-cases)
* **Snapshot testing** — Compare responses against golden files
* **Flaky test elimination** — Same seed = same output = no randomness-induced failures
* **Reproducible bug reports** — Share the seed value to reproduce exact responses
Tip
Config-level `seed` and query parameter `_mockd_seed` can be combined. The query parameter takes precedence if both are present.
## Sequences
[Section titled “Sequences”](#sequences)
Generate auto-incrementing values (useful for IDs):
```yaml
response:
statusCode: 200
body: |
{
"id": "{{sequence("order-id")}}",
"ticketNumber": "{{sequence("tickets", 1000)}}"
}
```
The optional second argument sets the starting value (default: 1). Sequences persist for the lifetime of the server.
Note
The `sequence()` function uses double quotes around the name. When writing body templates in YAML, use the literal block style (`body: |`) to avoid quote escaping issues.
## Complete Example
[Section titled “Complete Example”](#complete-example)
```yaml
mocks:
- id: create-order
type: http
http:
matcher:
method: POST
path: /api/orders
response:
statusCode: 201
headers:
Content-Type: "application/json"
Location: "/api/orders/{{uuid}}"
X-Request-ID: "{{request.header.X-Request-ID}}"
body: |
{
"id": "{{uuid}}",
"status": "pending",
"customer": {
"name": "{{request.body.customer.name}}",
"email": "{{lower request.body.customer.email}}"
},
"total": "{{request.body.total}}",
"createdAt": "{{now}}"
}
```
Request:
```bash
curl -X POST http://localhost:4280/api/orders \
-H "Content-Type: application/json" \
-H "X-Request-ID: req-123" \
-d '{
"customer": {"name": "Alice", "email": "ALICE@EXAMPLE.COM"},
"total": 49.99
}'
```
Response:
```json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"customer": {
"name": "Alice",
"email": "alice@example.com"
},
"total": "49.99",
"createdAt": "2026-02-24T10:30:00-06:00"
}
```
## Template Reference
[Section titled “Template Reference”](#template-reference)
| Expression | Description |
| ---------------------------- | -------------------------------------------------- |
| `{{request.method}}` | HTTP method |
| `{{request.path}}` | Request path |
| `{{request.url}}` | Full request URL |
| `{{request.pathParam.name}}` | Path parameter |
| `{{request.query.name}}` | Query parameter |
| `{{request.header.Name}}` | Request header |
| `{{request.body.field}}` | Body field (dot-nested) |
| `{{request.rawBody}}` | Raw request body string |
| `{{now}}` | Current timestamp (RFC3339) |
| `{{timestamp}}` | Unix timestamp (seconds) |
| `{{timestamp.iso}}` | ISO timestamp (RFC3339Nano UTC) |
| `{{timestamp.unix_ms}}` | Unix timestamp (milliseconds) |
| `{{uuid}}` | Random UUID |
| `{{uuid.short}}` | Short random ID (hex) |
| `{{random.int(min, max)}}` | Random integer in range (alias: `randomInt`) |
| `{{random.float(min, max)}}` | Random float in range (alias: `randomFloat`) |
| `{{random.string(length)}}` | Random alphanumeric string (alias: `randomString`) |
| `{{sequence("name")}}` | Auto-incrementing counter |
| `{{upper value}}` | Uppercase string |
| `{{lower value}}` | Lowercase string |
| `{{default value fallback}}` | Default if empty |
| `{{faker.name}}` | Random person name |
| `{{faker.email}}` | Random email address |
| `{{faker.creditCard}}` | Luhn-valid credit card number |
| `{{faker.ipv4}}` | Random IPv4 address |
| `{{faker.price}}` | Random price (e.g., 49.99) |
| `{{faker.*}}` | [35 types total](#faker-functions) |
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Request Matching](/guides/request-matching/) - Matching rules
* [Stateful Mocking](/guides/stateful-mocking/) - CRUD simulation
* [Configuration Reference](/reference/configuration/) - Full schema
# Sharing Mocks Publicly
> Learn how to expose your local mockd server to the internet for teammates, clients, or external services.
This guide covers how to expose your local mockd server to the internet, enabling teammates, clients, or external services to access your mocks.
## Overview
[Section titled “Overview”](#overview)
mockd supports several ways to share your mocks publicly:
| Method | Cost | Protocols | Best For |
| ------------------------------------------- | ---------- | -------------------- | --------------------------------------------- |
| [mockd tunnel (built-in)](#mockd-tunnel) | Free | All 7 protocols | Recommended for all users |
| [Third-party tunnels](#third-party-tunnels) | Free | HTTP, WebSocket, SSE | Alternative if built-in tunnel is unavailable |
| [Self-hosted relay](#self-hosted-relay) | Your infra | All protocols | Enterprise, air-gapped environments |
## Protocol Support
[Section titled “Protocol Support”](#protocol-support)
mockd’s built-in tunnel supports **all seven protocols** through a single secure connection on port 443:
| Protocol | Tunnel Support | How It Works |
| ---------- | -------------- | ------------------------------------------ |
| HTTP/HTTPS | Yes | Standard HTTPS |
| gRPC | Yes | Native HTTP/2 with trailers (not gRPC-web) |
| WebSocket | Yes | Upgrade proxied, bidirectional streaming |
| MQTT | Yes | TLS ALPN routing (`mqtt`) on port 443 |
| SSE | Yes | Streaming responses |
| GraphQL | Yes | Over HTTP |
| SOAP | Yes | Over HTTP |
## mockd Tunnel
[Section titled “mockd Tunnel”](#mockd-tunnel)
mockd includes a built-in cloud tunnel that exposes your local mocks to the internet with a single command. No signup required for anonymous tunnels (2-hour session, 100MB bandwidth).
### Quick Start
[Section titled “Quick Start”](#quick-start)
```bash
# Start mock server + tunnel in one shot (recommended)
mockd tunnel --config mocks.yaml
# Output:
# Connecting to relay at relay.mockd.io:443...
# Anonymous token acquired (2h session, 100MB bandwidth)
#
# Tunnel connected!
# Public URL: https://a1b2c3d4.tunnel.mockd.io
# Local server: http://localhost:4280
# Admin API: http://localhost:4290
```
Your mocks are now accessible at `https://a1b2c3d4.tunnel.mockd.io`.
Alternatively, if you already have a running mockd server, enable the tunnel on it:
```bash
# Start your mock server
mockd serve --config mocks.yaml
# In another terminal, enable the tunnel
mockd tunnel enable
```
### Multi-Protocol Tunneling
[Section titled “Multi-Protocol Tunneling”](#multi-protocol-tunneling)
All protocols are tunneled automatically through the single secure connection:
```bash
# Start tunnel with a config that includes gRPC, MQTT, etc.
mockd tunnel --config multi-protocol.yaml
# Test gRPC through the tunnel
grpcurl -d '{"name": "World"}' a1b2c3d4.tunnel.mockd.io:443 helloworld.Greeter/SayHello
# Test MQTT through the tunnel (requires TLS ALPN client)
mosquitto_pub -h a1b2c3d4.tunnel.mockd.io -p 443 --alpn mqtt \
--capath /etc/ssl/certs -t test/hello -m "Hello!"
```
### Tunnel Authentication
[Section titled “Tunnel Authentication”](#tunnel-authentication)
Protect your tunnel from unauthorized access:
Use exactly one auth mode per tunnel. `--auth-token`, `--auth-basic`, and `--allow-ips` are mutually exclusive.
```bash
# Require bearer token
mockd tunnel --config mocks.yaml --auth-token secret123
# Require HTTP Basic Auth
mockd tunnel --config mocks.yaml --auth-basic admin:password
# Restrict by IP range
mockd tunnel --config mocks.yaml --allow-ips "10.0.0.0/8,192.168.1.0/24"
```
### Use Cases
[Section titled “Use Cases”](#use-cases)
* **Webhook development**: Expose mocks to receive webhooks from Stripe, GitHub, etc.
* **Team sharing**: Share mocks with remote teammates without deploying
* **Client demos**: Show API mocks to stakeholders with a public URL
* **CI/CD integration**: Use tunneled endpoints in integration test pipelines
* **Mobile testing**: Test mobile apps against mocks on a real device
## Third-Party Tunnels
[Section titled “Third-Party Tunnels”](#third-party-tunnels)
For quick testing or if you’re using the OSS version, you can use free third-party tunnel services.
### localtunnel (Recommended for Testing)
[Section titled “localtunnel (Recommended for Testing)”](#localtunnel-recommended-for-testing)
[localtunnel](https://localtunnel.me) is free, requires no signup, and supports HTTP/WebSocket/SSE.
```bash
# Install
npm install -g localtunnel
# Start mockd
mockd serve
# In another terminal, create tunnel
lt --port 4280
# Output: your url is: https://random-name.loca.lt
```
**Testing your tunnel:**
```bash
# Add a mock
mockd http add --path /api/users --body '{"users": [{"id": 1, "name": "Alice"}]}'
# Test via tunnel (note the bypass header)
curl -H "bypass-tunnel-reminder: true" https://random-name.loca.lt/api/users
```
**WebSocket through localtunnel:**
```javascript
// localtunnel supports WebSocket upgrade
const ws = new WebSocket('wss://random-name.loca.lt/ws/chat');
ws.onopen = () => ws.send('Hello from tunnel!');
```
### ngrok
[Section titled “ngrok”](#ngrok)
[ngrok](https://ngrok.com) offers a free tier with signup. Better reliability than localtunnel.
```bash
# Install (see ngrok.com/download)
# Configure auth token (one-time)
ngrok config add-authtoken YOUR_TOKEN
# Start tunnel
ngrok http 4280
```
**ngrok features:**
* Stable URLs (paid)
* Request inspection dashboard
* Custom domains (paid)
### Cloudflare Tunnel
[Section titled “Cloudflare Tunnel”](#cloudflare-tunnel)
If you have a Cloudflare account with a domain, [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) provides free, unlimited HTTP tunneling.
```bash
# Install cloudflared
# See: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
# Quick tunnel (temporary URL)
cloudflared tunnel --url http://localhost:4280
# Named tunnel (persistent, requires setup)
cloudflared tunnel create mockd
cloudflared tunnel route dns mockd mocks.yourdomain.com
cloudflared tunnel run mockd
```
### Comparison
[Section titled “Comparison”](#comparison)
| Feature | localtunnel | ngrok (free) | Cloudflare Tunnel |
| ------------------ | ----------- | -------------- | ----------------- |
| Cost | Free | Free (limited) | Free |
| Signup required | No | Yes | Yes (+ domain) |
| Stable URLs | No | No (paid only) | Yes |
| WebSocket | Yes | Yes | Yes |
| SSE | Yes | Yes | Yes |
| TCP tunnels | No | Paid only | No |
| Request inspection | No | Yes | Yes |
## Tunnel Tiers
[Section titled “Tunnel Tiers”](#tunnel-tiers)
| Tier | Session Duration | Bandwidth | Subdomain | Signup |
| ------------- | ---------------- | --------- | --------------- | ------ |
| Anonymous | 2 hours | 100 MB | Random | No |
| Free | 8 hours | 1 GB | Random | Yes |
| Pro ($12/mo) | 24 hours | 5 GB/mo | Custom | Yes |
| Team ($29/mo) | Unlimited | 50 GB/mo | Custom + domain | Yes |
Anonymous tunnels require no signup or token — just run `mockd tunnel`.
## Self-Hosted Relay
[Section titled “Self-Hosted Relay”](#self-hosted-relay)
For enterprise users or those needing full control, you can run your own relay server.
### Docker Compose
[Section titled “Docker Compose”](#docker-compose)
```yaml
version: '3.8'
services:
mockd-relay:
image: ghcr.io/getmockd/relay:latest
ports:
- "80:80"
- "443:443"
environment:
- MOCKD_DOMAIN=relay.yourcompany.com
- MOCKD_TLS_EMAIL=admin@yourcompany.com
volumes:
- caddy_data:/data
volumes:
caddy_data:
```
### Connecting to Self-Hosted Relay
[Section titled “Connecting to Self-Hosted Relay”](#connecting-to-self-hosted-relay)
```bash
mockd tunnel --relay wss://relay.yourcompany.com/tunnel --token YOUR_TOKEN
```
### Kubernetes / Helm
[Section titled “Kubernetes / Helm”](#kubernetes--helm)
```bash
helm repo add mockd https://charts.mockd.dev
helm install mockd-relay mockd/relay \
--set domain=relay.yourcompany.com \
--set tls.email=admin@yourcompany.com
```
## Security Considerations
[Section titled “Security Considerations”](#security-considerations)
### Protecting Your Tunnel
[Section titled “Protecting Your Tunnel”](#protecting-your-tunnel)
By default, tunnels are public. Add authentication for sensitive mocks:
```bash
# Require bearer token
mockd tunnel --auth-token secret123
# Require HTTP Basic Auth
mockd tunnel --auth-basic admin:password
# Restrict by IP
mockd tunnel --allow-ips "10.0.0.0/8,192.168.1.0/24"
```
### What Gets Exposed
[Section titled “What Gets Exposed”](#what-gets-exposed)
When you create a tunnel:
* **Exposed**: The local port you specify with `--port` (HTTP, gRPC, WebSocket, SSE)
* **Exposed**: MQTT broker ports (via TLS ALPN routing)
* **NOT exposed**: Admin API (port 4290) — unless explicitly tunneled
* **NOT exposed**: Other local services
### Best Practices
[Section titled “Best Practices”](#best-practices)
1. **Use authentication** for any non-trivial testing
2. **Disable mocks** you don’t want publicly accessible
3. **Monitor usage** via mockd logs or cloud dashboard
4. **Stop tunnels** when not actively needed
5. **Use short-lived tunnels** for demos rather than persistent ones
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
### Tunnel connects but requests fail
[Section titled “Tunnel connects but requests fail”](#tunnel-connects-but-requests-fail)
Check that mockd is running and accessible locally:
```bash
curl http://localhost:4280/health
```
### WebSocket connections drop
[Section titled “WebSocket connections drop”](#websocket-connections-drop)
Some tunnel providers have idle timeouts. Configure heartbeat in your WebSocket mock config:
```yaml
# In your YAML config file
websocket:
path: /ws
heartbeat:
enabled: true
interval: "30s"
timeout: "10s"
```
### SSE stream cuts off
[Section titled “SSE stream cuts off”](#sse-stream-cuts-off)
Ensure your tunnel provider supports long-lived connections. localtunnel and ngrok both support SSE.
### ”No mock matched” errors
[Section titled “”No mock matched” errors”](#no-mock-matched-errors)
The tunnel is working but no mock matches the request. Check your mock configuration:
```bash
mockd list
```
## See Also
[Section titled “See Also”](#see-also)
* [CLI Reference: tunnel](/reference/cli/#mockd-tunnel)
* [gRPC Mocking](/protocols/grpc/)
* [MQTT Mocking](/protocols/mqtt/)
* [WebSocket Mocking](/protocols/websocket/)
* [SSE Streaming](/protocols/sse/)
# Stateful Mocking
> Simulate real CRUD APIs where resources persist across requests with create, update, and delete operations.
Stateful mocking allows mockd to simulate real CRUD APIs where resources persist across requests. Create, update, and delete operations modify state that subsequent requests can observe.
Workspace Isolation
Stateful resources are scoped to workspaces. Resources created in one workspace are independent from resources in other workspaces. Use `--workspace` to target a specific workspace.
## Overview
[Section titled “Overview”](#overview)
Traditional mocks return static responses. Stateful mocking maintains an in-memory store that:
* **POST** creates new resources
* **GET** retrieves current resources
* **PUT** replaces existing resources
* **DELETE** removes resources
Changes persist for the lifetime of the server session.
## Quick Start
[Section titled “Quick Start”](#quick-start)
### CLI Shortcut (Quick Prototyping)
[Section titled “CLI Shortcut (Quick Prototyping)”](#cli-shortcut-quick-prototyping)
The fastest way to get a stateful CRUD API running:
```bash
# Creates a data store + HTTP CRUD mocks in one step
mockd http add --path /api/users --stateful
```
Or create the resource manually:
```bash
mockd stateful add users
```
### Config File (Production)
[Section titled “Config File (Production)”](#config-file-production)
For production configs, use **tables** (pure data stores) and **extend** (explicit bindings from mocks to tables):
```yaml
version: "1.0"
tables:
- name: users
idField: id
mocks:
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
- id: create-user
type: http
http:
matcher: { method: POST, path: /api/users }
response: { statusCode: 201 }
- id: get-user
type: http
http:
matcher: { method: GET, path: /api/users/{id} }
response: { statusCode: 200 }
- id: update-user
type: http
http:
matcher: { method: PUT, path: /api/users/{id} }
response: { statusCode: 200 }
- id: delete-user
type: http
http:
matcher: { method: DELETE, path: /api/users/{id} }
response: { statusCode: 200 }
extend:
- mock: list-users
table: users
action: list
- mock: create-user
table: users
action: create
- mock: get-user
table: users
action: get
- mock: update-user
table: users
action: update
- mock: delete-user
table: users
action: delete
```
Start the server and interact:
```bash
# Create a user (returns 201 with auto-generated UUID id)
curl -X POST http://localhost:4280/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# Response: {"id": "a1b2c3d4-...", "name": "Alice", "email": "alice@example.com", ...}
# List users - returns paginated response
curl http://localhost:4280/api/users
# Response: {"data": [...], "meta": {"total": 1, "limit": 100, "offset": 0, "count": 1}}
# Get single user by ID
curl http://localhost:4280/api/users/a1b2c3d4-...
# Response: {"id": "a1b2c3d4-...", "name": "Alice", "email": "alice@example.com"}
# Update user
curl -X PUT http://localhost:4280/api/users/a1b2c3d4-... \
-H "Content-Type: application/json" \
-d '{"name": "Alice Smith", "email": "alice@example.com"}'
# Delete user
curl -X DELETE http://localhost:4280/api/users/a1b2c3d4-...
# Response: 204 No Content
# User is gone
curl http://localhost:4280/api/users/a1b2c3d4-...
# Response: 404 Not Found
```
## Configuration
[Section titled “Configuration”](#configuration)
### Tables
[Section titled “Tables”](#tables)
Tables are pure data stores — they hold seed data and a schema but have no HTTP routing attached. Routing is handled by extend bindings.
```yaml
tables:
- name: users
idField: id
seedData:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
```
| Field | Description | Default |
| ---------- | ------------------------------------ | -------- |
| `name` | Table name (used in extend bindings) | Required |
| `idField` | Field name for resource ID | `"id"` |
| `seedData` | Initial data array | `[]` |
### Extend Bindings
[Section titled “Extend Bindings”](#extend-bindings)
Each extend binding connects a mock endpoint to a table with a specific action:
```yaml
extend:
- mock: list-users # references mock id
table: users # references table name
action: list # CRUD action
```
| Field | Description | Required |
| ----------- | -------------------------------------------------------------- | -------- |
| `mock` | ID of the mock to bind | Yes |
| `table` | Name of the table | Yes |
| `action` | `list`, `get`, `create`, `update`, `patch`, `delete`, `custom` | Yes |
| `operation` | Custom operation name (when `action: custom`) | No |
POST-as-Update: When to use `patch` vs `update`
Most REST APIs use PUT for full replacement and PATCH for partial updates. But some APIs (notably Stripe and Twilio) use **POST for both creates and updates**. When the POST endpoint uses partial-merge semantics (only fields present in the body are updated, others are preserved), use `action: patch`:
```yaml
extend:
# Stripe uses POST to update customers — only sent fields change
- mock: stripe.PostCustomersId
table: customers
action: patch # NOT update — partial merge
# Traditional REST API uses PUT for full replacement
- mock: update-user
table: users
action: update # Full replacement — missing fields are removed
```
**Rule of thumb:** Use `update` for PUT endpoints (full replace). Use `patch` for PATCH endpoints or POST endpoints that do partial updates.
### Multiple Tables
[Section titled “Multiple Tables”](#multiple-tables)
```yaml
tables:
- name: users
seedData:
- id: "1"
name: "Alice"
- name: posts
seedData:
- id: "1"
title: "First Post"
- name: comments
idField: id
# parentField not needed — parent scoping is handled by mock path params
mocks:
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
- id: list-posts
type: http
http:
matcher: { method: GET, path: /api/posts }
response: { statusCode: 200 }
extend:
- mock: list-users
table: users
action: list
- mock: list-posts
table: posts
action: list
```
### Seed Data
[Section titled “Seed Data”](#seed-data)
Pre-populate tables:
```yaml
tables:
- name: users
seedData:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
```
## CRUD Operations
[Section titled “CRUD Operations”](#crud-operations)
### Create (POST)
[Section titled “Create (POST)”](#create-post)
```bash
POST /api/users
Content-Type: application/json
{"name": "Charlie", "email": "charlie@example.com"}
```
Response (`201 Created`):
```json
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Charlie",
"email": "charlie@example.com",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
```
IDs are auto-generated as UUIDs unless the request body includes an `id` field.
### Read Collection (GET)
[Section titled “Read Collection (GET)”](#read-collection-get)
```bash
GET /api/users
```
Response (paginated):
```json
{
"data": [
{"id": "1", "name": "Alice"},
{"id": "2", "name": "Bob"}
],
"meta": {
"total": 2,
"limit": 100,
"offset": 0,
"count": 2
}
}
```
### Read Single (GET)
[Section titled “Read Single (GET)”](#read-single-get)
```bash
GET /api/users/2
```
Response:
```json
{"id": "2", "name": "Bob", "email": "bob@example.com"}
```
Not found:
```bash
GET /api/users/999
```
Response: `404 Not Found`
### Update (PUT)
[Section titled “Update (PUT)”](#update-put)
Replace entire resource:
```bash
PUT /api/users/2
Content-Type: application/json
{"name": "Robert", "email": "robert@example.com"}
```
Response:
```json
{"id": "2", "name": "Robert", "email": "robert@example.com"}
```
### Delete (DELETE)
[Section titled “Delete (DELETE)”](#delete-delete)
```bash
DELETE /api/users/2
```
Response: `204 No Content`
## Nested Resources
[Section titled “Nested Resources”](#nested-resources)
Handle parent-child relationships using tables with extend bindings:
```yaml
tables:
- name: posts
seedData:
- id: "1"
title: "First Post"
- name: comments
seedData: []
mocks:
- id: list-comments
type: http
http:
matcher: { method: GET, path: /api/posts/{postId}/comments }
response: { statusCode: 200 }
- id: create-comment
type: http
http:
matcher: { method: POST, path: /api/posts/{postId}/comments }
response: { statusCode: 201 }
extend:
- mock: list-comments
table: comments
action: list
- mock: create-comment
table: comments
action: create
```
Comments are scoped to their parent post via the path parameter:
```bash
# Get comments for post 1
GET /api/posts/1/comments
# Create comment on post 1
POST /api/posts/1/comments
{"text": "Great post!"}
```
## ID Strategies
[Section titled “ID Strategies”](#id-strategies)
Tables support five ID generation strategies, controlled by the `idStrategy` field. When a create request includes an ID in the body, that ID is used regardless of strategy.
| Strategy | `idStrategy` | Example Output | Description |
| -------------- | ------------ | -------------------------------------- | ------------------------------------------------------------------------ |
| UUID (default) | `uuid` | `f47ac10b-58cc-4372-a567-0e02b2c3d479` | Standard UUID v4 |
| Prefix | `prefix` | `cus_a1b2c3d4e5f6g7h8` | Configurable prefix + 16 random hex characters (requires `idPrefix`) |
| ULID | `ulid` | `01HQJK5Y3N8RJZVP10XGBC94XR` | Universally Unique Lexicographically Sortable Identifier (time-sortable) |
| Sequence | `sequence` | `1`, `2`, `3` | Auto-incrementing integer starting from 1 |
| Short | `short` | `a1b2c3d4e5f6g7h8` | 16 random hex characters (no prefix) |
### Configuration
[Section titled “Configuration”](#configuration-1)
```yaml
tables:
# UUID (default — no config needed)
- name: users
idField: id
# Prefix — Stripe-style IDs like cus_abc123
- name: customers
idField: id
idStrategy: prefix
idPrefix: "cus_"
# ULID — time-sortable
- name: events
idField: id
idStrategy: ulid
# Sequence — auto-incrementing integers
- name: tickets
idField: id
idStrategy: sequence
# Short — compact hex IDs
- name: tokens
idField: id
idStrategy: short
```
When using `sequence`, the counter is reset to 0 on `POST /state/reset`. If seed data contains numeric IDs, the counter starts after the highest seed ID.
## Filtering, Sorting & Pagination
[Section titled “Filtering, Sorting & Pagination”](#filtering-sorting--pagination)
### Query Parameter Filtering
[Section titled “Query Parameter Filtering”](#query-parameter-filtering)
Filter by any field using exact-match query parameters. Any query parameter that isn’t a [reserved parameter](#reserved-query-parameters) is treated as a field filter:
```bash
# Single field filter
GET /api/users?status=active
# Multiple fields (AND logic)
GET /api/users?status=active&role=admin
# Filter by ID
GET /api/users?id=123
```
### Bracket Notation (Nested Fields)
[Section titled “Bracket Notation (Nested Fields)”](#bracket-notation-nested-fields)
Filter on nested object fields using bracket notation:
```bash
# Filter by nested field
GET /api/users?metadata[tier]=premium
# Multiple levels of nesting
GET /api/users?address[country]=US
```
Bracket notation resolves against the stored data — `metadata[tier]` matches items where `data.metadata.tier` equals `"premium"`.
### Sorting
[Section titled “Sorting”](#sorting)
Sort results by any field:
```bash
GET /api/users?sort=name&order=asc
GET /api/users?sort=createdAt&order=desc
```
| Parameter | Description | Default |
| --------- | -------------------------------------------------------------------- | ----------- |
| `sort` | Field to sort by (`id`, `createdAt`, `updatedAt`, or any data field) | `createdAt` |
| `order` | Sort direction: `asc` or `desc` | `desc` |
Sorting supports string, numeric (int, int64, float64), and time comparisons. Unknown types fall back to string comparison.
### Offset-Based Pagination
[Section titled “Offset-Based Pagination”](#offset-based-pagination)
```bash
GET /api/users?limit=10&offset=20
```
| Parameter | Description | Default |
| --------- | ----------------------- | ------- |
| `limit` | Maximum items to return | `100` |
| `offset` | Number of items to skip | `0` |
Response includes pagination metadata:
```json
{
"data": [...],
"meta": {
"total": 45,
"limit": 10,
"offset": 20,
"count": 10,
"has_more": true
}
}
```
### Cursor-Based Pagination
[Section titled “Cursor-Based Pagination”](#cursor-based-pagination)
For Stripe-style APIs, use cursor-based pagination with `starting_after` and `ending_before`:
```bash
# Get next page after a specific item
GET /v1/customers?limit=10&starting_after=cus_123
# Get previous page before a specific item
GET /v1/customers?limit=10&ending_before=cus_456
```
| Parameter | Description |
| ---------------- | --------------------------------------------------------------- |
| `starting_after` | Return items after the item with this ID (forward pagination) |
| `ending_before` | Return items before the item with this ID (backward pagination) |
Cursor pagination is mutually exclusive with `offset`. When a cursor parameter is present, `offset` is ignored. The response’s `has_more` field indicates whether more items exist beyond the current page.
### Parent Field Filtering
[Section titled “Parent Field Filtering”](#parent-field-filtering)
For sub-resource tables (e.g., invoice line items under invoices), the `parentField` configuration automatically filters items by the parent ID from the URL path parameter:
```yaml
tables:
- name: line_items
parentField: invoice # filters by this field
mocks:
- id: list-line-items
type: http
http:
matcher: { method: GET, path: /v1/invoices/{invoice}/lines }
response: { statusCode: 200 }
extend:
- mock: list-line-items
table: line_items
action: list
```
When a request hits `GET /v1/invoices/inv_123/lines`, mockd automatically filters `line_items` where `invoice == "inv_123"`.
### Reserved Query Parameters
[Section titled “Reserved Query Parameters”](#reserved-query-parameters)
These query parameters are reserved by mockd and are NOT treated as field filters:
| Category | Parameters |
| -------------- | ------------------------------------------------------------------------------------------------------------- |
| **Pagination** | `limit`, `offset`, `page`, `per_page`, `starting_after`, `ending_before`, `cursor`, `page_size`, `page_token` |
| **Sorting** | `sort`, `order`, `sort_by`, `order_by` |
| **Expansion** | `expand`, `expand[]`, `fields`, `include`, `exclude`, `select` |
| **Other** | `format`, `pretty`, `api_version`, `idempotency_key`, `request_id` |
## Relationships & Expand
[Section titled “Relationships & Expand”](#relationships--expand)
Tables can define relationships between fields and other tables. When a client requests expansion via `?expand[]`, mockd looks up the related item by ID and inlines the full object in place of the string ID.
### Defining Relationships
[Section titled “Defining Relationships”](#defining-relationships)
Add a `relationships` map to a table, where each key is a field name and the value specifies the target table:
```yaml
tables:
- name: customers
idField: id
idStrategy: prefix
idPrefix: "cus_"
seedData:
- { id: "cus_123", name: "Jenny Rosen", email: "jenny@example.com" }
- name: charges
idField: id
idStrategy: prefix
idPrefix: "ch_"
relationships:
customer: { table: customers }
seedData:
- { id: "ch_456", amount: 2000, currency: "usd", customer: "cus_123" }
```
| Field | Type | Description |
| ------- | ------ | ---------------------------------------------------------------------------------- |
| `table` | string | Name of the target table to look up |
| `field` | string | Field in the target table to match against (default: the target table’s `idField`) |
### Using ?expand\[]
[Section titled “Using ?expand\[\]”](#using-expand)
Expand fields on GET requests (both single-item and list endpoints):
```bash
# Without expand — customer is a string ID
GET /v1/charges/ch_456
# Response: {"id": "ch_456", "amount": 2000, "customer": "cus_123", ...}
# With expand — customer is inlined as the full object
GET /v1/charges/ch_456?expand[]=customer
# Response: {"id": "ch_456", "amount": 2000, "customer": {"id": "cus_123", "name": "Jenny Rosen", ...}, ...}
```
Two syntax styles are supported:
```bash
# Array-style (Stripe convention)
GET /v1/charges?expand[]=customer
# Comma-separated
GET /v1/charges?expand=customer,invoice
```
### Expand on List Endpoints
[Section titled “Expand on List Endpoints”](#expand-on-list-endpoints)
Expand is applied to every item in a list response:
```bash
GET /v1/charges?expand[]=customer
# Each charge in the response has its customer field expanded
```
### Graceful Degradation
[Section titled “Graceful Degradation”](#graceful-degradation)
* If a field has no defined relationship, the expand request for that field is silently ignored
* If the related item is not found (e.g., the referenced ID doesn’t exist in the target table), the field is left as the original string ID
* If the field value is empty or nil, it’s left as-is
### Real-World Example: Stripe Subscriptions
[Section titled “Real-World Example: Stripe Subscriptions”](#real-world-example-stripe-subscriptions)
```yaml
tables:
- name: subscriptions
idStrategy: prefix
idPrefix: "sub_"
relationships:
customer: { table: customers }
latest_invoice: { table: invoices }
seedData:
- { id: "sub_123", customer: "cus_123", status: "active", latest_invoice: "in_789" }
```
```bash
# Expand multiple related objects
GET /v1/subscriptions/sub_123?expand[]=customer&expand[]=latest_invoice
```
## Form URL-Encoded Body Handling
[Section titled “Form URL-Encoded Body Handling”](#form-url-encoded-body-handling)
When a request uses `Content-Type: application/x-www-form-urlencoded`, mockd automatically coerces form data into structured JSON. This is critical for SDK compatibility with APIs like Stripe and Twilio, which use form encoding for all requests.
### Type Coercion
[Section titled “Type Coercion”](#type-coercion)
String form values are automatically converted to their natural types:
| Form Value | Coerced To | Go Type |
| ------------------- | ------------------ | --------- |
| `"true"`, `"false"` | Boolean | `bool` |
| `"42"` | Integer | `int64` |
| `"3.14"` | Float | `float64` |
| `"inf"` | Null | `nil` |
| `"+15551234567"` | String (preserved) | `string` |
Values starting with `+` are NOT coerced to numbers — this preserves phone numbers like `+15551234567` that would otherwise be parsed as positive integers.
### Nested Object Expansion
[Section titled “Nested Object Expansion”](#nested-object-expansion)
Bracket-notation fields are converted to nested objects:
```plaintext
address[city]=New+York&address[state]=NY
```
becomes:
```json
{"address": {"city": "New York", "state": "NY"}}
```
### Array Coercion
[Section titled “Array Coercion”](#array-coercion)
Numeric-keyed bracket notation is converted to arrays:
```plaintext
items[0]=card&items[1]=bank_account
```
becomes:
```json
{"items": ["card", "bank_account"]}
```
This also works with nested objects inside arrays:
```plaintext
items[0][price]=price_123&items[1][price]=price_456
```
becomes:
```json
{"items": [{"price": "price_123"}, {"price": "price_456"}]}
```
### Why This Matters
[Section titled “Why This Matters”](#why-this-matters)
Stripe and Twilio SDKs send all API requests as form-encoded bodies, not JSON. Without this coercion, a Stripe SDK call like:
```go
stripe.Customer.Create(&stripe.CustomerParams{
Name: stripe.String("Jenny Rosen"),
})
```
Would arrive as `name=Jenny+Rosen` and be stored as a raw string instead of properly structured data. The coercion layer ensures mockd handles these SDKs transparently.
## Validation
[Section titled “Validation”](#validation)
Validate incoming requests before creating or updating resources. Validation ensures data integrity by checking field types, formats, constraints, and required fields.
### Quick Example
[Section titled “Quick Example”](#quick-example)
```yaml
tables:
- name: users
validation:
mode: strict
fields:
email:
type: string
required: true
format: email
username:
type: string
required: true
minLength: 3
maxLength: 30
pattern: "^[a-z][a-z0-9_]*$"
age:
type: integer
min: 0
max: 150
role:
type: string
enum: [admin, user, guest]
```
### Validation Modes
[Section titled “Validation Modes”](#validation-modes)
| Mode | Behavior |
| ------------ | ------------------------------------------------------ |
| `strict` | Reject request on any validation failure (default) |
| `warn` | Log warnings but allow request through |
| `permissive` | Only fail on critical errors (missing required fields) |
### Nested Fields
[Section titled “Nested Fields”](#nested-fields)
Validate nested object fields using dot notation:
```yaml
fields:
"address.city":
type: string
required: true
"items.sku":
type: string
pattern: "^SKU-[A-Z0-9]+$"
```
For nested objects, array validation, formats, patterns, and more, see the [Validation Guide](/guides/validation/).
## State Lifetime
[Section titled “State Lifetime”](#state-lifetime)
State exists only in memory and resets when the server stops. Use seed data to pre-populate resources on startup.
## Admin API
[Section titled “Admin API”](#admin-api)
Manage state via the admin API:
```bash
# Get state overview (resource list, item counts)
curl http://localhost:4290/state
# Reset all resources to seed data
curl -X POST http://localhost:4290/state/reset
# List all registered resources
curl http://localhost:4290/state/resources
# Get specific resource info
curl http://localhost:4290/state/resources/users
# Reset a specific resource to its seed data
curl -X POST http://localhost:4290/state/resources/users/reset
# Clear all items from a resource (does NOT restore seed data)
curl -X DELETE http://localhost:4290/state/resources/users
# List items in a resource
curl http://localhost:4290/state/resources/users/items
# Create an item via admin API
curl -X POST http://localhost:4290/state/resources/users/items \
-H "Content-Type: application/json" \
-d '{"name": "Charlie", "email": "charlie@example.com"}'
```
## Combined with Static Mocks
[Section titled “Combined with Static Mocks”](#combined-with-static-mocks)
Tables and extend bindings work alongside traditional static mocks:
```yaml
version: "1.0"
tables:
- name: users
seedData:
- id: "1"
name: "Alice"
mocks:
- id: health-check
type: http
http:
matcher: { method: GET, path: /api/health }
response: { statusCode: 200, body: '{"status": "ok"}' }
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
extend:
- mock: list-users
table: users
action: list
```
Static mocks without extend bindings return their configured response. Mocks with extend bindings route through the stateful table.
## Complete Example
[Section titled “Complete Example”](#complete-example)
```yaml
version: "1.0"
serverConfig:
httpPort: 4280
adminPort: 4290
tables:
- name: users
idField: id
seedData:
- id: "1"
name: "Admin"
role: "admin"
- name: posts
seedData: []
mocks:
- id: list-users
type: http
http:
matcher: { method: GET, path: /api/users }
response: { statusCode: 200 }
- id: create-user
type: http
http:
matcher: { method: POST, path: /api/users }
response: { statusCode: 201 }
- id: get-user
type: http
http:
matcher: { method: GET, path: /api/users/{id} }
response: { statusCode: 200 }
- id: list-posts
type: http
http:
matcher: { method: GET, path: /api/posts }
response: { statusCode: 200 }
- id: create-post
type: http
http:
matcher: { method: POST, path: /api/posts }
response: { statusCode: 201 }
extend:
- { mock: list-users, table: users, action: list }
- { mock: create-user, table: users, action: create }
- { mock: get-user, table: users, action: get }
- { mock: list-posts, table: posts, action: list }
- { mock: create-post, table: posts, action: create }
```
## Multi-Protocol State Sharing
[Section titled “Multi-Protocol State Sharing”](#multi-protocol-state-sharing)
Stateful resources are **protocol-agnostic**. The same in-memory store backs HTTP REST, SOAP, and other protocol handlers. Data created by one protocol is immediately visible to all others.
### SOAP + REST Sharing
[Section titled “SOAP + REST Sharing”](#soap--rest-sharing)
```yaml
version: "1.0"
tables:
- name: users
seedData:
- id: "1"
name: "Alice"
mocks:
- id: create-user
type: http
http:
matcher: { method: POST, path: /api/users }
response: { statusCode: 201 }
- type: soap
soap:
path: /soap/UserService
operations:
GetUser:
statefulResource: users
statefulAction: get
CreateUser:
statefulResource: users
statefulAction: create
extend:
- mock: create-user
table: users
action: create
```
```bash
# Create via REST
curl -X POST http://localhost:4280/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
# Retrieve via SOAP — same data store!
curl -X POST http://localhost:4280/soap/UserService \
-H "SOAPAction: GetUser" -H "Content-Type: text/xml" \
-d '
USER_ID_HERE
'
```
SOAP operations use `statefulResource` and `statefulAction` directly on the operation config (that hasn’t changed). REST endpoints use the tables+extend pattern. Both share the same underlying data store.
This is especially useful for testing systems that use REST internally but expose SOAP externally (or vice versa).
## Custom Operations
[Section titled “Custom Operations”](#custom-operations)
Custom operations compose reads, writes, and expression-evaluated transforms against stateful resources. They enable complex mock scenarios that span multiple resources.
### Example: Fund Transfer
[Section titled “Example: Fund Transfer”](#example-fund-transfer)
```yaml
tables:
- name: accounts
seedData:
- { id: "acct-1", owner: "Alice", balance: 1000 }
- { id: "acct-2", owner: "Bob", balance: 500 }
customOperations:
- name: TransferFunds
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
- type: read
resource: accounts
id: "input.destId"
as: dest
- type: update
resource: accounts
id: "input.sourceId"
set:
balance: "source.balance - input.amount"
- type: update
resource: accounts
id: "input.destId"
set:
balance: "dest.balance + input.amount"
response:
status: '"completed"'
newSourceBalance: "source.balance - input.amount"
newDestBalance: "dest.balance + input.amount"
```
### Step Types
[Section titled “Step Types”](#step-types)
| Step | Fields | Description |
| ---------- | ------------------------------------------ | -------------------------------------------------------------- |
| `read` | `resource`, `id`, `as` | Read an item, store in named variable |
| `create` | `resource`, `set`, `as` | Create an item with expression fields |
| `update` | `resource`, `id`, `set` | Update an item with expression fields |
| `delete` | `resource`, `id` | Delete an item |
| `set` | `var`, `value` | Set a context variable to an expression |
| `list` | `resource`, `as`, `filter` | Query a resource for multiple items and store the result array |
| `validate` | `condition`, `errorMessage`, `errorStatus` | Check a boolean condition; halt with an error if false |
#### List Step
[Section titled “List Step”](#list-step)
The `list` step queries a resource with optional filters and stores the result as an array in a named variable. This enables aggregation via [expr-lang](https://github.com/expr-lang/expr) builtins like `sum()`, `filter()`, `count()`, `map()`, and `reduce()`.
```yaml
steps:
- type: list
resource: transactions
as: txns
filter:
accountId: "input.accountId"
status: "'completed'"
- type: set
var: total
value: "sum(txns, .amount)"
```
The `filter` field is a map of field name to expression. Each expression is evaluated against the operation context. Literal strings must be quoted inside the expression (e.g., `"'completed'"`). The list step returns all matching items (no pagination limit).
#### Validate Step
[Section titled “Validate Step”](#validate-step)
The `validate` step evaluates a boolean expression and halts the operation with an error if the condition is false. This enables business logic validation within custom operations.
```yaml
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
- type: validate
condition: "source.balance >= input.amount"
errorMessage: "Insufficient funds"
errorStatus: 400
```
| Field | Type | Default | Description |
| -------------- | ------ | ---------------------------------- | --------------------------------------------- |
| `condition` | string | Required | Boolean expression — operation halts if false |
| `errorMessage` | string | `"validation failed: {condition}"` | Error message returned on failure |
| `errorStatus` | int | `400` | HTTP status code returned on failure |
### Expression Language
[Section titled “Expression Language”](#expression-language)
Steps use [expr-lang/expr](https://github.com/expr-lang/expr) for evaluating expressions. The environment includes:
* `input` — the request data
* Named variables from prior `read`/`create` steps
* Standard arithmetic, comparison, and string operators
### String Literals in Expressions
[Section titled “String Literals in Expressions”](#string-literals-in-expressions)
Custom operation expressions treat unquoted values as **variable references** and single-quoted values inside the YAML string as **string literals**. This distinction is critical:
```yaml
steps:
- type: update
resource: orders
id: "input.orderId"
set:
# Variable reference — reads the value of pi.status
status: "pi.status"
# String literal — sets the value to the string "succeeded"
status: '"succeeded"'
# Null coalescing — uses input value or falls back to a literal string
reason: 'input.cancellation_reason ?? "requested_by_customer"'
```
The YAML quoting rules:
* `"pi.status"` → evaluated as an expression (variable lookup)
* `'"succeeded"'` → the outer single quotes are YAML, inner double quotes make it an expr-lang string literal
* `'input.reason ?? "default"'` → expression with a string literal fallback
Common mistake
Writing `status: "succeeded"` (without inner quotes) will cause an expression evaluation error because expr-lang interprets `succeeded` as a variable name. Always use `'"literal"'` for string constants in expressions.
### Common Expression Patterns
[Section titled “Common Expression Patterns”](#common-expression-patterns)
Custom operation expressions support the full [expr-lang/expr](https://github.com/expr-lang/expr) syntax. Here are the most commonly used patterns:
| Pattern | Example | Description |
| ----------------- | ----------------------------------------- | ------------------------------------------------ |
| Variable access | `input.amount` | Read a field from input or a named variable |
| Nested access | `source.metadata.tier` | Dot-notation for nested fields |
| Arithmetic | `source.balance - input.amount` | Addition, subtraction, multiplication, division |
| Comparison | `source.balance >= input.amount` | `==`, `!=`, `>`, `<`, `>=`, `<=` |
| String literal | `'"succeeded"'` | Literal string value (note the quoting) |
| Boolean literal | `true`, `false` | Boolean constants |
| Numeric literal | `42`, `3.14` | Integer and float constants |
| Null coalescing | `input.reason ?? "requested_by_customer"` | Use left value if non-nil, otherwise right |
| String conversion | `string(input.amount)` | Convert a value to string |
| Array aggregation | `sum(txns, .amount)` | Sum a field across an array (from a `list` step) |
| Array filtering | `filter(txns, .status == "completed")` | Filter array items |
| Array count | `count(txns, .status == "pending")` | Count matching items |
| Array mapping | `map(txns, .amount)` | Extract a field from each item |
For the complete expression language reference, see the [expr-lang documentation](https://expr-lang.org/docs/language-definition).
### Consistency Modes
[Section titled “Consistency Modes”](#consistency-modes)
Custom operations support two consistency modes:
| Mode | Description |
| ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `best_effort` (default) | Steps execute sequentially. If a step fails, prior state changes persist. |
| `atomic` | Steps execute sequentially. If a step fails, all prior state changes from this operation are rolled back. |
```yaml
customOperations:
- name: TransferFunds
consistency: atomic
steps:
# ...
```
Atomic limitations
**Atomic provides rollback-on-failure within a single operation.** It does NOT provide isolation across concurrent requests — other requests may observe intermediate state during execution. This is a mock server, not a database transaction engine.
### Validating Operations Offline
[Section titled “Validating Operations Offline”](#validating-operations-offline)
Use `mockd stateful custom validate` to check operation definitions before registering them:
```bash
# Compile-check all expressions
mockd stateful custom validate --file transfer.yaml
# Evaluate expressions with sample input (preflight confidence, not a guarantee)
mockd stateful custom validate --file transfer.yaml \
--input '{"sourceId":"acct-1","destId":"acct-2","amount":100}' \
--check-expressions-runtime
# Verify referenced resources exist on the running server
mockd stateful custom validate --file transfer.yaml --check-resources
```
The `--check-expressions-runtime` flag provides **preflight confidence** by evaluating expressions with sample input and optional fixture data. It does not guarantee runtime success — actual resource data may differ from fixtures.
### Using with SOAP
[Section titled “Using with SOAP”](#using-with-soap)
Wire custom operations from SOAP operation configs:
```yaml
mocks:
- type: soap
soap:
path: /soap/BankService
operations:
TransferFunds:
soapAction: "http://bank.example.com/TransferFunds"
statefulResource: TransferFunds
statefulAction: custom
```
When a SOAP request arrives for the `TransferFunds` operation, the handler extracts the SOAP body as a map, passes it as input to the Bridge, executes the custom operation steps, and serializes the result back as an XML SOAP response.
### Using with HTTP Mocks
[Section titled “Using with HTTP Mocks”](#using-with-http-mocks)
Any HTTP mock can trigger a custom operation by setting the `statefulOperation` field instead of a static `response`:
```yaml
mocks:
- id: transfer-endpoint
type: http
http:
matcher:
method: POST
path: /api/transfer
statefulOperation: TransferFunds
```
When a `POST /api/transfer` request arrives, the JSON request body becomes the operation’s `input`, the custom operation steps execute, and the result is returned as a JSON response. This allows HTTP endpoints to run the same multi-step logic as SOAP operations — sharing both the operation definition and the underlying stateful data.
**Example usage (CLI):**
```bash
# Register the custom operation
mockd stateful custom validate --file transfer.yaml --check-resources
# Optional stronger preflight (sample input + runtime expression checks, no writes)
mockd stateful custom validate --file transfer.yaml \
--input '{"sourceId":"acct-1","destId":"acct-2","amount":100}' \
--check-expressions-runtime \
--fixtures-file transfer-fixtures.json
mockd stateful custom add --file transfer.yaml
# Create the HTTP mock wired to the operation
mockd add http --method POST --path /api/transfer --stateful-operation TransferFunds
# Call it
curl -X POST http://localhost:4280/api/transfer \
-H "Content-Type: application/json" \
-d '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
```
**Example usage (YAML config):**
```yaml
customOperations:
- name: TransferFunds
consistency: atomic
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
# ... more steps ...
response:
status: '"completed"'
mocks:
- type: http
http:
matcher:
method: POST
path: /api/transfer
statefulOperation: TransferFunds
```
### Using with the CLI
[Section titled “Using with the CLI”](#using-with-the-cli)
Custom operations can be executed directly from the CLI without any protocol handler:
```bash
mockd stateful custom validate --file transfer.yaml --input '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
mockd stateful custom validate --file transfer.yaml --input '{"sourceId":"acct-1","destId":"acct-2","amount":100}' --check-expressions-runtime --fixtures-file transfer-fixtures.json
mockd stateful custom run TransferFunds --input '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
```
This is useful for testing, scripting, and AI agent workflows where you want to manipulate stateful data through defined business logic without making HTTP/SOAP requests.
### Using with the Admin API
[Section titled “Using with the Admin API”](#using-with-the-admin-api)
Custom operations are also accessible via the admin REST API:
```bash
# List all operations
curl http://localhost:4290/state/operations
# Execute an operation
curl -X POST http://localhost:4290/state/operations/TransferFunds/execute \
-H "Content-Type: application/json" \
-d '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
```
## Importing Specs and Binding to Tables
[Section titled “Importing Specs and Binding to Tables”](#importing-specs-and-binding-to-tables)
Use `imports` to load external API specs (OpenAPI, WSDL) and bind the generated mocks to tables:
```yaml
version: "1.0"
imports:
- path: ./stripe-openapi.yaml
as: stripe
format: openapi
tables:
- name: customers
seedData:
- id: "cus_001"
name: "Alice"
email: "alice@example.com"
extend:
- mock: stripe.ListCustomers
table: customers
action: list
- mock: stripe.CreateCustomer
table: customers
action: create
- mock: stripe.GetCustomer
table: customers
action: get
```
Imported mocks receive IDs prefixed with the namespace using dot notation (e.g., `stripe.ListCustomers`). The `extend` bindings wire those generated mocks to your local tables, creating a stateful digital twin of the imported API.
To discover the available operationIds after importing a spec, use `mockd list` on a running server:
```bash
# List all mocks including imported ones
mockd list
# Filter to see just the imported namespace
mockd list | grep "stripe\."
```
Each operationId comes directly from the OpenAPI spec’s `operationId` field. For example, Stripe’s spec defines `operationId: PostCustomers` on `POST /v1/customers`, which becomes `stripe.PostCustomers` with the `as: stripe` namespace.
## Response Transform Pipeline
[Section titled “Response Transform Pipeline”](#response-transform-pipeline)
When a mock has an extend binding, the response flows through a transform pipeline:
1. **Request arrives** and matches a mock via the standard matcher
2. **Extend binding** routes the request to the table’s Bridge
3. **Bridge executes** the CRUD action (list, get, create, update, delete, custom)
4. **Result is serialized** as the response body (JSON for HTTP, XML for SOAP)
5. **Response headers and status code** from the mock definition are applied
The mock’s `response.body` field is ignored when an extend binding is active — the table’s data becomes the response. However, `response.statusCode` and `response.headers` are still respected.
## Response Transforms
[Section titled “Response Transforms”](#response-transforms)
Response transforms customize how stateful table data is shaped before it’s returned to the client. Without transforms, mockd returns its standard format — items with `id`, `createdAt`, `updatedAt`, and a list envelope of `{"data": [...], "meta": {...}}`. With transforms, you can match the exact response shape of any API — Stripe, Twilio, GitHub, or your own.
This is the feature that makes digital twins possible: the same underlying CRUD data can be returned in Stripe’s format, Twilio’s format, or any other convention.
### Where Transforms Are Defined
[Section titled “Where Transforms Are Defined”](#where-transforms-are-defined)
Transforms can be set at two levels:
1. **Table-level default** — applies to all bindings for that table
2. **Binding-level override** — overrides the table default for a specific endpoint
The resolution order is: binding override > table default > no transform (raw data).
```yaml
tables:
- name: customers
response: # table-level default
timestamps:
format: unix
extend:
- mock: get-customer
table: customers
action: get # uses the table default transform
- mock: list-customers
table: customers
action: list
response: # binding-level override (replaces table default)
timestamps:
format: iso8601
list:
dataField: results
```
### Transform Execution Order
[Section titled “Transform Execution Order”](#transform-execution-order)
When an item is returned from a table, transforms are applied in this order:
1. **Rename** — field keys are renamed (stored data unchanged)
2. **Hide** — fields are removed from the response
3. **WrapAsList** — array fields are wrapped in list object envelopes
4. **Timestamps** — format conversion and/or key renaming for `createdAt`/`updatedAt`
5. **Inject** — static fields are added to the response (always present, can’t be accidentally hidden or renamed)
This ordering is intentional: injected fields are added last so they are always present in the output regardless of hide/rename rules.
### Timestamps
[Section titled “Timestamps”](#timestamps)
Controls how `createdAt` and `updatedAt` fields appear in responses. mockd internally stores timestamps as RFC3339Nano strings; the transform converts them on output.
| Field | Type | Description |
| -------- | ------ | ------------------------------------------------------------- |
| `format` | string | Output format: `unix`, `iso8601`, `rfc3339` (default), `none` |
| `fields` | map | Rename timestamp keys (e.g., `createdAt` to `created`) |
**Formats:**
| Format | Output | Example |
| --------- | ------------------------------------------ | ---------------------------------- |
| `unix` | Integer epoch seconds | `1705312200` |
| `iso8601` | RFC3339 string | `"2024-01-15T10:30:00Z"` |
| `rfc3339` | RFC3339Nano string (default, no transform) | `"2024-01-15T10:30:00.000000000Z"` |
| `none` | Field removed entirely | *(field absent)* |
**Example: Stripe-style unix timestamps renamed to `created`:**
```yaml
tables:
- name: customers
response:
timestamps:
format: unix
fields:
createdAt: created
updatedAt: updated
```
A stored item like:
```json
{"id": "cus_123", "name": "Alice", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z"}
```
Is returned as:
```json
{"id": "cus_123", "name": "Alice", "created": 1705312200, "updated": 1705312200}
```
### Fields
[Section titled “Fields”](#fields)
Controls field-level modifications applied to every item response.
#### Inject
[Section titled “Inject”](#inject)
Adds static key-value pairs to every response. Values are literals — strings, numbers, booleans, nulls, or nested objects.
```yaml
response:
fields:
inject:
object: customer
livemode: false
api_version: "2024-01-01"
```
Every item response from this table will include `"object": "customer"`, `"livemode": false`, and `"api_version": "2024-01-01"` regardless of what’s stored in the table.
#### Hide
[Section titled “Hide”](#hide)
Removes fields from responses. The data is still stored in the table — it’s just not returned to clients. Useful for hiding internal fields or auto-generated fields you don’t want exposed.
```yaml
response:
fields:
hide:
- updatedAt
- _internalNotes
- metadata
```
#### Rename
[Section titled “Rename”](#rename)
Changes field keys in responses without modifying stored data. Key is the original field name, value is the output field name.
```yaml
response:
fields:
rename:
firstName: first_name
lastName: last_name
emailAddress: email
```
#### WrapAsList
[Section titled “WrapAsList”](#wrapaslist)
Wraps specified array fields in list object envelopes. This is essential for APIs like Stripe that represent nested collections as list objects (`{object: "list", data: [...], has_more: false}`) instead of plain arrays.
```yaml
response:
fields:
wrapAsList:
items:
url: "/v1/subscriptions/{{id}}/items"
lines:
url: "/v1/invoices/{{id}}/lines"
```
The `url` field supports `{{fieldName}}` template substitution from the parent item. For example, if the parent item has `id: "sub_123"`, the URL becomes `/v1/subscriptions/sub_123/items`.
A stored array field like:
```json
{"id": "sub_123", "items": [{"price": "price_gold"}]}
```
Is returned as:
```json
{
"id": "sub_123",
"items": {
"object": "list",
"data": [{"price": "price_gold"}],
"has_more": false,
"url": "/v1/subscriptions/sub_123/items"
}
}
```
Set the value to `null` (or omit the `url`) for a plain list wrapper without a URL.
### List Envelope
[Section titled “List Envelope”](#list-envelope)
Controls the shape of list (collection) responses. By default, mockd returns `{"data": [...], "meta": {"total": N, "limit": 100, "offset": 0, "count": N}}`. The list transform lets you customize the envelope to match any API convention.
| Field | Type | Default | Description |
| ------------- | ------- | -------- | ----------------------------------------------------------------- |
| `dataField` | string | `"data"` | Key for the items array |
| `extraFields` | map | `{}` | Static fields injected into the list envelope (not into items) |
| `metaFields` | map | `{}` | Rename pagination meta keys (`total`, `limit`, `offset`, `count`) |
| `hideMeta` | boolean | `false` | Omit pagination metadata entirely |
**Example: Stripe-style list envelope:**
Stripe lists look like `{"object": "list", "data": [...], "has_more": true, "url": "/v1/customers"}` with no separate meta object.
```yaml
response:
list:
dataField: data
extraFields:
object: list
url: /v1/customers
has_more: false
hideMeta: true
```
The `has_more` field is special: when included in `extraFields`, its value is dynamically computed from the pagination state rather than using the static value. This means `has_more` will be `true` when there are more items beyond the current page and `false` otherwise.
All other `extraFields` values are static and passed through as-is, including `null` values. For example, Twilio-style pagination fields like `next_page_uri: null` are included in the response exactly as configured.
**Example: Custom meta field names:**
```yaml
response:
list:
metaFields:
total: total_count
count: page_size
```
Returns:
```json
{
"data": [...],
"meta": {
"total_count": 45,
"limit": 10,
"offset": 0,
"page_size": 10
}
}
```
**Example: Twilio-style envelope:**
```yaml
response:
list:
dataField: results
extraFields:
page: 0
page_size: 50
metaFields:
total: total
hideMeta: false
```
### Verb Overrides
[Section titled “Verb Overrides”](#verb-overrides)
Customize the HTTP status code and response body for create and delete operations.
#### Create Override
[Section titled “Create Override”](#create-override)
By default, create operations return HTTP 201. Some APIs (like Stripe) return 200 for creates.
```yaml
response:
create:
status: 200
```
| Field | Type | Default | Description |
| -------- | ------- | ------- | ------------------------------------- |
| `status` | integer | `201` | HTTP status code for create responses |
#### Delete Override
[Section titled “Delete Override”](#delete-override)
By default, delete operations return HTTP 204 with no body. Transforms let you customize the status code, return a response body, and optionally preserve the item (soft delete).
| Field | Type | Default | Description |
| ---------- | ------- | ------- | ------------------------------------------------------------------- |
| `status` | integer | `204` | HTTP status code for delete responses |
| `body` | map | `nil` | Response body template (supports `{{item.fieldName}}` substitution) |
| `preserve` | boolean | `false` | When true, the item is NOT removed from the store (soft delete) |
**Example: Stripe-style soft delete with confirmation body:**
Stripe’s DELETE endpoints return 200 with a JSON body confirming what was deleted, and the item remains accessible (soft delete):
```yaml
response:
delete:
status: 200
preserve: true
body:
id: "{{item.id}}"
object: customer
deleted: true
```
When you `DELETE /v1/customers/cus_123`, this returns:
```json
{
"id": "cus_123",
"object": "customer",
"deleted": true
}
```
The `{{item.fieldName}}` syntax substitutes values from the item being deleted. The item data is read before the delete action, so all fields are available for template substitution. Only string values in the body template are processed for `{{item.*}}` patterns — non-string values (booleans, numbers) are returned as-is.
When `preserve: true`, the item remains in the table after the delete response is sent. This is useful for APIs that use soft-delete semantics where deleted resources can still be retrieved.
### Error Transforms
[Section titled “Error Transforms”](#error-transforms)
Customize the shape of error responses to match your target API’s error format. Without error transforms, mockd returns its standard format:
```json
{"error": "not found", "resource": "customers", "id": "cus_999", "statusCode": 404}
```
With error transforms, you can match any API’s error convention.
| Field | Type | Description |
| --------- | ------ | ------------------------------------------------------------------------------- |
| `wrap` | string | Nest the error object under a key (e.g., `"error"` produces `{"error": {...}}`) |
| `fields` | map | Map mockd error fields to custom field names |
| `inject` | map | Add static fields to every error response |
| `typeMap` | map | Map mockd error codes to custom type strings |
| `codeMap` | map | Map mockd error codes to custom code strings |
**Available source fields for `fields` mapping:** `message`, `code`, `type`, `resource`, `id`, `field`
**Available error codes for `typeMap` and `codeMap`:** `NOT_FOUND`, `CONFLICT`, `VALIDATION_ERROR`, `CAPACITY_EXCEEDED`, `INTERNAL_ERROR`
**Example: Stripe-style error format:**
```yaml
response:
errors:
wrap: error
fields:
message: message
type: type
code: code
typeMap:
NOT_FOUND: invalid_request_error
CONFLICT: invalid_request_error
VALIDATION_ERROR: invalid_request_error
CAPACITY_EXCEEDED: api_error
INTERNAL_ERROR: api_error
codeMap:
NOT_FOUND: resource_missing
CONFLICT: resource_already_exists
VALIDATION_ERROR: parameter_invalid
```
A 404 error for `GET /v1/customers/cus_nonexistent` returns:
```json
{
"error": {
"message": "not found",
"type": "invalid_request_error",
"code": "resource_missing"
}
}
```
You can also inject static fields into every error:
```yaml
response:
errors:
wrap: error
inject:
doc_url: "https://docs.example.com/errors"
request_log_url: "https://dashboard.example.com/logs"
```
### Complete Example: Stripe Digital Twin Transform
[Section titled “Complete Example: Stripe Digital Twin Transform”](#complete-example-stripe-digital-twin-transform)
This is the full response transform used by the Stripe digital twin. It shows all transform features working together with YAML anchors for reuse across tables:
YAML Anchors for Reuse
Both the Stripe and Twilio digital twin configs use **YAML anchors** (`&name`) and **aliases** (`*name`) to define shared transforms once and reuse them across tables. This is standard YAML — not a mockd feature — but it’s the recommended pattern for avoiding duplication in configs with multiple tables that share the same API conventions.
The `x-` prefixed keys (like `x-stripe-timestamps`) are **ignored by mockd** — they exist solely as YAML anchor hosts. Any top-level key starting with `x-` is treated as a comment/extension and not validated or processed.
```yaml
# YAML anchors for reuse across tables
x-stripe-timestamps: &stripe-timestamps
format: unix
fields:
createdAt: created
updatedAt: updated
x-stripe-hide: &stripe-hide
- updatedAt
x-stripe-errors: &stripe-errors
wrap: error
fields:
message: message
type: type
code: code
typeMap:
NOT_FOUND: invalid_request_error
CONFLICT: invalid_request_error
VALIDATION_ERROR: invalid_request_error
CAPACITY_EXCEEDED: api_error
INTERNAL_ERROR: api_error
codeMap:
NOT_FOUND: resource_missing
CONFLICT: resource_already_exists
VALIDATION_ERROR: parameter_invalid
tables:
- name: customers
idField: id
idStrategy: prefix
idPrefix: "cus_"
seedData:
- { id: "cus_123", name: "Jenny Rosen", email: "jenny.rosen@example.com" }
response:
timestamps: *stripe-timestamps
fields:
inject:
object: customer
livemode: false
hide: *stripe-hide
list:
dataField: data
extraFields:
object: list
url: /v1/customers
has_more: false
hideMeta: true
create:
status: 200
delete:
status: 200
preserve: true
body:
id: "{{item.id}}"
object: customer
deleted: true
errors: *stripe-errors
```
With this transform, mockd responses are indistinguishable from the real Stripe API:
* `GET /v1/customers` returns `{"object":"list","data":[...],"has_more":false,"url":"/v1/customers"}`
* `GET /v1/customers/cus_123` returns `{"id":"cus_123","object":"customer","created":1705312200,"livemode":false,...}`
* `POST /v1/customers` returns 200 (not 201) with the created customer
* `DELETE /v1/customers/cus_123` returns 200 with `{"id":"cus_123","object":"customer","deleted":true}`
* `GET /v1/customers/cus_nonexistent` returns `{"error":{"type":"invalid_request_error","code":"resource_missing","message":"not found"}}`
### Table-Level vs Binding-Level Transforms
[Section titled “Table-Level vs Binding-Level Transforms”](#table-level-vs-binding-level-transforms)
When you need different response shapes for different endpoints on the same table, use binding-level overrides:
```yaml
tables:
- name: customers
response:
# Table default: full transform
timestamps:
format: unix
fields: { createdAt: created }
fields:
inject: { object: customer }
extend:
# Standard endpoints use the table default
- { mock: get-customer, table: customers, action: get }
- { mock: list-customers, table: customers, action: list }
# This endpoint needs a different list envelope
- mock: search-customers
table: customers
action: list
response:
timestamps:
format: unix
fields: { createdAt: created }
fields:
inject: { object: customer }
list:
dataField: data
extraFields:
object: search_result
url: /v1/customers/search
has_more: false
hideMeta: true
```
Note that a binding-level override **replaces** the entire table default — it does not merge with it. If you want the same timestamp and field transforms, you must repeat them in the binding override.
## Custom Operations via Extend
[Section titled “Custom Operations via Extend”](#custom-operations-via-extend)
Extend bindings support `action: custom` to trigger multi-step custom operations:
```yaml
tables:
- name: accounts
seedData:
- { id: "acct-1", owner: "Alice", balance: 1000 }
- { id: "acct-2", owner: "Bob", balance: 500 }
customOperations:
- name: TransferFunds
consistency: atomic
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
- type: update
resource: accounts
id: "input.sourceId"
set: { balance: "source.balance - input.amount" }
response:
status: '"completed"'
mocks:
- id: transfer
type: http
http:
matcher: { method: POST, path: /api/transfer }
response: { statusCode: 200 }
extend:
- mock: transfer
table: accounts
action: custom
operation: TransferFunds
```
## CLI vs Config: When to Use Each
[Section titled “CLI vs Config: When to Use Each”](#cli-vs-config-when-to-use-each)
| Approach | Use Case |
| ----------------------------------- | --------------------------------------------- |
| `mockd http add --stateful` | Quick prototyping, one-off testing |
| `mockd stateful add` + manual mocks | Interactive exploration |
| `tables` + `extend` in config | Production configs, version-controlled setups |
| `imports` + `extend` | Digital twins of third-party APIs |
The CLI `--stateful` shortcut is equivalent to creating a table + mocks + extend bindings in a config file. Use it for speed; use config files for reproducibility.
For config-driven workflows, `mockd start` and `mockd serve` are interchangeable. `start` adds `--load` (directory loading) and `--watch` (auto-reload) flags. Both accept `--config` / `-c` for config files and `--detach` / `-d` for daemon mode.
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [SOAP Mocking](/protocols/soap/) - Full SOAP protocol guide with WSDL import
* [Proxy Recording](/guides/proxy-recording/) - Record real API traffic
* [Admin API Reference](/reference/admin-api/) - State management endpoints
* [Configuration Reference](/reference/configuration/) - Full schema
# Stream Recording
> Record WebSocket and SSE streams with full timing fidelity, then replay them as mocks for testing.
Record WebSocket and SSE (Server-Sent Events) streams with full timing fidelity, then replay them as mocks. Perfect for capturing real-time API behavior and creating reproducible test fixtures.
## Overview
[Section titled “Overview”](#overview)
Stream recording captures:
* **WebSocket**: Bidirectional message streams with frame types (text, binary, ping/pong, close)
* **SSE**: Server-sent event streams with event types, IDs, and retry hints
Each recording preserves:
* Message content and encoding
* Precise timing between frames
* Connection metadata (path, headers, query params)
* Protocol-specific details (close codes, subprotocols)
## Quick Start
[Section titled “Quick Start”](#quick-start)
### 1. Start mockd
[Section titled “1. Start mockd”](#1-start-mockd)
```bash
mockd serve --config mocks.json
```
### 2. Start Recording via Admin API
[Section titled “2. Start Recording via Admin API”](#2-start-recording-via-admin-api)
```bash
# Start recording WebSocket traffic on a specific path
curl -X POST http://localhost:4290/stream-recordings/start \
-H "Content-Type: application/json" \
-d '{"protocol": "websocket", "path": "/ws/chat", "name": "chat-session"}'
```
### 3. Generate Traffic
[Section titled “3. Generate Traffic”](#3-generate-traffic)
Connect to your WebSocket or SSE endpoint through mockd:
```bash
# WebSocket
wscat -c ws://localhost:4280/ws/chat
# SSE
curl -N http://localhost:4280/events
```
### 4. Stop Recording and View
[Section titled “4. Stop Recording and View”](#4-stop-recording-and-view)
```bash
# Stop recording
curl -X POST http://localhost:4290/stream-recordings/{id}/stop
# List recordings via CLI
mockd stream-recordings list
```
```plaintext
ID PROTOCOL PATH STATUS FRAMES DURATION SIZE
01HXYZ123456 websocket /ws/chat complete 42 12.3s 8.2 KB
01HXYZ789012 sse /events complete 15 5.1s 2.1 KB
```
### 5. Convert to Mock
[Section titled “5. Convert to Mock”](#5-convert-to-mock)
```bash
# Convert to mock config
mockd stream-recordings convert 01HXYZ123456 -o chat-scenario.json
# Start with the mock
mockd serve --config chat-scenario.json
```
## Recording via Admin API
[Section titled “Recording via Admin API”](#recording-via-admin-api)
### Start Recording
[Section titled “Start Recording”](#start-recording)
```bash
curl -X POST http://localhost:4290/stream-recordings/start \
-H "Content-Type: application/json" \
-d '{
"protocol": "websocket",
"path": "/ws/chat",
"name": "chat-session-1"
}'
```
Response:
```json
{
"sessionId": "01HXYZ123456789ABCDEF",
"recordingId": "01HXYZ123456789ABCDEF"
}
```
### Stop Recording
[Section titled “Stop Recording”](#stop-recording)
```bash
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/stop
```
### List Recordings
[Section titled “List Recordings”](#list-recordings)
```bash
# All recordings
curl http://localhost:4290/stream-recordings
# Filter by protocol
curl "http://localhost:4290/stream-recordings?protocol=websocket"
# Filter by path
curl "http://localhost:4290/stream-recordings?path=/ws/chat"
```
### Get Recording Details
[Section titled “Get Recording Details”](#get-recording-details)
```bash
curl http://localhost:4290/stream-recordings/01HXYZ123456
```
### Export Recording
[Section titled “Export Recording”](#export-recording)
```bash
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/export \
-o recording.json
```
### Convert to Mock
[Section titled “Convert to Mock”](#convert-to-mock)
```bash
curl -X POST http://localhost:4290/stream-recordings/01HXYZ123456/convert \
-H "Content-Type: application/json" \
-d '{"simplifyTiming": true}' \
-o mock-config.json
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
### List Recordings
[Section titled “List Recordings”](#list-recordings-1)
```bash
# Basic list
mockd stream-recordings list
# Filter by protocol
mockd stream-recordings list --protocol websocket
# Filter by status
mockd stream-recordings list --status complete
# JSON output
mockd stream-recordings list --json
# Pagination
mockd stream-recordings list --limit 10 --offset 20
```
### Show Recording Details
[Section titled “Show Recording Details”](#show-recording-details)
```bash
mockd stream-recordings show 01HXYZ123456
# JSON output
mockd stream-recordings show 01HXYZ123456 --json
```
Output:
```plaintext
ID: 01HXYZ123456789ABCDEF
Name: chat-session-1
Protocol: websocket
Path: /ws/chat
Status: complete
Started: 2024-01-15T10:30:00Z
Ended: 2024-01-15T10:30:12Z
Duration: 12.3s
Frames: 42
File Size: 8.2 KB
WebSocket Details:
Text Frames: 38
Binary Frames: 2
Ping/Pong: 2
Close Code: 1000
```
### Export Recording
[Section titled “Export Recording”](#export-recording-1)
```bash
# Export to stdout
mockd stream-recordings export 01HXYZ123456
# Export to file
mockd stream-recordings export 01HXYZ123456 -o recording.json
```
### Convert to Mock Config
[Section titled “Convert to Mock Config”](#convert-to-mock-config)
```bash
# Basic conversion
mockd stream-recordings convert 01HXYZ123456
# With timing normalization
mockd stream-recordings convert 01HXYZ123456 --simplify-timing
# Output to file
mockd stream-recordings convert 01HXYZ123456 -o scenario.json
# Fine-tune timing
mockd stream-recordings convert 01HXYZ123456 \
--simplify-timing \
--min-delay 50 \
--max-delay 2000
```
### Delete Recording
[Section titled “Delete Recording”](#delete-recording)
```bash
# Soft delete (recoverable)
mockd stream-recordings delete 01HXYZ123456
# Skip confirmation
mockd stream-recordings delete 01HXYZ123456 --force
# Permanent delete
mockd stream-recordings delete 01HXYZ123456 --permanent
```
### Storage Management
[Section titled “Storage Management”](#storage-management)
```bash
# View storage stats
mockd stream-recordings stats
# Permanently remove soft-deleted recordings
mockd stream-recordings vacuum
```
### Active Sessions
[Section titled “Active Sessions”](#active-sessions)
```bash
# List recordings in progress
mockd stream-recordings sessions
```
## Recording Format
[Section titled “Recording Format”](#recording-format)
Recordings are stored as JSON files with this structure:
```json
{
"id": "01HXYZ123456789ABCDEF",
"version": "1.0",
"name": "chat-session-1",
"protocol": "websocket",
"status": "complete",
"startTime": "2024-01-15T10:30:00Z",
"endTime": "2024-01-15T10:30:12Z",
"duration": 12345,
"metadata": {
"path": "/ws/chat",
"headers": {"Authorization": "[REDACTED]"},
"query": {"room": "general"},
"source": "manual"
},
"websocket": {
"subprotocol": "chat.v1",
"frames": [
{
"direction": "s2c",
"type": "text",
"data": "{\"type\":\"welcome\"}",
"timestamp": "2024-01-15T10:30:00.100Z",
"relativeMs": 100
},
{
"direction": "c2s",
"type": "text",
"data": "{\"type\":\"join\",\"room\":\"general\"}",
"timestamp": "2024-01-15T10:30:00.250Z",
"relativeMs": 250
}
],
"closeCode": 1000,
"closeReason": "Normal closure"
},
"stats": {
"frameCount": 42,
"bytesSent": 1200,
"bytesReceived": 800,
"fileSizeBytes": 8432
}
}
```
### Frame Direction
[Section titled “Frame Direction”](#frame-direction)
* `c2s` - Client to server (sent by client)
* `s2c` - Server to client (sent by server)
### Message Types (WebSocket)
[Section titled “Message Types (WebSocket)”](#message-types-websocket)
* `text` - UTF-8 text frame
* `binary` - Binary frame (base64 encoded in JSON)
* `ping` - Ping control frame
* `pong` - Pong control frame
* `close` - Close control frame
### SSE Recording Structure
[Section titled “SSE Recording Structure”](#sse-recording-structure)
```json
{
"protocol": "sse",
"sse": {
"events": [
{
"type": "message",
"data": "Hello world",
"id": "evt-001",
"timestamp": "2024-01-15T10:30:00.100Z",
"relativeMs": 100
}
]
}
}
```
## Sensitive Data Handling
[Section titled “Sensitive Data Handling”](#sensitive-data-handling)
By default, mockd redacts sensitive headers:
* `Authorization`
* `Cookie` / `Set-Cookie`
* `X-API-Key`
* `X-Auth-Token`
### Custom Redaction
[Section titled “Custom Redaction”](#custom-redaction)
Configure in `mockd.yaml`:
```yaml
recording:
filterHeaders:
- Authorization
- X-Custom-Secret
filterBodyKeys:
- password
- secret
- $.user.ssn
redactValue: "[REDACTED]"
```
## Storage Configuration
[Section titled “Storage Configuration”](#storage-configuration)
```yaml
recording:
dataDir: ~/.local/share/mockd/recordings
maxBytes: 524288000 # 500MB
warnPercent: 80 # Warn at 80% capacity
```
Storage location defaults:
* Linux: `~/.local/share/mockd/recordings/`
* macOS: `~/Library/Application Support/mockd/recordings/`
* Windows: `%LOCALAPPDATA%\mockd\recordings\`
## Use Cases
[Section titled “Use Cases”](#use-cases)
### Capture Production Behavior
[Section titled “Capture Production Behavior”](#capture-production-behavior)
Record real WebSocket traffic from production to create accurate test fixtures:
```bash
# Start mockd and the proxy
mockd serve
# Start recording via Admin API
curl -X POST http://localhost:4290/stream-recordings/start \
-H "Content-Type: application/json" \
-d '{"protocol": "websocket", "path": "/ws", "name": "prod-traffic"}'
# Run your app (configure to use mockd as WebSocket proxy)
WS_URL=ws://localhost:4280/ws npm test
# Stop recording and convert to mocks
curl -X POST http://localhost:4290/stream-recordings/{id}/stop
mockd stream-recordings convert {id} -o fixtures/chat.json
```
### Integration Testing
[Section titled “Integration Testing”](#integration-testing)
Create reproducible stream mocks for CI/CD:
```bash
# Start mockd with recorded scenario
mockd start --config fixtures/chat.json
# Run tests against mock
npm test
```
### Debug Timing Issues
[Section titled “Debug Timing Issues”](#debug-timing-issues)
Analyze frame timing in recordings:
```bash
mockd stream-recordings show 01HXYZ123456 --json | jq '.websocket.frames[] | {relativeMs, direction, type}'
```
## Admin API Reference
[Section titled “Admin API Reference”](#admin-api-reference)
| Method | Endpoint | Description |
| ------ | --------------------------------- | ------------------------- |
| GET | `/stream-recordings` | List recordings |
| GET | `/stream-recordings/stats` | Storage statistics |
| GET | `/stream-recordings/sessions` | Active recording sessions |
| POST | `/stream-recordings/start` | Start recording |
| POST | `/stream-recordings/vacuum` | Remove soft-deleted |
| GET | `/stream-recordings/{id}` | Get recording |
| DELETE | `/stream-recordings/{id}` | Delete recording |
| POST | `/stream-recordings/{id}/stop` | Stop recording session |
| POST | `/stream-recordings/{id}/export` | Export as JSON |
| POST | `/stream-recordings/{id}/convert` | Convert to mock config |
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Replay Modes](/guides/replay-modes/) - Learn about Pure, Synchronized, and Triggered replay
* [SSE Streaming](/guides/sse-streaming/) - SSE mock configuration
* [Admin API](/reference/admin-api/) - Stream recording API endpoints
# TLS/HTTPS Configuration
> Configure HTTPS for mockd mock server and proxy modes, including certificate generation and common use cases.
mockd supports HTTPS for both the mock server and proxy modes. This guide covers certificate generation, configuration, and common use cases.
## Mock Server HTTPS
[Section titled “Mock Server HTTPS”](#mock-server-https)
### Quick Start
[Section titled “Quick Start”](#quick-start)
Enable HTTPS with auto-generated self-signed certificates:
```bash
mockd start --config mocks.json --tls-auto --https-port 8443
```
mockd generates a self-signed certificate and starts HTTPS on port 8443.
### Custom Port
[Section titled “Custom Port”](#custom-port)
```bash
mockd start --config mocks.json --tls-auto --https-port 443
```
### With Your Own Certificates
[Section titled “With Your Own Certificates”](#with-your-own-certificates)
```bash
mockd start --config mocks.json \
--tls-cert ./certs/server.crt \
--tls-key ./certs/server.key \
--https-port 8443
```
### Configuration File
[Section titled “Configuration File”](#configuration-file)
```json
{
"server": {
"port": 4280,
"tls": {
"enabled": true,
"port": 8443,
"certFile": "./certs/server.crt",
"keyFile": "./certs/server.key"
}
},
"mocks": [...]
}
```
This starts HTTP on port 4280 and HTTPS on port 8443. To run HTTPS only, omit `server.port` or set `httpsRedirect: true`.
## Certificate Generation
[Section titled “Certificate Generation”](#certificate-generation)
### Self-Signed (Development)
[Section titled “Self-Signed (Development)”](#self-signed-development)
The simplest approach is to use the `--tls-auto` flag, which generates a self-signed certificate automatically:
```bash
mockd serve --config mocks.json --tls-auto --https-port 8443
```
Alternatively, generate certificates with OpenSSL:
```bash
# Generate private key
openssl genrsa -out ./certs/localhost.key 2048
# Generate self-signed certificate
openssl req -new -x509 -key ./certs/localhost.key \
-out ./certs/localhost.crt -days 365 -subj "/CN=localhost"
# Start with your certificates
mockd serve --config mocks.json \
--tls-cert ./certs/localhost.crt \
--tls-key ./certs/localhost.key \
--https-port 8443
```
With Subject Alternative Names:
```bash
openssl req -new -x509 -key ./certs/localhost.key \
-out ./certs/localhost.crt -days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=IP:127.0.0.1,IP:::1,DNS:myapp.local"
```
### CA Certificate (For Proxy)
[Section titled “CA Certificate (For Proxy)”](#ca-certificate-for-proxy)
Generate a CA for MITM proxying with the built-in command:
```bash
mockd proxy ca generate --ca-path ./certs
```
This generates a CA certificate and key in the `./certs` directory for use with proxy HTTPS interception.
## Proxy HTTPS
[Section titled “Proxy HTTPS”](#proxy-https)
### MITM Proxy Setup
[Section titled “MITM Proxy Setup”](#mitm-proxy-setup)
For the proxy to intercept and record HTTPS traffic, clients must trust the mockd CA. Without a CA, HTTPS connections are tunneled (TCP pass-through) and cannot be recorded.
1. **Generate CA Certificate**:
```bash
mockd proxy ca generate --ca-path ./certs
```
2. **Start Proxy with HTTPS Interception**:
```bash
mockd proxy start --ca-path ./certs
```
The proxy dynamically generates per-host TLS certificates signed by your CA, decrypting traffic for recording.
3. **Install CA on Client**:
See [Installing CA Certificates](#installing-ca-certificates) below.
### Export CA Certificate
[Section titled “Export CA Certificate”](#export-ca-certificate)
```bash
# Export to a file for distribution
mockd proxy ca export --ca-path ./certs -o mockd-ca.crt
```
## Installing CA Certificates
[Section titled “Installing CA Certificates”](#installing-ca-certificates)
### macOS
[Section titled “macOS”](#macos)
```bash
# Add to system keychain
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
./certs/mockd-ca.crt
# Or for current user only
security add-trusted-cert -r trustRoot \
-k ~/Library/Keychains/login.keychain \
./certs/mockd-ca.crt
```
### Linux
[Section titled “Linux”](#linux)
```bash
# Copy certificate
sudo cp ./certs/mockd-ca.crt /usr/local/share/ca-certificates/mockd-ca.crt
# Update certificate store
sudo update-ca-certificates
```
### Windows
[Section titled “Windows”](#windows)
```powershell
# Import to Trusted Root Certification Authorities
Import-Certificate -FilePath .\certs\mockd-ca.crt `
-CertStoreLocation Cert:\LocalMachine\Root
```
### Node.js
[Section titled “Node.js”](#nodejs)
```bash
export NODE_EXTRA_CA_CERTS=./certs/mockd-ca.crt
node app.js
```
### Python (requests)
[Section titled “Python (requests)”](#python-requests)
```python
import requests
requests.get('https://localhost:8443', verify='./certs/mockd-ca.crt')
```
### curl
[Section titled “curl”](#curl)
```bash
curl --cacert ./certs/mockd-ca.crt https://localhost:8443/api/users
```
### Docker
[Section titled “Docker”](#docker)
Mount the CA certificate:
```bash
docker run -v $(pwd)/certs/mockd-ca.crt:/etc/ssl/certs/mockd-ca.crt \
myapp
```
## TLS Options
[Section titled “TLS Options”](#tls-options)
### Minimum TLS Version
[Section titled “Minimum TLS Version”](#minimum-tls-version)
```json
{
"server": {
"tls": {
"enabled": true,
"minVersion": "1.2"
}
}
}
```
Supported: `1.0`, `1.1`, `1.2`, `1.3`
### Cipher Suites
[Section titled “Cipher Suites”](#cipher-suites)
```json
{
"server": {
"tls": {
"enabled": true,
"cipherSuites": [
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
}
}
}
```
### Client Certificate Authentication (mTLS)
[Section titled “Client Certificate Authentication (mTLS)”](#client-certificate-authentication-mtls)
Require client certificates:
```json
{
"server": {
"tls": {
"enabled": true,
"certFile": "./certs/server.crt",
"keyFile": "./certs/server.key",
"clientAuth": "require",
"clientCAs": ["./certs/client-ca.crt"]
}
}
}
```
Client auth modes:
* `none` - No client cert required
* `request` - Request but don’t require
* `require` - Require valid client cert
## Mixed HTTP/HTTPS
[Section titled “Mixed HTTP/HTTPS”](#mixed-httphttps)
Serve both protocols:
```json
{
"server": {
"port": 4280,
"tls": {
"enabled": true,
"port": 8443,
"certFile": "./certs/server.crt",
"keyFile": "./certs/server.key"
}
}
}
```
Both endpoints serve the same mocks:
* `http://localhost:4280`
* `https://localhost:8443`
## HTTPS Redirect
[Section titled “HTTPS Redirect”](#https-redirect)
Redirect HTTP to HTTPS:
```json
{
"server": {
"port": 4280,
"httpsRedirect": true,
"tls": {
"enabled": true,
"port": 8443
}
}
}
```
## Common Issues
[Section titled “Common Issues”](#common-issues)
### Certificate Not Trusted
[Section titled “Certificate Not Trusted”](#certificate-not-trusted)
**Symptom**: `CERT_AUTHORITY_INVALID` or similar errors
**Solution**: Install the CA certificate as described above, or use `--insecure` flags for testing:
```bash
curl -k https://localhost:8443/api/users
```
### Certificate Hostname Mismatch
[Section titled “Certificate Hostname Mismatch”](#certificate-hostname-mismatch)
**Symptom**: `HOSTNAME_MISMATCH` error
**Solution**: Generate certificate with correct SANs:
```bash
openssl req -new -x509 -key ./certs/localhost.key \
-out ./certs/localhost.crt -days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:myapp.local,IP:127.0.0.1"
```
Or use `--tls-auto` which generates a cert for `localhost` automatically.
### Certificate Expired
[Section titled “Certificate Expired”](#certificate-expired)
**Symptom**: `CERT_HAS_EXPIRED` error
**Solution**: Regenerate with longer validity:
```bash
openssl req -new -x509 -key ./certs/localhost.key \
-out ./certs/localhost.crt -days 3650 -subj "/CN=localhost"
```
### Permission Denied (Port 443)
[Section titled “Permission Denied (Port 443)”](#permission-denied-port-443)
**Symptom**: Cannot bind to port 443
**Solution**: Use a high port or grant capability:
```bash
# Use high port (recommended)
mockd start --tls-auto --https-port 8443
# Or grant capability (Linux) to bind port 443
sudo setcap 'cap_net_bind_service=+ep' $(which mockd)
```
## Security Considerations
[Section titled “Security Considerations”](#security-considerations)
1. **Never use self-signed certs in production** - They’re for development only
2. **Protect private keys** - Restrict file permissions:
```bash
chmod 600 ./certs/*.key
```
3. **Short-lived certificates** - Use shorter validity for development certs
4. **Don’t commit certs** - Add to `.gitignore`:
```plaintext
certs/
*.crt
*.key
*.pem
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Proxy Recording](/guides/proxy-recording/) - HTTPS proxy setup
* [CLI Reference](/reference/cli/) - Certificate commands
* [Configuration Reference](/reference/configuration/) - Full TLS options
# Troubleshooting Guide
> Solutions for common issues when using mockd, including mock matching problems, port conflicts, and protocol-specific debugging.
This guide covers common issues you may encounter when using mockd and how to resolve them.
## Quick Diagnostics
[Section titled “Quick Diagnostics”](#quick-diagnostics)
Before diving into specific issues, run these commands:
```bash
# Full diagnostic report
mockd doctor
# Check server health
mockd health
# Show what's running
mockd status
# List loaded mocks
mockd list
# View recent request logs
mockd logs --requests -n 10
```
## Mock Not Matching
[Section titled “Mock Not Matching”](#mock-not-matching)
Common reasons why your mock might not be matching:
1. **Method mismatch** - Check GET vs POST vs PUT
2. **Path mismatch** - Check exact path, trailing slashes
3. **Mock not enabled** - Check `enabled: true`
4. **Priority/ordering** - More specific mocks should come first
5. **Headers required** - Some mocks require specific headers
### Debug Steps
[Section titled “Debug Steps”](#debug-steps)
```bash
# Check what mocks are loaded
mockd list
# Show full IDs and paths without truncation
mockd list --no-truncate
# View request logs to see what's being received
mockd logs --requests
# Delete a problematic mock by path
mockd delete --path /api/users --method GET
# Use doctor to diagnose issues
mockd doctor
```
### Checklist
[Section titled “Checklist”](#checklist)
* Verify the HTTP method matches exactly (case-sensitive)
* Check for trailing slashes in paths (`/api/users` vs `/api/users/`)
* Confirm the mock file has `enabled: true` or the field is omitted (defaults to true)
* Review mock priority if you have overlapping patterns
* Check if the mock requires specific headers that aren’t being sent
## Port Already in Use
[Section titled “Port Already in Use”](#port-already-in-use)
If mockd fails to start with a “port already in use” error:
```bash
# Check what's using the port (macOS/Linux)
lsof -i :4280
# Check what's using the port (Windows)
netstat -ano | findstr :4280
# Check all mockd default ports
mockd ports
# Use a different port
mockd serve --port 3000
```
### Default Ports
[Section titled “Default Ports”](#default-ports)
| Port | Service | Override Flag |
| ----- | ------------------------------------------------- | -------------- |
| 4280 | Mock server (HTTP, GraphQL, WebSocket, SOAP, SSE) | `--port` |
| 4290 | Admin API | `--admin-port` |
| 50051 | gRPC (configurable per mock) | In YAML config |
| 1883 | MQTT (configurable per mock) | In YAML config |
### Solutions
[Section titled “Solutions”](#solutions)
* Kill the existing process using the port
* Choose a different port with the `--port` flag
* Check if another mockd instance is already running: `mockd ps`
* Stop a running instance: `mockd stop`
## Server Won’t Start
[Section titled “Server Won’t Start”](#server-wont-start)
If the server fails to start:
* Check config file syntax with `mockd validate mockd.yaml`
* Run `mockd doctor` for diagnostics
* Check permissions on data directory
* Verify mock files are valid YAML/JSON
### Common Causes
[Section titled “Common Causes”](#common-causes)
| Issue | Solution |
| ----------------------- | -------------------------------------------- |
| Invalid YAML syntax | Run `mockd validate mockd.yaml` |
| Missing required fields | Check mock schema requirements |
| Permission denied | Ensure write access to data directory |
| Invalid port number | Use a port between 1024-65535 |
| Proto file not found | Check `protoFile` path is relative to config |
## HTTP Issues
[Section titled “HTTP Issues”](#http-issues)
### Response Body Not Matching Expected
[Section titled “Response Body Not Matching Expected”](#response-body-not-matching-expected)
```bash
# Check the exact mock configuration
mockd get --json
# Check request logs for what was actually matched
mockd logs --requests -n 5
```
### Wrong Mock Matched
[Section titled “Wrong Mock Matched”](#wrong-mock-matched)
When multiple mocks could match, priority matters:
1. Mocks with more specific paths win (`/api/users/1` > `/api/users/{id}`)
2. Mocks with more matchers win (path + headers > path only)
3. Earlier mocks in config win if priority is equal
```bash
# List all mocks and their paths
mockd list --no-truncate
```
### Request Body Matching Not Working
[Section titled “Request Body Matching Not Working”](#request-body-matching-not-working)
* Ensure `Content-Type` header is sent with the request
* JSON body matching requires valid JSON in both request and matcher
* Check for extra whitespace or field ordering differences
## GraphQL Issues
[Section titled “GraphQL Issues”](#graphql-issues)
### Query Returns Empty Data
[Section titled “Query Returns Empty Data”](#query-returns-empty-data)
* Verify the operation name matches a resolver in your config
* Check that the schema defines the query type you’re calling
* Ensure introspection is enabled if your client requires it
```bash
# Test a GraphQL query directly
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ users { id name } }"}'
# Check introspection
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name } } }"}'
```
### Schema Validation Errors
[Section titled “Schema Validation Errors”](#schema-validation-errors)
```bash
# Validate a schema file
mockd graphql validate schema.graphql
```
### Resolver Not Found
[Section titled “Resolver Not Found”](#resolver-not-found)
* Resolver keys must be `Type.field` format: `Query.users`, `Mutation.createUser`
* Field names are case-sensitive
* Check that the schema defines the operation you’re resolving
## gRPC Issues
[Section titled “gRPC Issues”](#grpc-issues)
### Proto File Errors
[Section titled “Proto File Errors”](#proto-file-errors)
* Proto files must be valid protobuf syntax
* Check import paths if your proto references other files
* Verify the package name matches your service configuration
```bash
# List services from a proto file
mockd grpc list api.proto
# Verify gRPC server is running
grpcurl -plaintext localhost:50051 list
```
### ”Service not found” Errors
[Section titled “”Service not found” Errors”](#service-not-found-errors)
* Service names must be fully qualified: `package.ServiceName` (e.g., `users.UserService`)
* Method names are case-sensitive
* Check that the proto file path is correct relative to your config file
### Reflection Not Working
[Section titled “Reflection Not Working”](#reflection-not-working)
```bash
# Test gRPC reflection
grpcurl -plaintext localhost:50051 list
# If reflection is disabled, use the proto file directly
grpcurl -plaintext -proto ./service.proto \
localhost:50051 package.Service/Method
```
Ensure `reflection: true` is set in your gRPC config.
### Port Conflicts Between gRPC Mocks
[Section titled “Port Conflicts Between gRPC Mocks”](#port-conflicts-between-grpc-mocks)
Multiple gRPC mocks on the same port are automatically merged. If you see conflicts:
```bash
# Check which mocks are on port 50051
mockd list --type grpc
```
## WebSocket Issues
[Section titled “WebSocket Issues”](#websocket-issues)
### Connection Refused
[Section titled “Connection Refused”](#connection-refused)
* Verify the WebSocket path matches your mock: `/ws` vs `/ws/chat`
* Ensure you’re using the correct scheme: `ws://` (not `http://`)
* Check that mockd is running on the expected port
```bash
# Test WebSocket endpoint exists
curl -i http://localhost:4280/ws
# Connect with mockd CLI
mockd websocket connect ws://localhost:4280/ws
```
### Subprotocol Mismatch
[Section titled “Subprotocol Mismatch”](#subprotocol-mismatch)
If your client requires a specific subprotocol:
```yaml
websocket:
path: /ws
subprotocols:
- chat.v1
requireSubprotocol: true # Rejects connections without matching subprotocol
```
Check the `Sec-WebSocket-Protocol` header in your client connection.
### Messages Not Matching
[Section titled “Messages Not Matching”](#messages-not-matching)
* Matchers are evaluated in order — first match wins
* Check `matchType`: `exact`, `contains`, `prefix`, `regex`, `json`
* For JSON matching, verify the JSONPath expression is correct
### Connection Drops
[Section titled “Connection Drops”](#connection-drops)
* Enable heartbeat/keepalive in your WebSocket mock config
* Check `idleTimeout` — connections are closed after inactivity
* Some proxies/firewalls drop idle WebSocket connections
```yaml
websocket:
heartbeat:
enabled: true
interval: "30s"
timeout: "10s"
```
## MQTT Issues
[Section titled “MQTT Issues”](#mqtt-issues)
### Can’t Connect to Broker
[Section titled “Can’t Connect to Broker”](#cant-connect-to-broker)
* Default MQTT port is **1883** (not 4280)
* Check if authentication is required
```bash
# Test connection with mosquitto
mosquitto_sub -h localhost -p 1883 -t "test/#" -v
# With authentication
mosquitto_sub -h localhost -p 1883 -u user -P pass -t "test/#"
```
### Not Receiving Messages
[Section titled “Not Receiving Messages”](#not-receiving-messages)
* Check topic name spelling and wildcards (`+` for single level, `#` for multi-level)
* Verify QoS level — QoS 0 messages may be lost
* Check if retained messages are configured
```bash
# Subscribe to all topics
mockd mqtt subscribe "#"
# Subscribe with specific QoS
mockd mqtt subscribe --qos 1 "sensors/#"
```
### Authentication Denied
[Section titled “Authentication Denied”](#authentication-denied)
* Verify username/password match the `auth.users` config
* Check ACL rules — the user may not have access to the requested topic
* ACL access levels: `read`, `write`, `readwrite`/`all`
## SOAP Issues
[Section titled “SOAP Issues”](#soap-issues)
### ”No Operation Matched”
[Section titled “”No Operation Matched””](#no-operation-matched)
* Verify the `SOAPAction` header matches the configured `soapAction` value
* SOAPAction matching is exact (case-sensitive)
```bash
# Include the SOAPAction header
curl -X POST http://localhost:4280/soap/UserService \
-H "Content-Type: text/xml" \
-H "SOAPAction: http://example.com/GetUser" \
-d @request.xml
```
### WSDL Not Serving
[Section titled “WSDL Not Serving”](#wsdl-not-serving)
* Access the WSDL by appending `?wsdl` to the endpoint URL
* Check that the `wsdl` or `wsdlFile` field is set in your config
```bash
curl http://localhost:4280/soap/UserService?wsdl
```
### XPath Matching Not Working
[Section titled “XPath Matching Not Working”](#xpath-matching-not-working)
* XPath expressions are evaluated against the SOAP body (inside ``)
* Use `//Element/text()` to match element text content
* Namespace prefixes in XPath may need to match the request
## SSE Issues
[Section titled “SSE Issues”](#sse-issues)
### Stream Ends Immediately
[Section titled “Stream Ends Immediately”](#stream-ends-immediately)
* Check the `lifecycle.maxEvents` setting — it may be too low
* Verify `lifecycle.timeout` is long enough for your use case
* Ensure your client sends `Accept: text/event-stream`
```bash
# Test SSE connection
curl -N -H "Accept: text/event-stream" http://localhost:4280/events
```
### Events Not Repeating
[Section titled “Events Not Repeating”](#events-not-repeating)
* Set `timing.repeat` or configure lifecycle for continuous streams
* Check if `maxEvents` is limiting the number of events sent
### OpenAI Template Issues
[Section titled “OpenAI Template Issues”](#openai-template-issues)
* The `openai-chat` template expects `POST` method
* Verify `templateParams.tokens` is an array of strings
* Check that `includeDone: true` is set if your client expects `[DONE]`
## Performance Issues
[Section titled “Performance Issues”](#performance-issues)
If mockd is running slowly:
* Check mock count (large lists slow down matching)
* Consider using more specific matchers
* Review regex patterns for efficiency
### Performance Optimization
[Section titled “Performance Optimization”](#performance-optimization)
```bash
# Check current mock count and status
mockd status
# Show all ports in use
mockd ports
```
### Best Practices
[Section titled “Best Practices”](#best-practices)
* Use exact path matching when possible instead of regex
* Group related mocks into separate files
* Remove unused or disabled mocks
* Use path parameters (`{id}`) instead of regex for dynamic segments
## Getting Help
[Section titled “Getting Help”](#getting-help)
If you’re still experiencing issues:
1. Run `mockd doctor` for a comprehensive diagnostic report
2. Check the logs with `mockd logs`
3. Search existing issues on [GitHub](https://github.com/getmockd/mockd/issues)
4. Open a new issue with diagnostic output and reproduction steps
Include this information when reporting issues:
```bash
mockd version
mockd doctor
mockd status --json
```
# Request Validation
> Configure field-level validation for incoming requests to ensure your mock APIs behave like real APIs.
Mockd provides field-level validation for incoming requests, ensuring that your mock APIs behave like real APIs by rejecting malformed data. This guide covers how to configure validators for both stateful resources and HTTP mocks.
## Overview
[Section titled “Overview”](#overview)
Request validation allows you to:
* Enforce data types and constraints on incoming request bodies
* Return realistic error responses when validation fails
* Test client-side error handling without a real backend
* Document expected request formats alongside your mock definitions
Use validation when you want your mocks to reject invalid requests rather than accepting any data. This is especially useful for testing form validation, API client libraries, and error handling flows.
## Quick Start
[Section titled “Quick Start”](#quick-start)
Add a `validation` block to any stateful resource or HTTP mock:
```yaml
tables:
- name: users
seedData:
- id: "1"
email: "alice@example.com"
age: 28
validation:
fields:
email:
type: string
format: email
required: true
age:
type: integer
min: 0
max: 150
```
With this configuration, requests with an invalid email or out-of-range age will receive a `400 Bad Request` response with detailed error information.
## Validation for Stateful Resources
[Section titled “Validation for Stateful Resources”](#validation-for-stateful-resources)
Stateful resources support validation on create and update operations. The validation block sits at the resource level:
```yaml
tables:
- name: products
seedData:
- id: "1"
sku: "WIDGET-001"
name: "Blue Widget"
price: 29.99
validation:
mode: strict
onCreate:
fields:
sku:
type: string
pattern: "^[A-Z]+-\\d{3}$"
required: true
name:
type: string
minLength: 1
maxLength: 200
required: true
price:
type: number
min: 0
required: true
onUpdate:
fields:
name:
type: string
minLength: 1
maxLength: 200
price:
type: number
min: 0
```
When `onCreate` and `onUpdate` are omitted, the top-level `fields` apply to both operations. Use separate blocks when create and update have different requirements.
## Validation for HTTP Mocks
[Section titled “Validation for HTTP Mocks”](#validation-for-http-mocks)
For HTTP mocks, add validation inside the `http` block alongside `matcher` and `response`:
```yaml
mocks:
- id: contact-form
type: http
http:
matcher:
method: POST
path: /api/contact
validation:
mode: strict
fields:
name:
type: string
required: true
email:
type: string
format: email
required: true
message:
type: string
minLength: 10
maxLength: 1000
response:
statusCode: 200
body: '{"success": true}'
```
The validation runs before the response is generated. If validation fails, the configured error response is returned instead.
## Field Validators
[Section titled “Field Validators”](#field-validators)
### Types
[Section titled “Types”](#types)
Every field validator requires a `type` property:
| Type | Description |
| --------- | ---------------------- |
| `string` | Text values |
| `number` | Floating-point numbers |
| `integer` | Whole numbers only |
| `boolean` | True or false |
| `array` | Lists of values |
| `object` | Nested objects |
```yaml
fields:
username:
type: string
score:
type: number
count:
type: integer
active:
type: boolean
tags:
type: array
metadata:
type: object
```
### String Validation
[Section titled “String Validation”](#string-validation)
String fields support length constraints, patterns, and format validation:
```yaml
fields:
username:
type: string
minLength: 3
maxLength: 32
pattern: "^[a-zA-Z0-9_]+$"
email:
type: string
format: email
website:
type: string
format: uri
userId:
type: string
format: uuid
```
Available formats:
| Format | Description |
| ---------- | -------------------------- |
| `email` | Valid email address |
| `uuid` | UUID v4 format |
| `date` | ISO 8601 date (YYYY-MM-DD) |
| `datetime` | ISO 8601 datetime |
| `uri` | Valid URI |
| `ipv4` | IPv4 address |
| `ipv6` | IPv6 address |
| `hostname` | Valid hostname |
### Number Validation
[Section titled “Number Validation”](#number-validation)
Number and integer fields support range constraints:
```yaml
fields:
price:
type: number
min: 0
max: 99999.99
quantity:
type: integer
min: 1
max: 100
temperature:
type: number
exclusiveMin: -273.15
rating:
type: number
min: 0
exclusiveMax: 5
```
* `min` / `max`: Inclusive bounds
* `exclusiveMin` / `exclusiveMax`: Exclusive bounds
### Array Validation
[Section titled “Array Validation”](#array-validation)
Array fields can constrain length and validate items:
```yaml
fields:
tags:
type: array
minItems: 1
maxItems: 10
uniqueItems: true
items:
type: string
minLength: 1
maxLength: 50
scores:
type: array
items:
type: integer
min: 0
max: 100
```
The `items` property defines a validator applied to each element in the array.
### Enum Validation
[Section titled “Enum Validation”](#enum-validation)
Restrict values to a predefined set:
```yaml
fields:
status:
type: string
enum:
- pending
- approved
- rejected
priority:
type: integer
enum:
- 1
- 2
- 3
```
### Required and Nullable
[Section titled “Required and Nullable”](#required-and-nullable)
Control whether fields must be present and whether they accept null:
```yaml
fields:
name:
type: string
required: true
middleName:
type: string
nullable: true
email:
type: string
required: true
nullable: false
```
* `required: true` - Field must be present in the request
* `nullable: true` - Field may be explicitly set to null
* By default, fields are optional and non-nullable
## Nested Field Validation
[Section titled “Nested Field Validation”](#nested-field-validation)
Use dot notation to validate nested objects and array elements:
```yaml
fields:
address.street:
type: string
required: true
address.city:
type: string
required: true
address.zipCode:
type: string
pattern: "^\\d{5}(-\\d{4})?$"
items.sku:
type: string
required: true
items.quantity:
type: integer
min: 1
```
For arrays, the path `items.sku` validates the `sku` field on every object in the `items` array. This works with deeply nested structures:
```yaml
fields:
order.shipping.address.country:
type: string
enum:
- US
- CA
- MX
```
## Validation Modes
[Section titled “Validation Modes”](#validation-modes)
Control how validation failures are handled with the `mode` property:
| Mode | Behavior |
| ------------ | ----------------------------------------------------------------------------------------------------- |
| `strict` | Reject invalid requests with 400 error (default) |
| `warn` | Log warning but process the request normally |
| `permissive` | Only reject on critical errors (missing required fields); log and allow all other validation failures |
```yaml
validation:
mode: warn
fields:
email:
type: string
format: email
```
Use `warn` mode during development to identify validation issues without breaking functionality. Switch to `strict` for testing error handling paths.
## Error Response Format
[Section titled “Error Response Format”](#error-response-format)
When validation fails in strict mode, mockd returns an RFC 7807 Problem Details response:
```json
{
"type": "validation_error",
"title": "Request Validation Failed",
"status": 400,
"detail": "2 validation errors",
"errors": [
{
"field": "email",
"location": "body",
"code": "format",
"message": "must be a valid email",
"received": "not-an-email",
"expected": "format: email",
"hint": "Example: user@example.com"
},
{
"field": "age",
"location": "body",
"code": "min",
"message": "must be >= 0",
"received": -5,
"expected": ">= 0",
"hint": "Increase the value of 'age'"
}
]
}
```
The response uses `Content-Type: application/problem+json` and includes:
* `type`: Error type identifier
* `title`: Human-readable error title
* `status`: HTTP status code
* `detail`: Summary of what went wrong
* `errors`: Array of individual field errors, each with:
* `field`: The field that failed validation
* `location`: Where the field was found (e.g., `body`)
* `code`: Machine-readable error code (e.g., `required`, `format`, `min`, `max_length`, `pattern`)
* `message`: Human-readable error description
* `received`: The value that was submitted (if applicable)
* `expected`: What was expected
* `hint`: Suggestion for fixing the error
## Auto-Inference from Seed Data
[Section titled “Auto-Inference from Seed Data”](#auto-inference-from-seed-data)
When seed data is provided without explicit validation rules, mockd can infer basic type validation:
```yaml
tables:
- name: users
validation:
auto: true
seedData:
- id: "1"
email: "alice@example.com"
age: 28
active: true
```
This automatically creates validators based on the seed data types. Explicit field validators override inferred ones. Use this for quick prototyping, but define explicit validators for production mocks.
## Complete Examples
[Section titled “Complete Examples”](#complete-examples)
### Stateful Resource with Full Validation
[Section titled “Stateful Resource with Full Validation”](#stateful-resource-with-full-validation)
```yaml
tables:
- name: orders
seedData:
- id: "1"
customerId: "cust_abc123"
status: pending
items:
- sku: "WIDGET-001"
quantity: 2
shippingAddress:
street: "123 Main St"
city: "Springfield"
zipCode: "12345"
validation:
mode: strict
onCreate:
fields:
customerId:
type: string
pattern: "^cust_[a-z0-9]+$"
required: true
status:
type: string
enum:
- pending
- processing
- shipped
- delivered
items:
type: array
minItems: 1
required: true
items.sku:
type: string
required: true
items.quantity:
type: integer
min: 1
required: true
shippingAddress.street:
type: string
required: true
shippingAddress.city:
type: string
required: true
shippingAddress.zipCode:
type: string
pattern: "^\\d{5}$"
required: true
onUpdate:
fields:
status:
type: string
enum:
- pending
- processing
- shipped
- delivered
```
### HTTP Mock with Validation
[Section titled “HTTP Mock with Validation”](#http-mock-with-validation)
```yaml
mocks:
- id: newsletter-subscribe
type: http
http:
matcher:
method: POST
path: /api/newsletter/subscribe
validation:
mode: strict
fields:
email:
type: string
format: email
required: true
firstName:
type: string
minLength: 1
maxLength: 100
preferences:
type: object
preferences.frequency:
type: string
enum:
- daily
- weekly
- monthly
preferences.topics:
type: array
items:
type: string
maxItems: 5
uniqueItems: true
response:
statusCode: 201
body: '{"subscribed": true, "message": "Successfully subscribed to newsletter"}'
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Stateful Mocking](/guides/stateful-mocking/) - Learn more about CRUD resources and state management
* [Configuration](/reference/configuration/) - Explore all configuration options for your mock server
# Workspaces
> Isolate mocks, state, and logs with workspaces for multi-tenant testing, environment separation, and parallel test runs.
Workspaces provide isolated environments within a single mockd instance. Each workspace has its own mocks, stateful resources, request logs, and custom operations — completely independent from other workspaces.
## When to Use Workspaces
[Section titled “When to Use Workspaces”](#when-to-use-workspaces)
* **Multi-tenant testing** — simulate different customer environments side by side
* **Environment isolation** — separate dev, staging, and QA mock configurations
* **Parallel test runs** — each test suite gets its own workspace so tests don’t interfere
* **Team collaboration** — teammates work on different API mocks without conflicts
## Creating Workspaces
[Section titled “Creating Workspaces”](#creating-workspaces)
### CLI
[Section titled “CLI”](#cli)
```bash
# Create a workspace
mockd workspace create -n "Payment API" -d "Stripe mock environment"
# Create and switch to it immediately
mockd workspace create -n "Payment API" --use
```
### MCP
[Section titled “MCP”](#mcp)
```json
{ "action": "create", "name": "Payment API" }
```
### Admin API
[Section titled “Admin API”](#admin-api)
```bash
curl -X POST http://localhost:4290/workspaces \
-H "Content-Type: application/json" \
-d '{"name": "Payment API", "description": "Stripe mock environment"}'
```
## Switching Workspaces
[Section titled “Switching Workspaces”](#switching-workspaces)
Once created, switch to a workspace so all subsequent commands target it:
```bash
# Switch to a workspace (persists in context config)
mockd workspace use ws_abc123
# Verify current workspace
mockd workspace show
```
The active workspace is saved in `~/.config/mockd/contexts.yaml` and persists across terminal sessions.
## Workspace Scoping
[Section titled “Workspace Scoping”](#workspace-scoping)
When a workspace is active, all operations are scoped to it:
| Resource | Scoped? | Details |
| ------------------------- | ------- | ---------------------------------------------------------- |
| Mocks | Yes | Each workspace has its own set of mocks |
| Stateful tables/resources | Yes | Data stores are independent per workspace |
| Request logs | Yes | Logs are filtered by workspace |
| Custom operations | Yes | Operations are registered per workspace |
| Import/Export | Yes | Imports go into the active workspace; exports come from it |
| Chaos config | **No** | Chaos injection is global across all workspaces |
## The `--workspace` Flag
[Section titled “The --workspace Flag”](#the---workspace-flag)
Instead of switching the persistent workspace, you can scope a single command with the `--workspace` flag:
```bash
# List mocks in a specific workspace without switching
mockd list --workspace ws_abc123
# Import into a specific workspace
mockd import openapi.yaml --workspace ws_abc123
# Export from a specific workspace
mockd export --workspace ws_abc123 -o mocks.yaml
# View logs for a specific workspace
mockd logs --workspace ws_abc123
```
This flag is available on every CLI command as a global flag.
## Environment Variable
[Section titled “Environment Variable”](#environment-variable)
Set `MOCKD_WORKSPACE` to scope all commands to a workspace without using `--workspace` on every call:
```bash
export MOCKD_WORKSPACE=ws_abc123
mockd list # scoped to ws_abc123
mockd import ... # scoped to ws_abc123
```
### Resolution Order
[Section titled “Resolution Order”](#resolution-order)
When multiple sources specify a workspace, mockd uses this priority:
1. `--workspace` flag (highest priority)
2. `MOCKD_WORKSPACE` environment variable
3. Context config (`mockd workspace use`)
4. Default workspace (no workspace — global scope)
## Listing and Managing Workspaces
[Section titled “Listing and Managing Workspaces”](#listing-and-managing-workspaces)
```bash
# List all workspaces
mockd workspace list
# Output:
# CURRENT ID NAME BASE PATH TYPE DESCRIPTION
# * ws_abc123 Payment API / local Stripe mock environment
# ws_def456 Comms API / local Twilio mock environment
# Delete a workspace
mockd workspace delete ws_def456
# Force delete (skip confirmation)
mockd workspace delete ws_def456 --force
# Clear workspace selection (revert to default)
mockd workspace clear
```
## Practical Examples
[Section titled “Practical Examples”](#practical-examples)
### Parallel Test Suites
[Section titled “Parallel Test Suites”](#parallel-test-suites)
Give each test suite its own workspace so tests can run concurrently without mock collisions:
```bash
# In test setup
WORKSPACE_ID=$(mockd workspace create -n "test-suite-$RANDOM" --json | jq -r '.id')
# Run tests scoped to this workspace
MOCKD_WORKSPACE=$WORKSPACE_ID pytest tests/
# Teardown
mockd workspace delete $WORKSPACE_ID --force
```
### Multi-API Development
[Section titled “Multi-API Development”](#multi-api-development)
When building against multiple third-party APIs, use separate workspaces to keep configurations clean:
```bash
# Stripe mocks
mockd workspace create -n "Stripe" --use
mockd import stripe-openapi.yaml
# Switch to Twilio
mockd workspace create -n "Twilio" --use
mockd import twilio-openapi.yaml
# Switch between them as needed
mockd workspace use ws_abc123 # back to Stripe
```
### MCP Workflow
[Section titled “MCP Workflow”](#mcp-workflow)
AI assistants can manage workspaces via the `manage_workspace` MCP tool:
```json
// List workspaces
{ "action": "list" }
// Create a workspace
{ "action": "create", "name": "Integration Tests" }
// Switch to a workspace
{ "action": "switch", "id": "ws_abc123" }
```
All subsequent `manage_mock`, `manage_state`, and `import_mocks` calls are automatically scoped to the active workspace.
## Limitations
[Section titled “Limitations”](#limitations)
* **Chaos config is global.** Chaos fault injection (latency, error rates, circuit breakers) applies to all workspaces. You cannot configure chaos per workspace.
* **Persistence does not round-trip workspace context.** On server restart, persisted mocks retain their `workspaceId` field, but the active workspace selection (set via `mockd workspace use`) resets. Re-select with `mockd workspace use` after restart.
* **Port conflicts.** If workspaces contain gRPC or MQTT mocks that bind to the same port, only one can be active at a time.
## See Also
[Section titled “See Also”](#see-also)
* [CLI Reference: workspace commands](/reference/cli/#mockd-workspace)
* [Admin API: workspace endpoints](/reference/admin-api/#get-workspaces)
* [Stateful Mocking](/guides/stateful-mocking/) — stateful resources are workspace-scoped
* [MCP Server](/guides/mcp-server/) — `manage_workspace` tool
# GraphQL Mocking
> Create mock GraphQL endpoints for testing GraphQL clients with queries, mutations, subscriptions, and full introspection support.
GraphQL mocking enables you to create mock GraphQL endpoints for testing GraphQL clients. Configure queries, mutations, subscriptions, and introspection with flexible resolvers.
## Overview
[Section titled “Overview”](#overview)
mockd’s GraphQL support includes:
* **Schema validation** - Define schemas inline or from files
* **Query/Mutation resolvers** - Return mock data for operations
* **Argument matching** - Conditional responses based on arguments
* **Introspection** - Full introspection support for tooling
* **Subscriptions** - WebSocket-based real-time data streaming
* **Template support** - Dynamic responses with variables
## Quick Start
[Section titled “Quick Start”](#quick-start)
Create a minimal GraphQL mock:
```yaml
version: "1.0"
mocks:
- id: my-graphql-api
name: User API
type: graphql
enabled: true
graphql:
path: /graphql
introspection: true
schema: |
type Query {
users: [User!]!
user(id: ID!): User
}
type User {
id: ID!
name: String!
email: String
}
resolvers:
Query.users:
response:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
Query.user:
response:
id: "1"
name: "Alice"
email: "alice@example.com"
```
Start the server and test:
```bash
# Start mockd
mockd serve --config mockd.yaml
# Query users
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ users { id name } }"}'
# Response:
# {"data":{"users":[{"id":"1","name":"Alice"},{"id":"2","name":"Bob"}]}}
```
## Configuration
[Section titled “Configuration”](#configuration)
### Full Configuration Reference
[Section titled “Full Configuration Reference”](#full-configuration-reference)
```yaml
mocks:
- id: graphql-endpoint
name: My GraphQL API
type: graphql
enabled: true
graphql:
# Endpoint path (required)
path: /graphql
# Schema definition - use either inline or file
schema: |
type Query {
# Schema SDL here
}
# OR
schemaFile: ./schemas/api.graphql
# Enable introspection queries (default: false)
introspection: true
# Resolver configurations
resolvers:
Query.fieldName:
response: # Response data
delay: "100ms" # Optional delay
match: # Optional argument matching
args:
id: "123"
error: # Return an error instead
message: "Error message"
path: ["fieldName"]
extensions:
code: ERROR_CODE
# Subscription configurations (WebSocket)
subscriptions:
messageAdded:
events:
- data: { id: "1", text: "Hello" }
- data: { id: "2", text: "World" }
delay: "1s"
timing:
fixedDelay: "500ms"
repeat: true
```
### Configuration Fields
[Section titled “Configuration Fields”](#configuration-fields)
| Field | Type | Description |
| --------------- | ------- | ----------------------------------------- |
| `path` | string | GraphQL endpoint path (e.g., `/graphql`) |
| `schema` | string | Inline GraphQL SDL schema |
| `schemaFile` | string | Path to external `.graphql` schema file |
| `introspection` | boolean | Enable `__schema` and `__type` queries |
| `resolvers` | map | Resolver configurations by field path |
| `subscriptions` | map | Subscription configurations by field name |
## Schema Definition
[Section titled “Schema Definition”](#schema-definition)
Define your GraphQL schema either inline or in an external file.
### Inline Schema
[Section titled “Inline Schema”](#inline-schema)
```yaml
graphql:
schema: |
type Query {
user(id: ID!): User
users(status: String): [User!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User
deleteUser(id: ID!): Boolean!
}
type User {
id: ID!
email: String!
name: String!
role: Role!
status: Status!
createdAt: String!
profile: Profile
}
type Profile {
bio: String
avatar: String
location: String
}
input CreateUserInput {
email: String!
name: String!
role: Role
}
input UpdateUserInput {
email: String
name: String
role: Role
}
enum Role {
ADMIN
USER
GUEST
}
enum Status {
ACTIVE
INACTIVE
PENDING
}
```
### External Schema File
[Section titled “External Schema File”](#external-schema-file)
```yaml
graphql:
schemaFile: ./schemas/api.graphql
```
Create `schemas/api.graphql`:
```graphql
type Query {
user(id: ID!): User
users: [User!]!
}
type User {
id: ID!
name: String!
email: String
}
```
### Schema Validation
[Section titled “Schema Validation”](#schema-validation)
Validate your schema before starting the server:
```bash
mockd graphql validate schema.graphql
```
Output:
```plaintext
Schema valid: schema.graphql
Types: 5
Queries: 2
Mutations: 3
```
## Resolvers
[Section titled “Resolvers”](#resolvers)
Resolvers define how GraphQL fields return mock data. Use the format `Type.field` to specify resolvers.
### Basic Resolver
[Section titled “Basic Resolver”](#basic-resolver)
Return static data for a query:
```yaml
resolvers:
Query.users:
response:
- id: "user_001"
email: "alice@example.com"
name: "Alice Smith"
role: "ADMIN"
- id: "user_002"
email: "bob@example.com"
name: "Bob Johnson"
role: "USER"
Query.user:
response:
id: "user_001"
email: "alice@example.com"
name: "Alice Smith"
role: "ADMIN"
profile:
bio: "Platform administrator"
location: "San Francisco, CA"
```
### Mutation Resolvers
[Section titled “Mutation Resolvers”](#mutation-resolvers)
```yaml
resolvers:
Mutation.createUser:
response:
id: "user_new"
email: "newuser@example.com"
name: "New User"
role: "USER"
status: "PENDING"
createdAt: "{{now}}"
Mutation.updateUser:
response:
id: "user_001"
email: "alice.updated@example.com"
name: "Alice Smith (Updated)"
updatedAt: "{{now}}"
Mutation.deleteUser:
response: true
```
### Response Delay
[Section titled “Response Delay”](#response-delay)
Simulate network latency:
```yaml
resolvers:
Query.users:
response:
- id: "1"
name: "Alice"
delay: 500ms
Query.slowQuery:
response: { status: "completed" }
delay: 2s
```
### Dynamic Responses with Templates
[Section titled “Dynamic Responses with Templates”](#dynamic-responses-with-templates)
Use template expressions in responses:
```yaml
resolvers:
Mutation.createUser:
response:
id: "{{uuid}}"
name: "{{request.body.variables.name}}"
email: "{{request.body.variables.email}}"
createdAt: "{{now}}"
Query.user:
response:
id: "{{args.id}}"
name: "User {{args.id}}"
```
Available templates:
| Template | Description |
| --------------------------------- | ------------------------- |
| `{{uuid}}` | Random UUID |
| `{{now}}` | Current ISO timestamp |
| `{{timestamp}}` | Unix timestamp |
| `{{args.fieldName}}` | Argument value from query |
| `{{request.body.variables.name}}` | Variable from request |
## Argument Matching
[Section titled “Argument Matching”](#argument-matching)
Return different responses based on query arguments.
### Match Specific Arguments
[Section titled “Match Specific Arguments”](#match-specific-arguments)
```yaml
resolvers:
Query.user:
match:
args:
id: "123"
response:
id: "123"
name: "John Doe"
email: "john@example.com"
```
### Multiple Resolvers with Different Matches
[Section titled “Multiple Resolvers with Different Matches”](#multiple-resolvers-with-different-matches)
Define multiple resolver entries by using resolver lists (when you need conditional matching, configure multiple mocks):
```yaml
mocks:
# Resolver for specific user
- id: graphql-user-123
type: graphql
enabled: true
graphql:
path: /graphql
schema: |
type Query { user(id: ID!): User }
type User { id: ID!, name: String!, email: String }
resolvers:
Query.user:
match:
args:
id: "123"
response:
id: "123"
name: "Admin User"
email: "admin@example.com"
```
### Error on Specific Arguments
[Section titled “Error on Specific Arguments”](#error-on-specific-arguments)
Return an error for certain inputs:
```yaml
resolvers:
Mutation.deleteUser:
match:
args:
id: "nonexistent"
error:
message: "User not found"
path: ["deleteUser"]
extensions:
code: NOT_FOUND
```
## Introspection
[Section titled “Introspection”](#introspection)
When introspection is enabled, mockd responds to `__schema` and `__type` queries based on your schema definition.
### Enable Introspection
[Section titled “Enable Introspection”](#enable-introspection)
```yaml
graphql:
introspection: true
```
### Test Introspection
[Section titled “Test Introspection”](#test-introspection)
```bash
# Query schema types
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name } } }"}'
# Query specific type
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __type(name: \"User\") { name fields { name type { name } } } }"}'
```
Introspection enables GraphQL tooling:
* GraphQL IDEs (GraphiQL, Apollo Studio)
* Code generators
* Schema documentation tools
* Client libraries with auto-completion
### Disable for Production-like Testing
[Section titled “Disable for Production-like Testing”](#disable-for-production-like-testing)
```yaml
graphql:
introspection: false
```
Queries to `__schema` will return:
```json
{"errors":[{"message":"introspection is disabled"}]}
```
## Variables and Arguments
[Section titled “Variables and Arguments”](#variables-and-arguments)
Handle GraphQL variables in queries and mutations.
### Query with Variables
[Section titled “Query with Variables”](#query-with-variables)
```graphql
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
```
Request:
```bash
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query GetUser($id: ID!) { user(id: $id) { id name } }",
"variables": {"id": "123"}
}'
```
### Named Operations
[Section titled “Named Operations”](#named-operations)
```graphql
query ListUsers {
users {
id
name
}
}
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
```
Specify operation name:
```bash
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query ListUsers { users { id name } } query GetUser($id: ID!) { user(id: $id) { id name email } }",
"operationName": "GetUser",
"variables": {"id": "123"}
}'
```
### Using Variables in Resolvers
[Section titled “Using Variables in Resolvers”](#using-variables-in-resolvers)
Reference argument values in responses:
```yaml
resolvers:
Query.user:
response:
id: "{{args.id}}"
name: "User {{args.id}}"
fetchedAt: "{{now}}"
```
## Subscriptions
[Section titled “Subscriptions”](#subscriptions)
GraphQL subscriptions stream real-time data over WebSocket connections.
### Configuration
[Section titled “Configuration”](#configuration-1)
```yaml
graphql:
schema: |
type Query {
_: String
}
type Subscription {
messageAdded(channel: String!): Message
notifications: Notification
}
type Message {
id: ID!
text: String!
timestamp: String!
}
type Notification {
id: ID!
type: String!
message: String!
}
subscriptions:
messageAdded:
events:
- data:
id: "1"
text: "Hello from mockd!"
timestamp: "2024-01-15T10:00:00Z"
- data:
id: "2"
text: "Another message"
timestamp: "2024-01-15T10:00:01Z"
delay: "1s"
timing:
fixedDelay: "500ms"
repeat: false
notifications:
events:
- data:
id: "n1"
type: "info"
message: "System update available"
timing:
fixedDelay: "2s"
repeat: true
```
### Timing Options
[Section titled “Timing Options”](#timing-options)
| Field | Description | Example |
| ------------- | --------------------------------- | ----------------- |
| `fixedDelay` | Fixed delay between events | `"500ms"`, `"2s"` |
| `randomDelay` | Random delay range | `"100ms-500ms"` |
| `repeat` | Repeat events after sequence ends | `true`/`false` |
### Event-specific Delays
[Section titled “Event-specific Delays”](#event-specific-delays)
```yaml
subscriptions:
messageAdded:
events:
- data: { id: "1", text: "Immediate" }
- data: { id: "2", text: "After 1 second" }
delay: "1s"
- data: { id: "3", text: "After 2 more seconds" }
delay: "2s"
```
### Variable Substitution
[Section titled “Variable Substitution”](#variable-substitution)
Use subscription arguments in event data:
```yaml
subscriptions:
messageAdded:
events:
- data:
id: "1"
channel: "{{args.channel}}"
text: "Message in {{vars.channel}}"
```
### WebSocket Protocols
[Section titled “WebSocket Protocols”](#websocket-protocols)
mockd supports both GraphQL WebSocket protocols:
* **graphql-transport-ws** (modern) - Recommended
* **graphql-ws** / **subscriptions-transport-ws** (legacy)
Connect with your client:
```javascript
// Apollo Client
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4280/graphql',
})
);
```
## Error Responses
[Section titled “Error Responses”](#error-responses)
Configure error responses for testing error handling.
### Simple Error
[Section titled “Simple Error”](#simple-error)
```yaml
resolvers:
Query.restrictedData:
error:
message: "Unauthorized access"
```
Response:
```json
{
"data": null,
"errors": [{
"message": "Unauthorized access"
}]
}
```
### Error with Path and Extensions
[Section titled “Error with Path and Extensions”](#error-with-path-and-extensions)
```yaml
resolvers:
Mutation.deleteUser:
match:
args:
id: "protected"
error:
message: "Cannot delete protected user"
path: ["deleteUser"]
extensions:
code: FORBIDDEN
userId: "protected"
reason: "System account"
```
Response:
```json
{
"data": {"deleteUser": null},
"errors": [{
"message": "Cannot delete protected user",
"path": ["deleteUser"],
"extensions": {
"code": "FORBIDDEN",
"userId": "protected",
"reason": "System account"
}
}]
}
```
## Examples
[Section titled “Examples”](#examples)
### E-Commerce API
[Section titled “E-Commerce API”](#e-commerce-api)
```yaml
mocks:
- id: ecommerce-graphql
name: E-Commerce API
type: graphql
enabled: true
graphql:
path: /graphql
introspection: true
schema: |
type Query {
products(category: String, limit: Int): [Product!]!
product(id: ID!): Product
cart: Cart
orders: [Order!]!
}
type Mutation {
addToCart(productId: ID!, quantity: Int!): Cart!
checkout: Order!
}
type Product {
id: ID!
name: String!
price: Float!
category: String!
inStock: Boolean!
}
type Cart {
id: ID!
items: [CartItem!]!
total: Float!
}
type CartItem {
product: Product!
quantity: Int!
}
type Order {
id: ID!
items: [CartItem!]!
total: Float!
status: String!
createdAt: String!
}
resolvers:
Query.products:
response:
- id: "prod_001"
name: "Wireless Headphones"
price: 79.99
category: "Electronics"
inStock: true
- id: "prod_002"
name: "Running Shoes"
price: 129.99
category: "Sports"
inStock: true
- id: "prod_003"
name: "Coffee Maker"
price: 49.99
category: "Home"
inStock: false
Query.product:
response:
id: "{{args.id}}"
name: "Product {{args.id}}"
price: 99.99
category: "General"
inStock: true
Query.cart:
response:
id: "cart_001"
items:
- product:
id: "prod_001"
name: "Wireless Headphones"
price: 79.99
quantity: 2
total: 159.98
Mutation.addToCart:
response:
id: "cart_001"
items:
- product:
id: "{{args.productId}}"
name: "Added Product"
price: 99.99
quantity: "{{args.quantity}}"
total: 199.98
Mutation.checkout:
response:
id: "order_{{uuid}}"
items: []
total: 159.98
status: "CONFIRMED"
createdAt: "{{now}}"
delay: 500ms
```
### User Authentication API
[Section titled “User Authentication API”](#user-authentication-api)
```yaml
mocks:
- id: auth-graphql
name: Auth API
type: graphql
enabled: true
graphql:
path: /graphql
introspection: true
schema: |
type Query {
me: User
}
type Mutation {
login(email: String!, password: String!): AuthPayload!
register(input: RegisterInput!): AuthPayload!
refreshToken(token: String!): AuthPayload!
}
type User {
id: ID!
email: String!
name: String!
role: String!
}
type AuthPayload {
token: String!
refreshToken: String!
user: User!
expiresAt: String!
}
input RegisterInput {
email: String!
password: String!
name: String!
}
resolvers:
Query.me:
response:
id: "user_current"
email: "user@example.com"
name: "Current User"
role: "USER"
Mutation.login:
response:
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
refreshToken: "refresh_{{uuid}}"
user:
id: "user_001"
email: "{{args.email}}"
name: "Authenticated User"
role: "USER"
expiresAt: "{{now}}"
delay: 200ms
Mutation.register:
response:
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
refreshToken: "refresh_{{uuid}}"
user:
id: "user_{{uuid}}"
email: "{{request.body.variables.input.email}}"
name: "{{request.body.variables.input.name}}"
role: "USER"
expiresAt: "{{now}}"
```
### Real-time Chat with Subscriptions
[Section titled “Real-time Chat with Subscriptions”](#real-time-chat-with-subscriptions)
```yaml
mocks:
- id: chat-graphql
name: Chat API
type: graphql
enabled: true
graphql:
path: /graphql
introspection: true
schema: |
type Query {
messages(roomId: ID!): [Message!]!
rooms: [Room!]!
}
type Mutation {
sendMessage(roomId: ID!, text: String!): Message!
createRoom(name: String!): Room!
}
type Subscription {
messageAdded(roomId: ID!): Message
userTyping(roomId: ID!): TypingIndicator
}
type Message {
id: ID!
roomId: ID!
text: String!
sender: User!
timestamp: String!
}
type Room {
id: ID!
name: String!
members: [User!]!
}
type User {
id: ID!
name: String!
avatar: String
}
type TypingIndicator {
userId: ID!
userName: String!
isTyping: Boolean!
}
resolvers:
Query.messages:
response:
- id: "msg_001"
roomId: "{{args.roomId}}"
text: "Welcome to the room!"
sender:
id: "user_system"
name: "System"
timestamp: "2024-01-15T10:00:00Z"
Query.rooms:
response:
- id: "room_general"
name: "General"
members:
- id: "user_001"
name: "Alice"
- id: "room_random"
name: "Random"
members: []
Mutation.sendMessage:
response:
id: "msg_{{uuid}}"
roomId: "{{args.roomId}}"
text: "{{args.text}}"
sender:
id: "user_current"
name: "You"
timestamp: "{{now}}"
subscriptions:
messageAdded:
events:
- data:
id: "msg_live_001"
roomId: "{{args.roomId}}"
text: "Someone joined the room"
sender:
id: "user_002"
name: "Bob"
timestamp: "2024-01-15T10:01:00Z"
- data:
id: "msg_live_002"
roomId: "{{args.roomId}}"
text: "Hello everyone!"
sender:
id: "user_002"
name: "Bob"
timestamp: "2024-01-15T10:01:05Z"
delay: "2s"
timing:
fixedDelay: "1s"
userTyping:
events:
- data:
userId: "user_002"
userName: "Bob"
isTyping: true
- data:
userId: "user_002"
userName: "Bob"
isTyping: false
delay: "2s"
timing:
fixedDelay: "5s"
repeat: true
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
### Add a GraphQL Mock
[Section titled “Add a GraphQL Mock”](#add-a-graphql-mock)
Create GraphQL mocks directly from the command line using `mockd graphql add`:
```bash
# Simple query resolver
mockd graphql add --path /graphql --operation users \
--response '["Alice", "Bob"]'
# Query with nested object response
mockd graphql add --path /graphql --operation user \
--response '{"id":"1","name":"Alice","email":"alice@example.com"}'
# Mutation resolver
mockd graphql add --path /graphql --operation createUser \
--mutation --response '{"id":"new_1","name":"Created User"}'
```
Output:
```plaintext
Created mock: gql_3c1df10ed1566392
Type: graphql
Path: /graphql
Operation: Query.users
```
The `--response` flag takes raw JSON that becomes the resolver data. For a query like `{ users { name } }`, the response is automatically wrapped: `{"data":{"users":["Alice","Bob"]}}`.
#### Add Command Flags
[Section titled “Add Command Flags”](#add-command-flags)
| Flag | Description |
| ------------- | -------------------------------------------------- |
| `--path` | GraphQL endpoint path (default: `/graphql`) |
| `--operation` | Operation/field name (e.g., `users`, `createUser`) |
| `--response` | JSON response data for the resolver |
| `--mutation` | Create a Mutation resolver instead of Query |
| `--admin-url` | Admin API URL (default: `http://localhost:4290`) |
### Validate Schema
[Section titled “Validate Schema”](#validate-schema)
Validate a GraphQL schema file:
```bash
mockd graphql validate schema.graphql
```
```plaintext
Schema valid: schema.graphql
Types: 8
Queries: 3
Mutations: 4
```
### Execute Query
[Section titled “Execute Query”](#execute-query)
Execute a query against a running GraphQL endpoint:
```bash
# Simple query
mockd graphql query http://localhost:4280/graphql "{ users { id name } }"
# Query with variables
mockd graphql query http://localhost:4280/graphql \
"query GetUser(\$id: ID!) { user(id: \$id) { name } }" \
-v '{"id": "123"}'
# Query from file
mockd graphql query http://localhost:4280/graphql @query.graphql
# With custom headers
mockd graphql query http://localhost:4280/graphql "{ me { name } }" \
-H "Authorization:Bearer token123"
# Specify operation name
mockd graphql query http://localhost:4280/graphql @operations.graphql \
-o GetUserById \
-v '{"id": "456"}'
```
### Query Command Options
[Section titled “Query Command Options”](#query-command-options)
| Flag | Description |
| ----------------- | -------------------------------------------- |
| `-v, --variables` | JSON string of variables |
| `-o, --operation` | Operation name for multi-operation documents |
| `-H, --header` | Additional headers (`key:value,key2:value2`) |
| `--pretty` | Pretty print output (default: true) |
### Initialize GraphQL Template
[Section titled “Initialize GraphQL Template”](#initialize-graphql-template)
Create a new project with GraphQL configuration:
```bash
mockd init --template graphql-api
```
This generates a complete GraphQL mock configuration with:
* User type with CRUD operations
* Query and Mutation types
* Sample resolvers
* Introspection enabled
## Testing Tips
[Section titled “Testing Tips”](#testing-tips)
### Test Query Parsing
[Section titled “Test Query Parsing”](#test-query-parsing)
Validate queries against your schema before running tests:
```bash
# Check if query is valid against schema
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ invalidField }"}'
# Response shows validation error:
# {"errors":[{"message":"validation error: ..."}]}
```
### Test Error Handling
[Section titled “Test Error Handling”](#test-error-handling)
Configure error responses to test client error handling:
```yaml
resolvers:
Query.users:
error:
message: "Service temporarily unavailable"
extensions:
code: SERVICE_UNAVAILABLE
retryAfter: 30
```
### Test with Different Content Types
[Section titled “Test with Different Content Types”](#test-with-different-content-types)
mockd supports multiple content types:
```bash
# application/json (default)
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ users { id } }"}'
# application/graphql
curl -X POST http://localhost:4280/graphql \
-H "Content-Type: application/graphql" \
-d '{ users { id } }'
# GET request with query parameters
curl "http://localhost:4280/graphql?query=%7B%20users%20%7B%20id%20%7D%20%7D"
```
### Test Latency Simulation
[Section titled “Test Latency Simulation”](#test-latency-simulation)
Use delays to test timeout handling:
```yaml
resolvers:
Query.slowQuery:
response: { status: "ok" }
delay: 5s # Test client timeout behavior
```
### Test with GraphQL Clients
[Section titled “Test with GraphQL Clients”](#test-with-graphql-clients)
Use your favorite GraphQL client or IDE:
* **GraphiQL** - In-browser IDE
* **Apollo Studio** - Full-featured GraphQL IDE
* **Postman** - API testing with GraphQL support
* **Insomnia** - REST and GraphQL client
All support introspection for auto-completion when `introspection: true`.
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Response Templating](/guides/response-templating) - Dynamic response values
* [Request Matching](/guides/request-matching) - HTTP-level matching
* [Configuration Reference](/reference/configuration) - Full configuration schema
# gRPC Mocking
> Create mock gRPC services for testing gRPC clients with protobuf support, all RPC types, request matching, and server reflection.
gRPC mocking enables you to create mock gRPC services for testing gRPC clients. Configure unary RPCs, streaming methods, and server reflection with protobuf definitions.
## Overview
[Section titled “Overview”](#overview)
mockd’s gRPC support includes:
* **Protobuf support** - Use `.proto` files to define your service schema
* **All RPC types** - Unary, server streaming, client streaming, and bidirectional
* **Request matching** - Conditional responses based on metadata and request fields
* **Server reflection** - Enable tooling discovery with `grpcurl` and gRPC UI
* **Error simulation** - Return gRPC status codes with detailed error messages
* **Template support** - Dynamic responses with variables
## Quick Start
[Section titled “Quick Start”](#quick-start)
Create a minimal gRPC mock. First, create your proto file `protos/greeter.proto`:
```protobuf
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
```
Then create your mockd configuration:
```yaml
version: "1.0"
mocks:
- id: my-grpc-service
name: Greeter Service
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./protos/greeter.proto
reflection: true
services:
helloworld.Greeter:
methods:
SayHello:
response:
message: "Hello, World!"
```
Start the server and test:
```bash
# Start mockd
mockd serve --config mockd.yaml
# List services (requires grpcurl)
grpcurl -plaintext localhost:50051 list
# Call SayHello
grpcurl -plaintext -d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHello
# Response:
# {
# "message": "Hello, World!"
# }
```
## Configuration
[Section titled “Configuration”](#configuration)
### Full Configuration Reference
[Section titled “Full Configuration Reference”](#full-configuration-reference)
```yaml
mocks:
- id: grpc-endpoint
name: My gRPC Service
type: grpc
enabled: true
grpc:
# gRPC server port (required)
port: 50051
# Proto file path (required)
protoFile: ./protos/service.proto
# Multiple proto files (alternative to protoFile)
protoFiles:
- ./protos/service.proto
- ./protos/messages.proto
# Import paths for proto dependencies
importPaths:
- ./protos
- ./vendor/googleapis
# Enable gRPC server reflection (default: false)
reflection: true
# Service and method configurations
services:
package.ServiceName:
methods:
MethodName:
response: # Single response (unary/server streaming)
responses: # Multiple responses (streaming)
delay: "100ms" # Response delay
streamDelay: "50ms" # Delay between stream messages
match: # Conditional matching
metadata:
key: "value"
request:
field: "value"
error: # Return gRPC error
code: NOT_FOUND
message: "Resource not found"
variants: # Additional match variants for THIS method (first-match-wins)
- match:
request:
field: "other-value"
response:
field: "other"
```
### Configuration Fields
[Section titled “Configuration Fields”](#configuration-fields)
| Field | Type | Description |
| ------------- | --------- | --------------------------------- |
| `port` | int | gRPC server port |
| `protoFile` | string | Path to a single `.proto` file |
| `protoFiles` | \[]string | Paths to multiple `.proto` files |
| `importPaths` | \[]string | Additional proto import paths |
| `reflection` | boolean | Enable gRPC server reflection |
| `services` | map | Service and method configurations |
## Proto File Configuration
[Section titled “Proto File Configuration”](#proto-file-configuration)
mockd requires protobuf definitions to validate and parse messages. Proto files must be provided as file paths.
### Single Proto File
[Section titled “Single Proto File”](#single-proto-file)
```yaml
grpc:
protoFile: ./protos/service.proto
```
Create `protos/service.proto`:
```protobuf
syntax = "proto3";
package users;
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (stream User) {}
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
```
### Multiple Proto Files
[Section titled “Multiple Proto Files”](#multiple-proto-files)
When your service spans multiple proto files:
```yaml
grpc:
protoFiles:
- ./protos/service.proto
- ./protos/messages.proto
- ./protos/common.proto
```
### Import Paths
[Section titled “Import Paths”](#import-paths)
Configure import paths for proto dependencies:
```yaml
grpc:
protoFile: ./protos/service.proto
importPaths:
- ./protos
- ./vendor/googleapis
- ./third_party/protobuf
```
This allows proto files to import other definitions:
```protobuf
import "google/protobuf/timestamp.proto";
import "common/types.proto";
```
## Service Definition
[Section titled “Service Definition”](#service-definition)
Configure responses for each service and method using the fully qualified service name (package.ServiceName).
### Basic Service Configuration
[Section titled “Basic Service Configuration”](#basic-service-configuration)
```yaml
services:
helloworld.Greeter:
methods:
SayHello:
response:
message: "Hello!"
users.UserService:
methods:
GetUser:
response:
id: "123"
name: "John Doe"
email: "john@example.com"
```
### Multiple Methods
[Section titled “Multiple Methods”](#multiple-methods)
```yaml
services:
users.UserService:
methods:
GetUser:
response:
id: "123"
name: "John Doe"
email: "john@example.com"
CreateUser:
response:
id: "{{uuid}}"
name: "New User"
created_at: "{{now}}"
delay: "100ms"
DeleteUser:
response:
success: true
```
## Method Responses
[Section titled “Method Responses”](#method-responses)
### Unary RPC (Single Request, Single Response)
[Section titled “Unary RPC (Single Request, Single Response)”](#unary-rpc-single-request-single-response)
The most common RPC type - one request, one response:
```yaml
services:
UserService:
methods:
GetUser:
response:
id: "123"
name: "John Doe"
email: "john@example.com"
role: "ADMIN"
```
### Server Streaming (Single Request, Multiple Responses)
[Section titled “Server Streaming (Single Request, Multiple Responses)”](#server-streaming-single-request-multiple-responses)
Return multiple messages in response to a single request:
```yaml
services:
UserService:
methods:
ListUsers:
responses:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
- id: "3"
name: "Carol"
email: "carol@example.com"
streamDelay: "100ms"
```
### Client Streaming (Multiple Requests, Single Response)
[Section titled “Client Streaming (Multiple Requests, Single Response)”](#client-streaming-multiple-requests-single-response)
Receive multiple messages and return a single response:
```yaml
services:
UserService:
methods:
BatchCreate:
response:
count: 3
success: true
```
### Bidirectional Streaming
[Section titled “Bidirectional Streaming”](#bidirectional-streaming)
Both client and server send multiple messages:
```yaml
services:
ChatService:
methods:
Chat:
responses:
- type: "ack"
message: "Received"
- type: "ack"
message: "Processed"
streamDelay: "50ms"
```
### Response Delay
[Section titled “Response Delay”](#response-delay)
Simulate network latency:
```yaml
services:
UserService:
methods:
GetUser:
response:
id: "123"
name: "John"
delay: "500ms"
SlowOperation:
response:
status: "completed"
delay: "2s"
```
### Stream Delay
[Section titled “Stream Delay”](#stream-delay)
Control timing between streamed messages:
```yaml
services:
UserService:
methods:
ListUsers:
responses:
- id: "1"
name: "Alice"
- id: "2"
name: "Bob"
- id: "3"
name: "Carol"
streamDelay: "200ms" # 200ms between each message
```
### Dynamic Responses with Templates
[Section titled “Dynamic Responses with Templates”](#dynamic-responses-with-templates)
Use template expressions in responses:
```yaml
services:
UserService:
methods:
CreateUser:
response:
id: "{{uuid}}"
name: "New User"
created_at: "{{now}}"
timestamp: "{{timestamp}}"
GetUser:
response:
id: "user_123"
name: "Dynamic User"
fetched_at: "{{now}}"
```
Available templates:
| Template | Description |
| --------------- | --------------------- |
| `{{uuid}}` | Random UUID |
| `{{now}}` | Current ISO timestamp |
| `{{timestamp}}` | Unix timestamp |
## Request Matching
[Section titled “Request Matching”](#request-matching)
Return different responses based on metadata or request field values.
### Metadata Matching
[Section titled “Metadata Matching”](#metadata-matching)
Match requests based on gRPC metadata (headers):
```yaml
services:
UserService:
methods:
GetUser:
match:
metadata:
authorization: "Bearer token123"
x-request-id: "req-*"
response:
id: "123"
name: "Authenticated User"
```
Metadata matching supports:
* Exact match: `authorization: "Bearer token123"`
* Wildcard match: `x-request-id: "req-*"`
### Request Field Matching
[Section titled “Request Field Matching”](#request-field-matching)
Match based on message field values:
```yaml
services:
UserService:
methods:
GetUser:
match:
request:
id: "123"
response:
id: "123"
name: "John Doe"
email: "john@example.com"
```
### Combined Matching
[Section titled “Combined Matching”](#combined-matching)
Match both metadata and request fields:
```yaml
services:
UserService:
methods:
GetUser:
match:
metadata:
authorization: "Bearer valid-token"
request:
id: "123"
response:
id: "123"
name: "John Doe"
email: "john@example.com"
```
### Multiple Match Conditions
[Section titled “Multiple Match Conditions”](#multiple-match-conditions)
A single RPC (service + method) can serve different responses depending on the request. mockd evaluates the configured match variants **in order** and uses the **first one whose `match` passes** (first-match-wins). A variant with no `match` (or an empty `match`) matches every request, so it acts as a **default** — put it **last** so the more specific variants win. If no variant matches and there is no default, the call returns `UNIMPLEMENTED`.
There are two equivalent ways to define multiple variants for the same RPC.
#### Option A — multiple mocks on the same port
[Section titled “Option A — multiple mocks on the same port”](#option-a--multiple-mocks-on-the-same-port)
Define each variant as its own mock. mockd merges mocks that share a port, service, and method into a single gRPC server, preserving the order in which they were added:
```yaml
mocks:
# Match specific user
- id: grpc-user-123
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./user.proto
services:
UserService:
methods:
GetUser:
match:
request:
id: "123"
response:
id: "123"
name: "Admin User"
# Different match condition for the SAME service + method
- id: grpc-user-999
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./user.proto
services:
UserService:
methods:
GetUser:
match:
request:
id: "999"
response:
id: "999"
name: "Jane Doe"
# Catch-all (no match) — returns NOT_FOUND for any other id.
# Listed last so the specific variants above take precedence.
- id: grpc-user-not-found
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./user.proto
services:
UserService:
methods:
GetUser:
error:
code: NOT_FOUND
message: "User not found"
```
A second mock for the same service + method is appended as an additional match variant **as long as its `match` differs**. A mock whose `match` is identical to an existing one — or that has no `match` when a catch-all already exists — is rejected, because it would silently shadow the earlier mock.
#### Option B — `variants` on a single mock
[Section titled “Option B — variants on a single mock”](#option-b--variants-on-a-single-mock)
Alternatively, keep everything in one mock and list the extra variants inline under `variants`. The top-level config is the first (primary) variant; each entry in `variants` is evaluated after it, in order:
```yaml
mocks:
- id: grpc-user
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./user.proto
services:
UserService:
methods:
GetUser:
# Primary variant
match:
request:
id: "123"
response:
id: "123"
name: "Admin User"
# Additional variants, evaluated in order
variants:
- match:
request:
id: "999"
response:
id: "999"
name: "Jane Doe"
# Default (no match) — ordered last.
- error:
code: NOT_FOUND
message: "User not found"
```
Both options behave identically at request time. Use Option A when each variant is managed independently (e.g. created via separate API calls), and Option B when you want a single self-contained mock definition.
## gRPC Errors
[Section titled “gRPC Errors”](#grpc-errors)
Return gRPC status codes with detailed error information.
### Basic Error
[Section titled “Basic Error”](#basic-error)
```yaml
services:
UserService:
methods:
GetUser:
error:
code: NOT_FOUND
message: "User not found"
```
### Error with Details
[Section titled “Error with Details”](#error-with-details)
Error details use structured type keys (`bad_request`, `error_info`, `retry_info`):
```yaml
services:
UserService:
methods:
GetUser:
error:
code: NOT_FOUND
message: "User not found"
details:
error_info:
reason: "USER_NOT_FOUND"
domain: "user-service"
bad_request:
violations:
- field: "user_id"
description: "No user exists with ID 123"
retry_info:
retry_delay: "5s"
```
### Common gRPC Status Codes
[Section titled “Common gRPC Status Codes”](#common-grpc-status-codes)
| Code | Description |
| --------------------- | ------------------------------- |
| `OK` | Success |
| `CANCELLED` | Operation cancelled |
| `UNKNOWN` | Unknown error |
| `INVALID_ARGUMENT` | Invalid argument provided |
| `DEADLINE_EXCEEDED` | Timeout exceeded |
| `NOT_FOUND` | Resource not found |
| `ALREADY_EXISTS` | Resource already exists |
| `PERMISSION_DENIED` | Permission denied |
| `RESOURCE_EXHAUSTED` | Resource exhausted (rate limit) |
| `FAILED_PRECONDITION` | Precondition failed |
| `ABORTED` | Operation aborted |
| `OUT_OF_RANGE` | Out of range |
| `UNIMPLEMENTED` | Not implemented |
| `INTERNAL` | Internal error |
| `UNAVAILABLE` | Service unavailable |
| `DATA_LOSS` | Data loss |
| `UNAUTHENTICATED` | Not authenticated |
### Conditional Errors
[Section titled “Conditional Errors”](#conditional-errors)
Return errors based on request conditions:
```yaml
services:
UserService:
methods:
GetUser:
match:
request:
id: "forbidden"
error:
code: PERMISSION_DENIED
message: "Access to this user is forbidden"
details:
user_id: "forbidden"
required_role: "ADMIN"
```
## Reflection Support
[Section titled “Reflection Support”](#reflection-support)
Enable gRPC server reflection to allow tooling to discover services and methods at runtime.
### Enable Reflection
[Section titled “Enable Reflection”](#enable-reflection)
```yaml
grpc:
reflection: true
```
### Benefits of Reflection
[Section titled “Benefits of Reflection”](#benefits-of-reflection)
With reflection enabled, clients can:
* Discover available services and methods
* Get message type information
* Use tools like `grpcurl` without proto files
* Enable IDE auto-completion
### Testing Reflection
[Section titled “Testing Reflection”](#testing-reflection)
```bash
# List all services
grpcurl -plaintext localhost:50051 list
# Output:
# grpc.reflection.v1alpha.ServerReflection
# helloworld.Greeter
# Describe a service
grpcurl -plaintext localhost:50051 describe helloworld.Greeter
# Output:
# helloworld.Greeter is a service:
# service Greeter {
# rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
# }
# Describe a message
grpcurl -plaintext localhost:50051 describe helloworld.HelloRequest
```
### Disable for Production-like Testing
[Section titled “Disable for Production-like Testing”](#disable-for-production-like-testing)
```yaml
grpc:
reflection: false
```
Without reflection, clients need proto files to make requests.
## Examples
[Section titled “Examples”](#examples)
### User Service
[Section titled “User Service”](#user-service)
Create `protos/users.proto`:
```protobuf
syntax = "proto3";
package users;
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (stream User) {}
rpc CreateUser (CreateUserRequest) returns (User) {}
rpc UpdateUser (UpdateUserRequest) returns (User) {}
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse) {}
}
message GetUserRequest { string id = 1; }
message ListUsersRequest { int32 page_size = 1; string page_token = 2; }
message CreateUserRequest { string name = 1; string email = 2; string role = 3; }
message UpdateUserRequest { string id = 1; string name = 2; string email = 3; }
message DeleteUserRequest { string id = 1; }
message DeleteUserResponse { bool success = 1; }
message User {
string id = 1;
string name = 2;
string email = 3;
string role = 4;
string created_at = 5;
string updated_at = 6;
}
```
Then configure in `mockd.yaml`:
```yaml
version: "1.0"
mocks:
- id: user-grpc-service
name: User Service
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./protos/users.proto
reflection: true
services:
users.UserService:
methods:
GetUser:
response:
id: "user_001"
name: "John Doe"
email: "john@example.com"
role: "USER"
created_at: "2024-01-15T10:00:00Z"
delay: "50ms"
ListUsers:
responses:
- id: "user_001"
name: "Alice Smith"
email: "alice@example.com"
role: "ADMIN"
- id: "user_002"
name: "Bob Johnson"
email: "bob@example.com"
role: "USER"
streamDelay: "100ms"
CreateUser:
response:
id: "{{uuid}}"
name: "New User"
email: "new@example.com"
role: "USER"
created_at: "{{now}}"
DeleteUser:
response:
success: true
```
### Chat Service with Bidirectional Streaming
[Section titled “Chat Service with Bidirectional Streaming”](#chat-service-with-bidirectional-streaming)
Create `protos/chat.proto`:
```protobuf
syntax = "proto3";
package chat;
service ChatService {
rpc SendMessage (ChatMessage) returns (ChatAck) {}
rpc StreamMessages (ChatRoom) returns (stream ChatMessage) {}
rpc Chat (stream ChatMessage) returns (stream ChatMessage) {}
}
message ChatRoom { string room_id = 1; }
message ChatMessage {
string id = 1;
string room_id = 2;
string sender = 3;
string text = 4;
string timestamp = 5;
}
message ChatAck { string message_id = 1; bool delivered = 2; }
```
Then configure:
```yaml
version: "1.0"
mocks:
- id: chat-grpc-service
name: Chat Service
type: grpc
enabled: true
grpc:
port: 50052
protoFile: ./protos/chat.proto
reflection: true
services:
chat.ChatService:
methods:
SendMessage:
response:
message_id: "{{uuid}}"
delivered: true
delay: "20ms"
StreamMessages:
responses:
- id: "msg_001"
room_id: "general"
sender: "system"
text: "Welcome to the chat!"
- id: "msg_002"
room_id: "general"
sender: "alice"
text: "Hello everyone!"
streamDelay: "500ms"
Chat:
responses:
- id: "echo_001"
sender: "bot"
text: "Message received"
streamDelay: "100ms"
```
### Order Service with Error Handling
[Section titled “Order Service with Error Handling”](#order-service-with-error-handling)
Create `protos/orders.proto`:
```protobuf
syntax = "proto3";
package orders;
service OrderService {
rpc GetOrder (GetOrderRequest) returns (Order) {}
rpc CreateOrder (CreateOrderRequest) returns (Order) {}
rpc CancelOrder (CancelOrderRequest) returns (CancelOrderResponse) {}
}
message GetOrderRequest { string order_id = 1; }
message CreateOrderRequest { string customer_id = 1; repeated OrderItem items = 2; }
message OrderItem { string product_id = 1; int32 quantity = 2; }
message CancelOrderRequest { string order_id = 1; string reason = 2; }
message CancelOrderResponse { bool success = 1; string message = 2; }
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
string status = 4;
double total = 5;
string created_at = 6;
}
```
Then configure with multiple match conditions:
```yaml
version: "1.0"
mocks:
# Successful order lookup
- id: order-grpc-success
name: Order Service - Success
type: grpc
enabled: true
grpc:
port: 50053
protoFile: ./protos/orders.proto
reflection: true
services:
orders.OrderService:
methods:
GetOrder:
match:
request:
order_id: "order_123"
response:
id: "order_123"
customer_id: "cust_001"
status: "CONFIRMED"
total: 99.99
CreateOrder:
response:
id: "{{uuid}}"
customer_id: "cust_001"
status: "PENDING"
created_at: "{{now}}"
delay: "200ms"
# Order not found
- id: order-grpc-not-found
name: Order Service - Not Found
type: grpc
enabled: true
grpc:
port: 50053
protoFile: ./protos/orders.proto
services:
orders.OrderService:
methods:
GetOrder:
match:
request:
order_id: "nonexistent"
error:
code: NOT_FOUND
message: "Order not found"
# Cancel order - permission denied
- id: order-grpc-permission-denied
name: Order Service - Permission Denied
type: grpc
enabled: true
grpc:
port: 50053
protoFile: ./protos/orders.proto
services:
orders.OrderService:
methods:
CancelOrder:
match:
request:
order_id: "shipped_order"
error:
code: FAILED_PRECONDITION
message: "Cannot cancel shipped order"
details:
order_id: "shipped_order"
status: "SHIPPED"
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
### Add a gRPC Mock
[Section titled “Add a gRPC Mock”](#add-a-grpc-mock)
Create gRPC mocks directly from the command line using `mockd grpc add`:
```bash
# Basic unary RPC
mockd grpc add --proto ./protos/greeter.proto \
--service helloworld.Greeter \
--rpc-method SayHello \
--response '{"message": "Hello!"}'
# With custom gRPC port
mockd grpc add --proto ./protos/users.proto \
--service users.UserService \
--rpc-method GetUser \
--response '{"id": "123", "name": "John Doe"}' \
--grpc-port 50052
```
Output:
```plaintext
Created mock: grpc_1dc8695005df8cde
Type: grpc
Service: helloworld.Greeter
Method: SayHello
```
When adding multiple methods to the same port, mocks are automatically merged:
```bash
# Add another method to the same service
mockd grpc add --proto ./protos/users.proto \
--service users.UserService \
--rpc-method CreateUser \
--response '{"id": "new_1", "name": "New User"}'
# Output:
# Merged into mock: grpc_1dc8695005df8cde
# Type: grpc
# Added:
# - users.UserService/CreateUser
# Total services:
# - users.UserService/GetUser
# - users.UserService/CreateUser
```
#### Add Command Flags
[Section titled “Add Command Flags”](#add-command-flags)
| Flag | Description |
| -------------- | ------------------------------------------------------ |
| `--proto` | Path to `.proto` file (required) |
| `--service` | Fully qualified service name (e.g., `package.Service`) |
| `--rpc-method` | RPC method name |
| `--response` | JSON response body |
| `--grpc-port` | gRPC server port (default: 50051) |
| `--admin-url` | Admin API URL (default: `http://localhost:4290`) |
### List Services and Methods
[Section titled “List Services and Methods”](#list-services-and-methods)
Inspect a proto file to see available services and methods:
```bash
# List services from a proto file
mockd grpc list api.proto
# With import path
mockd grpc list api.proto --import ./proto
```
Output:
```plaintext
Proto: api.proto
Service: users.UserService
GetUser(GetUserRequest) → User
ListUsers(ListUsersRequest) → stream User
CreateUser(CreateUserRequest) → User
```
### Call a gRPC Method
[Section titled “Call a gRPC Method”](#call-a-grpc-method)
Test gRPC endpoints directly from the CLI:
```bash
# Call a unary method
mockd grpc call localhost:50051 users.UserService/GetUser '{"id": "123"}'
# With metadata
mockd grpc call localhost:50051 users.UserService/GetUser '{"id": "123"}' \
-m "authorization:Bearer token123"
# Request body from file
mockd grpc call localhost:50051 users.UserService/CreateUser @request.json
# Plaintext mode (no TLS)
mockd grpc call localhost:50051 users.UserService/GetUser '{"id": "123"}' --plaintext
```
### Call Command Options
[Section titled “Call Command Options”](#call-command-options)
| Flag | Description |
| ---------------- | ---------------------------------------- |
| `-m, --metadata` | gRPC metadata as `key:value,key2:value2` |
| `--plaintext` | Use plaintext (no TLS, default: true) |
| `--pretty` | Pretty print output (default: true) |
### Initialize gRPC Template
[Section titled “Initialize gRPC Template”](#initialize-grpc-template)
Create a new project with gRPC configuration:
```bash
mockd init --template grpc-service
```
This generates a complete gRPC mock configuration with:
* Greeter service with multiple RPC types
* Unary, server streaming, client streaming, and bidirectional examples
* Reflection enabled
* Sample proto definition
## Testing with grpcurl
[Section titled “Testing with grpcurl”](#testing-with-grpcurl)
[grpcurl](https://github.com/fullstorydev/grpcurl) is a command-line tool for interacting with gRPC servers.
### Installation
[Section titled “Installation”](#installation)
```bash
# macOS
brew install grpcurl
# Linux (download from releases)
curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.9/grpcurl_1.8.9_linux_x86_64.tar.gz | tar xz
# Go install
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
```
### List Services
[Section titled “List Services”](#list-services)
```bash
# With reflection enabled
grpcurl -plaintext localhost:50051 list
# Output:
# grpc.reflection.v1alpha.ServerReflection
# helloworld.Greeter
```
### Describe Service
[Section titled “Describe Service”](#describe-service)
```bash
grpcurl -plaintext localhost:50051 describe helloworld.Greeter
# Output:
# helloworld.Greeter is a service:
# service Greeter {
# rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
# rpc SayHelloStream ( .helloworld.HelloRequest ) returns ( stream .helloworld.HelloReply );
# }
```
### Call Unary Method
[Section titled “Call Unary Method”](#call-unary-method)
```bash
grpcurl -plaintext \
-d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHello
# Output:
# {
# "message": "Hello, World!"
# }
```
### Call Server Streaming Method
[Section titled “Call Server Streaming Method”](#call-server-streaming-method)
```bash
grpcurl -plaintext \
-d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHelloStream
# Output (multiple messages):
# {
# "message": "Hello! (1/3)"
# }
# {
# "message": "Hello again! (2/3)"
# }
# {
# "message": "Hello one more time! (3/3)"
# }
```
### With Metadata
[Section titled “With Metadata”](#with-metadata)
```bash
grpcurl -plaintext \
-H "authorization: Bearer token123" \
-H "x-request-id: req-001" \
-d '{"id": "123"}' \
localhost:50051 users.UserService/GetUser
```
### Without Reflection
[Section titled “Without Reflection”](#without-reflection)
If reflection is disabled, provide the proto file:
```bash
grpcurl -plaintext \
-proto ./protos/service.proto \
-d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHello
```
### From File
[Section titled “From File”](#from-file)
```bash
# Save request to file
echo '{"name": "World"}' > request.json
# Call with file input
grpcurl -plaintext \
-d @ \
localhost:50051 helloworld.Greeter/SayHello < request.json
```
## Testing Tips
[Section titled “Testing Tips”](#testing-tips)
### Test All RPC Types
[Section titled “Test All RPC Types”](#test-all-rpc-types)
Ensure your client handles all streaming types correctly:
```yaml
services:
TestService:
methods:
# Unary
UnaryMethod:
response: { status: "ok" }
# Server streaming
ServerStream:
responses:
- { seq: 1 }
- { seq: 2 }
- { seq: 3 }
streamDelay: "100ms"
# Client streaming
ClientStream:
response:
count: 5
processed: true
# Bidirectional
BidiStream:
responses:
- { echo: "received" }
streamDelay: "50ms"
```
### Test Error Handling
[Section titled “Test Error Handling”](#test-error-handling)
Verify your client handles gRPC errors properly:
```yaml
services:
UserService:
methods:
# Test not found
GetUser:
match:
request:
id: "nonexistent"
error:
code: NOT_FOUND
message: "User not found"
# Test validation error
CreateUser:
match:
request:
email: ""
error:
code: INVALID_ARGUMENT
message: "Email is required"
# Test authentication error
DeleteUser:
error:
code: UNAUTHENTICATED
message: "Authentication required"
```
### Test Timeouts
[Section titled “Test Timeouts”](#test-timeouts)
Use delays to test client timeout behavior:
```yaml
services:
SlowService:
methods:
SlowMethod:
response: { status: "completed" }
delay: "30s" # Will trigger client timeout
```
### Test with Different Clients
[Section titled “Test with Different Clients”](#test-with-different-clients)
mockd works with any gRPC client:
* **grpcurl** - Command-line testing
* **BloomRPC** - GUI client for gRPC
* **Postman** - API testing with gRPC support
* **gRPC UI** - Web-based gRPC client
* **Your application** - Native gRPC clients in any language
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Response Templating](/guides/response-templating) - Dynamic response values
* [Request Matching](/guides/request-matching) - Advanced matching patterns
* [Configuration Reference](/reference/configuration) - Full configuration schema
# MQTT Mocking
> Create mock MQTT brokers for testing IoT devices, sensor networks, and real-time messaging systems with configurable topics, authentication, and device simulation.
MQTT mocking enables testing of IoT devices, sensor networks, and real-time messaging systems without connecting to actual MQTT brokers. mockd provides a full-featured MQTT broker with configurable topics, authentication, QoS levels, and device simulation.
## Overview
[Section titled “Overview”](#overview)
Use MQTT mocks when you need to:
* Test IoT applications and device communication
* Simulate sensor networks and telemetry data
* Develop smart home or industrial automation systems
* Create reproducible test scenarios for MQTT clients
* Mock message queues and pub/sub patterns
* Test real-time notification systems
## Quick Start
[Section titled “Quick Start”](#quick-start)
Create a minimal MQTT broker mock:
```yaml
version: "1.0"
mocks:
- id: mqtt-broker
name: Simple MQTT Broker
type: mqtt
enabled: true
mqtt:
port: 1883
topics:
- topic: sensors/temperature
qos: 1
messages:
- payload: '{"value": 22.5, "unit": "celsius"}'
repeat: true
interval: "5s"
```
Start the server and test:
```bash
# Start mockd
mockd serve --config mockd.yaml
# Subscribe to messages using mockd CLI
mockd mqtt subscribe sensors/temperature
# Or use mosquitto client
mosquitto_sub -h localhost -p 1883 -t "sensors/#"
```
## Configuration
[Section titled “Configuration”](#configuration)
### Full MQTT Spec
[Section titled “Full MQTT Spec”](#full-mqtt-spec)
```yaml
version: "1.0"
mocks:
- id: mqtt-full-example
name: IoT MQTT Broker
type: mqtt
enabled: true
mqtt:
# Required: broker port
port: 1883
# TLS configuration (optional)
tls:
enabled: true
certFile: ./certs/server.crt
keyFile: ./certs/server.key
# Authentication (optional)
auth:
enabled: true
users:
- username: device
password: secret123
acl:
- topic: "sensors/#"
access: write
- topic: "commands/#"
access: read
- username: admin
password: admin123
acl:
- topic: "#"
access: readwrite
# Topic configurations
topics:
- topic: sensors/temperature
qos: 1
retain: true
messages:
- payload: '{"value": 22.5}'
interval: "5s"
repeat: true
- topic: commands/+/execute
qos: 2
onPublish:
response:
payload: '{"status": "executed"}'
forward: responses/device
- topic: devices/{device_id}/telemetry
qos: 0
deviceSimulation:
enabled: true
deviceCount: 10
deviceIdPattern: "device_{index}"
```
### Configuration Reference
[Section titled “Configuration Reference”](#configuration-reference)
| Field | Type | Description |
| -------- | ------- | -------------------------------- |
| `port` | integer | MQTT broker port (default: 1883) |
| `tls` | object | TLS/SSL configuration |
| `auth` | object | Authentication settings |
| `topics` | array | Topic configurations |
## Topics and Messages
[Section titled “Topics and Messages”](#topics-and-messages)
Topics define how the broker handles message publishing and subscription.
### Basic Topic Configuration
[Section titled “Basic Topic Configuration”](#basic-topic-configuration)
```yaml
topics:
- topic: sensors/temperature
qos: 1
retain: true
messages:
- payload: |
{
"deviceId": "temp_001",
"value": 22.5,
"unit": "celsius",
"timestamp": "{{ now }}"
}
interval: "5s"
repeat: true
```
### Topic Fields
[Section titled “Topic Fields”](#topic-fields)
| Field | Type | Description |
| ------------------ | ------- | --------------------------------------- |
| `topic` | string | Topic pattern (supports wildcards) |
| `qos` | integer | Quality of Service level (0, 1, or 2) |
| `retain` | boolean | Retain last message for new subscribers |
| `messages` | array | Predefined messages to publish |
| `onPublish` | object | Handler for received messages |
| `deviceSimulation` | object | Simulate multiple devices |
### Topic Wildcards
[Section titled “Topic Wildcards”](#topic-wildcards)
MQTT supports two wildcard characters for topic subscriptions:
```yaml
# Single-level wildcard (+)
# Matches exactly one topic level
topics:
- topic: sensors/+/temperature # matches sensors/room1/temperature
# matches sensors/room2/temperature
# Multi-level wildcard (#)
# Matches any number of levels (must be last character)
topics:
- topic: sensors/# # matches sensors/temperature
# matches sensors/humidity
# matches sensors/room1/temp
```
### Message Configuration
[Section titled “Message Configuration”](#message-configuration)
Define messages to publish automatically on a topic:
```yaml
messages:
- payload: '{"temperature": 22.5}'
delay: "0s" # Initial delay before first publish
repeat: true # Continuously publish at interval
interval: "5s" # Time between repeated publishes
```
Message fields:
| Field | Type | Description |
| ---------- | -------- | ---------------------------------- |
| `payload` | string | Message content (string or JSON) |
| `delay` | duration | Initial delay before first publish |
| `repeat` | boolean | Continuously publish at interval |
| `interval` | duration | Time between repeated publishes |
### Templated Payloads
[Section titled “Templated Payloads”](#templated-payloads)
Use template expressions in message payloads:
```yaml
messages:
- payload: |
{
"deviceId": "sensor_001",
"temperature": {{ random.int(18, 28) }},
"humidity": {{ random.int(40, 80) }},
"timestamp": "{{ now }}"
}
interval: "5s"
repeat: true
```
MQTT templates use the same engine as HTTP responses — all 35 faker types, random functions, timestamps, and string functions are available. See the full list in the [Response Templating guide](/guides/response-templating/).
Common templates for MQTT payloads:
| Template | Description |
| ------------------------------ | ----------------------- |
| `{{ now }}` | Current ISO timestamp |
| `{{ uuid }}` | Random UUID |
| `{{ timestamp }}` | Unix timestamp |
| `{{ random.int(min, max) }}` | Random integer in range |
| `{{ random.float(min, max) }}` | Random float in range |
| `{{ faker.name }}` | Random person name |
| `{{ faker.ipv4 }}` | Random IPv4 address |
| `{{ faker.latitude }}` | Random latitude |
| `{{ faker.longitude }}` | Random longitude |
Tip
Faker type names are case-insensitive: `{{ faker.ipv4 }}`, `{{ faker.IPv4 }}`, and `{{ faker.IPV4 }}` all work. All 35 faker types from the [full faker reference](/guides/response-templating/#faker-functions) are supported in MQTT payloads.
### Retained Messages
[Section titled “Retained Messages”](#retained-messages)
Retained messages are stored on the broker and delivered to new subscribers immediately:
```yaml
topics:
- topic: devices/status
retain: true
messages:
- payload: '{"status": "online", "uptime": 12345}'
```
When a client subscribes to `devices/status`, they immediately receive the last retained message.
## QoS Levels
[Section titled “QoS Levels”](#qos-levels)
MQTT defines three Quality of Service levels for message delivery:
### QoS 0 - At Most Once
[Section titled “QoS 0 - At Most Once”](#qos-0---at-most-once)
Fire and forget. No acknowledgment required. Messages may be lost.
```yaml
topics:
- topic: sensors/motion
qos: 0
messages:
- payload: '{"detected": true}'
```
Use for:
* High-frequency sensor data where occasional loss is acceptable
* Non-critical notifications
* Status updates that will be superseded
### QoS 1 - At Least Once
[Section titled “QoS 1 - At Least Once”](#qos-1---at-least-once)
Message delivered at least once. May have duplicates. Requires acknowledgment.
```yaml
topics:
- topic: sensors/temperature
qos: 1
messages:
- payload: '{"value": 22.5}'
```
Use for:
* Important sensor readings
* Command acknowledgments
* Data that should not be lost
### QoS 2 - Exactly Once
[Section titled “QoS 2 - Exactly Once”](#qos-2---exactly-once)
Message delivered exactly once. Highest overhead with four-step handshake.
```yaml
topics:
- topic: commands/critical
qos: 2
onPublish:
response:
payload: '{"status": "executed"}'
```
Use for:
* Financial transactions
* Critical commands
* Messages where duplicates cause problems
## Publish Handlers
[Section titled “Publish Handlers”](#publish-handlers)
Respond to messages received on a topic using publish handlers.
### Basic Response
[Section titled “Basic Response”](#basic-response)
```yaml
topics:
- topic: commands/device/+
qos: 1
onPublish:
response:
payload: '{"status": "acknowledged", "timestamp": "{{ now }}"}'
delay: "100ms"
```
### Forward Messages
[Section titled “Forward Messages”](#forward-messages)
Forward received messages to another topic:
```yaml
topics:
- topic: commands/device/+
qos: 1
onPublish:
response:
payload: '{"status": "acknowledged"}'
forward: responses/device
```
### Handler Fields
[Section titled “Handler Fields”](#handler-fields)
| Field | Type | Description |
| ------------------ | -------- | ------------------------------- |
| `response` | object | Message to publish in response |
| `response.payload` | string | Response message content |
| `response.delay` | duration | Delay before sending response |
| `forward` | string | Topic to forward the message to |
### Using Message Content in Responses
[Section titled “Using Message Content in Responses”](#using-message-content-in-responses)
Reference the received message in your response:
```yaml
topics:
- topic: commands/+/execute
qos: 1
onPublish:
response:
payload: |
{
"status": "executed",
"command": "{{ message.payload }}",
"executedAt": "{{ now }}"
}
```
## Authentication
[Section titled “Authentication”](#authentication)
Configure username/password authentication with Access Control Lists (ACL).
### Enable Authentication
[Section titled “Enable Authentication”](#enable-authentication)
```yaml
mqtt:
port: 1883
auth:
enabled: true
users:
- username: sensor-gateway
password: gateway-secret
acl:
- topic: "sensors/#"
access: readwrite
- username: dashboard
password: readonly-pass
acl:
- topic: "sensors/#"
access: read
```
### User Configuration
[Section titled “User Configuration”](#user-configuration)
| Field | Type | Description |
| ---------- | ------ | -------------------- |
| `username` | string | Login username |
| `password` | string | Login password |
| `acl` | array | Access control rules |
### ACL Rules
[Section titled “ACL Rules”](#acl-rules)
| Field | Type | Description |
| -------- | ------ | ---------------------------------- |
| `topic` | string | Topic pattern (supports wildcards) |
| `access` | string | Access level |
Access levels:
| Level | Description |
| -------------------- | -------------------------- |
| `read` / `subscribe` | Subscribe only |
| `write` / `publish` | Publish only |
| `readwrite` / `all` | Both subscribe and publish |
### Role-Based Access Example
[Section titled “Role-Based Access Example”](#role-based-access-example)
```yaml
auth:
enabled: true
users:
# IoT devices - can only publish sensor data
- username: device
password: device123
acl:
- topic: "sensors/#"
access: write
- topic: "commands/#"
access: read
# Monitoring dashboard - read-only access
- username: monitor
password: monitor123
acl:
- topic: "#"
access: read
# Admin - full access
- username: admin
password: admin123
acl:
- topic: "#"
access: all
```
## TLS Configuration
[Section titled “TLS Configuration”](#tls-configuration)
Enable TLS/SSL for encrypted MQTT connections.
### Basic TLS Setup
[Section titled “Basic TLS Setup”](#basic-tls-setup)
```yaml
mqtt:
port: 8883
tls:
enabled: true
certFile: ./certs/server.crt
keyFile: ./certs/server.key
```
### Generate Self-Signed Certificates
[Section titled “Generate Self-Signed Certificates”](#generate-self-signed-certificates)
```bash
# Generate private key
openssl genrsa -out server.key 2048
# Generate self-signed certificate
openssl req -new -x509 -key server.key -out server.crt -days 365
# Connect with TLS
mosquitto_sub -h localhost -p 8883 --cafile server.crt -t "sensors/#"
```
## Device Simulation
[Section titled “Device Simulation”](#device-simulation)
Simulate multiple IoT devices publishing to topics.
### Configuration
[Section titled “Configuration”](#configuration-1)
```yaml
topics:
- topic: devices/{device_id}/telemetry
qos: 1
deviceSimulation:
enabled: true
deviceCount: 100
deviceIdPattern: "device_{index}"
```
This creates 100 virtual devices (`device_1` through `device_100`) each publishing to their own topic:
* `devices/device_1/telemetry`
* `devices/device_2/telemetry`
* …
* `devices/device_100/telemetry`
### Device Simulation Fields
[Section titled “Device Simulation Fields”](#device-simulation-fields)
| Field | Type | Description |
| ----------------- | ------- | ------------------------------------------------------------------------- |
| `enabled` | boolean | Enable device simulation |
| `deviceCount` | integer | Number of virtual devices |
| `deviceIdPattern` | string | Pattern for device IDs (`{n}`, `{id}`, or `{index}` replaced with number) |
### Simulating Device Fleet
[Section titled “Simulating Device Fleet”](#simulating-device-fleet)
```yaml
topics:
# Temperature sensors
- topic: sensors/temperature/{device_id}
qos: 1
deviceSimulation:
enabled: true
deviceCount: 50
deviceIdPattern: "temp_sensor_{index}"
messages:
- payload: |
{
"deviceId": "{{deviceId}}",
"temperature": {{ random.int(15, 35) }},
"timestamp": "{{ now }}"
}
interval: "10s"
repeat: true
# Motion sensors
- topic: sensors/motion/{device_id}
qos: 0
deviceSimulation:
enabled: true
deviceCount: 20
deviceIdPattern: "motion_{index}"
```
## Examples
[Section titled “Examples”](#examples)
### IoT Sensor Network
[Section titled “IoT Sensor Network”](#iot-sensor-network)
```yaml
version: "1.0"
mocks:
- id: iot-sensors
name: IoT Sensor Network
type: mqtt
enabled: true
mqtt:
port: 1883
auth:
enabled: true
users:
- username: sensor
password: sensor123
acl:
- topic: "sensors/#"
access: write
- username: gateway
password: gateway123
acl:
- topic: "#"
access: all
topics:
# Temperature sensor
- topic: sensors/temperature
qos: 1
retain: true
messages:
- payload: |
{
"deviceId": "temp_001",
"type": "temperature",
"value": {{ random.int(18, 28) }},
"unit": "celsius",
"timestamp": "{{ now }}"
}
interval: "5s"
repeat: true
# Humidity sensor
- topic: sensors/humidity
qos: 1
retain: true
messages:
- payload: |
{
"deviceId": "humid_001",
"type": "humidity",
"value": {{ random.int(40, 80) }},
"unit": "percent",
"timestamp": "{{ now }}"
}
interval: "5s"
repeat: true
# Motion sensor (less frequent)
- topic: sensors/motion
qos: 0
messages:
- payload: |
{
"deviceId": "motion_001",
"type": "motion",
"detected": true,
"zone": "entrance",
"timestamp": "{{ now }}"
}
interval: "30s"
repeat: true
# Device status (retained)
- topic: devices/status
qos: 1
retain: true
messages:
- payload: |
{
"deviceId": "gateway_001",
"status": "online",
"uptime": {{ random.int(1000, 50000) }},
"firmware": "1.2.3",
"timestamp": "{{ now }}"
}
delay: "1s"
```
### Command and Response System
[Section titled “Command and Response System”](#command-and-response-system)
```yaml
version: "1.0"
mocks:
- id: command-system
name: Device Command System
type: mqtt
enabled: true
mqtt:
port: 1883
topics:
# Command topic - listens for commands and responds
- topic: commands/device/+
qos: 2
onPublish:
response:
payload: |
{
"status": "acknowledged",
"command": "{{ message.payload }}",
"executedAt": "{{ now }}",
"id": "{{ uuid }}"
}
delay: "50ms"
forward: responses/device
# Response topic for monitoring
- topic: responses/device
qos: 1
retain: true
# Alert topic with high QoS
- topic: alerts/+
qos: 2
retain: true
```
### Smart Home System
[Section titled “Smart Home System”](#smart-home-system)
```yaml
version: "1.0"
mocks:
- id: smart-home
name: Smart Home Hub
type: mqtt
enabled: true
mqtt:
port: 1883
auth:
enabled: true
users:
- username: homeassistant
password: ha-secret
acl:
- topic: "#"
access: all
- username: light
password: light123
acl:
- topic: "home/lights/#"
access: readwrite
- username: thermostat
password: thermo123
acl:
- topic: "home/climate/#"
access: readwrite
topics:
# Living room light
- topic: home/lights/living_room/state
qos: 1
retain: true
messages:
- payload: '{"on": true, "brightness": 80, "color": "#ffffff"}'
# Living room light commands
- topic: home/lights/living_room/set
qos: 1
onPublish:
response:
payload: |
{
"on": true,
"brightness": 80,
"updated": "{{ now }}"
}
forward: home/lights/living_room/state
# Thermostat
- topic: home/climate/thermostat/state
qos: 1
retain: true
messages:
- payload: |
{
"current_temp": {{ random.int(18, 24) }},
"target_temp": 21,
"mode": "heat",
"humidity": {{ random.int(40, 60) }}
}
interval: "30s"
repeat: true
# Door sensor
- topic: home/security/front_door
qos: 1
retain: true
messages:
- payload: '{"state": "closed", "battery": 95}'
# Motion sensor
- topic: home/security/motion/hallway
qos: 0
messages:
- payload: |
{
"motion": true,
"timestamp": "{{ now }}"
}
interval: "60s"
repeat: true
```
### Notification Service
[Section titled “Notification Service”](#notification-service)
```yaml
version: "1.0"
mocks:
- id: notifications
name: Push Notification Service
type: mqtt
enabled: true
mqtt:
port: 1883
topics:
# User notifications
- topic: notifications/user/+
qos: 1
messages:
- payload: |
{
"id": "{{ uuid }}",
"type": "info",
"title": "System Update",
"body": "New features are available",
"timestamp": "{{ now }}"
}
interval: "30s"
repeat: true
# Broadcast notifications
- topic: notifications/broadcast
qos: 1
retain: true
messages:
- payload: |
{
"id": "{{ uuid }}",
"type": "announcement",
"title": "Maintenance Notice",
"body": "Scheduled maintenance at midnight",
"priority": "high"
}
delay: "5s"
# Notification acknowledgments
- topic: notifications/ack/+
qos: 1
onPublish:
response:
payload: '{"status": "received", "timestamp": "{{ now }}"}'
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
mockd provides CLI tools for creating MQTT mocks and interacting with MQTT brokers.
### Add an MQTT Mock
[Section titled “Add an MQTT Mock”](#add-an-mqtt-mock)
Create MQTT mocks directly from the command line using `mockd mqtt add`:
```bash
# Simple topic with payload
mockd mqtt add --topic sensors/temperature \
--payload '{"value": 22.5, "unit": "celsius"}'
# With QoS level
mockd mqtt add --topic sensors/humidity \
--payload '{"value": 65}' \
--qos 1
# Custom MQTT port
mockd mqtt add --topic alerts/critical \
--payload '{"level": "high", "message": "Temperature exceeded"}' \
--mqtt-port 1884
```
Output:
```plaintext
Merged into mock: mqtt_1c8177012ffef553
Type: mqtt
Added:
- sensors/temperature
Total topics:
- sensors/temperature
```
MQTT mocks on the same port are automatically merged. Adding more topics appends to the existing broker:
```bash
mockd mqtt add --topic sensors/humidity \
--payload '{"value": 65}' --qos 1
# Output:
# Merged into mock: mqtt_1c8177012ffef553
# Type: mqtt
# Added:
# - sensors/humidity
# Total topics:
# - sensors/temperature
# - sensors/humidity
```
#### Add Command Flags
[Section titled “Add Command Flags”](#add-command-flags)
| Flag | Description |
| ------------- | ------------------------------------------------- |
| `--topic` | MQTT topic name (required) |
| `--payload` | Message payload (string or JSON) |
| `--qos` | Quality of Service level: 0, 1, or 2 (default: 0) |
| `--mqtt-port` | MQTT broker port (default: 1883) |
| `--admin-url` | Admin API URL (default: `http://localhost:4290`) |
### mqtt publish
[Section titled “mqtt publish”](#mqtt-publish)
Publish a message to an MQTT topic:
```bash
# Publish a simple message
mockd mqtt publish localhost:1883 sensors/temperature "25.5"
# Publish to a custom broker
mockd mqtt publish mqtt.example.com:1883 sensors/temp "25.5"
# Publish with authentication
mockd mqtt publish -u user -P pass localhost:1883 sensors/temp "25.5"
# Publish with QoS 1 and retain
mockd mqtt publish --qos 1 --retain localhost:1883 sensors/temp "25.5"
# Publish JSON payload
mockd mqtt publish localhost:1883 sensors/data '{"temp": 25.5, "humidity": 60}'
# Publish from file
mockd mqtt publish localhost:1883 sensors/config @config.json
```
Flags:
| Flag | Description |
| ---------------- | -------------------------------------------------- |
| `-m, --message` | Message to publish (alternative to positional arg) |
| `-u, --username` | MQTT username |
| `-P, --password` | MQTT password |
| `-q, --qos` | QoS level 0, 1, or 2 (default: 0) |
| `-r, --retain` | Retain message on broker |
### mqtt subscribe
[Section titled “mqtt subscribe”](#mqtt-subscribe)
Subscribe to a topic and print received messages:
```bash
# Subscribe to a topic
mockd mqtt subscribe localhost:1883 sensors/temperature
# Subscribe with wildcard
mockd mqtt subscribe localhost:1883 "sensors/#"
# Subscribe to single-level wildcard
mockd mqtt subscribe localhost:1883 "sensors/+/temperature"
# Receive only 5 messages then exit
mockd mqtt subscribe -c 5 localhost:1883 sensors/temperature
# Subscribe with timeout
mockd mqtt subscribe -t 30s localhost:1883 sensors/temperature
# Subscribe with authentication
mockd mqtt subscribe -u user -P pass localhost:1883 sensors/temperature
# Subscribe with QoS 1
mockd mqtt subscribe --qos 1 localhost:1883 sensors/temperature
```
Flags:
| Flag | Description |
| ---------------- | --------------------------------------------- |
| `-u, --username` | MQTT username |
| `-P, --password` | MQTT password |
| `-q, --qos` | QoS level 0, 1, or 2 (default: 0) |
| `-c, --count` | Number of messages to receive (0 = unlimited) |
| `-t, --timeout` | Timeout duration (e.g., 30s, 5m) |
### mqtt status
[Section titled “mqtt status”](#mqtt-status)
Show MQTT broker status from the admin API:
```bash
# Default admin URL
mockd mqtt status
# Custom admin URL
mockd mqtt status --admin-url http://localhost:9091
# JSON output
mockd mqtt status --json
```
Flags:
| Flag | Description |
| ------------- | ----------------------------------------------------- |
| `--admin-url` | Admin API base URL (default: ) |
| `--json` | Output in JSON format |
## Testing
[Section titled “Testing”](#testing)
### Using mockd CLI
[Section titled “Using mockd CLI”](#using-mockd-cli)
```bash
# Start server
mockd serve --config mockd.yaml &
# Subscribe to all sensors in background
mockd mqtt subscribe localhost:1883 "sensors/#" &
# Publish test messages
mockd mqtt publish localhost:1883 sensors/temperature '{"value": 25.5}'
mockd mqtt publish localhost:1883 sensors/humidity '{"value": 65}'
# Test command/response
mockd mqtt subscribe localhost:1883 responses/device &
mockd mqtt publish localhost:1883 commands/device/001 '{"action": "restart"}'
```
### Using Mosquitto Clients
[Section titled “Using Mosquitto Clients”](#using-mosquitto-clients)
Install mosquitto clients:
```bash
# Ubuntu/Debian
apt install mosquitto-clients
# macOS
brew install mosquitto
# Subscribe to all sensors
mosquitto_sub -h localhost -p 1883 -t "sensors/#" -v
# Subscribe to specific topic
mosquitto_sub -h localhost -p 1883 -t "sensors/temperature"
# Publish a message
mosquitto_pub -h localhost -p 1883 -t "sensors/temperature" -m '{"value": 25.5}'
# Publish with QoS 1
mosquitto_pub -h localhost -p 1883 -t "sensors/temperature" -m '{"value": 25.5}' -q 1
# Subscribe with authentication
mosquitto_sub -h localhost -p 1883 -u device -P secret123 -t "sensors/#"
# Publish with retain flag
mosquitto_pub -h localhost -p 1883 -t "status" -m "online" -r
```
### Integration Tests (JavaScript)
[Section titled “Integration Tests (JavaScript)”](#integration-tests-javascript)
```javascript
const mqtt = require('mqtt');
describe('MQTT Mock', () => {
let client;
beforeEach((done) => {
client = mqtt.connect('mqtt://localhost:1883');
client.on('connect', done);
});
afterEach(() => {
client.end();
});
test('receives temperature readings', (done) => {
client.subscribe('sensors/temperature', (err) => {
expect(err).toBeNull();
});
client.on('message', (topic, message) => {
const data = JSON.parse(message.toString());
expect(topic).toBe('sensors/temperature');
expect(data).toHaveProperty('value');
expect(data).toHaveProperty('unit');
done();
});
});
test('publishes and receives message', (done) => {
const testTopic = 'test/messages';
const testMessage = { id: 1, text: 'hello' };
client.subscribe(testTopic);
client.on('message', (topic, message) => {
const data = JSON.parse(message.toString());
expect(data).toEqual(testMessage);
done();
});
client.publish(testTopic, JSON.stringify(testMessage));
});
});
```
### Integration Tests (Go)
[Section titled “Integration Tests (Go)”](#integration-tests-go)
```go
package main
import (
"encoding/json"
"testing"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
func TestMQTTMock(t *testing.T) {
opts := mqtt.NewClientOptions().
AddBroker("tcp://localhost:1883").
SetClientID("test-client")
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
t.Fatalf("Failed to connect: %v", token.Error())
}
defer client.Disconnect(250)
// Subscribe and receive message
received := make(chan []byte, 1)
client.Subscribe("sensors/temperature", 1, func(c mqtt.Client, m mqtt.Message) {
received <- m.Payload()
})
select {
case msg := <-received:
var data map[string]interface{}
if err := json.Unmarshal(msg, &data); err != nil {
t.Fatalf("Failed to parse message: %v", err)
}
if _, ok := data["value"]; !ok {
t.Error("Expected 'value' field in message")
}
case <-time.After(10 * time.Second):
t.Fatal("Timeout waiting for message")
}
}
```
### Integration Tests (Python)
[Section titled “Integration Tests (Python)”](#integration-tests-python)
```python
import pytest
import paho.mqtt.client as mqtt
import json
import time
def test_mqtt_subscribe():
received = []
def on_message(client, userdata, msg):
received.append(json.loads(msg.payload))
client = mqtt.Client()
client.on_message = on_message
client.connect("localhost", 1883)
client.subscribe("sensors/temperature")
client.loop_start()
# Wait for message
time.sleep(6)
client.loop_stop()
client.disconnect()
assert len(received) > 0
assert "value" in received[0]
def test_mqtt_publish():
received = []
def on_message(client, userdata, msg):
received.append(json.loads(msg.payload))
client = mqtt.Client()
client.on_message = on_message
client.connect("localhost", 1883)
client.subscribe("test/topic")
client.loop_start()
# Publish message
client.publish("test/topic", json.dumps({"test": True}))
time.sleep(1)
client.loop_stop()
client.disconnect()
assert len(received) == 1
assert received[0]["test"] == True
```
### Testing with Authentication
[Section titled “Testing with Authentication”](#testing-with-authentication)
```bash
# Start server with auth enabled
mockd serve --config mockd-auth.yaml &
# Test valid credentials
mosquitto_sub -h localhost -p 1883 -u sensor -P sensor123 -t "sensors/#"
# Test invalid credentials (should fail)
mosquitto_sub -h localhost -p 1883 -u invalid -P wrong -t "sensors/#"
# Test ACL (device user can only write to sensors)
mosquitto_pub -h localhost -p 1883 -u device -P device123 -t "sensors/temp" -m "25"
mosquitto_sub -h localhost -p 1883 -u device -P device123 -t "commands/#" # denied
```
### Testing QoS Levels
[Section titled “Testing QoS Levels”](#testing-qos-levels)
```bash
# QoS 0 - At most once
mosquitto_pub -h localhost -p 1883 -t "qos/test" -m "qos0" -q 0
# QoS 1 - At least once
mosquitto_pub -h localhost -p 1883 -t "qos/test" -m "qos1" -q 1
# QoS 2 - Exactly once
mosquitto_pub -h localhost -p 1883 -t "qos/test" -m "qos2" -q 2
# Subscribe with specific QoS
mosquitto_sub -h localhost -p 1883 -t "qos/test" -q 2
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Response Templating](/guides/response-templating) - Dynamic response values
* [WebSocket Mocking](/protocols/websocket) - Real-time bidirectional communication
* [TLS/HTTPS](/guides/tls-https) - Secure connections
# OAuth / OIDC Mocking
> Simulate a full OAuth 2.0 and OpenID Connect provider for testing authentication and authorization flows without external identity providers.
OAuth/OIDC mocking enables you to run a complete mock identity provider locally. Configure clients, users, scopes, and token lifetimes to test every authentication flow your application supports.
## Overview
[Section titled “Overview”](#overview)
mockd’s OAuth/OIDC support includes:
* **Full OIDC provider** — Discovery document, JWKS, userinfo, and ID tokens
* **All major grant types** — Authorization Code (with PKCE), Client Credentials, Password, Refresh Token, Implicit
* **RS256 JWT signing** — Real RSA key pair generated per instance with JWKS endpoint
* **Token introspection** — RFC 7662 compliant introspection endpoint
* **Token revocation** — RFC 7009 compliant revocation endpoint
* **Scope validation** — Configurable allowed scopes with enforcement
* **Multiple clients and users** — Define as many as your tests require
## Quick Start
[Section titled “Quick Start”](#quick-start)
### CLI
[Section titled “CLI”](#cli)
Add an OAuth mock with a single command:
```bash
# OAuth/OIDC mock with sensible defaults
mockd oauth add
# Custom issuer, client, and user
mockd oauth add --name "Auth Server" \
--issuer http://localhost:4280/auth \
--client-id my-app --client-secret s3cret \
--oauth-user admin --oauth-password admin123
```
### Configuration File
[Section titled “Configuration File”](#configuration-file)
Create a minimal OAuth mock in your `mockd.yaml`:
```yaml
version: "1.0"
mocks:
- id: my-auth-server
name: Auth Server
type: oauth
enabled: true
oauth:
issuer: http://localhost:4280
tokenExpiry: "1h"
refreshExpiry: "7d"
defaultScopes:
- openid
- profile
- email
clients:
- clientId: my-app
clientSecret: my-secret
redirectUris:
- http://localhost:3000/callback
grantTypes:
- authorization_code
- client_credentials
- refresh_token
- password
users:
- username: testuser
password: testpass
claims:
sub: "user-001"
email: "testuser@example.com"
name: "Test User"
```
Start the server and test:
```bash
# Start mockd
mockd serve --config mockd.yaml
# Get a token using client credentials
curl -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=my-app" \
-d "client_secret=my-secret"
# Response:
# {
# "access_token": "eyJhbGciOiJSUzI1NiIs...",
# "token_type": "Bearer",
# "expires_in": 3600,
# "scope": "openid profile email"
# }
```
## Endpoints
[Section titled “Endpoints”](#endpoints)
mockd exposes the standard OAuth 2.0 and OIDC endpoints:
| Endpoint | Method | Description |
| ----------------------------------- | --------- | --------------------------------------------------------- |
| `/.well-known/openid-configuration` | GET | OIDC discovery document |
| `/.well-known/jwks.json` | GET | JSON Web Key Set for token verification |
| `/authorize` | GET, POST | Authorization endpoint (code + implicit flows) |
| `/token` | POST | Token endpoint (all grant types) |
| `/userinfo` | GET, POST | OIDC UserInfo — returns claims for the authenticated user |
| `/introspect` | POST | Token introspection (RFC 7662) |
| `/revoke` | POST | Token revocation (RFC 7009) |
All endpoints are mounted relative to the mock’s base path. If your issuer is `http://localhost:4280`, then the token endpoint is `http://localhost:4280/token`.
## Grant Types
[Section titled “Grant Types”](#grant-types)
### Client Credentials
[Section titled “Client Credentials”](#client-credentials)
Machine-to-machine authentication. No user context — the client authenticates with its own credentials.
```bash
curl -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "scope=openid profile"
```
Client credentials can also be sent via HTTP Basic authentication:
```bash
curl -X POST http://localhost:4280/token \
-u "my-app:my-secret" \
-d "grant_type=client_credentials"
```
Response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile"
}
```
The `sub` claim in the JWT is set to the `client_id` for this grant type.
### Resource Owner Password
[Section titled “Resource Owner Password”](#resource-owner-password)
Authenticate with a username and password. Requires a configured user.
```bash
curl -X POST http://localhost:4280/token \
-d "grant_type=password" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "username=testuser" \
-d "password=testpass" \
-d "scope=openid profile email"
```
Response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "aB3xY9...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"scope": "openid profile email"
}
```
An `id_token` is returned when the `openid` scope is requested. A `refresh_token` is included if the client has `refresh_token` in its `grantTypes`.
### Authorization Code
[Section titled “Authorization Code”](#authorization-code)
The standard browser-based redirect flow. mockd auto-approves the authorization request using the first configured user (no login page needed for testing).
**Step 1 — Redirect to authorize:**
```bash
curl -v "http://localhost:4280/authorize?\
client_id=my-app&\
redirect_uri=http://localhost:3000/callback&\
response_type=code&\
scope=openid profile email&\
state=random-state-value"
```
mockd responds with a `302` redirect to your `redirect_uri` with the authorization code:
```plaintext
Location: http://localhost:3000/callback?code=abc123...&state=random-state-value
```
**Step 2 — Exchange code for tokens:**
```bash
curl -X POST http://localhost:4280/token \
-d "grant_type=authorization_code" \
-d "code=abc123..." \
-d "redirect_uri=http://localhost:3000/callback" \
-d "client_id=my-app" \
-d "client_secret=my-secret"
```
Response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "xY9aB3...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"scope": "openid profile email"
}
```
Authorization codes are single-use and expire after 10 minutes.
### Authorization Code with PKCE
[Section titled “Authorization Code with PKCE”](#authorization-code-with-pkce)
For public clients (SPAs, mobile apps) that cannot securely store a client secret. mockd supports both `S256` and `plain` challenge methods.
**Step 1 — Generate a code verifier and challenge:**
```bash
# Generate a random code_verifier
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=/+' | head -c 43)
# Compute the S256 code_challenge
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
```
**Step 2 — Redirect to authorize with PKCE parameters:**
```bash
curl -v "http://localhost:4280/authorize?\
client_id=my-app&\
redirect_uri=http://localhost:3000/callback&\
response_type=code&\
scope=openid profile&\
state=random-state&\
code_challenge=$CODE_CHALLENGE&\
code_challenge_method=S256"
```
**Step 3 — Exchange code with the verifier (no client secret required for public clients):**
```bash
curl -X POST http://localhost:4280/token \
-d "grant_type=authorization_code" \
-d "code=abc123..." \
-d "redirect_uri=http://localhost:3000/callback" \
-d "client_id=my-app" \
-d "code_verifier=$CODE_VERIFIER"
```
If the client has a `clientSecret` configured, it must still be provided. PKCE is an additional verification layer, not a replacement for confidential client authentication.
### Refresh Token
[Section titled “Refresh Token”](#refresh-token)
Exchange a refresh token for a new access token. The original refresh token is returned (not rotated).
```bash
curl -X POST http://localhost:4280/token \
-d "grant_type=refresh_token" \
-d "refresh_token=xY9aB3..." \
-d "client_id=my-app" \
-d "client_secret=my-secret"
```
Response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "xY9aB3...",
"scope": "openid profile email"
}
```
You can optionally request a narrower scope:
```bash
curl -X POST http://localhost:4280/token \
-d "grant_type=refresh_token" \
-d "refresh_token=xY9aB3..." \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "scope=openid"
```
### Implicit
[Section titled “Implicit”](#implicit)
The implicit flow returns an access token directly in the redirect URL fragment. This flow is discouraged in modern applications but is supported for legacy client testing.
```bash
curl -v "http://localhost:4280/authorize?\
client_id=my-app&\
redirect_uri=http://localhost:3000/callback&\
response_type=token&\
scope=openid profile&\
state=random-state"
```
mockd responds with a `302` redirect:
```plaintext
Location: http://localhost:3000/callback#access_token=eyJ...&token_type=Bearer&expires_in=3600&state=random-state&scope=openid+profile
```
## Configuration
[Section titled “Configuration”](#configuration)
### Full Configuration Reference
[Section titled “Full Configuration Reference”](#full-configuration-reference)
```yaml
mocks:
- id: auth-server
name: My Auth Server
type: oauth
enabled: true
oauth:
# Issuer URL — used in JWT `iss` claim and discovery document (default: https://mock-oauth.local)
issuer: http://localhost:4280
# Access token lifetime (default: 1h)
tokenExpiry: "1h"
# Refresh token lifetime (default: 7d)
refreshExpiry: "7d"
# Allowed scopes — requests for unlisted scopes are rejected (default: openid, profile, email)
defaultScopes:
- openid
- profile
- email
- api:read
- api:write
# Default claims added to every access token
defaultClaims:
aud: "https://api.example.com"
# OAuth clients
clients:
# Confidential client (server-side app)
- clientId: backend-service
clientSecret: backend-secret
redirectUris: []
grantTypes:
- client_credentials
# Confidential client (web app with login)
- clientId: web-app
clientSecret: web-secret
redirectUris:
- http://localhost:3000/callback
- http://localhost:3000/silent-renew
grantTypes:
- authorization_code
- refresh_token
- password
# Public client (SPA with PKCE, no client secret)
- clientId: spa-app
clientSecret: ""
redirectUris:
- http://localhost:5173/callback
grantTypes:
- authorization_code
- refresh_token
# Users for password and authorization_code flows
users:
- username: alice
password: alice123
claims:
sub: "user-alice"
email: "alice@example.com"
email_verified: true
name: "Alice Smith"
given_name: "Alice"
family_name: "Smith"
picture: "https://example.com/alice.jpg"
- username: bob
password: bob123
claims:
sub: "user-bob"
email: "bob@example.com"
email_verified: true
name: "Bob Johnson"
```
### Configuration Fields
[Section titled “Configuration Fields”](#configuration-fields)
| Field | Type | Default | Description |
| --------------- | --------------- | -------------------------------- | ---------------------------------------------------------- |
| `issuer` | string | `https://mock-oauth.local` | Issuer URL for JWT `iss` claim and discovery endpoints |
| `tokenExpiry` | string | `"1h"` | Access token lifetime (Go duration or `Nd` for days) |
| `refreshExpiry` | string | `"7d"` | Refresh token lifetime |
| `defaultScopes` | string\[] | `["openid", "profile", "email"]` | Allowed scopes — requests for unlisted scopes are rejected |
| `defaultClaims` | map | `{}` | Claims added to every access token |
| `clients` | ClientConfig\[] | | OAuth client definitions |
| `users` | UserConfig\[] | | User definitions for password/authorization\_code flows |
### Client Configuration
[Section titled “Client Configuration”](#client-configuration)
| Field | Type | Description |
| -------------- | --------- | -------------------------------------------------------------------------------------------- |
| `clientId` | string | OAuth client identifier |
| `clientSecret` | string | Client secret (empty string for public clients) |
| `redirectUris` | string\[] | Allowed redirect URIs for authorization flows |
| `grantTypes` | string\[] | Allowed grant types: `authorization_code`, `client_credentials`, `refresh_token`, `password` |
### User Configuration
[Section titled “User Configuration”](#user-configuration)
| Field | Type | Description |
| ---------- | ------ | ---------------------------------------------------------------------------------------------- |
| `username` | string | Login username |
| `password` | string | Login password |
| `claims` | map | User claims (included in ID tokens and `/userinfo`). Common: `sub`, `email`, `name`, `picture` |
## Token Validation
[Section titled “Token Validation”](#token-validation)
mockd signs all tokens with RS256 using a 2048-bit RSA key pair generated at startup. The public key is available via the JWKS endpoint.
### JWKS Endpoint
[Section titled “JWKS Endpoint”](#jwks-endpoint)
```bash
curl http://localhost:4280/.well-known/jwks.json
```
Response:
```json
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "mock-key-id",
"alg": "RS256",
"n": "base64url-encoded-modulus...",
"e": "AQAB"
}
]
}
```
### Validating Tokens in Your Application
[Section titled “Validating Tokens in Your Application”](#validating-tokens-in-your-application)
Use the OIDC discovery document to configure your JWT validation library. Point it at the mock issuer and it will fetch the JWKS automatically.
**Go (using go-jose or similar):**
```go
// Configure your middleware to use the mock issuer
issuer := "http://localhost:4280"
jwksURL := issuer + "/.well-known/jwks.json"
// Most JWT libraries will fetch and cache the JWKS from this URL
validator := jwt.NewValidator(
jwt.WithIssuer(issuer),
jwt.WithJWKSURL(jwksURL),
)
```
**Node.js (using jose):**
```javascript
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('http://localhost:4280/.well-known/jwks.json')
);
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'http://localhost:4280',
});
```
### Token Introspection
[Section titled “Token Introspection”](#token-introspection)
Resource servers can verify tokens via the introspection endpoint (RFC 7662). This is useful when you don’t want to validate JWTs locally.
```bash
curl -X POST http://localhost:4280/introspect \
-u "my-app:my-secret" \
-d "token=eyJhbGciOiJSUzI1NiIs..."
```
Active token response:
```json
{
"active": true,
"scope": "openid profile email",
"client_id": "my-app",
"sub": "user-001",
"token_type": "Bearer",
"exp": 1700000000,
"iat": 1699996400,
"iss": "http://localhost:4280"
}
```
Expired or invalid token response:
```json
{
"active": false
}
```
### Token Revocation
[Section titled “Token Revocation”](#token-revocation)
Revoke an access token or refresh token (RFC 7009):
```bash
curl -X POST http://localhost:4280/revoke \
-u "my-app:my-secret" \
-d "token=eyJhbGciOiJSUzI1NiIs..."
```
The endpoint always returns `200 OK` regardless of whether the token existed, per the RFC specification. Revoked tokens will return `active: false` from the introspection endpoint and will be rejected by the `/userinfo` endpoint.
## OIDC Discovery
[Section titled “OIDC Discovery”](#oidc-discovery)
The discovery document at `/.well-known/openid-configuration` advertises all supported endpoints and capabilities:
```bash
curl http://localhost:4280/.well-known/openid-configuration
```
```json
{
"issuer": "http://localhost:4280",
"authorization_endpoint": "http://localhost:4280/authorize",
"token_endpoint": "http://localhost:4280/token",
"userinfo_endpoint": "http://localhost:4280/userinfo",
"jwks_uri": "http://localhost:4280/.well-known/jwks.json",
"revocation_endpoint": "http://localhost:4280/revoke",
"introspection_endpoint": "http://localhost:4280/introspect",
"response_types_supported": ["code", "token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "auth_time", "nonce", "email", "email_verified", "name", "given_name", "family_name", "picture"],
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token", "password"],
"code_challenge_methods_supported": ["S256", "plain"]
}
```
Most OAuth/OIDC libraries can auto-configure themselves from this document. Point your library at `http://localhost:4280/.well-known/openid-configuration` and it will discover all endpoints automatically.
## Testing Patterns
[Section titled “Testing Patterns”](#testing-patterns)
### Testing Auth Middleware
[Section titled “Testing Auth Middleware”](#testing-auth-middleware)
Verify your API correctly rejects unauthenticated requests and accepts valid tokens:
```bash
# Get a token
TOKEN=$(curl -s -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=my-app" \
-d "client_secret=my-secret" | jq -r '.access_token')
# Authenticated request to your API (which validates against mockd's JWKS)
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/protected
# Verify rejection without a token
curl -v http://localhost:3000/api/protected
# Should return 401
```
### Testing Token Refresh
[Section titled “Testing Token Refresh”](#testing-token-refresh)
Simulate token expiration and renewal:
```yaml
oauth:
tokenExpiry: "5s" # Short-lived access tokens for testing
refreshExpiry: "1h"
```
```bash
# Get initial tokens
RESPONSE=$(curl -s -X POST http://localhost:4280/token \
-d "grant_type=password" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "username=testuser" \
-d "password=testpass")
REFRESH_TOKEN=$(echo "$RESPONSE" | jq -r '.refresh_token')
# Wait for access token to expire
sleep 6
# Refresh the token
curl -X POST http://localhost:4280/token \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN" \
-d "client_id=my-app" \
-d "client_secret=my-secret"
```
### Testing Scope Enforcement
[Section titled “Testing Scope Enforcement”](#testing-scope-enforcement)
Verify your API enforces scope requirements:
```bash
# Token with limited scopes
TOKEN=$(curl -s -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "scope=api:read" | jq -r '.access_token')
# Should succeed — read-only endpoint
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/items
# Should fail — write endpoint requires api:write scope
curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/items \
-d '{"name": "new item"}'
# Your API should return 403
```
### Testing Invalid Credentials
[Section titled “Testing Invalid Credentials”](#testing-invalid-credentials)
Verify your application handles authentication errors:
```bash
# Invalid client credentials
curl -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=wrong-id" \
-d "client_secret=wrong-secret"
# Response:
# {"error": "invalid_client", "error_description": "invalid client credentials"}
# Invalid user credentials
curl -X POST http://localhost:4280/token \
-d "grant_type=password" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "username=testuser" \
-d "password=wrong-password"
# Response:
# {"error": "invalid_grant", "error_description": "invalid user credentials"}
# Invalid scope
curl -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "scope=nonexistent"
# Response:
# {"error": "invalid_scope", "error_description": "scope \"nonexistent\" is not supported"}
```
### Testing Token Revocation Flow
[Section titled “Testing Token Revocation Flow”](#testing-token-revocation-flow)
Verify your application handles revoked tokens:
```bash
# Get a token
TOKEN=$(curl -s -X POST http://localhost:4280/token \
-d "grant_type=client_credentials" \
-d "client_id=my-app" \
-d "client_secret=my-secret" | jq -r '.access_token')
# Token works
curl -H "Authorization: Bearer $TOKEN" http://localhost:4280/userinfo
# Returns user info
# Revoke it
curl -X POST http://localhost:4280/revoke \
-u "my-app:my-secret" \
-d "token=$TOKEN"
# Token no longer works
curl -H "Authorization: Bearer $TOKEN" http://localhost:4280/userinfo
# Returns 401
# Introspection confirms revocation
curl -X POST http://localhost:4280/introspect \
-u "my-app:my-secret" \
-d "token=$TOKEN"
# Returns {"active": false}
```
## Examples
[Section titled “Examples”](#examples)
### Microservice-to-Microservice Auth
[Section titled “Microservice-to-Microservice Auth”](#microservice-to-microservice-auth)
Service-to-service communication using client credentials:
```yaml
mocks:
- id: service-auth
name: Service Auth
type: oauth
enabled: true
oauth:
issuer: http://localhost:4280
tokenExpiry: "30m"
defaultScopes:
- service:read
- service:write
clients:
- clientId: order-service
clientSecret: order-secret
grantTypes:
- client_credentials
- clientId: inventory-service
clientSecret: inventory-secret
grantTypes:
- client_credentials
```
```bash
# Order service gets a token
curl -X POST http://localhost:4280/token \
-u "order-service:order-secret" \
-d "grant_type=client_credentials" \
-d "scope=service:read service:write"
```
### Single Page Application with PKCE
[Section titled “Single Page Application with PKCE”](#single-page-application-with-pkce)
Public client using authorization code + PKCE:
```yaml
mocks:
- id: spa-auth
name: SPA Auth Provider
type: oauth
enabled: true
oauth:
issuer: http://localhost:4280
tokenExpiry: "15m"
refreshExpiry: "7d"
defaultScopes:
- openid
- profile
- email
- offline_access
clients:
- clientId: my-spa
clientSecret: ""
redirectUris:
- http://localhost:5173/callback
- http://localhost:5173/silent-renew
grantTypes:
- authorization_code
- refresh_token
users:
- username: demo
password: demo
claims:
sub: "user-demo"
email: "demo@example.com"
name: "Demo User"
picture: "https://i.pravatar.cc/150?u=demo"
```
### Multi-Tenant API
[Section titled “Multi-Tenant API”](#multi-tenant-api)
Multiple clients with different scope permissions:
```yaml
mocks:
- id: multi-tenant-auth
name: Multi-Tenant Auth
type: oauth
enabled: true
oauth:
issuer: http://localhost:4280
tokenExpiry: "1h"
refreshExpiry: "30d"
defaultScopes:
- openid
- profile
- email
- tenant:read
- tenant:write
- admin
defaultClaims:
aud: "https://api.example.com"
clients:
- clientId: tenant-a
clientSecret: secret-a
redirectUris:
- http://tenant-a.localhost:3000/callback
grantTypes:
- authorization_code
- refresh_token
- password
- clientId: tenant-b
clientSecret: secret-b
redirectUris:
- http://tenant-b.localhost:3000/callback
grantTypes:
- authorization_code
- refresh_token
- password
- clientId: admin-cli
clientSecret: admin-secret
grantTypes:
- client_credentials
users:
- username: alice
password: alice123
claims:
sub: "user-alice"
email: "alice@tenant-a.com"
name: "Alice (Tenant A)"
tenant_id: "tenant-a"
roles: ["editor"]
- username: bob
password: bob123
claims:
sub: "user-bob"
email: "bob@tenant-b.com"
name: "Bob (Tenant B)"
tenant_id: "tenant-b"
roles: ["viewer"]
```
```bash
# Alice logs in through Tenant A's client
curl -X POST http://localhost:4280/token \
-d "grant_type=password" \
-d "client_id=tenant-a" \
-d "client_secret=secret-a" \
-d "username=alice" \
-d "password=alice123" \
-d "scope=openid profile tenant:read tenant:write"
# Admin CLI gets a service token
curl -X POST http://localhost:4280/token \
-u "admin-cli:admin-secret" \
-d "grant_type=client_credentials" \
-d "scope=admin tenant:read tenant:write"
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Configuration Reference](/reference/configuration) — Full configuration schema
* [Response Templating](/guides/response-templating) — Dynamic response values
* [CLI Reference](/reference/cli) — All CLI commands and flags
# SOAP/WSDL Mocking
> Mock SOAP/XML web services with WSDL support, XPath matching, and fault handling
SOAP mocking enables you to create mock SOAP/XML web service endpoints for testing enterprise integrations and legacy systems. Configure WSDL-based services with operation mocking, XPath request matching, and SOAP fault handling.
## Overview
[Section titled “Overview”](#overview)
mockd’s SOAP support includes:
* **WSDL support** - Define services inline or from external WSDL files
* **Operation mocking** - Return mock responses for SOAP operations
* **XPath matching** - Conditional responses based on request content
* **SOAP faults** - Return fault responses for error testing
* **Namespace handling** - Full XML namespace support
* **Template support** - Dynamic responses with variables
* **Automatic WSDL serving** - WSDL endpoint at `?wsdl` suffix
## Quick Start
[Section titled “Quick Start”](#quick-start)
Create a minimal SOAP mock:
```yaml
version: "1.0"
mocks:
- id: my-soap-service
name: User Service
type: soap
enabled: true
soap:
path: /soap/UserService
wsdl: |
operations:
GetUser:
soapAction: "http://example.com/GetUser"
response: |
123
John Doe
john@example.com
```
Start the server and test:
```bash
# Start mockd
mockd serve --config mockd.yaml
# Call the SOAP service
curl -X POST http://localhost:4280/soap/UserService \
-H "Content-Type: text/xml" \
-H "SOAPAction: http://example.com/GetUser" \
-d '
123
'
# Get the WSDL
curl http://localhost:4280/soap/UserService?wsdl
```
## Configuration
[Section titled “Configuration”](#configuration)
### Full Configuration Reference
[Section titled “Full Configuration Reference”](#full-configuration-reference)
```yaml
mocks:
- id: soap-service
name: My SOAP Service
type: soap
enabled: true
soap:
# Endpoint path (required)
path: /soap/MyService
# WSDL definition - use either inline or file
wsdl: |
...
# OR
wsdlFile: ./wsdl/service.wsdl
# Operation configurations
operations:
OperationName:
soapAction: "http://example.com/OperationName"
response: |
...
delay: "100ms"
match:
xpath:
"//ElementName/text()": "value"
fault:
code: soap:Client
message: "Error message"
detail: "..."
```
### Configuration Fields
[Section titled “Configuration Fields”](#configuration-fields)
| Field | Type | Description |
| ------------ | ------ | ---------------------------------------------- |
| `path` | string | SOAP endpoint path (e.g., `/soap/UserService`) |
| `wsdl` | string | Inline WSDL definition |
| `wsdlFile` | string | Path to external WSDL file |
| `operations` | map | Operation configurations by operation name |
### Operation Fields
[Section titled “Operation Fields”](#operation-fields)
| Field | Type | Description |
| ------------ | ------ | ---------------------------------------- |
| `soapAction` | string | SOAPAction header value for matching |
| `response` | string | XML response body |
| `delay` | string | Response delay (e.g., `"100ms"`, `"2s"`) |
| `match` | object | XPath-based request matching |
| `fault` | object | SOAP fault response configuration |
## WSDL Configuration
[Section titled “WSDL Configuration”](#wsdl-configuration)
### Inline WSDL
[Section titled “Inline WSDL”](#inline-wsdl)
Define the WSDL directly in your config:
```yaml
soap:
path: /soap/OrderService
wsdl: |
```
### External WSDL File
[Section titled “External WSDL File”](#external-wsdl-file)
Reference an external WSDL file:
```yaml
soap:
path: /soap/OrderService
wsdlFile: ./wsdl/orders.wsdl
```
The WSDL is served at `http://localhost:4280/soap/OrderService?wsdl` regardless of whether it’s inline or from a file.
## Operations
[Section titled “Operations”](#operations)
Operations define how each SOAP action is handled. Each operation maps a SOAPAction header to a response.
### Basic Operation
[Section titled “Basic Operation”](#basic-operation)
```yaml
operations:
GetUser:
soapAction: "http://example.com/GetUser"
response: |
123
John Doe
john@example.com
```
### Multiple Operations
[Section titled “Multiple Operations”](#multiple-operations)
```yaml
operations:
GetUser:
soapAction: "http://example.com/GetUser"
response: |
123
John Doe
CreateUser:
soapAction: "http://example.com/CreateUser"
response: |
{{uuid}}
Created
{{now}}
DeleteUser:
soapAction: "http://example.com/DeleteUser"
response: |
true
ListUsers:
soapAction: "http://example.com/ListUsers"
response: |
1Alice
2Bob
3Carol
```
### Response Delay
[Section titled “Response Delay”](#response-delay)
Simulate slow backend services:
```yaml
operations:
GetReport:
soapAction: "http://example.com/GetReport"
delay: "2s"
response: |
report_001
Complete
```
## XPath Matching
[Section titled “XPath Matching”](#xpath-matching)
Use XPath expressions to match specific request elements and return conditional responses.
### Basic XPath Match
[Section titled “Basic XPath Match”](#basic-xpath-match)
```yaml
operations:
GetUser:
soapAction: "http://example.com/GetUser"
match:
xpath:
"//UserId/text()": "123"
response: |
123
John Doe
john@example.com
```
### Multiple XPath Conditions
[Section titled “Multiple XPath Conditions”](#multiple-xpath-conditions)
Match on multiple elements simultaneously:
```yaml
operations:
SearchUsers:
soapAction: "http://example.com/SearchUsers"
match:
xpath:
"//Department/text()": "Engineering"
"//Status/text()": "active"
response: |
AliceEngineering
BobEngineering
```
### Conditional Responses with Multiple Mocks
[Section titled “Conditional Responses with Multiple Mocks”](#conditional-responses-with-multiple-mocks)
Return different responses for different XPath matches by creating multiple mocks:
```yaml
mocks:
# Match user 123
- id: soap-user-123
type: soap
enabled: true
soap:
path: /soap/UserService
operations:
GetUser:
soapAction: "http://example.com/GetUser"
match:
xpath:
"//UserId/text()": "123"
response: |
123John Doe
# Match user 456
- id: soap-user-456
type: soap
enabled: true
soap:
path: /soap/UserService
operations:
GetUser:
soapAction: "http://example.com/GetUser"
match:
xpath:
"//UserId/text()": "456"
response: |
456Jane Smith
# Not found — no XPath match, acts as fallback
- id: soap-user-not-found
type: soap
enabled: true
soap:
path: /soap/UserService
operations:
GetUser:
soapAction: "http://example.com/GetUser"
fault:
code: soap:Client
message: "User not found"
```
### XPath Patterns
[Section titled “XPath Patterns”](#xpath-patterns)
| Pattern | Description | Example |
| ---------------------- | ------------------------ | -------------------------- |
| `//Element` | Select element anywhere | `"//UserId"` |
| `/Root/Child` | Absolute path | `"/Envelope/Body/Request"` |
| `//Element/text()` | Element text content | `"//UserId/text()"` |
| `//Element[@attr]` | Element with attribute | `"//User[@active]"` |
| `//Element[@attr='x']` | Specific attribute value | `"//User[@type='admin']"` |
## Fault Responses
[Section titled “Fault Responses”](#fault-responses)
Return SOAP fault responses for error testing.
### Basic Fault
[Section titled “Basic Fault”](#basic-fault)
```yaml
operations:
GetUser:
soapAction: "http://example.com/GetUser"
match:
xpath:
"//UserId/text()": "invalid"
fault:
code: soap:Client
message: "Invalid user ID format"
```
This generates:
```xml
soap:Client
Invalid user ID format
```
### Fault with Detail
[Section titled “Fault with Detail”](#fault-with-detail)
Include structured error details:
```yaml
operations:
TransferFunds:
soapAction: "http://example.com/TransferFunds"
match:
xpath:
"//Amount/text()": "0"
fault:
code: soap:Client
message: "Invalid transfer amount"
detail: |
INVALID_AMOUNT
0.01
1000000.00
```
### Server-Side Fault
[Section titled “Server-Side Fault”](#server-side-fault)
Simulate backend failures:
```yaml
operations:
ProcessPayment:
soapAction: "http://example.com/ProcessPayment"
fault:
code: soap:Server
message: "Payment gateway unavailable"
detail: |
30
```
### Common SOAP Fault Codes
[Section titled “Common SOAP Fault Codes”](#common-soap-fault-codes)
| Code | Description | Use Case |
| ---------------------- | ----------------------- | ------------------------------------ |
| `soap:Client` | Client-side error | Invalid request, missing data |
| `soap:Server` | Server-side error | Internal errors, service unavailable |
| `soap:MustUnderstand` | Header processing error | Required header not understood |
| `soap:VersionMismatch` | SOAP version mismatch | Wrong SOAP version |
## Dynamic Responses with Templates
[Section titled “Dynamic Responses with Templates”](#dynamic-responses-with-templates)
Use template expressions in responses:
```yaml
operations:
CreateUser:
soapAction: "http://example.com/CreateUser"
response: |
{{uuid}}
New User
{{now}}
```
Available templates:
| Template | Description |
| --------------- | --------------------- |
| `{{uuid}}` | Random UUID |
| `{{now}}` | Current ISO timestamp |
| `{{timestamp}}` | Unix timestamp |
## Examples
[Section titled “Examples”](#examples)
### Payment Processing Service
[Section titled “Payment Processing Service”](#payment-processing-service)
```yaml
version: "1.0"
mocks:
- id: payment-soap
name: Payment Service
type: soap
enabled: true
soap:
path: /soap/PaymentService
operations:
ProcessPayment:
soapAction: "http://example.com/ProcessPayment"
response: |
{{uuid}}
APPROVED
99.99
USD
{{now}}
RefundPayment:
soapAction: "http://example.com/RefundPayment"
delay: "500ms"
response: |
{{uuid}}
PROCESSED
{{now}}
GetTransaction:
soapAction: "http://example.com/GetTransaction"
response: |
txn_001
99.99
USD
COMPLETED
4242
2024-01-15T10:00:00Z
```
### Weather Service with XPath Matching
[Section titled “Weather Service with XPath Matching”](#weather-service-with-xpath-matching)
```yaml
version: "1.0"
mocks:
- id: weather-soap-nyc
name: Weather Service - NYC
type: soap
enabled: true
soap:
path: /soap/WeatherService
operations:
GetWeather:
soapAction: "http://example.com/GetWeather"
match:
xpath:
"//City/text()": "New York"
response: |
New York
72
Fahrenheit
Partly Cloudy
65
- id: weather-soap-london
name: Weather Service - London
type: soap
enabled: true
soap:
path: /soap/WeatherService
operations:
GetWeather:
soapAction: "http://example.com/GetWeather"
match:
xpath:
"//City/text()": "London"
response: |
London
18
Celsius
Rainy
80
- id: weather-soap-default
name: Weather Service - Default
type: soap
enabled: true
soap:
path: /soap/WeatherService
operations:
GetWeather:
soapAction: "http://example.com/GetWeather"
fault:
code: soap:Client
message: "City not found. Supported cities: New York, London"
```
### Enterprise Integration with Multiple Services
[Section titled “Enterprise Integration with Multiple Services”](#enterprise-integration-with-multiple-services)
```yaml
version: "1.0"
mocks:
- id: crm-soap
name: CRM Service
type: soap
enabled: true
soap:
path: /soap/CRMService
operations:
GetCustomer:
soapAction: "urn:crm:GetCustomer"
response: |
CUST-001
Acme Corp
Enterprise
Active
John Smith
CreateLead:
soapAction: "urn:crm:CreateLead"
response: |
{{uuid}}
New
{{now}}
- id: inventory-soap
name: Inventory Service
type: soap
enabled: true
soap:
path: /soap/InventoryService
operations:
CheckStock:
soapAction: "urn:inventory:CheckStock"
response: |
-
WIDGET-001
true
250
US-EAST-1
ReserveStock:
soapAction: "urn:inventory:ReserveStock"
delay: "200ms"
response: |
{{uuid}}
Reserved
{{now}}
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
### Add a SOAP Mock
[Section titled “Add a SOAP Mock”](#add-a-soap-mock)
Create SOAP mocks directly from the command line using `mockd soap add`:
```bash
# Simple operation
mockd soap add --path /soap/weather --action GetWeather \
--response '72'
# With a specific SOAPAction
mockd soap add --path /soap/users --action GetUser \
--response '123John'
```
Output:
```plaintext
Created mock: soap_4b349e0c7719f577
Type: soap
Path: /soap/weather
Operation: GetWeather
```
#### Add Command Flags
[Section titled “Add Command Flags”](#add-command-flags)
| Flag | Description |
| --------------------- | ---------------------------------------------------------------------- |
| `--path` | SOAP endpoint path (required) |
| `--action` | SOAP operation/action name (required) |
| `--response` | XML response body |
| `--stateful-resource` | Stateful resource name (e.g., `users`) |
| `--stateful-action` | Stateful action: `list`, `get`, `create`, `update`, `delete`, `custom` |
| `--admin-url` | Admin API URL (default: `http://localhost:4290`) |
Note
The CLI flag is `--action`, not `--operation`. This matches the SOAPAction header terminology.
#### Stateful SOAP via CLI
[Section titled “Stateful SOAP via CLI”](#stateful-soap-via-cli)
Wire a SOAP operation directly to a stateful resource from the command line:
```bash
# First, create a stateful resource (if one doesn't exist)
mockd stateful add users
# Wire SOAP operations to the resource
mockd soap add --path /soap --action ListUsers --stateful-resource users --stateful-action list
mockd soap add --path /soap --action GetUser --stateful-resource users --stateful-action get
mockd soap add --path /soap --action CreateUser --stateful-resource users --stateful-action create
```
`--stateful-resource` and `--stateful-action` must be used together. When set, the SOAP operation reads/writes from the named stateful resource instead of returning a canned response.
For complex SOAP mocks with WSDL definitions, XPath matching, or multiple operations, use a YAML config file instead of the CLI.
### List SOAP Mocks
[Section titled “List SOAP Mocks”](#list-soap-mocks)
```bash
# List all mocks (includes SOAP)
mockd list
# Filter to SOAP mocks
mockd list --type soap
# JSON output
mockd list --type soap --json
```
### Delete a SOAP Mock
[Section titled “Delete a SOAP Mock”](#delete-a-soap-mock)
```bash
mockd delete soap_4b349e0c7719f577
```
## Testing
[Section titled “Testing”](#testing)
### Test with curl
[Section titled “Test with curl”](#test-with-curl)
Basic SOAP request:
```bash
curl -X POST http://localhost:4280/soap/UserService \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: http://example.com/GetUser" \
-d '
123
'
```
### Verify WSDL Endpoint
[Section titled “Verify WSDL Endpoint”](#verify-wsdl-endpoint)
```bash
# Fetch WSDL
curl http://localhost:4280/soap/UserService?wsdl
# Verify WSDL returns XML
curl -sI http://localhost:4280/soap/UserService?wsdl | grep Content-Type
# Content-Type: text/xml; charset=utf-8
```
### Test Fault Responses
[Section titled “Test Fault Responses”](#test-fault-responses)
Trigger a SOAP fault by sending a request that matches fault conditions:
```bash
curl -X POST http://localhost:4280/soap/UserService \
-H "Content-Type: text/xml" \
-H "SOAPAction: http://example.com/GetUser" \
-d '
invalid
'
# Returns SOAP Fault with HTTP 500
```
### Test with SOAP Clients
[Section titled “Test with SOAP Clients”](#test-with-soap-clients)
mockd works with standard SOAP tools and libraries:
* **SoapUI** - Import the WSDL from `http://localhost:4280/soap/UserService?wsdl`
* **Postman** - Use SOAP request type with WSDL import
* **Python zeep** - Point to the WSDL endpoint
* **.NET WCF** - Add service reference using the WSDL URL
* **Java JAX-WS** - Generate client stubs from WSDL
### Python zeep Example
[Section titled “Python zeep Example”](#python-zeep-example)
```python
from zeep import Client
client = Client('http://localhost:4280/soap/UserService?wsdl')
result = client.service.GetUser(UserId='123')
print(result)
```
### Java Example
[Section titled “Java Example”](#java-example)
```java
import javax.xml.ws.Service;
import java.net.URL;
import javax.xml.namespace.QName;
URL wsdlUrl = new URL("http://localhost:4280/soap/UserService?wsdl");
QName serviceName = new QName("http://example.com/user", "UserService");
Service service = Service.create(wsdlUrl, serviceName);
UserPortType port = service.getPort(UserPortType.class);
GetUserResponse response = port.getUser("123");
```
### Integration Tests (Go)
[Section titled “Integration Tests (Go)”](#integration-tests-go)
```go
package main
import (
"bytes"
"io"
"net/http"
"strings"
"testing"
)
func TestSOAPMock(t *testing.T) {
soapRequest := `
123
`
req, _ := http.NewRequest("POST",
"http://localhost:4280/soap/UserService",
bytes.NewBufferString(soapRequest))
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
req.Header.Set("SOAPAction", "http://example.com/GetUser")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("Expected 200, got %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
if !strings.Contains(string(body), "John Doe") {
t.Error("Response does not contain expected user name")
}
}
```
## Testing Tips
[Section titled “Testing Tips”](#testing-tips)
### Verify SOAPAction Header Matching
[Section titled “Verify SOAPAction Header Matching”](#verify-soapaction-header-matching)
SOAP mocks match on the `SOAPAction` header. Ensure your client sends it:
```bash
# This will match
curl -X POST http://localhost:4280/soap/UserService \
-H "SOAPAction: http://example.com/GetUser" \
-H "Content-Type: text/xml" \
-d @request.xml
# This will NOT match (missing SOAPAction)
curl -X POST http://localhost:4280/soap/UserService \
-H "Content-Type: text/xml" \
-d @request.xml
```
### Test Error Handling
[Section titled “Test Error Handling”](#test-error-handling)
Create mocks that return SOAP faults to verify your client’s error handling:
```yaml
operations:
# Timeout simulation
SlowOperation:
soapAction: "http://example.com/SlowOp"
delay: "30s"
response: |
done
# Server error
FailingOperation:
soapAction: "http://example.com/FailOp"
fault:
code: soap:Server
message: "Internal service error"
```
### Debug with Request Logs
[Section titled “Debug with Request Logs”](#debug-with-request-logs)
Use mockd’s request log to see incoming SOAP requests:
```bash
# View recent requests
curl http://localhost:4290/logs?limit=5
# Or via CLI
mockd logs --limit 5
```
### Use with CI/CD
[Section titled “Use with CI/CD”](#use-with-cicd)
Start mockd in the background for integration tests:
```bash
# Start in background
mockd start -d --config soap-mocks.yaml
# Run your SOAP client tests
./run-soap-tests.sh
# Stop when done
mockd stop
```
## Stateful SOAP Operations
[Section titled “Stateful SOAP Operations”](#stateful-soap-operations)
SOAP operations can be wired to stateful CRUD resources, enabling shared state between REST and SOAP protocols. A REST `POST /api/users` creates a user that a SOAP `GetUser` can retrieve — and vice versa.
### Configuration
[Section titled “Configuration”](#configuration-1)
Add `statefulResource` and `statefulAction` to any SOAP operation:
```yaml
version: "1.0"
tables:
- name: users
seedData:
- { id: "1", name: "Alice", email: "alice@example.com" }
mocks:
- type: soap
name: User SOAP Service
soap:
path: /soap/UserService
operations:
GetUser:
soapAction: "http://example.com/GetUser"
statefulResource: users
statefulAction: get
ListUsers:
soapAction: "http://example.com/ListUsers"
statefulResource: users
statefulAction: list
CreateUser:
soapAction: "http://example.com/CreateUser"
statefulResource: users
statefulAction: create
UpdateUser:
soapAction: "http://example.com/UpdateUser"
statefulResource: users
statefulAction: update
DeleteUser:
soapAction: "http://example.com/DeleteUser"
statefulResource: users
statefulAction: delete
```
### Supported Actions
[Section titled “Supported Actions”](#supported-actions)
| Action | Description | Request Data | Response |
| -------- | -------------------- | --------------------- | ------------------------- |
| `get` | Retrieve single item | ID extracted from XML | Item as XML |
| `list` | List all items | Optional filters | Items wrapped in XML |
| `create` | Create new item | Fields from XML body | Created item as XML |
| `update` | Replace item (PUT) | ID + fields from XML | Updated item as XML |
| `patch` | Partial update | ID + partial fields | Updated item as XML |
| `delete` | Remove item | ID extracted from XML | Empty response |
| `custom` | Multi-step operation | Defined by steps | Expression-built response |
### How It Works
[Section titled “How It Works”](#how-it-works)
1. SOAP request arrives and is routed to the matching operation
2. XML body is parsed and converted to a `map[string]interface{}`
3. The stateful Bridge executes the CRUD action against the shared store
4. The result is converted back to XML and wrapped in a SOAP envelope
5. Errors map to SOAP faults (e.g., not-found → `soap:Client`)
### Cross-Protocol State Sharing
[Section titled “Cross-Protocol State Sharing”](#cross-protocol-state-sharing)
The key insight: stateful resources are **protocol-agnostic**. The same in-memory store backs both HTTP REST and SOAP:
```bash
# Create via REST
curl -X POST http://localhost:4280/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
# → {"id": "abc-123", "name": "Alice", ...}
# Retrieve the same user via SOAP
curl -X POST http://localhost:4280/soap/UserService \
-H "SOAPAction: http://example.com/GetUser" \
-H "Content-Type: text/xml" \
-d '
abc-123
'
# → SOAP envelope with user data
```
## WSDL Import
[Section titled “WSDL Import”](#wsdl-import)
Generate SOAP mock configurations from existing WSDL files.
### Using the SOAP Import Command
[Section titled “Using the SOAP Import Command”](#using-the-soap-import-command)
```bash
# Basic import — generates static response mocks
mockd soap import service.wsdl
# Stateful import — detects CRUD operations and wires to stateful resources
mockd soap import service.wsdl --stateful
# Output to a specific file
mockd soap import service.wsdl -o mocks.yaml
# Output as JSON
mockd soap import service.wsdl --format json
```
### Using the General Import Command
[Section titled “Using the General Import Command”](#using-the-general-import-command)
WSDL files are auto-detected by the general import command:
```bash
mockd import service.wsdl
```
### Stateful Heuristics
[Section titled “Stateful Heuristics”](#stateful-heuristics)
With the `--stateful` flag, the importer detects CRUD patterns in operation names:
| Pattern | Detected Action |
| --------------------------- | --------------- |
| `GetUser`, `FindOrder` | `get` |
| `ListUsers`, `SearchOrders` | `list` |
| `CreateUser`, `AddOrder` | `create` |
| `UpdateUser`, `ModifyOrder` | `update` |
| `DeleteUser`, `RemoveOrder` | `delete` |
The importer generates both the `statefulResources` definitions and the SOAP operations with `statefulResource`/`statefulAction` fields pre-filled. For new projects, consider converting the generated `statefulResources` to [tables and extend bindings](/reference/configuration/#tables) for a cleaner separation of data and routing.
## Custom Operations
[Section titled “Custom Operations”](#custom-operations)
Custom operations compose multiple reads, writes, and expression-evaluated transforms against stateful resources. This enables complex mock scenarios like fund transfers.
### Configuration
[Section titled “Configuration”](#configuration-2)
```yaml
customOperations:
- name: TransferFunds
consistency: atomic
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
- type: read
resource: accounts
id: "input.destId"
as: dest
- type: update
resource: accounts
id: "input.sourceId"
set:
balance: "source.balance - input.amount"
- type: update
resource: accounts
id: "input.destId"
set:
balance: "dest.balance + input.amount"
response:
status: '"completed"'
newSourceBalance: "source.balance - input.amount"
newDestBalance: "dest.balance + input.amount"
```
### Referencing from SOAP
[Section titled “Referencing from SOAP”](#referencing-from-soap)
```yaml
operations:
TransferFunds:
soapAction: "http://example.com/TransferFunds"
statefulResource: TransferFunds # Name of the custom operation
statefulAction: custom
```
### Managing Custom Operations via CLI
[Section titled “Managing Custom Operations via CLI”](#managing-custom-operations-via-cli)
Custom operations can also be managed and executed directly from the CLI — useful for testing, scripting, and AI agent workflows:
```bash
# Validate before registering (optional but recommended)
mockd stateful custom validate --file transfer.yaml --check-resources
mockd stateful custom validate --file transfer.yaml \
--input '{"sourceId":"acct-1","destId":"acct-2","amount":100}' \
--check-expressions-runtime \
--fixtures-file transfer-fixtures.json
# Register a custom operation from a YAML file
mockd stateful custom add --file transfer.yaml
# Or inline as JSON
mockd stateful custom add --definition '{"name":"TransferFunds","steps":[...]}'
# List registered operations
mockd stateful custom list
# Execute directly (no HTTP/SOAP request needed)
mockd stateful custom run TransferFunds --input '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
# Wire to an HTTP endpoint too
mockd http add -m POST --path /api/transfer --stateful-operation TransferFunds
```
See [`mockd stateful custom`](/reference/cli/#mockd-stateful-custom) in the CLI reference for the full command set.
### Step Types
[Section titled “Step Types”](#step-types)
| Step | Description |
| -------- | ------------------------------------------------------- |
| `read` | Read an item from a resource, store in a named variable |
| `create` | Create a new item in a resource |
| `update` | Update an item using expression-evaluated fields |
| `delete` | Delete an item from a resource |
| `set` | Set a context variable to an expression result |
### Expression Language
[Section titled “Expression Language”](#expression-language)
Custom operations use [expr-lang/expr](https://github.com/expr-lang/expr) for expressions. The environment includes:
* `input` — the request data (parsed from SOAP XML, GraphQL variables, etc.)
* Named variables from prior `read` steps (e.g., `source.balance`)
* All Go arithmetic, comparison, and string operators
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Stateful Mocking](/guides/stateful-mocking/) - Complete stateful resource guide
* [Response Templating](/guides/response-templating/) - Dynamic response values
* [Import/Export](/guides/import-export/) - Import existing SOAP mocks
* [Chaos Engineering](/guides/chaos-engineering/) - Simulate failures
* [Configuration Reference](/reference/configuration/) - Full configuration schema
# Server-Sent Events (SSE) and Streaming
> Mock SSE streams, AI chat completions, real-time feeds, and chunked transfers
mockd supports Server-Sent Events (SSE) and HTTP chunked transfer encoding for simulating streaming APIs like AI chat completions, real-time feeds, and large file downloads.
## Quick Start
[Section titled “Quick Start”](#quick-start)
### Using CLI
[Section titled “Using CLI”](#using-cli)
Create SSE mocks directly from the command line:
```bash
# Basic SSE with custom events
mockd http add --path /events --sse \
--sse-event 'connected:{"status":"ok"}' \
--sse-event 'update:{"count":1}' \
--sse-event 'update:{"count":2}' \
--sse-delay 500
# OpenAI-compatible streaming
mockd http add -m POST --path /v1/chat/completions --sse --sse-template openai-chat
# Notification stream template
mockd http add --path /notifications --sse --sse-template notification-stream
# Infinite keepalive stream
mockd http add --path /stream --sse \
--sse-event 'ping:{}' \
--sse-delay 1000 \
--sse-repeat 0
# SSE with keepalive pings every 15 seconds
mockd http add --path /long-poll --sse \
--sse-event 'data:{"value":1}' \
--sse-keepalive 15
```
**CLI SSE Flags:**
| Flag | Description | Default |
| ----------------- | ------------------------------------- | ------- |
| `--sse` | Enable SSE streaming | |
| `--sse-event` | Event (type:data), repeatable | |
| `--sse-delay` | Delay between events (ms) | `100` |
| `--sse-template` | Built-in template | |
| `--sse-repeat` | Repeat count (0 = infinite) | `1` |
| `--sse-keepalive` | Keepalive interval in seconds (min 5) | `0` |
### Using Configuration File (YAML)
[Section titled “Using Configuration File (YAML)”](#using-configuration-file-yaml)
```yaml
version: "1.0"
mocks:
- id: basic-sse
name: Basic SSE Stream
type: http
enabled: true
http:
matcher:
method: GET
path: /events
sse:
events:
- data: "Hello"
- data: "World"
timing:
fixedDelay: 1000
```
Or in JSON:
```json
{
"id": "basic-sse",
"type": "http",
"http": {
"matcher": { "method": "GET", "path": "/events" },
"sse": {
"events": [
{ "data": "Hello" },
{ "data": "World" }
],
"timing": { "fixedDelay": 1000 }
}
}
}
```
### OpenAI-Compatible Streaming
[Section titled “OpenAI-Compatible Streaming”](#openai-compatible-streaming)
```yaml
mocks:
- id: openai-mock
name: OpenAI Chat Mock
type: http
enabled: true
http:
matcher:
method: POST
path: /v1/chat/completions
sse:
template: openai-chat
templateParams:
tokens: ["Hello", "!", " How", " can", " I", " help", "?"]
model: gpt-4
finishReason: stop
includeDone: true
delayPerToken: 50
```
Or in JSON:
```json
{
"id": "openai-mock",
"type": "http",
"http": {
"matcher": { "method": "POST", "path": "/v1/chat/completions" },
"sse": {
"template": "openai-chat",
"templateParams": {
"tokens": ["Hello", "!", " How", " can", " I", " help", "?"],
"model": "gpt-4",
"finishReason": "stop",
"includeDone": true,
"delayPerToken": 50
}
}
}
}
```
## SSE Configuration
[Section titled “SSE Configuration”](#sse-configuration)
### Events
[Section titled “Events”](#events)
Define events to send to clients:
```yaml
sse:
events:
- type: message
data: "Event payload"
id: event-1
retry: 3000
```
Or in JSON:
```json
{
"sse": {
"events": [
{
"type": "message",
"data": "Event payload",
"id": "event-1",
"retry": 3000
}
]
}
}
```
| Field | Description |
| --------- | ------------------------------------------------ |
| `type` | Event type name (optional, for client filtering) |
| `data` | Event payload (string or JSON object) |
| `id` | Event ID (for Last-Event-ID resumption) |
| `retry` | Reconnection interval in milliseconds |
| `comment` | SSE comment (not delivered as event) |
### Timing Control
[Section titled “Timing Control”](#timing-control)
Control event delivery timing:
```yaml
sse:
timing:
initialDelay: 100
fixedDelay: 500
randomDelay:
min: 100
max: 500
burst:
count: 5
interval: 10
pause: 1000
perEventDelays: [100, 200, 500]
```
Or in JSON:
```json
{
"sse": {
"timing": {
"initialDelay": 100,
"fixedDelay": 500,
"randomDelay": { "min": 100, "max": 500 },
"burst": { "count": 5, "interval": 10, "pause": 1000 },
"perEventDelays": [100, 200, 500]
}
}
}
```
| Field | Description |
| ---------------- | -------------------------------------------------- |
| `initialDelay` | Delay before first event (ms) |
| `fixedDelay` | Constant delay between events (ms) |
| `randomDelay` | Random delay range (min/max ms) |
| `burst` | Send events in bursts (count/interval/pause in ms) |
| `perEventDelays` | Specific delay for each event |
### Lifecycle Management
[Section titled “Lifecycle Management”](#lifecycle-management)
Control connection behavior:
```yaml
sse:
lifecycle:
keepaliveInterval: 15
timeout: 300
maxEvents: 100
connectionTimeout: 60
termination:
type: graceful
finalEvent:
type: close
data: "Stream ended"
closeDelay: 0
```
Or in JSON:
```json
{
"sse": {
"lifecycle": {
"keepaliveInterval": 15,
"timeout": 300,
"maxEvents": 100,
"connectionTimeout": 60,
"termination": {
"type": "graceful",
"finalEvent": { "type": "close", "data": "Stream ended" },
"closeDelay": 0
}
}
}
}
```
| Field | Description |
| ------------------------ | ----------------------------------------------- |
| `keepaliveInterval` | Keepalive ping interval (seconds, min 5) |
| `timeout` | Connection timeout (seconds) |
| `maxEvents` | Maximum events before closing |
| `connectionTimeout` | Maximum stream duration (seconds) |
| `termination.type` | Termination type: “graceful”, “abrupt”, “error” |
| `termination.finalEvent` | Event to send on graceful close |
| `termination.closeDelay` | Delay in ms before closing after final event |
## Built-in Templates
[Section titled “Built-in Templates”](#built-in-templates)
### openai-chat
[Section titled “openai-chat”](#openai-chat)
OpenAI Chat Completions streaming format:
```json
{
"sse": {
"template": "openai-chat",
"templateParams": {
"tokens": ["Hello", " World"],
"model": "gpt-4",
"finishReason": "stop",
"includeDone": true,
"delayPerToken": 50
}
}
}
```
### notification-stream
[Section titled “notification-stream”](#notification-stream)
Real-time notification stream:
```json
{
"sse": {
"template": "notification-stream",
"templateParams": {
"notifications": [
{ "type": "alert", "message": "System update" }
],
"includeTimestamp": true,
"includeId": true,
"eventType": "notification"
}
}
}
```
## Random Placeholders
[Section titled “Random Placeholders”](#random-placeholders)
Use placeholders in event data for dynamic values:
| Placeholder | Description | Example |
| ------------------ | ------------------ | -------------------------------------- |
| `$random(min,max)` | Random integer | `$random(1,100)` |
| `$uuid` | UUID v4 | `550e8400-e29b-41d4-a716-446655440000` |
| `$timestamp` | ISO 8601 timestamp | `2024-01-15T10:30:00Z` |
| `$pick(a,b,c)` | Random choice | `$pick(red,green,blue)` |
Example:
```json
{
"data": {
"id": "$uuid",
"value": "$random(1,100)",
"status": "$pick(active,pending,complete)",
"timestamp": "$timestamp"
}
}
```
## HTTP Chunked Transfer
[Section titled “HTTP Chunked Transfer”](#http-chunked-transfer)
For non-SSE streaming (file downloads, NDJSON):
### Basic Chunked Response
[Section titled “Basic Chunked Response”](#basic-chunked-response)
```json
{
"chunked": {
"data": "Large content to stream in chunks...",
"chunkSize": 1024,
"chunkDelay": 100
}
}
```
### NDJSON Streaming
[Section titled “NDJSON Streaming”](#ndjson-streaming)
```json
{
"chunked": {
"format": "ndjson",
"ndjsonItems": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
],
"chunkDelay": 100
}
}
```
## Admin API
[Section titled “Admin API”](#admin-api)
### List Connections
[Section titled “List Connections”](#list-connections)
```plaintext
GET /sse/connections
```
### Get Connection
[Section titled “Get Connection”](#get-connection)
```plaintext
GET /sse/connections/{id}
```
### Close Connection
[Section titled “Close Connection”](#close-connection)
```plaintext
DELETE /sse/connections/{id}
```
### Get Stats
[Section titled “Get Stats”](#get-stats)
```plaintext
GET /sse/stats
```
### Mock-Specific Operations
[Section titled “Mock-Specific Operations”](#mock-specific-operations)
```plaintext
GET /mocks/{id}/sse/connections
DELETE /mocks/{id}/sse/connections
GET /mocks/{id}/sse/buffer
DELETE /mocks/{id}/sse/buffer
```
## Testing with curl
[Section titled “Testing with curl”](#testing-with-curl)
```bash
# Basic SSE
curl -N -H "Accept: text/event-stream" http://localhost:4280/events
# OpenAI streaming
curl -N -X POST \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"stream": true, "messages": [{"role": "user", "content": "Hi"}]}' \
http://localhost:4280/v1/chat/completions
# Chunked download
curl -N http://localhost:4280/download/file
# NDJSON stream
curl -N http://localhost:4280/api/logs/stream
```
## Browser EventSource
[Section titled “Browser EventSource”](#browser-eventsource)
```javascript
const source = new EventSource('/events');
source.onmessage = (event) => {
console.log('Message:', event.data);
};
source.addEventListener('custom-type', (event) => {
console.log('Custom event:', event.data);
});
source.onerror = (error) => {
console.error('Error:', error);
};
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Stream Recording](/guides/stream-recording/) - Record SSE and WebSocket streams
* [Response Templating](/guides/response-templating/) - Dynamic response values
* [Admin API](/reference/admin-api/) - Manage SSE connections
# WebSocket Mocking
> Create mock WebSocket endpoints for real-time bidirectional communication testing with message matching, response templating, and scripted scenarios.
WebSocket mocking enables testing of real-time bidirectional communication without connecting to actual backend services. mockd provides full WebSocket support with message matching, response templating, and scripted scenarios.
## Overview
[Section titled “Overview”](#overview)
Use WebSocket mocks when you need to:
* Test chat applications, notifications, or live updates
* Simulate real-time data feeds (stock prices, sports scores, IoT sensors)
* Develop frontends before backend WebSocket services are ready
* Create reproducible test scenarios for bidirectional protocols
* Debug client-side WebSocket handling
## Quick Start
[Section titled “Quick Start”](#quick-start)
Create a minimal WebSocket mock:
```yaml
version: "1.0"
mocks:
- id: simple-ws
name: Simple WebSocket
type: websocket
websocket:
path: /ws
echoMode: true
```
Start the server and connect:
```bash
# Start mockd
mockd serve --config mockd.yaml
# Connect with the mockd CLI
mockd websocket connect ws://localhost:4280/ws
# Or use wscat
wscat -c ws://localhost:4280/ws
```
In echo mode, any message you send is echoed back.
## Configuration
[Section titled “Configuration”](#configuration)
### Full WebSocket Spec
[Section titled “Full WebSocket Spec”](#full-websocket-spec)
```yaml
version: "1.0"
mocks:
- id: ws-full-example
name: Full WebSocket Example
type: websocket
enabled: true
websocket:
# Required: endpoint path
path: /ws/chat
# Subprotocol negotiation
subprotocols:
- chat.v1
- chat.v2
requireSubprotocol: true # Reject connections without matching subprotocol
# Connection limits
maxMessageSize: 65536 # Maximum message size in bytes
maxConnections: 100 # Maximum concurrent connections
idleTimeout: "5m" # Disconnect after inactivity
# Echo mode: reflect messages back to sender
echoMode: false
# Heartbeat/keepalive
heartbeat:
enabled: true
interval: "30s" # Send ping every 30 seconds
timeout: "10s" # Disconnect if no pong within 10 seconds
# Message matchers (evaluated in order)
matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
# Default response when no matcher matches
defaultResponse:
type: json
value:
error: "Unknown message"
# Scripted scenario (optional)
scenario:
name: welcome-flow
steps:
- type: send
message:
type: json
value: { "type": "welcome" }
```
### Configuration Reference
[Section titled “Configuration Reference”](#configuration-reference)
| Field | Type | Description |
| -------------------- | --------- | ----------------------------------------------- |
| `path` | string | Endpoint path (required) |
| `subprotocols` | string\[] | Supported WebSocket subprotocols |
| `requireSubprotocol` | boolean | Reject connections without matching subprotocol |
| `maxMessageSize` | integer | Maximum message size in bytes |
| `maxConnections` | integer | Maximum concurrent connections |
| `idleTimeout` | duration | Connection idle timeout (e.g., ”30s”, “5m”) |
| `echoMode` | boolean | Echo received messages back to client |
| `heartbeat` | object | Ping/pong keepalive configuration |
| `matchers` | array | Message matchers with responses |
| `defaultResponse` | object | Response when no matcher matches |
| `scenario` | object | Scripted message sequence |
## Message Matchers
[Section titled “Message Matchers”](#message-matchers)
Matchers define how to respond to incoming WebSocket messages. Each matcher has a `match` criteria and `response` configuration.
### Match Types
[Section titled “Match Types”](#match-types)
#### Exact Match
[Section titled “Exact Match”](#exact-match)
Match the entire message exactly:
```yaml
matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
```
#### Contains Match
[Section titled “Contains Match”](#contains-match)
Match if the message contains a substring:
```yaml
matchers:
- match:
type: contains
value: "hello"
response:
type: text
value: "Hello from mockd!"
```
#### Prefix Match
[Section titled “Prefix Match”](#prefix-match)
Match if the message starts with a prefix:
```yaml
matchers:
- match:
type: prefix
value: "CMD:"
response:
type: text
value: "Command received"
```
#### Suffix Match
[Section titled “Suffix Match”](#suffix-match)
Match if the message ends with a suffix:
```yaml
matchers:
- match:
type: suffix
value: "?"
response:
type: text
value: "That's a question!"
```
#### Regex Match
[Section titled “Regex Match”](#regex-match)
Match using a regular expression:
```yaml
matchers:
- match:
type: regex
value: "user_\\d+"
response:
type: json
value:
matched: true
pattern: "user ID"
```
#### JSON Path Match
[Section titled “JSON Path Match”](#json-path-match)
Match specific fields in JSON messages using JSONPath:
```yaml
matchers:
# Match messages where $.type equals "ping"
- match:
type: json
path: "$.type"
value: "ping"
response:
type: json
value:
type: "pong"
timestamp: "{{now}}"
# Match messages where $.action equals "subscribe"
- match:
type: json
path: "$.action"
value: "subscribe"
response:
type: json
value:
action: "subscribed"
channel: "{{message.channel}}"
```
### Message Type Filter
[Section titled “Message Type Filter”](#message-type-filter)
Filter by WebSocket message type (text or binary):
```yaml
matchers:
- match:
type: exact
value: "ping"
messageType: text # Only match text messages
response:
type: text
value: "pong"
- match:
type: regex
value: ".*"
messageType: binary # Only match binary messages
response:
type: text
value: "Binary message received"
```
### No Response
[Section titled “No Response”](#no-response)
Match without sending a response (useful for logging or scenario progression):
```yaml
matchers:
- match:
type: json
path: "$.type"
value: "heartbeat"
noResponse: true
```
### Matcher Priority
[Section titled “Matcher Priority”](#matcher-priority)
Matchers are evaluated in order. The first matching rule wins:
```yaml
matchers:
# Specific match first
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
# Generic match later
- match:
type: regex
value: ".*"
response:
type: text
value: "Unknown command"
```
## Responses
[Section titled “Responses”](#responses)
### Response Types
[Section titled “Response Types”](#response-types)
#### Text Response
[Section titled “Text Response”](#text-response)
```yaml
response:
type: text
value: "Hello, World!"
```
#### JSON Response
[Section titled “JSON Response”](#json-response)
```yaml
response:
type: json
value:
status: "ok"
timestamp: "{{now}}"
data:
message: "Welcome!"
```
#### Binary Response
[Section titled “Binary Response”](#binary-response)
```yaml
response:
type: binary
value: "SGVsbG8gV29ybGQh" # Base64 encoded
```
### Response Delay
[Section titled “Response Delay”](#response-delay)
Add artificial latency:
```yaml
response:
type: json
value:
status: "processed"
delay: "500ms"
```
### Templated Responses
[Section titled “Templated Responses”](#templated-responses)
Use template expressions in responses:
```yaml
response:
type: json
value:
id: "{{uuid}}"
timestamp: "{{now}}"
echo: "{{message}}" # Echo the received message
```
Available template variables:
| Expression | Description |
| --------------- | ---------------------------- |
| `{{message}}` | The received message content |
| `{{now}}` | Current ISO timestamp |
| `{{timestamp}}` | Unix timestamp (seconds) |
| `{{uuid}}` | Random UUID |
### Default Response
[Section titled “Default Response”](#default-response)
Define a fallback for unmatched messages:
```yaml
websocket:
path: /ws
matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
defaultResponse:
type: json
value:
type: "error"
message: "Unknown command"
received: "{{message}}"
```
### Echo Mode
[Section titled “Echo Mode”](#echo-mode)
When `echoMode: true`, messages are echoed back to the client. This is useful for testing client message handling:
```yaml
websocket:
path: /ws/echo
echoMode: true
```
Echo mode works alongside matchers. Matchers are checked first; if none match and no default response is configured, the message is echoed.
## Scenarios
[Section titled “Scenarios”](#scenarios)
Scenarios enable scripted message sequences for complex testing flows.
### Basic Scenario
[Section titled “Basic Scenario”](#basic-scenario)
```yaml
websocket:
path: /ws/onboarding
scenario:
name: welcome-flow
steps:
# Send welcome message immediately on connect
- type: send
message:
type: json
value:
type: "welcome"
message: "Connected to server"
# Wait for client ready signal
- type: wait
match:
type: json
path: "$.type"
value: "ready"
timeout: "10s"
# Send session info
- type: send
message:
type: json
value:
type: "session_start"
sessionId: "{{uuid}}"
```
### Scenario Step Types
[Section titled “Scenario Step Types”](#scenario-step-types)
#### Send Step
[Section titled “Send Step”](#send-step)
Send a message to the client:
```yaml
- type: send
message:
type: json
value:
event: "notification"
data: "Hello!"
```
#### Expect Step
[Section titled “Expect Step”](#expect-step)
Wait for a client message matching specific criteria:
```yaml
- type: expect
match:
type: json
path: "$.type"
value: "acknowledge"
timeout: "5s"
optional: false # Fail if timeout expires (default)
```
#### Wait Step
[Section titled “Wait Step”](#wait-step)
Pause for a duration:
```yaml
- type: wait
duration: "2s"
```
### Looping Scenarios
[Section titled “Looping Scenarios”](#looping-scenarios)
Repeat the scenario when it completes:
```yaml
scenario:
name: heartbeat-loop
loop: true
steps:
- type: wait
duration: "5s"
- type: send
message:
type: json
value:
type: "heartbeat"
timestamp: "{{now}}"
```
### Reset on Reconnect
[Section titled “Reset on Reconnect”](#reset-on-reconnect)
Reset scenario state when a client reconnects:
```yaml
scenario:
name: tutorial
resetOnReconnect: true
steps:
- type: send
message:
type: text
value: "Welcome! Let's begin the tutorial..."
```
## Examples
[Section titled “Examples”](#examples)
### Chat Application
[Section titled “Chat Application”](#chat-application)
```yaml
version: "1.0"
mocks:
- id: chat-room
name: Chat Room
type: websocket
websocket:
path: /ws/chat
subprotocols:
- chat
- json
maxMessageSize: 65536
idleTimeout: "10m"
maxConnections: 100
heartbeat:
enabled: true
interval: "30s"
timeout: "10s"
matchers:
# Join room
- match:
type: json
path: "$.type"
value: "join"
response:
type: json
value:
type: "joined"
message: "Welcome to the chat room!"
timestamp: "{{now}}"
# Send message
- match:
type: json
path: "$.type"
value: "message"
response:
type: json
value:
type: "message_ack"
id: "{{uuid}}"
timestamp: "{{now}}"
# Leave room
- match:
type: json
path: "$.type"
value: "leave"
response:
type: json
value:
type: "left"
message: "Goodbye!"
# Typing indicator
- match:
type: json
path: "$.type"
value: "typing"
noResponse: true
# Status command
- match:
type: exact
value: "status"
response:
type: json
value:
type: "status"
users: 42
uptime: "{{timestamp}}"
# Help command
- match:
type: exact
value: "help"
response:
type: text
value: |
Available commands:
- {"type": "join", "username": "..."}: Join chat
- {"type": "message", "content": "..."}: Send message
- {"type": "leave"}: Leave chat
- status: Get server status
- help: Show this help
defaultResponse:
type: json
value:
type: "error"
message: "Unknown command. Type 'help' for available commands."
```
### Notification Service
[Section titled “Notification Service”](#notification-service)
```yaml
version: "1.0"
mocks:
- id: notifications
name: Push Notifications
type: websocket
websocket:
path: /ws/notifications
heartbeat:
enabled: true
interval: "30s"
matchers:
# Subscribe to channel
- match:
type: json
path: "$.action"
value: "subscribe"
response:
type: json
value:
action: "subscribed"
channel: "{{message.channel}}"
# Unsubscribe
- match:
type: json
path: "$.action"
value: "unsubscribe"
response:
type: json
value:
action: "unsubscribed"
channel: "{{message.channel}}"
# Send periodic notifications
scenario:
name: notification-stream
loop: true
steps:
- type: wait
duration: "10s"
- type: send
message:
type: json
value:
type: "notification"
id: "{{uuid}}"
title: "New update available"
body: "Check out the latest features"
timestamp: "{{now}}"
```
### Real-Time Data Feed
[Section titled “Real-Time Data Feed”](#real-time-data-feed)
```yaml
version: "1.0"
mocks:
- id: stock-ticker
name: Stock Ticker
type: websocket
websocket:
path: /ws/stocks
maxConnections: 1000
matchers:
# Subscribe to symbol
- match:
type: json
path: "$.action"
value: "subscribe"
response:
type: json
value:
action: "subscribed"
symbol: "{{message.symbol}}"
message: "You will receive updates for this symbol"
# Simulate price updates
scenario:
name: price-updates
loop: true
steps:
- type: wait
duration: "1s"
- type: send
message:
type: json
value:
type: "price_update"
symbol: "MOCK"
price: 123.45
change: 1.23
volume: 1000000
timestamp: "{{now}}"
```
### GraphQL Subscriptions
[Section titled “GraphQL Subscriptions”](#graphql-subscriptions)
```yaml
version: "1.0"
mocks:
- id: graphql-ws
name: GraphQL WebSocket
type: websocket
websocket:
path: /graphql
subprotocols:
- graphql-ws
- graphql-transport-ws
requireSubprotocol: true
matchers:
# Connection init
- match:
type: json
path: "$.type"
value: "connection_init"
response:
type: json
value:
type: "connection_ack"
# Subscribe
- match:
type: json
path: "$.type"
value: "subscribe"
response:
type: json
value:
type: "next"
id: "{{message.id}}"
payload:
data:
onMessage:
id: "{{uuid}}"
content: "Subscription active"
# Ping
- match:
type: json
path: "$.type"
value: "ping"
response:
type: json
value:
type: "pong"
```
## CLI Commands
[Section titled “CLI Commands”](#cli-commands)
mockd provides CLI tools for creating WebSocket mocks and interacting with WebSocket endpoints.
### Add a WebSocket Mock
[Section titled “Add a WebSocket Mock”](#add-a-websocket-mock)
Create WebSocket mocks directly from the command line using `mockd websocket add`:
```bash
# Echo mode — reflects messages back to sender
mockd websocket add --path /ws/echo --echo
# With a custom path
mockd websocket add --path /ws/chat --echo
```
Output:
```plaintext
Created mock: websocket_0b5ebb9fa569a655
Type: websocket
Path: /ws/echo
Echo: enabled
```
#### Add Command Flags
[Section titled “Add Command Flags”](#add-command-flags)
| Flag | Description |
| ------------- | ------------------------------------------------ |
| `--path` | WebSocket endpoint path (required) |
| `--echo` | Enable echo mode (reflect messages back) |
| `--admin-url` | Admin API URL (default: `http://localhost:4290`) |
Tip
For WebSocket mocks with matchers, scenarios, or complex configurations, use a YAML config file instead of the CLI. The CLI `add` command creates simple echo-mode endpoints for quick testing.
### websocket connect
[Section titled “websocket connect”](#websocket-connect)
Start an interactive WebSocket session (REPL mode):
```bash
# Basic connection
mockd websocket connect ws://localhost:4280/ws
# With custom headers
mockd websocket connect -H "Authorization:Bearer token" ws://localhost:4280/ws
# With subprotocol
mockd websocket connect --subprotocol graphql-ws ws://localhost:4280/graphql
# JSON output format
mockd websocket connect --json ws://localhost:4280/ws
```
Flags:
* `-H, --header` - Custom headers (key:value), repeatable
* `--subprotocol` - WebSocket subprotocol
* `-t, --timeout` - Connection timeout (default: 30s)
* `--json` - Output messages in JSON format
### websocket send
[Section titled “websocket send”](#websocket-send)
Send a single message and exit:
```bash
# Send text message
mockd websocket send ws://localhost:4280/ws "hello"
# Send JSON message
mockd websocket send ws://localhost:4280/ws '{"action":"ping"}'
# Send from file
mockd websocket send ws://localhost:4280/ws @message.json
# With custom headers
mockd websocket send -H "Authorization:Bearer token" ws://localhost:4280/ws "hello"
```
Flags:
* `-H, --header` - Custom headers (key:value), repeatable
* `--subprotocol` - WebSocket subprotocol
* `-t, --timeout` - Connection timeout (default: 30s)
* `--json` - Output result in JSON format
### websocket listen
[Section titled “websocket listen”](#websocket-listen)
Stream incoming messages:
```bash
# Listen indefinitely
mockd websocket listen ws://localhost:4280/ws
# Listen for 10 messages then exit
mockd websocket listen -n 10 ws://localhost:4280/ws
# JSON output
mockd websocket listen --json ws://localhost:4280/ws
# With headers
mockd websocket listen -H "Authorization:Bearer token" ws://localhost:4280/ws
```
Flags:
* `-H, --header` - Custom headers (key:value), repeatable
* `--subprotocol` - WebSocket subprotocol
* `-t, --timeout` - Connection timeout (default: 30s)
* `-n, --count` - Number of messages to receive (0 = unlimited)
* `--json` - Output messages in JSON format
### websocket status
[Section titled “websocket status”](#websocket-status)
Show WebSocket mock status from the admin API:
```bash
# Default admin URL
mockd websocket status
# Custom admin URL
mockd websocket status --admin-url http://localhost:9091
# JSON output
mockd websocket status --json
```
Flags:
* `--admin-url` - Admin API base URL (default: )
* `--json` - Output in JSON format
## Testing WebSocket Mocks
[Section titled “Testing WebSocket Mocks”](#testing-websocket-mocks)
### Using mockd CLI
[Section titled “Using mockd CLI”](#using-mockd-cli)
```bash
# Start server
mockd serve --config mockd.yaml &
# Test echo mode
mockd websocket send ws://localhost:4280/ws/echo "test message"
# Interactive testing
mockd websocket connect ws://localhost:4280/ws/chat
> {"type": "join", "username": "testuser"}
< {"type": "joined", "message": "Welcome to the chat room!", "timestamp": "..."}
> help
< Available commands: ...
```
### Using wscat
[Section titled “Using wscat”](#using-wscat)
```bash
# Install wscat
npm install -g wscat
# Connect and interact
wscat -c ws://localhost:4280/ws/chat
> ping
< pong
> {"type": "join", "username": "alice"}
< {"type": "joined", "message": "Welcome to the chat room!", "timestamp": "..."}
```
### Using curl (for connection testing)
[Section titled “Using curl (for connection testing)”](#using-curl-for-connection-testing)
```bash
# Verify WebSocket endpoint exists (returns 404 for non-upgrade requests)
curl -i http://localhost:4280/ws
# Test with upgrade headers
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
http://localhost:4280/ws
```
### Using websocat
[Section titled “Using websocat”](#using-websocat)
```bash
# Install websocat
cargo install websocat
# Simple connection
websocat ws://localhost:4280/ws
# One-shot message
echo "ping" | websocat ws://localhost:4280/ws
# With subprotocol
websocat --protocol chat ws://localhost:4280/ws/chat
```
### Integration Tests (JavaScript)
[Section titled “Integration Tests (JavaScript)”](#integration-tests-javascript)
```javascript
const WebSocket = require('ws');
describe('WebSocket Mock', () => {
let ws;
beforeEach((done) => {
ws = new WebSocket('ws://localhost:4280/ws/chat');
ws.on('open', done);
});
afterEach(() => {
ws.close();
});
test('responds to ping with pong', (done) => {
ws.on('message', (data) => {
expect(data.toString()).toBe('pong');
done();
});
ws.send('ping');
});
test('handles JSON messages', (done) => {
ws.on('message', (data) => {
const response = JSON.parse(data.toString());
expect(response.type).toBe('joined');
expect(response.message).toContain('Welcome');
done();
});
ws.send(JSON.stringify({ type: 'join', username: 'testuser' }));
});
});
```
### Integration Tests (Go)
[Section titled “Integration Tests (Go)”](#integration-tests-go)
```go
package main
import (
"testing"
"github.com/gorilla/websocket"
)
func TestWebSocketMock(t *testing.T) {
conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:4280/ws/chat", nil)
if err != nil {
t.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Test ping/pong
if err := conn.WriteMessage(websocket.TextMessage, []byte("ping")); err != nil {
t.Fatalf("Failed to send: %v", err)
}
_, msg, err := conn.ReadMessage()
if err != nil {
t.Fatalf("Failed to read: %v", err)
}
if string(msg) != "pong" {
t.Errorf("Expected 'pong', got '%s'", msg)
}
}
```
### Integration Tests (Python)
[Section titled “Integration Tests (Python)”](#integration-tests-python)
```python
import pytest
import websocket
import json
def test_websocket_ping():
ws = websocket.create_connection("ws://localhost:4280/ws/chat")
ws.send("ping")
result = ws.recv()
assert result == "pong"
ws.close()
def test_websocket_json():
ws = websocket.create_connection("ws://localhost:4280/ws/chat")
ws.send(json.dumps({"type": "join", "username": "testuser"}))
result = json.loads(ws.recv())
assert result["type"] == "joined"
assert "Welcome" in result["message"]
ws.close()
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
* [Response Templating](/guides/response-templating) - Dynamic response values
* [Request Matching](/guides/request-matching) - HTTP request matching patterns
* [Stateful Mocking](/guides/stateful-mocking) - CRUD simulation with state
# Admin API Reference
> Complete reference for the mockd Admin API, providing runtime management of mocks, state, recordings, and server configuration.
The Admin API provides runtime management of the mockd server.
Web Dashboard
When built with the `dashboard` build tag (the default for release binaries), the admin port also serves the **web dashboard** at its root path (`GET /`). API routes take priority over the dashboard catch-all, so all endpoints documented below work exactly the same. Open in your browser to access the dashboard. See the [Dashboard Guide](/guides/dashboard/) for details.
## Overview
[Section titled “Overview”](#overview)
mockd uses a **three-port architecture** separating the control plane from the data plane:
| Port | Default | Purpose |
| ------------------ | ------- | -------------------------------------------- |
| **Mock Server** | `4280` | Data plane - serves your mock endpoints |
| **Admin API** | `4290` | Control plane - management and configuration |
| **Engine Control** | `4281` | Internal - Admin-to-Engine communication |
4280/api/users
```bash
mockd start --port 4280 --admin-port 4290
# Admin at: http://localhost:4290/mocks
```
The Engine Control port (`4281`) is used internally for communication between the Admin API and the mock engine. In most cases, you don’t need to interact with it directly.
Base URL: `http://localhost:4290` (or your configured `--admin-port`)
## Authentication
[Section titled “Authentication”](#authentication)
The admin API requires API key authentication by default. The API key is auto-generated on first start and stored at `~/.local/share/mockd/admin-api-key`.
### Using the API Key
[Section titled “Using the API Key”](#using-the-api-key)
```bash
# Get your API key
cat ~/.local/share/mockd/admin-api-key
# Use with X-API-Key header
curl -H "X-API-Key: YOUR_KEY" http://localhost:4290/mocks
# Or use Bearer token
curl -H "Authorization: Bearer YOUR_KEY" http://localhost:4290/mocks
# Or use query parameter
curl "http://localhost:4290/mocks?api_key=YOUR_KEY"
```
### Disabling Authentication
[Section titled “Disabling Authentication”](#disabling-authentication)
For local development or CI, you can disable authentication:
```bash
mockd serve --no-auth
```
### Unauthenticated Endpoints
[Section titled “Unauthenticated Endpoints”](#unauthenticated-endpoints)
These endpoints work without authentication:
* `GET /health` - Health check
* `GET /metrics` - Prometheus metrics
### Production Recommendations
[Section titled “Production Recommendations”](#production-recommendations)
* Keep authentication enabled
* Bind to localhost only (`--host localhost`)
* Use a firewall to restrict access
* Run admin port on internal network only
***
## Endpoints
[Section titled “Endpoints”](#endpoints)
### Health & Readiness
[Section titled “Health & Readiness”](#health--readiness)
#### GET /health
[Section titled “GET /health”](#get-health)
Liveness check. Always returns 200 if the admin server is running. No authentication required.
**Response:**
```json
{
"status": "ok",
"uptime": 3600,
"timestamp": "2024-01-15T10:30:00Z"
}
```
#### GET /ready
[Section titled “GET /ready”](#get-ready)
Readiness check. Returns 200 when the server has finished loading config and is ready to serve traffic. Returns 503 during initialization (e.g., while importing a large spec). No authentication required.
**Response (ready):**
```json
{
"status": "ready",
"uptime": 3600,
"timestamp": "2024-01-15T10:30:00Z"
}
```
**Response (initializing):** `503 Service Unavailable`
```json
{
"status": "initializing",
"timestamp": "2024-01-15T10:30:00Z"
}
```
#### GET /\_\_mockd/health (Engine Port)
[Section titled “GET /\_\_mockd/health (Engine Port)”](#get-__mockdhealth-engine-port)
Liveness check on the **mock engine port** (default 4280). This is available even when no mocks are configured and always takes priority over mock matching.
**Response:**
```json
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00Z"
}
```
#### GET /\_\_mockd/ready (Engine Port)
[Section titled “GET /\_\_mockd/ready (Engine Port)”](#get-__mockdready-engine-port)
Readiness check on the **mock engine port** (default 4280). Reports the number of loaded mocks.
**Response:**
```json
{
"status": "ready",
"checks": {
"mocks": {
"count": 12,
"status": "ok"
}
}
}
```
Note
The engine port also responds to `/health` and `/ready` (without the `/__mockd/` prefix) as fallbacks — but only when no mock is configured to match those paths. The `/__mockd/` prefixed versions always take priority over mock matching.
Workspace Filtering
Most endpoints accept a `?workspaceId=` query parameter to scope operations to a specific workspace. When set:
* `GET /mocks` returns only mocks in that workspace
* `GET /requests` returns only request logs from that workspace
* `GET /state/*` returns only stateful resources in that workspace
* `POST /import` imports into that workspace
* `GET /export` exports from that workspace
If `workspaceId` is omitted, the default (empty) workspace is used, which includes all mocks not assigned to a specific workspace.
***
### Metrics
[Section titled “Metrics”](#metrics)
#### GET /metrics
[Section titled “GET /metrics”](#get-metrics)
Prometheus-compatible metrics endpoint. No authentication required.
**Response:** Prometheus text format
```plaintext
# HELP mockd_uptime_seconds Server uptime in seconds
# TYPE mockd_uptime_seconds gauge
mockd_uptime_seconds 3600
# HELP mockd_http_requests_total Total HTTP requests processed
# TYPE mockd_http_requests_total counter
mockd_http_requests_total{method="GET",path="/api/users",status="200"} 42
# HELP mockd_http_request_duration_seconds HTTP request latency
# TYPE mockd_http_request_duration_seconds histogram
mockd_http_request_duration_seconds_bucket{le="0.01"} 100
mockd_http_request_duration_seconds_bucket{le="0.1"} 150
mockd_http_request_duration_seconds_bucket{le="+Inf"} 155
# Go runtime metrics
go_goroutines 12
go_memstats_heap_alloc_bytes 4194304
```
**Prometheus Configuration:**
```yaml
scrape_configs:
- job_name: 'mockd'
static_configs:
- targets: ['localhost:4290']
metrics_path: /metrics
```
***
### Ports
[Section titled “Ports”](#ports)
#### GET /ports
[Section titled “GET /ports”](#get-ports)
List all ports in use by mockd, grouped by component.
**Response:**
```json
{
"ports": [
{
"port": 4290,
"protocol": "HTTP",
"component": "Admin API",
"status": "running"
},
{
"port": 4280,
"protocol": "HTTP",
"component": "Mock Engine",
"status": "running"
},
{
"port": 1883,
"protocol": "MQTT",
"component": "MQTT Broker",
"status": "running"
},
{
"port": 50051,
"protocol": "gRPC",
"component": "gRPC Server",
"status": "running"
}
]
}
```
The response includes ports for:
* Admin API (HTTP)
* Mock Engine (HTTP/HTTPS)
* Protocol handlers (gRPC, MQTT, WebSocket, SSE, GraphQL, SOAP)
**Note:** Ports with TLS enabled will include `"tls": true` in the response.
***
### Mock Management
[Section titled “Mock Management”](#mock-management)
WebSocket: active clients reconnect on mock changes
When a WebSocket mock is updated or deleted, all clients currently connected to that endpoint receive a **close frame with code 1012 (Service Restart)**. Most WebSocket client libraries treat code 1012 as a signal to reconnect automatically. On reconnect, the client establishes a fresh connection that uses the new mock configuration.
This applies to `PUT /mocks/{id}`, `DELETE /mocks/{id}`, `POST /mocks/{id}/toggle` (when disabling), bulk `DELETE /mocks` (delete all), and `POST /config` with `replace: true`.
#### GET /mocks
[Section titled “GET /mocks”](#get-mocks)
List all configured mocks.
**Response:**
```json
{
"mocks": [
{
"id": "http_abc123",
"type": "http",
"name": "Get users",
"enabled": true,
"workspaceId": "local",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"http": {
"matcher": {
"method": "GET",
"path": "/api/users"
},
"response": {
"statusCode": 200,
"body": "[]"
}
}
}
],
"count": 1
}
```
#### GET /mocks/{id}
[Section titled “GET /mocks/{id}”](#get-mocksid)
Get a specific mock by ID.
#### POST /mocks
[Section titled “POST /mocks”](#post-mocks)
Add a new mock at runtime.
**Request:**
```json
{
"type": "http",
"name": "Get users",
"http": {
"matcher": {
"method": "GET",
"path": "/api/users"
},
"response": {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "[{\"id\": 1, \"name\": \"Alice\"}]"
}
}
}
```
The `type` field determines the protocol. Protocol-specific config goes under the matching key (`http`, `graphql`, `grpc`, `websocket`, `mqtt`, `soap`, `oauth`).
Note
For convenience, bare `matcher`/`response` fields (without the `type`/`http` wrapper) are also accepted for HTTP mocks — the server infers `type: "http"` automatically. The wrapped format is recommended for clarity and required for non-HTTP protocols.
**Response:** Returns the created mock with generated ID (HTTP 201).
**Port Sharing for gRPC and MQTT:**
When creating a gRPC or MQTT mock on a port that’s already in use by another mock of the **same protocol** in the **same workspace**, the new services/topics are **merged** into the existing mock instead of creating a new one. This mirrors real-world behavior where a single gRPC server serves multiple services and a single MQTT broker handles multiple topics.
For gRPC, a second mock targeting the **same service + method** is also merged: when its `match` condition **differs** from the existing config, it is appended as an additional [match variant](/protocols/grpc/#multiple-match-conditions) for that method (evaluated in order, first-match-wins). Only a **true duplicate** — an identical `match`, or an empty `match` when a catch-all already exists — is rejected, since it would silently shadow the earlier mock.
**Merge Response (HTTP 200):**
```json
{
"action": "merged",
"message": "Merged into existing gRPC server on port 50051",
"targetMockId": "grpc_abc123",
"addedServices": ["myapp.HealthService/Check"],
"totalServices": ["myapp.UserService/GetUser", "myapp.HealthService/Check"],
"mock": { ... }
}
```
**Conflict cases (HTTP 409):**
* Different protocols on the same port (e.g., gRPC on an MQTT port)
* A gRPC service/method with an identical or shadowing `match` already exists, or an MQTT topic already exists
* Different workspaces trying to use the same port
#### PUT /mocks/{id}
[Section titled “PUT /mocks/{id}”](#put-mocksid)
Update an existing mock.
#### DELETE /mocks/{id}
[Section titled “DELETE /mocks/{id}”](#delete-mocksid)
Remove a mock.
#### POST /mocks/{id}/toggle
[Section titled “POST /mocks/{id}/toggle”](#post-mocksidtoggle)
Toggle a mock’s enabled state.
***
### Mock Verification
[Section titled “Mock Verification”](#mock-verification)
#### GET /mocks/{id}/verify
[Section titled “GET /mocks/{id}/verify”](#get-mocksidverify)
Get verification status for a mock (call count, timestamps).
**Response:**
```json
{
"mockId": "abc123",
"callCount": 5,
"firstCalledAt": "2024-01-15T10:30:00Z",
"lastCalledAt": "2024-01-15T10:35:00Z"
}
```
#### POST /mocks/{id}/verify
[Section titled “POST /mocks/{id}/verify”](#post-mocksidverify)
Verify mock was called expected number of times.
**Request:**
```json
{
"atLeast": 1,
"atMost": 10,
"exactly": null
}
```
**Response:**
```json
{
"success": true,
"message": "Mock called 5 times, expected at least 1"
}
```
#### GET /mocks/{id}/invocations
[Section titled “GET /mocks/{id}/invocations”](#get-mocksidinvocations)
List all invocations of a mock.
**Response:**
```json
{
"invocations": [
{
"timestamp": "2024-01-15T10:30:00Z",
"method": "GET",
"path": "/api/users",
"headers": {"User-Agent": "curl/7.68.0"},
"body": ""
}
],
"count": 5
}
```
#### DELETE /mocks/{id}/invocations
[Section titled “DELETE /mocks/{id}/invocations”](#delete-mocksidinvocations)
Reset verification data for a specific mock.
#### DELETE /verify
[Section titled “DELETE /verify”](#delete-verify)
Reset all verification data for all mocks.
***
### State Management (Stateful Resources)
[Section titled “State Management (Stateful Resources)”](#state-management-stateful-resources)
#### GET /state
[Section titled “GET /state”](#get-state)
Get stateful resource overview.
**Response:**
```json
{
"resources": [
{"name": "users", "itemCount": 10, "seedCount": 2, "idField": "id"},
{"name": "posts", "itemCount": 5, "seedCount": 3, "idField": "id"}
],
"total": 2,
"totalItems": 15,
"resourceList": ["users", "posts"]
}
```
#### POST /state/reset
[Section titled “POST /state/reset”](#post-statereset)
Reset all stateful resources to their seed data.
#### GET /state/resources
[Section titled “GET /state/resources”](#get-stateresources)
List all stateful resources (returns array).
#### POST /state/resources
[Section titled “POST /state/resources”](#post-stateresources)
Create a new stateful resource at runtime.
**Request:**
```json
{
"name": "products",
"idField": "id"
}
```
| Field | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------ |
| `name` | string | Yes | Resource name (must be unique) |
| `idField` | string | No | Custom ID field name (default: `id`) |
**Response:** `201 Created`
#### GET /state/resources/{name}
[Section titled “GET /state/resources/{name}”](#get-stateresourcesname)
Get details for a specific resource (item count, seed count, ID field).
#### POST /state/resources/{name}/reset
[Section titled “POST /state/resources/{name}/reset”](#post-stateresourcesnamereset)
Reset a specific resource to its seed data.
#### POST /reset
[Section titled “POST /reset”](#post-reset)
**Alias for `POST /state/reset`.** Resets all stateful tables to their seed data. Convenient shorthand for test setup scripts.
**Response:**
```json
{
"status": "reset",
"tables": 5
}
```
#### POST /reset/{table}
[Section titled “POST /reset/{table}”](#post-resettable)
**Alias for `POST /state/resources/{table}/reset`.** Resets a specific table to its seed data.
**Response:**
```json
{
"status": "reset",
"table": "customers"
}
```
#### DELETE /state/resources/{name}
[Section titled “DELETE /state/resources/{name}”](#delete-stateresourcesname)
Clear all items from a specific resource (does NOT restore seed data — use reset for that).
#### GET /state/resources/{name}/items
[Section titled “GET /state/resources/{name}/items”](#get-stateresourcesnameitems)
List items in a specific resource.
#### GET /state/resources/{name}/items/{id}
[Section titled “GET /state/resources/{name}/items/{id}”](#get-stateresourcesnameitemsid)
Get a specific item by ID.
#### POST /state/resources/{name}/items
[Section titled “POST /state/resources/{name}/items”](#post-stateresourcesnameitems)
Create a new item in a resource.
***
### Request History
[Section titled “Request History”](#request-history)
#### GET /requests
[Section titled “GET /requests”](#get-requests)
Get recent request history.
**Query Parameters:**
| Parameter | Description | Default |
| --------- | ------------------------- | ------- |
| `limit` | Max requests to return | `100` |
| `offset` | Pagination offset | `0` |
| `path` | Filter by path pattern | |
| `method` | Filter by HTTP method | |
| `matched` | Filter by matched mock ID | |
**Response:**
```json
{
"requests": [
{
"id": "req-123",
"timestamp": "2024-01-15T10:30:00Z",
"method": "GET",
"path": "/api/users",
"matchedMockID": "abc123",
"responseStatus": 200,
"durationMs": 5
}
],
"total": 150
}
```
#### GET /requests/{id}
[Section titled “GET /requests/{id}”](#get-requestsid)
Get details of a specific request including headers, body, and response.
#### DELETE /requests
[Section titled “DELETE /requests”](#delete-requests)
Clear request history.
#### GET /requests/stream
[Section titled “GET /requests/stream”](#get-requestsstream)
Server-Sent Events (SSE) endpoint for streaming new requests in real-time.
**Headers:**
```plaintext
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
```
**Events:**
```plaintext
event: connected
data: {"message": "Connected to request stream"}
event: request
data: {"id": "req-123", "method": "GET", "path": "/api/users", ...}
```
**Usage with curl:**
```bash
curl -N http://localhost:4290/requests/stream
```
**Usage with JavaScript:**
```javascript
const eventSource = new EventSource('http://localhost:4290/requests/stream');
eventSource.addEventListener('connected', (e) => {
console.log('Connected to request stream');
});
eventSource.addEventListener('request', (e) => {
const request = JSON.parse(e.data);
console.log('New request:', request.method, request.path);
});
```
This endpoint is useful for:
* Real-time request monitoring dashboards
* Live debugging during development
* Integration with external logging systems
***
### Proxy Management
[Section titled “Proxy Management”](#proxy-management)
#### GET /proxy/status
[Section titled “GET /proxy/status”](#get-proxystatus)
Get current proxy status.
**Response:**
```json
{
"running": true,
"port": 8888,
"mode": "record",
"sessionId": "session-123"
}
```
#### POST /proxy/start
[Section titled “POST /proxy/start”](#post-proxystart)
Start the MITM proxy.
**Request:**
```json
{
"port": 8888,
"mode": "record",
"sessionName": "my-session"
}
```
#### POST /proxy/stop
[Section titled “POST /proxy/stop”](#post-proxystop)
Stop the proxy.
#### PUT /proxy/mode
[Section titled “PUT /proxy/mode”](#put-proxymode)
Change proxy mode.
**Request:**
```json
{
"mode": "passthrough"
}
```
Modes: `record`, `passthrough`, `playback`
#### GET /proxy/filters
[Section titled “GET /proxy/filters”](#get-proxyfilters)
Get current recording filters.
#### PUT /proxy/filters
[Section titled “PUT /proxy/filters”](#put-proxyfilters)
Update recording filters.
***
### CA Certificate (HTTPS Interception)
[Section titled “CA Certificate (HTTPS Interception)”](#ca-certificate-https-interception)
#### GET /proxy/ca
[Section titled “GET /proxy/ca”](#get-proxyca)
Check if CA certificate exists.
**Response:**
```json
{
"exists": true,
"path": "/path/to/ca.crt",
"fingerprint": "AB:CD:EF:...",
"expiresAt": "2034-01-15T00:00:00Z",
"organization": "mockd Local CA"
}
```
#### POST /proxy/ca
[Section titled “POST /proxy/ca”](#post-proxyca)
Generate a new CA certificate.
**Request:**
```json
{
"caPath": "/path/to/store/ca"
}
```
#### GET /proxy/ca/download
[Section titled “GET /proxy/ca/download”](#get-proxycadownload)
Download the CA certificate (PEM format).
***
### Recording Sessions
[Section titled “Recording Sessions”](#recording-sessions)
#### GET /sessions
[Section titled “GET /sessions”](#get-sessions)
List all recording sessions.
#### POST /sessions
[Section titled “POST /sessions”](#post-sessions)
Create a new recording session.
#### GET /sessions/{id}
[Section titled “GET /sessions/{id}”](#get-sessionsid)
Get session details.
#### DELETE /sessions/{id}
[Section titled “DELETE /sessions/{id}”](#delete-sessionsid)
Delete a session.
***
### Recordings
[Section titled “Recordings”](#recordings)
#### GET /recordings
[Section titled “GET /recordings”](#get-recordings)
List all HTTP recordings.
**Query Parameters:**
| Parameter | Description |
| ----------- | ---------------------- |
| `sessionId` | Filter by session |
| `method` | Filter by HTTP method |
| `path` | Filter by path pattern |
#### GET /recordings/{id}
[Section titled “GET /recordings/{id}”](#get-recordingsid)
Get a specific recording.
#### DELETE /recordings/{id}
[Section titled “DELETE /recordings/{id}”](#delete-recordingsid)
Delete a recording.
#### DELETE /recordings
[Section titled “DELETE /recordings”](#delete-recordings)
Clear all recordings.
#### POST /recordings/convert
[Section titled “POST /recordings/convert”](#post-recordingsconvert)
Convert recordings to mocks.
**Request:**
```json
{
"sessionId": "session-123",
"deduplicate": true,
"includeHeaders": false
}
```
**Response:**
```json
{
"mockIds": ["mock-1", "mock-2"],
"count": 2
}
```
#### POST /recordings/export
[Section titled “POST /recordings/export”](#post-recordingsexport)
Export recordings to JSON or YAML.
**Request:**
```json
{
"format": "json",
"sessionId": "session-123"
}
```
| Field | Type | Description |
| -------------- | --------- | --------------------------------------------- |
| `format` | string | Output format: `"json"` (default) or `"yaml"` |
| `sessionId` | string | Optional: filter by session |
| `recordingIds` | string\[] | Optional: export specific recordings |
**YAML Export Example:**
```json
{
"format": "yaml",
"sessionId": "session-123"
}
```
Returns `Content-Type: application/x-yaml` for YAML format.
***
### Configuration
[Section titled “Configuration”](#configuration)
#### GET /config
[Section titled “GET /config”](#get-config)
Export current mock configuration.
#### POST /config
[Section titled “POST /config”](#post-config)
Import mock configuration.
**Request:**
```json
{
"config": {
"version": "1.0",
"mocks": [...]
},
"replace": false
}
```
***
### Export Formats
[Section titled “Export Formats”](#export-formats)
#### GET /insomnia.yaml
[Section titled “GET /insomnia.yaml”](#get-insomniayaml)
Export mocks as Insomnia v5 collection (YAML format, recommended).
**Response:** `Content-Type: application/x-yaml`
```yaml
type: collection.insomnia.rest/5.0
name: mockd Mocks
meta:
id: mockd_export
created: 1705315800000
modified: 1705315800000
resources:
- _id: wrk_mockd
_type: workspace
name: mockd Mocks
- _id: req_get_users
_type: request
parentId: wrk_mockd
name: Get Users
method: GET
url: http://localhost:4280/api/users
headers: []
parameters: []
```
**Usage:**
```bash
# Download and import into Insomnia
curl -o mockd-collection.yaml http://localhost:4290/insomnia.yaml
# Then: File > Import > From File in Insomnia
```
#### GET /insomnia.json
[Section titled “GET /insomnia.json”](#get-insomniajson)
Export mocks as Insomnia v4 collection (JSON format, legacy).
**Response:** `Content-Type: application/json`
```json
{
"_type": "export",
"__export_format": 4,
"__export_source": "mockd",
"resources": [
{
"_id": "wrk_mockd",
"_type": "workspace",
"name": "mockd Mocks"
},
{
"_id": "req_get_users",
"_type": "request",
"parentId": "wrk_mockd",
"name": "Get Users",
"method": "GET",
"url": "http://localhost:4280/api/users"
}
]
}
```
**Query Parameters:**
| Parameter | Description |
| ---------------------------- | ---------------------------------------- |
| `format=yaml` or `format=v5` | Force v5 YAML format on `/insomnia.json` |
**Features:**
* Exports all HTTP mocks as Insomnia requests
* Includes request headers and query parameters
* Creates appropriate Content-Type headers for JSON/XML bodies
* Organizes mocks in a workspace structure
* Supports SSE mocks (adds `Accept: text/event-stream` header)
* Supports SOAP mocks (adds SOAPAction headers)
***
### SSE Management
[Section titled “SSE Management”](#sse-management)
#### GET /sse/connections
[Section titled “GET /sse/connections”](#get-sseconnections)
List active SSE connections.
**Response:**
```json
{
"connections": [
{
"id": "sse-abc123",
"mockId": "mock-1",
"path": "/events",
"clientIp": "127.0.0.1",
"userAgent": "Mozilla/5.0",
"connectedAt": "2024-01-15T10:30:00Z",
"eventsSent": 42,
"bytesSent": 1024,
"status": "active"
}
],
"stats": {
"totalConnections": 10,
"activeConnections": 1,
"totalEventsSent": 500,
"totalBytesSent": 51200,
"connectionErrors": 0,
"connectionsByMock": {"mock-1": 1}
}
}
```
#### GET /sse/connections/{id}
[Section titled “GET /sse/connections/{id}”](#get-sseconnectionsid)
Get SSE connection details.
#### DELETE /sse/connections/{id}
[Section titled “DELETE /sse/connections/{id}”](#delete-sseconnectionsid)
Close an SSE connection.
SSE auto-disconnect on mock update
When an SSE mock is updated or deleted, all active SSE connections to that endpoint are automatically closed. Clients should reconnect to pick up the new configuration.
#### GET /sse/stats
[Section titled “GET /sse/stats”](#get-ssestats)
Get SSE statistics.
***
### WebSocket Management
[Section titled “WebSocket Management”](#websocket-management)
#### GET /websocket/connections
[Section titled “GET /websocket/connections”](#get-websocketconnections)
List active WebSocket connections.
**Response:**
```json
{
"connections": [
{
"id": "ws-abc123",
"mockId": "mock-1",
"path": "/ws/chat",
"connectedAt": "2024-01-15T10:30:00Z",
"messagesSent": 15,
"messagesRecv": 10,
"status": "connected"
}
],
"stats": {
"totalConnections": 50,
"activeConnections": 1,
"totalMessagesSent": 500,
"totalMessagesRecv": 300,
"connectionsByMock": {"mock-1": 1}
}
}
```
#### GET /websocket/connections/{id}
[Section titled “GET /websocket/connections/{id}”](#get-websocketconnectionsid)
Get connection details.
#### DELETE /websocket/connections/{id}
[Section titled “DELETE /websocket/connections/{id}”](#delete-websocketconnectionsid)
Close a WebSocket connection.
#### POST /websocket/connections/{id}/send
[Section titled “POST /websocket/connections/{id}/send”](#post-websocketconnectionsidsend)
Send a text or binary message to a specific active WebSocket connection.
**Request:**
```json
{
"type": "text",
"data": "Hello from server"
}
```
| Field | Type | Description |
| ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Message type: `"text"` (default) or `"binary"` |
| `data` | string | Message payload. For `"text"`, a plain UTF-8 string. For `"binary"`, a **base64-encoded** string — the server decodes it before writing raw bytes to the WebSocket. |
**Response:**
```json
{
"message": "Message sent",
"connection": "ws-abc123",
"type": "text"
}
```
Returns `404` if the connection is not found.
#### GET /mocks/{id}/websocket/connections
[Section titled “GET /mocks/{id}/websocket/connections”](#get-mocksidwebsocketconnections)
List active WebSocket connections for a specific mock.
**Response:** Same format as `GET /websocket/connections`, filtered to the given mock ID.
#### DELETE /mocks/{id}/websocket/connections
[Section titled “DELETE /mocks/{id}/websocket/connections”](#delete-mocksidwebsocketconnections)
Close all WebSocket connections for a specific mock.
#### GET /websocket/stats
[Section titled “GET /websocket/stats”](#get-websocketstats)
Get WebSocket statistics.
***
### Stream Recordings (WebSocket/SSE)
[Section titled “Stream Recordings (WebSocket/SSE)”](#stream-recordings-websocketsse)
#### GET /stream-recordings
[Section titled “GET /stream-recordings”](#get-stream-recordings)
List stream recordings.
#### GET /stream-recordings/{id}
[Section titled “GET /stream-recordings/{id}”](#get-stream-recordingsid)
Get stream recording details.
#### DELETE /stream-recordings/{id}
[Section titled “DELETE /stream-recordings/{id}”](#delete-stream-recordingsid)
Delete a stream recording.
#### POST /stream-recordings/{id}/export
[Section titled “POST /stream-recordings/{id}/export”](#post-stream-recordingsidexport)
Export stream recording.
#### POST /stream-recordings/{id}/convert
[Section titled “POST /stream-recordings/{id}/convert”](#post-stream-recordingsidconvert)
Convert stream recording to mock.
#### POST /stream-recordings/{id}/replay
[Section titled “POST /stream-recordings/{id}/replay”](#post-stream-recordingsidreplay)
Start replaying a stream recording.
#### GET /replay
[Section titled “GET /replay”](#get-replay)
List active replay sessions.
#### GET /replay/{id}
[Section titled “GET /replay/{id}”](#get-replayid)
Get replay session status.
#### DELETE /replay/{id}
[Section titled “DELETE /replay/{id}”](#delete-replayid)
Stop a replay session.
***
### MQTT Connection Management
[Section titled “MQTT Connection Management”](#mqtt-connection-management)
#### GET /mqtt-connections
[Section titled “GET /mqtt-connections”](#get-mqtt-connections)
List active MQTT client connections.
**Response:**
```json
{
"connections": [
{
"id": "client-abc123",
"brokerId": "mqtt-broker-1",
"connectedAt": "2024-01-15T10:30:00Z",
"subscriptions": ["sensors/#", "devices/+"],
"protocolVersion": 5,
"username": "device-1",
"remoteAddr": "192.168.1.10:54321",
"status": "connected"
}
],
"stats": {
"connectedClients": 1,
"totalSubscriptions": 2,
"topicCount": 5,
"port": 1883,
"tlsEnabled": false,
"authEnabled": false,
"subscriptionsByClient": {"client-abc123": 2}
}
}
```
#### GET /mqtt-connections/{id}
[Section titled “GET /mqtt-connections/{id}”](#get-mqtt-connectionsid)
Get details of a specific MQTT client connection.
#### DELETE /mqtt-connections/{id}
[Section titled “DELETE /mqtt-connections/{id}”](#delete-mqtt-connectionsid)
Disconnect an MQTT client.
#### GET /mqtt-connections/stats
[Section titled “GET /mqtt-connections/stats”](#get-mqtt-connectionsstats)
Get MQTT connection statistics.
***
### gRPC Stream Management
[Section titled “gRPC Stream Management”](#grpc-stream-management)
#### GET /grpc/connections
[Section titled “GET /grpc/connections”](#get-grpcconnections)
List active gRPC streaming RPCs.
**Response:**
```json
{
"streams": [
{
"id": "grpc-stream-1",
"method": "/myapp.ChatService/StreamMessages",
"streamType": "bidi",
"clientAddr": "127.0.0.1:54321",
"connectedAt": "2024-01-15T10:30:00Z",
"messagesSent": 15,
"messagesRecv": 10
}
],
"stats": {
"activeStreams": 1,
"totalStreams": 50,
"totalRPCs": 200,
"totalMessagesSent": 1000,
"totalMessagesRecv": 800,
"streamsByMethod": {"/myapp.ChatService/StreamMessages": 1}
}
}
```
#### GET /grpc/connections/{id}
[Section titled “GET /grpc/connections/{id}”](#get-grpcconnectionsid)
Get details of a specific gRPC stream.
#### DELETE /grpc/connections/{id}
[Section titled “DELETE /grpc/connections/{id}”](#delete-grpcconnectionsid)
Cancel a gRPC stream. The client receives a `codes.Unavailable` status, signaling it should reconnect.
#### GET /grpc/stats
[Section titled “GET /grpc/stats”](#get-grpcstats)
Get gRPC stream statistics.
***
### gRPC Management
[Section titled “gRPC Management”](#grpc-management)
#### GET /grpc
[Section titled “GET /grpc”](#get-grpc)
List all registered gRPC servers.
**Response:**
```json
{
"servers": [
{
"id": "grpc-server-1",
"address": ":50051",
"running": true
}
],
"count": 1
}
```
#### GET /grpc/{id}/status
[Section titled “GET /grpc/{id}/status”](#get-grpcidstatus)
Get gRPC server status.
***
### MQTT Recording
[Section titled “MQTT Recording”](#mqtt-recording)
#### GET /mqtt
[Section titled “GET /mqtt”](#get-mqtt)
List all registered MQTT brokers.
**Response:**
```json
{
"brokers": [
{
"id": "mqtt-broker-1",
"port": 1883,
"running": true,
"recordingEnabled": false
}
],
"count": 1
}
```
#### GET /mqtt/{id}/status
[Section titled “GET /mqtt/{id}/status”](#get-mqttidstatus)
Get MQTT broker status.
#### POST /mqtt/{id}/record/start
[Section titled “POST /mqtt/{id}/record/start”](#post-mqttidrecordstart)
Start recording MQTT messages.
#### POST /mqtt/{id}/record/stop
[Section titled “POST /mqtt/{id}/record/stop”](#post-mqttidrecordstop)
Stop recording MQTT messages.
#### GET /mqtt-recordings
[Section titled “GET /mqtt-recordings”](#get-mqtt-recordings)
List MQTT recordings.
**Query Parameters:**
| Parameter | Description |
| -------------- | ------------------------------------------------- |
| `topicPattern` | Filter by topic (supports MQTT wildcards + and #) |
| `clientId` | Filter by client ID |
| `direction` | Filter by direction (publish/subscribe) |
| `limit` | Max recordings to return |
| `offset` | Pagination offset |
#### GET /mqtt-recordings/{id}
[Section titled “GET /mqtt-recordings/{id}”](#get-mqtt-recordingsid)
Get a specific MQTT recording.
#### DELETE /mqtt-recordings/{id}
[Section titled “DELETE /mqtt-recordings/{id}”](#delete-mqtt-recordingsid)
Delete an MQTT recording.
#### DELETE /mqtt-recordings
[Section titled “DELETE /mqtt-recordings”](#delete-mqtt-recordings)
Clear all MQTT recordings.
#### GET /mqtt-recordings/stats
[Section titled “GET /mqtt-recordings/stats”](#get-mqtt-recordingsstats)
Get MQTT recording statistics.
#### POST /mqtt-recordings/convert
[Section titled “POST /mqtt-recordings/convert”](#post-mqtt-recordingsconvert)
Convert MQTT recordings to mock config.
**Request:**
```json
{
"recordingIds": ["mqtt-abc123"],
"topicPattern": "sensors/#",
"deduplicate": true,
"includeQoS": true,
"includeRetain": true
}
```
#### POST /mqtt-recordings/{id}/convert
[Section titled “POST /mqtt-recordings/{id}/convert”](#post-mqtt-recordingsidconvert)
Convert a single MQTT recording to mock config.
#### POST /mqtt-recordings/export
[Section titled “POST /mqtt-recordings/export”](#post-mqtt-recordingsexport)
Export all MQTT recordings as JSON.
***
### SOAP Recording
[Section titled “SOAP Recording”](#soap-recording)
#### GET /soap
[Section titled “GET /soap”](#get-soap)
List all registered SOAP handlers.
**Response:**
```json
{
"handlers": [
{
"id": "soap-handler-1",
"path": "/soap/service",
"recordingEnabled": false
}
],
"count": 1
}
```
#### GET /soap/{id}/status
[Section titled “GET /soap/{id}/status”](#get-soapidstatus)
Get SOAP handler status.
#### POST /soap/{id}/record/start
[Section titled “POST /soap/{id}/record/start”](#post-soapidrecordstart)
Start recording SOAP requests.
#### POST /soap/{id}/record/stop
[Section titled “POST /soap/{id}/record/stop”](#post-soapidrecordstop)
Stop recording SOAP requests.
#### GET /soap-recordings
[Section titled “GET /soap-recordings”](#get-soap-recordings)
List SOAP recordings.
**Query Parameters:**
| Parameter | Description |
| ------------ | ------------------------------------- |
| `endpoint` | Filter by endpoint path |
| `operation` | Filter by operation name |
| `soapAction` | Filter by SOAPAction header |
| `hasFault` | Filter by fault presence (true/false) |
| `limit` | Max recordings to return |
| `offset` | Pagination offset |
#### GET /soap-recordings/{id}
[Section titled “GET /soap-recordings/{id}”](#get-soap-recordingsid)
Get a specific SOAP recording.
#### DELETE /soap-recordings/{id}
[Section titled “DELETE /soap-recordings/{id}”](#delete-soap-recordingsid)
Delete a SOAP recording.
#### DELETE /soap-recordings
[Section titled “DELETE /soap-recordings”](#delete-soap-recordings)
Clear all SOAP recordings.
#### GET /soap-recordings/stats
[Section titled “GET /soap-recordings/stats”](#get-soap-recordingsstats)
Get SOAP recording statistics.
#### POST /soap-recordings/convert
[Section titled “POST /soap-recordings/convert”](#post-soap-recordingsconvert)
Convert SOAP recordings to mock config.
**Request:**
```json
{
"recordingIds": ["soap-abc123"],
"endpoint": "/soap/service",
"operation": "GetUser",
"deduplicate": true,
"includeDelay": false,
"preserveFaults": true
}
```
#### POST /soap-recordings/{id}/convert
[Section titled “POST /soap-recordings/{id}/convert”](#post-soap-recordingsidconvert)
Convert a single SOAP recording to mock config.
#### POST /soap-recordings/export
[Section titled “POST /soap-recordings/export”](#post-soap-recordingsexport)
Export all SOAP recordings as JSON.
***
### Chaos Injection
[Section titled “Chaos Injection”](#chaos-injection)
#### GET /chaos
[Section titled “GET /chaos”](#get-chaos)
Get current chaos configuration.
**Response:**
```json
{
"enabled": true,
"latency": {
"min": "100ms",
"max": "500ms",
"probability": 1.0
},
"errorRate": {
"probability": 0.1,
"statusCodes": [500, 502, 503],
"defaultCode": 500
}
}
```
#### PUT /chaos
[Section titled “PUT /chaos”](#put-chaos)
Update chaos configuration.
**Request:**
```json
{
"enabled": true,
"latency": {
"min": "50ms",
"max": "200ms",
"probability": 1.0
},
"errorRate": {
"probability": 0.1,
"statusCodes": [500, 503],
"defaultCode": 503
}
}
```
**Latency Config Fields:**
| Field | Type | Description |
| ------------- | ------ | -------------------------------------------------- |
| `min` | string | Minimum latency (Go duration, e.g., “50ms”, “1s”) |
| `max` | string | Maximum latency (Go duration, e.g., “200ms”, “2s”) |
| `probability` | float | Probability of applying latency (0.0 to 1.0) |
**Error Rate Config Fields:**
| Field | Type | Description |
| ------------- | ------ | ------------------------------------------------------- |
| `probability` | float | Probability of returning an error (0.0 to 1.0) |
| `statusCodes` | int\[] | List of HTTP status codes to randomly choose from |
| `defaultCode` | int | Default status code if statusCodes is empty (e.g., 500) |
#### GET /chaos/stats
[Section titled “GET /chaos/stats”](#get-chaosstats)
Get chaos injection statistics (total injected, latency count, error count, bandwidth count).
#### DELETE /chaos/stats
[Section titled “DELETE /chaos/stats”](#delete-chaosstats)
Reset chaos injection statistics counters to zero.
#### GET /chaos/profiles
[Section titled “GET /chaos/profiles”](#get-chaosprofiles)
List all available chaos profiles.
**Response:**
```json
[
{"name": "slow-api", "description": "Simulates slow upstream API"},
{"name": "flaky", "description": "Unreliable service with random errors"},
{"name": "offline", "description": "Service completely down"}
]
```
#### GET /chaos/profiles/{name}
[Section titled “GET /chaos/profiles/{name}”](#get-chaosprofilesname)
Get a specific chaos profile’s configuration.
#### POST /chaos/profiles/{name}/apply
[Section titled “POST /chaos/profiles/{name}/apply”](#post-chaosprofilesnameapply)
Apply a named chaos profile. This overwrites the current chaos configuration with the profile’s settings.
**Available profiles:** `slow-api`, `degraded`, `flaky`, `offline`, `timeout`, `rate-limited`, `mobile-3g`, `satellite`, `dns-flaky`, `overloaded`
#### GET /chaos/faults
[Section titled “GET /chaos/faults”](#get-chaosfaults)
Get the current state of all stateful chaos fault instances (circuit breakers, retry-after trackers, progressive degradation counters).
**Response:**
```json
{
"circuitBreakers": {
"0:0": {
"state": "closed",
"tripCount": 0,
"requestCount": 15,
"failureCount": 2
}
},
"retryAfterTrackers": {},
"progressiveDegradation": {
"1:0": {
"currentDelay": "150ms",
"requestCount": 10,
"errorCount": 0
}
}
}
```
Keys follow the format `ruleIdx:faultIdx` (e.g., `"0:0"` = first fault in the first rule).
#### POST /chaos/circuit-breaker/{key}/trip
[Section titled “POST /chaos/circuit-breaker/{key}/trip”](#post-chaoscircuit-breakerkeytrip)
Manually trip a circuit breaker, forcing it into the open state.
**Path Parameters:**
| Parameter | Description |
| --------- | -------------------------------------------------------------- |
| `key` | Circuit breaker key in `ruleIdx:faultIdx` format (e.g., `0:0`) |
#### POST /chaos/circuit-breaker/{key}/reset
[Section titled “POST /chaos/circuit-breaker/{key}/reset”](#post-chaoscircuit-breakerkeyreset)
Reset a circuit breaker back to the closed state.
**Path Parameters:**
| Parameter | Description |
| --------- | -------------------------------------------------------------- |
| `key` | Circuit breaker key in `ruleIdx:faultIdx` format (e.g., `0:0`) |
***
### Workspaces
[Section titled “Workspaces”](#workspaces)
#### GET /workspaces
[Section titled “GET /workspaces”](#get-workspaces)
List all workspaces.
**Response:**
```json
{
"workspaces": [
{
"id": "ws_abc123",
"name": "Payment API",
"type": "local",
"description": "Stripe mock environment"
}
],
"count": 1
}
```
#### POST /workspaces
[Section titled “POST /workspaces”](#post-workspaces)
Create a new workspace.
**Request:**
```json
{
"name": "Payment API",
"type": "local",
"description": "Stripe mock environment"
}
```
#### GET /workspaces/{id}
[Section titled “GET /workspaces/{id}”](#get-workspacesid)
Get workspace details.
#### PUT /workspaces/{id}
[Section titled “PUT /workspaces/{id}”](#put-workspacesid)
Update a workspace.
#### DELETE /workspaces/{id}
[Section titled “DELETE /workspaces/{id}”](#delete-workspacesid)
Delete a workspace.
***
## Error Responses
[Section titled “Error Responses”](#error-responses)
All errors return a consistent format:
```json
{
"error": "error_code",
"message": "Human readable message"
}
```
### Common Error Codes
[Section titled “Common Error Codes”](#common-error-codes)
| Code | HTTP Status | Description |
| ------------------ | ----------- | ----------------------- |
| `not_found` | 404 | Resource not found |
| `invalid_json` | 400 | Invalid JSON in request |
| `validation_error` | 400 | Validation failed |
| `missing_field` | 400 | Required field missing |
***
## Examples
[Section titled “Examples”](#examples)
### Reset State Before Tests
[Section titled “Reset State Before Tests”](#reset-state-before-tests)
```bash
curl -X POST http://localhost:4290/state/reset
```
### Add Mock at Runtime
[Section titled “Add Mock at Runtime”](#add-mock-at-runtime)
```bash
curl -X POST http://localhost:4290/mocks \
-H "Content-Type: application/json" \
-d '{
"type": "http",
"name": "Test endpoint",
"http": {
"matcher": {"method": "GET", "path": "/api/test"},
"response": {"statusCode": 200, "body": "{\"test\": true}"}
}
}'
```
### Check Request History
[Section titled “Check Request History”](#check-request-history)
```bash
curl "http://localhost:4290/requests?limit=10&path=/api/users"
```
### Start Proxy Recording
[Section titled “Start Proxy Recording”](#start-proxy-recording)
```bash
curl -X POST http://localhost:4290/proxy/start \
-H "Content-Type: application/json" \
-d '{"port": 8888, "mode": "record", "sessionName": "test-session"}'
```
### Convert Recordings to Mocks
[Section titled “Convert Recordings to Mocks”](#convert-recordings-to-mocks)
```bash
curl -X POST http://localhost:4290/recordings/convert \
-H "Content-Type: application/json" \
-d '{"deduplicate": true}'
```
## See Also
[Section titled “See Also”](#see-also)
* [CLI Reference](/reference/cli) - Command-line options
* [Configuration Reference](/reference/configuration) - Config file format
* [Stateful Mocking](/guides/stateful-mocking) - State management
# CLI Reference
> Complete reference for the mockd command-line interface, including all commands, flags, and usage examples.
Complete reference for the mockd command-line interface.
## Global Flags
[Section titled “Global Flags”](#global-flags)
These flags apply to all commands:
| Flag | Description |
| --------------- | ------------------------------------------------------------------------------------ |
| `-h, --help` | Show help message |
| `-v, --version` | Show version information |
| `--admin-url` | Admin API base URL (default: ) |
| `--workspace` | Workspace to scope operations to (overrides MOCKD\_WORKSPACE env and context config) |
| `--json` | Output command results in JSON format |
***
## Autocomplete
[Section titled “Autocomplete”](#autocomplete)
mockd leverages Cobra to generate autocomplete scripts for popular shells.
### mockd completion
[Section titled “mockd completion”](#mockd-completion)
Generate the autocompletion script for the specified shell.
```bash
mockd completion [command]
```
**Commands:**
| Command | Description |
| ------------ | ------------------------------------------------- |
| `bash` | Generate the autocompletion script for bash |
| `fish` | Generate the autocompletion script for fish |
| `powershell` | Generate the autocompletion script for powershell |
| `zsh` | Generate the autocompletion script for zsh |
**Examples:**
```bash
# Load bash completion in the current shell
source <(mockd completion bash)
# Load zsh completion in the current shell
source <(mockd completion zsh)
# Configure bash completion permanently (Linux)
mockd completion bash > /etc/bash_completion.d/mockd
```
***
## Server Commands
[Section titled “Server Commands”](#server-commands)
### mockd serve
[Section titled “mockd serve”](#mockd-serve)
Start the mock server (default command).
```bash
mockd serve [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------------- | ----- | ------------------------------------------------------------- | ------- |
| `--port` | `-p` | HTTP server port | `4280` |
| `--admin-port` | `-a` | Admin API port | `4290` |
| `--config` | `-c` | Path to mock configuration file | |
| `--https-port` | | HTTPS server port (0 = disabled) | `0` |
| `--read-timeout` | | Read timeout in seconds | `30` |
| `--write-timeout` | | Write timeout in seconds | `30` |
| `--request-timeout` | | Request timeout in seconds (sets both read and write timeout) | `0` |
| `--max-log-entries` | | Maximum request log entries | `1000` |
| `--max-connections` | | Maximum concurrent HTTP connections (0 = unlimited) | `0` |
| `--auto-cert` | | Auto-generate TLS certificate | `true` |
**CORS Flags:**
| Flag | Description | Default |
| ---------------- | --------------------------------------------------------------------------------- | ------- |
| `--cors-origins` | Comma-separated CORS allowed origins (e.g., `'*'` or `'https://app.example.com'`) | |
**Rate Limiting Flags:**
| Flag | Description | Default |
| -------------- | ------------------------------------------------ | ------- |
| `--rate-limit` | Rate limit in requests per second (0 = disabled) | `0` |
**Persistence Flags:**
| Flag | Description | Default |
| -------------- | ------------------------------------------------------ | ------- |
| `--no-persist` | Disable persistent storage (mocks are lost on restart) | `false` |
**Storage Flags:**
| Flag | Description | Default |
| ------------ | ------------------------------------------- | ---------------------- |
| `--data-dir` | Data directory for persistent storage | `~/.local/share/mockd` |
| `--no-auth` | Disable API key authentication on admin API | `false` |
**TLS Flags:**
| Flag | Description |
| ------------ | ------------------------------------- |
| `--tls-cert` | Path to TLS certificate file |
| `--tls-key` | Path to TLS private key file |
| `--tls-auto` | Auto-generate self-signed certificate |
**mTLS Flags:**
| Flag | Description |
| -------------------- | ------------------------------------------------------------------------------ |
| `--mtls-enabled` | Enable mTLS client certificate validation |
| `--mtls-client-auth` | Client auth mode (none, request, require, verify-if-given, require-and-verify) |
| `--mtls-ca` | Path to CA certificate for client validation |
| `--mtls-allowed-cns` | Comma-separated list of allowed Common Names |
**Audit Flags:**
| Flag | Description |
| ----------------- | ------------------------------------ |
| `--audit-enabled` | Enable audit logging |
| `--audit-file` | Path to audit log file |
| `--audit-level` | Log level (debug, info, warn, error) |
**Runtime Mode Flags (register with control plane):**
| Flag | Description | Default |
| ----------------- | ------------------------------------------------ | ---------------------- |
| `--register` | Register with control plane as a runtime | |
| `--control-plane` | Control plane URL | `https://api.mockd.io` |
| `--token` | Runtime token (or MOCKD\_RUNTIME\_TOKEN env var) | |
| `--name` | Runtime name (required with —register) | |
| `--labels` | Runtime labels (key=value,key2=value2) | |
**Pull Mode Flags:**
| Flag | Description |
| --------- | -------------------------------------- |
| `--pull` | mockd:// URI to pull and serve |
| `--cache` | Local cache directory for pulled mocks |
**GraphQL Flags:**
| Flag | Description | Default |
| ------------------ | --------------------------- | ---------- |
| `--graphql-schema` | Path to GraphQL schema file | |
| `--graphql-path` | GraphQL endpoint path | `/graphql` |
**OAuth Flags:**
| Flag | Description |
| ----------------- | --------------------- |
| `--oauth-enabled` | Enable OAuth provider |
| `--oauth-issuer` | OAuth issuer URL |
| `--oauth-port` | OAuth server port |
**Chaos Flags:**
| Flag | Description |
| -------------------- | -------------------------------------------------------------------------------- |
| `--chaos-profile` | Apply a built-in chaos profile at startup (e.g., `slow-api`, `flaky`, `offline`) |
| `--chaos-enabled` | Enable chaos injection with custom settings |
| `--chaos-latency` | Add random latency (e.g., “10ms-100ms”) |
| `--chaos-error-rate` | Error rate (0.0-1.0) |
> **Note:** `--chaos-profile` cannot be combined with `--chaos-enabled`, `--chaos-latency`, or `--chaos-error-rate`. Use either a profile or custom settings, not both.
**MCP Flags:**
| Flag | Description | Default |
| -------------------- | ------------------------------------------------------ | ------- |
| `--mcp` | Enable MCP (Model Context Protocol) HTTP server | `false` |
| `--mcp-port` | MCP server port | `9091` |
| `--mcp-allow-remote` | Allow remote MCP connections (default: localhost only) | `false` |
**Validation Flags:**
| Flag | Description | Default |
| ----------------- | ------------------------------------------- | ------- |
| `--validate-spec` | Path to OpenAPI spec for request validation | |
| `--validate-fail` | Fail on validation error | `false` |
**Daemon Flags:**
| Flag | Short | Description | Default |
| ------------ | ----- | -------------------------------------- | -------------------- |
| `--detach` | `-d` | Run server in background (daemon mode) | |
| `--pid-file` | | Path to PID file | `~/.mockd/mockd.pid` |
**Logging Flags:**
| Flag | Description | Default |
| ----------------- | ------------------------------------ | ------- |
| `--log-level` | Log level (debug, info, warn, error) | `info` |
| `--log-format` | Log format (text, json) | `text` |
| `--loki-endpoint` | Loki endpoint for log aggregation | |
**Tracing Flags:**
| Flag | Description | Default |
| ----------------- | ------------------------------------------------------------------------------------ | ------- |
| `--otlp-endpoint` | OTLP HTTP endpoint for distributed tracing (e.g., ) | |
| `--trace-sampler` | Trace sampling ratio (0.0-1.0) | `1.0` |
**Examples:**
```bash
# Start with defaults
mockd serve
# Start with config file on custom port
mockd serve --config mocks.json --port 3000
# Register as a runtime
mockd serve --register --name ci-runner-1 --token $MOCKD_RUNTIME_TOKEN
# Pull and serve from cloud
mockd serve --pull mockd://acme/payment-api
# Start with TLS using certificate files
mockd serve --tls-cert server.crt --tls-key server.key --https-port 8443
# Start with mTLS enabled
mockd serve --mtls-enabled --mtls-ca ca.crt --tls-cert server.crt --tls-key server.key
# Start with audit logging
mockd serve --audit-enabled --audit-file audit.log --audit-level debug
# Start in daemon/background mode
mockd serve -d
# Start with distributed tracing (send traces to Jaeger)
mockd serve --otlp-endpoint http://localhost:4318/v1/traces
# Start with JSON structured logging
mockd serve --log-level debug --log-format json
# Send logs to Loki for aggregation
mockd serve --loki-endpoint http://localhost:3100/loki/api/v1/push
# Allow CORS from any origin
mockd serve --cors-origins '*'
# Allow CORS from specific origins
mockd serve --cors-origins 'https://app.example.com,https://admin.example.com'
# Rate limit to 100 requests per second
mockd serve --rate-limit 100
# Ephemeral mode — mocks are lost on restart
mockd serve --no-persist
# Watch config file for changes and auto-reload
# Combine: ephemeral server with CORS and rate limiting
mockd serve --no-persist --cors-origins '*' --rate-limit 50 --config mocks.yaml
```
***
### mockd start
[Section titled “mockd start”](#mockd-start)
Start the mock server (alias for serve with additional directory loading features).
```bash
mockd start [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------------- | ----- | ------------------------------------------------------- | -------------------- |
| `--port` | `-p` | HTTP server port | `4280` |
| `--admin-port` | `-a` | Admin API port | `4290` |
| `--config` | `-c` | Path to mock configuration file | |
| `--load` | | Load mocks from directory | |
| `--validate` | | Validate files before serving (with —load) | |
| `--watch` | | Watch for file changes and auto-reload (with —load) | `false` |
| `--engine-name` | | Name for this engine when registering with admin | |
| `--admin-url` | | Admin server URL to register with (enables engine mode) | |
| `--https-port` | | HTTPS server port (0 = disabled) | `0` |
| `--read-timeout` | | Read timeout in seconds | `30` |
| `--write-timeout` | | Write timeout in seconds | `30` |
| `--max-log-entries` | | Maximum request log entries | `1000` |
| `--auto-cert` | | Auto-generate TLS certificate | `true` |
| `--detach` | `-d` | Run server in background (daemon mode) | `false` |
| `--pid-file` | | Path to PID file | `~/.mockd/mockd.pid` |
| `--log-level` | | Log level (debug, info, warn, error) | `info` |
| `--log-format` | | Log format (text, json) | `text` |
Also supports all TLS, mTLS, Audit, GraphQL, gRPC, OAuth, MQTT, Chaos, Validation, and Storage flags from `serve`.
**Examples:**
```bash
# Start with defaults
mockd start
# Start with config file on custom port
mockd start --config mocks.json --port 3000
# Start with HTTPS enabled
mockd start --https-port 8443
# Load mocks from directory
mockd start --load ./mocks/
# Validate mocks before serving
mockd start --load ./mocks/ --validate
```
***
### mockd stop
[Section titled “mockd stop”](#mockd-stop)
Stop a running mockd server.
```bash
mockd stop [component] [flags]
```
**Arguments:**
| Argument | Description |
| ----------- | ----------------------------------------------------------------------------- |
| `component` | Optional component to stop: “admin” or “engine” (if not specified, stops all) |
**Flags:**
| Flag | Short | Description | Default |
| ------------ | ----- | --------------------------------------- | -------------------- |
| `--pid-file` | | Path to PID file | `~/.mockd/mockd.pid` |
| `--force` | `-f` | Send SIGKILL instead of SIGTERM | |
| `--timeout` | | Timeout in seconds to wait for shutdown | `10` |
**Examples:**
```bash
# Stop all components
mockd stop
# Force stop
mockd stop --force
# Stop with custom PID file
mockd stop --pid-file /tmp/mockd.pid
# Stop with longer timeout
mockd stop --timeout 30
```
***
### mockd status
[Section titled “mockd status”](#mockd-status)
Show status of running mockd server.
```bash
mockd status [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------ | --------------------- | -------------------- |
| `--pid-file` | Path to PID file | `~/.mockd/mockd.pid` |
| `--json` | Output in JSON format | |
**Examples:**
```bash
# Check server status
mockd status
# Output as JSON
mockd status --json
# Use custom PID file
mockd status --pid-file /tmp/mockd.pid
```
***
### mockd mcp
[Section titled “mockd mcp”](#mockd-mcp)
Start the MCP (Model Context Protocol) server in stdio mode. Reads JSON-RPC from stdin, writes responses to stdout. Used by AI-powered editors (Claude Code, Cursor, Windsurf) to interact with mockd directly.
If no mockd server is running, **auto-starts a background daemon** so AI assistants work with zero setup. The daemon survives the MCP session and is shared across multiple sessions.
```bash
mockd mcp [flags]
```
**Flags:**
| Flag | Description | Default |
| -------------- | ------------------------------------------------------ | ------------------------------------- |
| `--admin-url` | Connect to a specific admin API URL (skips auto-start) | |
| `--data-dir` | Project-scoped data directory (starts separate daemon) | |
| `--config` | Config file to load on daemon startup | |
| `--port` | Mock server port for project daemon | `4280` (or `14280` with `--data-dir`) |
| `--admin-port` | Admin API port for project daemon | `4290` (or `14290` with `--data-dir`) |
| `--log-level` | Log level for stderr output (debug, info, warn, error) | `warn` |
**Connection Strategy:**
1. If `--admin-url` is given, connects directly to that server
2. Otherwise, checks the PID file and default URL for a running server
3. If nothing is running, auto-starts `mockd start --detach --no-auth`
**Examples:**
```bash
# Basic usage — auto-starts daemon if needed
mockd mcp
# Connect to a specific server (no auto-start)
mockd mcp --admin-url http://localhost:4290
# Project-scoped isolation (separate daemon per project)
mockd mcp --data-dir ./mockd-data
# Load a config file when auto-starting
mockd mcp --data-dir ./mockd-data --config mocks.yaml
# Custom ports for project daemon
mockd mcp --data-dir ./mockd-data --port 5000 --admin-port 5001
```
**Editor Integration:**
Add to your MCP config file:
```json
{
"mcpServers": {
"mockd": {
"command": "mockd",
"args": ["mcp"]
}
}
}
```
See the [MCP Server guide](/guides/mcp-server/) for detailed editor setup (Claude Code, Cursor, Windsurf).
***
### mockd engine
[Section titled “mockd engine”](#mockd-engine)
Run a headless mock engine — no admin API, no data persistence, no PID files. Ideal for CI/CD pipelines, Docker containers, and environments where you need a lightweight, stateless mock server.
```bash
mockd engine [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ----------------- | ----- | ----------------------------------------- | --------- |
| `--config` | `-c` | Path to mock config file (YAML or JSON) | Required |
| `--port` | `-p` | HTTP server port (0 = OS auto-assign) | `4280` |
| `--host` | | Bind address | `0.0.0.0` |
| `--print-url` | | Print the server URL to stdout on startup | `false` |
| `--log-level` | | Log level (debug, info, warn, error) | `warn` |
| `--log-format` | | Log format (text, json) | `text` |
| `--read-timeout` | | HTTP read timeout in seconds | `30` |
| `--write-timeout` | | HTTP write timeout in seconds | `30` |
Unlike `mockd serve` or `mockd start`, `mockd engine`:
* Does **not** start an admin API
* Does **not** persist data to disk
* Does **not** create PID files or support daemon mode
* Loads all mocks from a single config file
* Runs in the foreground until SIGTERM/SIGINT
**Examples:**
```bash
# Start with a config file
mockd engine --config mocks.yaml
# Auto-assign a port and print it (great for CI where ports may conflict)
mockd engine --config mocks.yaml --port 0 --print-url
# JSON logs for CI log parsing
mockd engine --config mocks.yaml --log-format json
# Custom timeouts for slow-response testing
mockd engine --config mocks.yaml --read-timeout 60 --write-timeout 60
```
**CI/CD Usage:**
```bash
# In a GitHub Action or CI script
mockd engine --config test-mocks.yaml --port 0 --print-url &
MOCKD_URL=$(head -1 /dev/stdin)
# Run tests against $MOCKD_URL
pytest tests/ --base-url "$MOCKD_URL"
kill %1
```
***
### mockd up
[Section titled “mockd up”](#mockd-up)
Start local admins and engines defined in `mockd.yaml`. Validates the project configuration, starts servers, and bootstraps workspaces seamlessly.
```bash
mockd up [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | -------------------------------------------------- | ------- |
| `--config` | `-f` | Config file path (can be specified multiple times) | |
| `--detach` | `-d` | Run in background (daemon mode) | `false` |
| `--log-level` | | Log level (debug, info, warn, error) | `info` |
**Examples:**
```bash
mockd up
mockd up -f custom-mockd.yaml -d
```
***
### mockd down
[Section titled “mockd down”](#mockd-down)
Stop all services started by a previous `mockd up` command.
```bash
mockd down [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------ | ------------------------- | -------------------- |
| `--pid-file` | Path to PID file | `~/.mockd/mockd.pid` |
| `--timeout` | Shutdown timeout duration | `30s` |
***
### mockd ports
[Section titled “mockd ports”](#mockd-ports)
Show all ports in use by the running mockd server.
```bash
mockd ports [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| -------------- | ----- | ----------------------- | -------------------- |
| `--pid-file` | | Path to PID file | `~/.mockd/mockd.pid` |
| `--admin-port` | `-a` | Admin API port to query | `4290` |
| `--json` | | Output in JSON format | |
**Output:**
The command displays a table of all ports with their protocol, component, and status:
```plaintext
PORT PROTOCOL COMPONENT STATUS
------- ---------- --------------- --------
1883 MQTT MQTT Broker running
4280 HTTP Mock Engine running
4290 HTTP Admin API running
50051 gRPC gRPC Server running
```
**Examples:**
```bash
# Show all ports
mockd ports
# Output as JSON
mockd ports --json
# Query a different admin port
mockd ports --admin-port 8090
```
***
### mockd init
[Section titled “mockd init”](#mockd-init)
Create a starter mockd configuration file.
```bash
mockd init [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| --------------- | ----- | -------------------------------------------- | ---------------------- |
| `--force` | | Overwrite existing config file | |
| `--output` | `-o` | Output filename | `mockd.yaml` |
| `--format` | | Output format: yaml or json | inferred from filename |
| `--interactive` | `-i` | Interactive mode - prompts for configuration | |
| `--template` | `-t` | Template to use | `default` |
**Templates:**
| Template | Description |
| ---------------- | -------------------------------------- |
| `default` | Basic HTTP mocks (hello, echo, health) |
| `crud` | Full REST CRUD API for resources |
| `websocket-chat` | Chat room WebSocket endpoint with echo |
| `graphql-api` | GraphQL API with User CRUD resolvers |
| `grpc-service` | gRPC Greeter service with reflection |
| `mqtt-iot` | MQTT broker with IoT sensor topics |
**Examples:**
```bash
# Create default mockd.yaml
mockd init
# List available templates
mockd init --template list
# Use CRUD API template
mockd init --template crud
# Use WebSocket template with custom output
mockd init -t websocket-chat -o websocket.yaml
# Interactive setup
mockd init -i
# Create with custom filename
mockd init -o my-mocks.yaml
# Create JSON config
mockd init --format json -o mocks.json
# Overwrite existing config
mockd init --force
```
***
## Mock Creation Commands
[Section titled “Mock Creation Commands”](#mock-creation-commands)
The `mockd` CLI organizes mock creation by protocol. Each protocol has its own ` add` subcommand.
By default, if a mock already exists with the same method and path (or equivalent identifiers), it is **updated in place** (upsert behavior). The command prints “Updated mock: …” when an existing mock is modified and “Created mock: …” when a new mock is created.
Upsert by Default
Use `--allow-duplicate` if you intentionally need multiple mocks on the same route (e.g., with different matchers like headers or query parameters).
Interactive Forms
If you omit **required flags** for complex mock types (like `mockd grpc add` without a `--proto`), mockd will gracefully fall back to an interactive terminal form using `charmbracelet/huh` to walk you through the configuration step-by-step!
### Global Add Flags
[Section titled “Global Add Flags”](#global-add-flags)
The following flags apply to **all** ` add` commands:
| Flag | Short | Description | Default |
| ------------------- | ----- | ----------------------------------------------------------------- | ----------------------- |
| `--name` | `-n` | Mock display name | |
| `--allow-duplicate` | | Create a second mock even if one already exists on the same route | `false` |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
| `--json` | | Output in JSON format | |
***
### mockd http add
[Section titled “mockd http add”](#mockd-http-add)
Add or update an HTTP or SSE mock endpoint.
```bash
mockd http add [flags]
```
**HTTP Flags:**
| Flag | Short | Description | Default |
| ----------------------- | ----- | ----------------------------------------------------------------- | -------- |
| `--method` | `-m` | HTTP method to match | `GET` |
| `--path` | | URL path to match | Required |
| `--status` | `-s` | Response status code | `200` |
| `--body` | `-b` | Response body | |
| `--body-file` | | Read response body from file | |
| `--header` | `-H` | Response header (key:value), repeatable | |
| `--match-header` | | Required request header (key:value), repeatable | |
| `--match-query` | | Required query param (key=value or key:value), repeatable | |
| `--match-body-contains` | | Match requests whose body contains this string | |
| `--path-pattern` | | Regex path pattern for matching (alternative to `--path`) | |
| `--priority` | | Mock priority (higher = matched first) | |
| `--delay` | | Response delay in milliseconds | |
| `--stateful-operation` | | Wire to a custom stateful operation (e.g., TransferFunds) | |
| `--table` | | Bind to a stateful resource table (e.g., users) | |
| `--bind` | | Stateful action: list, get, create, update, patch, delete, custom | |
**SSE Flags (for streaming):**
| Flag | Description | Default |
| ----------------- | --------------------------------------------------- | ------- |
| `--sse` | Enable SSE streaming response | |
| `--sse-event` | SSE event (type:data), repeatable | |
| `--sse-delay` | Delay between events in milliseconds | `100` |
| `--sse-template` | Built-in template: openai-chat, notification-stream | |
| `--sse-repeat` | Repeat events N times (0 = infinite) | `1` |
| `--sse-keepalive` | Keepalive interval in milliseconds (0 = disabled) | `0` |
**Examples:**
```bash
mockd http add --path /api/users --status 200 --body '[{"id":1}]'
mockd http add -m POST --path /api/users -s 201 -b '{"created": true}'
mockd http add --path /events --sse --sse-event 'connected:{"status":"ok"}'
mockd http add -m POST --path /api/transfer --stateful-operation TransferFunds
# Stateful bindings — bind to a table for automatic CRUD
mockd http add --path /api/users --table users --bind list
mockd http add -m POST --path /api/users --table users --bind create
mockd http add --path /api/users/{id} --table users --bind get
mockd http add -m PUT --path /api/users/{id} --table users --bind update
mockd http add -m DELETE --path /api/users/{id} --table users --bind delete
mockd http add -m POST --path /api/users/{id}/verify --table users --bind custom --stateful-operation VerifyUser
```
***
### mockd websocket add
[Section titled “mockd websocket add”](#mockd-websocket-add)
Add or update a WebSocket mock endpoint.
```bash
mockd websocket add [flags]
```
**WebSocket Flags:**
| Flag | Description | Default |
| ----------- | ------------------------------- | -------- |
| `--path` | WebSocket path | Required |
| `--message` | Default response message (JSON) | |
| `--echo` | Enable echo mode | |
**Examples:**
```bash
mockd websocket add --path /ws/chat --echo
mockd websocket add --path /ws/events --message '{"type": "connected"}'
```
***
### mockd graphql add
[Section titled “mockd graphql add”](#mockd-graphql-add)
Add or update a GraphQL mock endpoint.
```bash
mockd graphql add [flags]
```
**GraphQL Flags:**
| Flag | Description | Default |
| ------------- | --------------------------------- | ---------- |
| `--path` | GraphQL endpoint path | `/graphql` |
| `--operation` | Operation name | Required |
| `--op-type` | Operation type: query or mutation | `query` |
| `--response` | JSON response data | |
**Examples:**
```bash
mockd graphql add --operation getUser --response '{"data": {"user": {"id": "1"}}}'
```
***
### mockd graphql validate
[Section titled “mockd graphql validate”](#mockd-graphql-validate)
Validate a GraphQL schema file.
```bash
mockd graphql validate
```
***
### mockd graphql query
[Section titled “mockd graphql query”](#mockd-graphql-query)
Execute a query against a GraphQL endpoint.
```bash
mockd graphql query [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ---------- | ----- | ------------------------------------------ | ------- |
| `--header` | `-H` | Additional headers (key:value,key2:value2) | |
| `--pretty` | | Pretty print output | `true` |
**Examples:**
```bash
mockd graphql query http://localhost:4280/graphql 'query { getUser { id name } }'
```
***
### mockd grpc add
[Section titled “mockd grpc add”](#mockd-grpc-add)
Add or update a gRPC mock endpoint.
```bash
mockd grpc add [flags]
```
**gRPC Flags:**
| Flag | Description | Default |
| -------------- | ------------------------------------------------ | ------- |
| `--proto` | Path to .proto file (required, repeatable) | |
| `--proto-path` | Import path for proto dependencies (repeatable) | |
| `--service` | Service name, e.g., myapp.UserService (required) | |
| `--rpc-method` | RPC method name (required) | |
| `--response` | JSON response data | |
| `--grpc-port` | gRPC server port | `50051` |
**Examples:**
```bash
mockd grpc add --proto ./user.proto --service myapp.UserService --rpc-method GetUser --response '{"id": "1"}'
```
***
### mockd grpc call
[Section titled “mockd grpc call”](#mockd-grpc-call)
Call a gRPC endpoint directly from the CLI.
```bash
mockd grpc call [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | ------------------------- | ------- |
| `--metadata` | `-m` | gRPC metadata (key:value) | |
| `--plaintext` | | Use plaintext connection | `true` |
| `--pretty` | | Pretty print JSON output | `true` |
**Examples:**
```bash
mockd grpc call localhost:50051 myapp.UserService/GetUser '{"id": "1"}'
```
***
### mockd mqtt add
[Section titled “mockd mqtt add”](#mockd-mqtt-add)
Add or update an MQTT mock endpoint.
```bash
mockd mqtt add [flags]
```
**MQTT Flags:**
| Flag | Description | Default |
| ------------- | --------------------------- | -------- |
| `--topic` | Topic pattern | Required |
| `--payload` | Response payload | |
| `--qos` | QoS level: 0, 1, or 2 | `0` |
| `--mqtt-port` | MQTT broker port (required) | |
**Examples:**
```bash
mockd mqtt add --topic sensors/temperature --payload '{"temp": 72.5}' --qos 1
```
***
### mockd mqtt publish
[Section titled “mockd mqtt publish”](#mockd-mqtt-publish)
Publish a message to an MQTT topic.
```bash
mockd mqtt publish [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------ | ----- | -------------------------------------------------- | ------- |
| `--message` | `-m` | Message to publish (alternative to positional arg) | |
| `--qos` | `-q` | QoS level (0, 1, 2) | `0` |
| `--retain` | `-r` | Retain message | `false` |
| `--username` | `-u` | MQTT username | |
| `--password` | `-P` | MQTT password | |
***
### mockd mqtt subscribe
[Section titled “mockd mqtt subscribe”](#mockd-mqtt-subscribe)
Subscribe to an MQTT topic and print messages to the console.
```bash
mockd mqtt subscribe [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------ | ----- | ----------------------------------------------------------- | ------- |
| `--qos` | `-q` | QoS level (0, 1, 2) | `0` |
| `--username` | `-u` | MQTT username | |
| `--password` | `-P` | MQTT password | |
| `--count` | `-c` | Number of messages to receive before exiting (0 = infinite) | `0` |
***
### mockd mqtt status
[Section titled “mockd mqtt status”](#mockd-mqtt-status)
Show the current mockd MQTT broker status.
```bash
mockd mqtt status [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | ------------------ | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
***
### mockd soap add
[Section titled “mockd soap add”](#mockd-soap-add)
Add or update a SOAP mock endpoint.
```bash
mockd soap add [flags]
```
**SOAP Flags:**
| Flag | Description | Default |
| --------------------- | ---------------------------------------------------------------------- | -------- |
| `--path` | SOAP endpoint path | `/soap` |
| `--operation` | SOAP operation name | Required |
| `--soap-action` | SOAPAction header value | |
| `--response` | XML response body | |
| `--stateful-resource` | Stateful resource name (e.g., `users`) | |
| `--stateful-action` | Stateful action: `list`, `get`, `create`, `update`, `delete`, `custom` | |
> `--stateful-resource` and `--stateful-action` must be used together. When set, the operation reads/writes the named stateful resource instead of returning a canned response.
**Examples:**
```bash
# Canned response
mockd soap add --operation GetWeather --soap-action "http://example.com/GetWeather" --response '72'
# Stateful: list all users from the "users" resource
mockd soap add --path /soap --action GetUsers --stateful-resource users --stateful-action list
# Stateful: get a single user by ID
mockd soap add --path /soap --action GetUser --stateful-resource users --stateful-action get
```
***
### mockd soap import
[Section titled “mockd soap import”](#mockd-soap-import)
Generate SOAP mock configurations from a WSDL file.
```bash
mockd soap import [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------ | ----- | ------------------------------- | ------- |
| `--stateful` | | Enable stateful CRUD heuristics | `false` |
| `--output` | `-o` | Output file path | stdout |
| `--format` | `-f` | Output format (yaml/json) | `yaml` |
**Examples:**
```bash
# Generate static SOAP mocks from WSDL
mockd soap import service.wsdl
# Generate stateful mocks (auto-detects CRUD operations)
mockd soap import service.wsdl --stateful
# Save to a file
mockd soap import service.wsdl --stateful -o soap-mocks.yaml
# Output as JSON
mockd soap import service.wsdl --format json
```
When `--stateful` is enabled, the importer detects CRUD patterns in operation names (Get, List, Create, Update, Delete) and generates both `statefulResources` definitions and SOAP operations pre-wired with `statefulResource`/`statefulAction` fields. For new projects, consider converting the generated `statefulResources` to [tables and extend bindings](/reference/configuration/#tables) for a cleaner separation of data and routing.
***
### mockd soap validate
[Section titled “mockd soap validate”](#mockd-soap-validate)
Validate a WSDL file against standard schema rules.
```bash
mockd soap validate
```
***
### mockd soap call
[Section titled “mockd soap call”](#mockd-soap-call)
Execute a SOAP call against an endpoint.
```bash
mockd soap call [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ----------- | ----- | ------------------------------------------ | ------- |
| `--header` | `-H` | Additional headers (key:value,key2:value2) | |
| `--pretty` | | Pretty print output | `true` |
| `--action` | | SOAPAction header value | |
| `--soap12` | | Use SOAP 1.2 envelope format | `false` |
| `--timeout` | | Request timeout in seconds | `30` |
**Examples:**
```bash
mockd soap call http://localhost:4280/soap "http://example.com/GetWeather" '...'
# SOAP 1.2 with custom action
mockd soap call --soap12 --action "http://example.com/GetWeather" http://localhost:4280/soap GetWeather
```
***
### mockd oauth add
[Section titled “mockd oauth add”](#mockd-oauth-add)
Add or update an OAuth/OIDC mock provider. This creates a full OAuth/OIDC mock server with standard endpoints including `/.well-known/openid-configuration`, `/token`, `/authorize`, `/userinfo`, and `/jwks`.
```bash
mockd oauth add [flags]
```
**OAuth Flags:**
| Flag | Description | Default |
| ------------------ | ------------------- | ----------------------- |
| `--issuer` | OAuth issuer URL | `http://localhost:4280` |
| `--client-id` | OAuth client ID | `test-client` |
| `--client-secret` | OAuth client secret | `test-secret` |
| `--oauth-user` | Test username | `testuser` |
| `--oauth-password` | Test password | `password` |
**Examples:**
```bash
mockd oauth add
mockd oauth add --name "Auth Server" --issuer http://localhost:4280/auth --client-id my-app --client-secret s3cret --oauth-user admin --oauth-password admin123
```
***
### mockd oauth list
[Section titled “mockd oauth list”](#mockd-oauth-list)
List all OAuth mocks.
```bash
mockd oauth list [flags]
```
***
### mockd oauth get
[Section titled “mockd oauth get”](#mockd-oauth-get)
Get details of a specific OAuth mock.
```bash
mockd oauth get [flags]
```
***
### mockd oauth delete
[Section titled “mockd oauth delete”](#mockd-oauth-delete)
Delete an OAuth mock.
```bash
mockd oauth delete [flags]
```
***
### mockd oauth status
[Section titled “mockd oauth status”](#mockd-oauth-status)
Show the OAuth provider status.
```bash
mockd oauth status [flags]
```
***
### mockd stateful
[Section titled “mockd stateful”](#mockd-stateful)
Manage stateful CRUD resources. Stateful resources provide in-memory data stores that can be shared across protocols (HTTP REST, SOAP, GraphQL, gRPC, etc.).
```bash
mockd stateful [command]
```
**Commands:**
| Command | Description |
| ------- | -------------------------------------- |
| `add` | Create a stateful CRUD resource |
| `list` | List all stateful resources |
| `reset` | Reset a stateful resource to seed data |
***
### mockd stateful add
[Section titled “mockd stateful add”](#mockd-stateful-add)
Create a new stateful CRUD resource (data store). Resources are created as pure data stores with no auto-generated HTTP endpoints. Use [extend bindings](/reference/configuration/#extend-bindings) in config files to wire mocks to tables, or use `mockd http add --stateful` for quick CLI prototyping.
```bash
mockd stateful add [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------ | -------------------- | ------- |
| `--id-field` | Custom ID field name | `id` |
**Examples:**
```bash
# Create a data store
mockd stateful add users
# Custom ID field
mockd stateful add orders --id-field orderId
# Quick prototyping: create resource + HTTP CRUD mocks in one step
mockd http add --path /api/users --stateful
```
***
### mockd stateful list
[Section titled “mockd stateful list”](#mockd-stateful-list)
List all stateful resources and their item counts.
```bash
mockd stateful list [flags]
```
**Flags:**
| Flag | Description | Default |
| ---------- | ---------------------------------- | ------- |
| `--limit` | Maximum items to show per resource | `100` |
| `--offset` | Skip this many items | `0` |
| `--sort` | Sort field | |
| `--order` | Sort order (`asc` or `desc`) | |
**Example output:**
```plaintext
Stateful Resources (3):
NAME ITEMS SEED ID FIELD
---- ----- ---- --------
users 5 3 id
products 12 10 id
orders 0 0 orderId
Total items across all resources: 17
```
***
### mockd stateful reset
[Section titled “mockd stateful reset”](#mockd-stateful-reset)
Reset a stateful resource to its initial seed data state. All current items are removed and replaced with the original seed data (if any).
```bash
mockd stateful reset [flags]
```
**Examples:**
```bash
# Reset users to seed data
mockd stateful reset users
# Reset with JSON output
mockd stateful reset products --json
```
***
### mockd stateful custom
[Section titled “mockd stateful custom”](#mockd-stateful-custom)
Manage custom multi-step operations that run against stateful resources. Custom operations define a pipeline of steps (read, create, update, delete, set) with expression-based logic using [expr-lang/expr](https://github.com/expr-lang/expr). They can be invoked via CLI, REST API, SOAP, or any protocol that supports the stateful bridge.
```bash
mockd stateful custom [command]
```
**Commands:**
| Command | Description |
| ---------- | -------------------------------------------------- |
| `list` | List all registered custom operations |
| `get` | Show details of a custom operation |
| `add` | Register a new custom operation |
| `validate` | Validate a custom operation definition (no writes) |
| `run` | Execute a custom operation |
| `delete` | Delete a custom operation |
***
### mockd stateful custom list
[Section titled “mockd stateful custom list”](#mockd-stateful-custom-list)
List all registered custom operations.
```bash
mockd stateful custom list [flags]
```
**Example:**
```bash
mockd stateful custom list
mockd stateful custom list --json
```
***
### mockd stateful custom get
[Section titled “mockd stateful custom get”](#mockd-stateful-custom-get)
Show details of a custom operation including its steps and response template.
```bash
mockd stateful custom get [flags]
```
**Example:**
```bash
mockd stateful custom get TransferFunds
mockd stateful custom get TransferFunds --json
```
***
### mockd stateful custom add
[Section titled “mockd stateful custom add”](#mockd-stateful-custom-add)
Register a new custom operation from a file or inline definition.
```bash
mockd stateful custom add [flags]
```
**Flags:**
| Flag | Description | Default |
| -------------- | ---------------------------------------------------------- | ------- |
| `--file` | Path to YAML/JSON file containing the operation definition | |
| `--definition` | Inline JSON operation definition | |
**Examples:**
```bash
# From a YAML file
mockd stateful custom validate --file transfer.yaml
mockd stateful custom add --file transfer.yaml
# Inline JSON definition
mockd stateful custom add --definition '{
"name": "TransferFunds",
"consistency": "atomic",
"steps": [
{"type": "read", "resource": "accounts", "id": "input.sourceId", "as": "source"},
{"type": "read", "resource": "accounts", "id": "input.destId", "as": "dest"},
{"type": "update", "resource": "accounts", "id": "input.sourceId", "set": {"balance": "source.balance - input.amount"}},
{"type": "update", "resource": "accounts", "id": "input.destId", "set": {"balance": "dest.balance + input.amount"}}
],
"response": {"status": "\"completed\""}
}'
```
**Operation Definition Format:**
```yaml
name: TransferFunds
consistency: atomic
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
- type: read
resource: accounts
id: "input.destId"
as: dest
- type: set
as: total
value: "source.balance + dest.balance"
- type: update
resource: accounts
id: "input.sourceId"
set:
balance: "source.balance - input.amount"
- type: update
resource: accounts
id: "input.destId"
set:
balance: "dest.balance + input.amount"
response:
status: '"completed"'
total: "string(total)"
```
**Step Types:**
| Type | Description | Required Fields |
| -------- | ---------------------------------------------- | ----------------------- |
| `read` | Read a single item from a resource | `resource`, `id`, `as` |
| `create` | Create a new item in a resource | `resource`, `set` |
| `update` | Update an existing item in a resource | `resource`, `id`, `set` |
| `delete` | Delete an item from a resource | `resource`, `id` |
| `set` | Set a computed value in the expression context | `var`, `value` |
> All `id`, `value`, and `set` field values are **expr expressions** evaluated against an environment containing `input` (the request data) and all previously computed variables from `as` and `set.var`.
***
### mockd stateful custom validate
[Section titled “mockd stateful custom validate”](#mockd-stateful-custom-validate)
Validate a custom operation definition locally before registering it. This command performs preflight checks and does not mutate server state.
```bash
mockd stateful custom validate [flags]
```
**Flags:**
| Flag | Description | Default |
| ----------------------------- | ---------------------------------------------------------------------- | ------- |
| `--file` | Path to YAML/JSON file containing the operation definition | |
| `--definition` | Inline JSON operation definition | |
| `--input` | Inline JSON input example for expression compile checks | |
| `--input-file` | Path to JSON file containing input example | |
| `--fixtures-file` | Path to JSON/YAML fixtures file for runtime expression checks | |
| `--check-resources` | Verify referenced stateful resources exist on the running admin/engine | `false` |
| `--check-expressions-runtime` | Evaluate expressions with sample input/fixtures (no writes) | `false` |
| `--strict` | Treat validation warnings as errors | `false` |
**Examples:**
```bash
# Validate a YAML definition locally (no writes)
mockd stateful custom validate --file transfer.yaml
# Validate with example input to catch expression/env issues
mockd stateful custom validate --file transfer.yaml \
--input '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
# Validate and verify referenced stateful resources exist on the running engine
mockd stateful custom validate --file transfer.yaml --check-resources
# Runtime-check expressions with sample input + fixtures (no writes)
mockd stateful custom validate --file transfer.yaml \
--input '{"sourceId":"acct-1","destId":"acct-2","amount":100}' \
--check-expressions-runtime \
--fixtures-file transfer-fixtures.json
# Fail on warnings (e.g., empty update/create set maps)
mockd stateful custom validate --file transfer.yaml --strict
```
**Fixtures file (optional, recommended for `--check-expressions-runtime`):**
```json
{
"resources": {
"accounts": {
"acct-1": { "id": "acct-1", "balance": 500 },
"acct-2": { "id": "acct-2", "balance": 200 }
}
},
"vars": {
"source": { "id": "acct-1", "balance": 500 }
}
}
```
If fixtures are missing for `read`/`update` aliases, validation still runs using synthetic placeholders and emits warnings (or fails under `--strict`).
***
### mockd stateful custom run
[Section titled “mockd stateful custom run”](#mockd-stateful-custom-run)
Execute a registered custom operation with the given input.
```bash
mockd stateful custom run [flags]
```
**Flags:**
| Flag | Description | Default |
| -------------- | -------------------------------------------- | ------- |
| `--input` | Inline JSON input for the operation | |
| `--input-file` | Path to JSON file containing operation input | |
**Examples:**
```bash
# Run with inline input
mockd stateful custom run TransferFunds --input '{"sourceId":"acct-1","destId":"acct-2","amount":100}'
# Run with input from file
mockd stateful custom run TransferFunds --input-file transfer-input.json
# Run with no input
mockd stateful custom run TransferFunds
```
***
### mockd stateful custom delete
[Section titled “mockd stateful custom delete”](#mockd-stateful-custom-delete)
Delete a registered custom operation.
```bash
mockd stateful custom delete [flags]
```
**Example:**
```bash
mockd stateful custom delete TransferFunds
```
***
### mockd new
[Section titled “mockd new”](#mockd-new)
Create mocks from templates.
```bash
mockd new [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------ | ----- | ----------------------------------------------- | ------- |
| `--template` | `-t` | Template: blank, crud, auth, pagination, errors | `blank` |
| `--name` | `-n` | Collection name | |
| `--output` | `-o` | Output file | stdout |
| `--resource` | | Resource name (for crud/pagination templates) | |
**Templates:**
| Template | Description |
| ------------ | ---------------------------------------------------------- |
| `blank` | Empty mock collection |
| `crud` | REST CRUD endpoints (GET list, GET one, POST, PUT, DELETE) |
| `auth` | Authentication flow (login, logout, refresh, me) |
| `pagination` | List endpoints with cursor/offset pagination |
| `errors` | Common HTTP error responses (400, 401, 403, 404, 500) |
**Examples:**
```bash
# Create a blank collection
mockd new -t blank -o mocks.yaml
# Create CRUD endpoints for users
mockd new -t crud --resource users -o users.yaml
# Create auth endpoints
mockd new -t auth -n "Auth API" -o auth.yaml
```
***
### mockd list
[Section titled “mockd list”](#mockd-list)
List all configured mocks.
```bash
mockd list [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| --------------- | ----- | ----------------------------------------------------------------- | ----------------------- |
| `--config` | `-c` | List mocks from config file (no server needed) | |
| `--type` | `-t` | Filter by type: http, websocket, graphql, grpc, mqtt, soap, oauth | |
| `--no-truncate` | `-w` | Show full IDs and paths without truncation | `false` |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
| `--json` | | Output in JSON format | |
**Examples:**
```bash
# List all mocks from running server
mockd list
# List mocks from config file (no server needed)
mockd list --config mockd.yaml
# List only WebSocket mocks
mockd list --type websocket
# Show full IDs and paths (useful for copy-pasting IDs into delete commands)
mockd list --no-truncate
# List as JSON
mockd list --json
# List from remote server
mockd list --admin-url http://remote:4290
# List mocks in a specific workspace
mockd list --workspace ws_abc123
```
***
### mockd get
[Section titled “mockd get”](#mockd-get)
Get details of a specific mock.
```bash
mockd get [flags]
```
**Arguments:**
| Argument | Description |
| --------- | ------------------------------------- |
| `mock-id` | ID of the mock to retrieve (required) |
**Flags:**
\| Flag | Description | Default | |------|-------|-------------|---------| | `--admin-url` | Admin API base URL | `http://localhost:4290` | | `--json` | Output in JSON format | |
**Examples:**
```bash
# Get mock details
mockd get abc123
# Get as JSON
mockd get abc123 --json
```
***
### mockd delete
[Section titled “mockd delete”](#mockd-delete)
Delete mocks by ID, ID prefix, or path. Also available as `mockd remove` and `mockd rm`.
```bash
mockd delete [] [flags]
```
**Arguments:**
| Argument | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mock-id` | Full or prefix of the mock ID. Supports prefix matching — if the prefix uniquely identifies one mock, it is deleted. If the prefix matches multiple mocks, all matches are shown and you are asked to be more specific. |
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | ------------------------------------------- | ----------------------- |
| `--path` | | Delete mocks matching a URL path | |
| `--method` | | Filter by HTTP method (used with `--path`) | |
| `--yes` | `-y` | Skip confirmation when multiple mocks match | `false` |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
**Examples:**
```bash
# Delete by exact ID
mockd delete http_abc123def456
# Delete by ID prefix (must uniquely match one mock)
mockd delete http_abc
# Output: Deleted mock: http_abc123def456
# If prefix is ambiguous, shows matches and asks for more specificity
mockd delete http_a
# Output:
# Multiple mocks match prefix "http_a":
# http_abc123def456 GET /api/users
# http_aef789012345 POST /api/users
# Please provide a more specific prefix.
# Delete all mocks on a path
mockd delete --path /api/users
# Delete only GET mocks on a path
mockd delete --path /api/users --method GET
# Skip confirmation when deleting multiple mocks
mockd delete --path /api/users -y
# Using aliases
mockd remove http_abc123
mockd rm http_abc123
```
***
### mockd remove / mockd rm
[Section titled “mockd remove / mockd rm”](#mockd-remove--mockd-rm)
Hidden aliases for `mockd delete`. All flags and arguments are identical.
```bash
mockd remove [] [flags]
mockd rm [] [flags]
```
See [mockd delete](#mockd-delete) for full documentation.
***
### mockd update
[Section titled “mockd update”](#mockd-update)
Update an existing mock endpoint by ID. Only specified fields are modified — unspecified fields retain their current values.
```bash
mockd update [flags]
```
**Arguments:**
| Argument | Description |
| -------- | ----------------------------------- |
| `id` | ID of the mock to update (required) |
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | ----------------------------------------------------------------- | ------- |
| `--body` | `-b` | New response body | |
| `--body-file` | | Read response body from file | |
| `--status` | `-s` | New response status code | |
| `--header` | `-H` | Response header (key:value), repeatable | |
| `--delay` | | Response delay in milliseconds | |
| `--table` | | Bind to a stateful resource table | |
| `--bind` | | Stateful action: list, get, create, update, patch, delete, custom | |
| `--operation` | | Custom operation name (for `--bind custom`) | |
| `--name` | `-n` | Mock display name | |
| `--enabled` | | Enable or disable the mock (`true`/`false`) | |
> `--table` and `--bind` must be used together. When set, the mock is bound to the specified table and the response/SSE/statefulOperation fields are cleared.
**Examples:**
```bash
# Change response status code
mockd update http_abc123 --status 201
# Change response body
mockd update http_abc123 --body '{"updated": true}'
# Read body from file
mockd update http_abc123 --body-file response.json
# Bind to a stateful table
mockd update http_abc123 --table users --bind list
# Bind to a custom operation
mockd update http_abc123 --table users --bind custom --operation VerifyUser
# Add response delay
mockd update http_abc123 --delay 500
# Disable a mock
mockd update http_abc123 --enabled false
# Re-enable a mock
mockd update http_abc123 --enabled true
# Rename a mock
mockd update http_abc123 --name "Get Users v2"
# Set a response header
mockd update http_abc123 -H "X-Custom:value"
```
***
## Workspace Commands
[Section titled “Workspace Commands”](#workspace-commands)
### mockd workspace
[Section titled “mockd workspace”](#mockd-workspace)
Manage workspaces within the current context. Workspaces provide isolated environments for mocks, stateful resources, request logs, and configuration.
```bash
mockd workspace [command]
```
Running `mockd workspace` without a subcommand shows the current workspace.
**Subcommands:**
| Command | Aliases | Description |
| ------------- | -------------- | --------------------------------------- |
| `show` | | Show current workspace (default) |
| `list` | `ls` | List all workspaces on the server |
| `create` | | Create a new workspace |
| `use ` | | Switch to a different workspace |
| `delete ` | `rm`, `remove` | Delete a workspace |
| `clear` | | Clear workspace selection (use default) |
***
### mockd workspace show
[Section titled “mockd workspace show”](#mockd-workspace-show)
Show the current workspace and context information.
```bash
mockd workspace show
```
***
### mockd workspace list
[Section titled “mockd workspace list”](#mockd-workspace-list)
List all workspaces on the server.
```bash
mockd workspace list [flags]
```
**Example output:**
```plaintext
CURRENT ID NAME TYPE DESCRIPTION
* ws_abc123 Payment API local Stripe mock environment
ws_def456 Comms API local Twilio mock environment
```
***
### mockd workspace create
[Section titled “mockd workspace create”](#mockd-workspace-create)
Create a new workspace.
```bash
mockd workspace create [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| --------------- | ----- | --------------------------------------- | ------- |
| `--name` | `-n` | Workspace name (required) | |
| `--description` | `-d` | Workspace description | |
| `--type` | | Workspace type | `local` |
| `--use` | | Switch to this workspace after creating | `false` |
**Examples:**
```bash
# Create a workspace
mockd workspace create -n "Payment API" -d "Stripe mock environment"
# Create and switch to it
mockd workspace create -n "Payment API" --use
```
***
### mockd workspace use
[Section titled “mockd workspace use”](#mockd-workspace-use)
Switch to a different workspace. All subsequent commands will be scoped to this workspace.
```bash
mockd workspace use
```
**Examples:**
```bash
mockd workspace use ws_abc123
```
> **Tip:** You can also use `--workspace ` on any command to scope a single operation without switching the persistent context.
***
### mockd workspace delete
[Section titled “mockd workspace delete”](#mockd-workspace-delete)
Delete a workspace. Prompts for confirmation unless `--force` is given.
```bash
mockd workspace delete [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| --------- | ----- | ----------------- | ------- |
| `--force` | `-f` | Skip confirmation | `false` |
***
### mockd workspace clear
[Section titled “mockd workspace clear”](#mockd-workspace-clear)
Clear the current workspace selection, reverting to the default workspace.
```bash
mockd workspace clear
```
***
## Import/Export Commands
[Section titled “Import/Export Commands”](#importexport-commands)
### mockd import
[Section titled “mockd import”](#mockd-import)
Import mocks from various sources and formats. Imported configurations may include `statefulResources` definitions (legacy) or `tables` and `extend` bindings (recommended), which are persisted to the admin file store and survive restarts. Runtime data for stateful resources is in-memory only and resets to seed data on restart.
```bash
mockd import [flags]
```
**Arguments:**
| Argument | Description |
| -------- | ----------------------------------------- |
| `source` | Path to file, or cURL command (in quotes) |
**Flags:**
| Flag | Short | Description | Default |
| ------------------ | ----- | --------------------------------------- | ----------------------- |
| `--format` | `-f` | Force format (auto-detected if omitted) | |
| `--replace` | | Replace all existing mocks | merge |
| `--dry-run` | | Preview import without saving | |
| `--include-static` | | Include static assets (for HAR imports) | |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
**Supported Formats:**
| Format | Description |
| ---------- | -------------------------------------------------- |
| `mockd` | Mockd native format (YAML/JSON) |
| `openapi` | OpenAPI 3.x or Swagger 2.0 |
| `postman` | Postman Collection v2.x |
| `har` | HTTP Archive (browser recordings) |
| `wiremock` | WireMock JSON mappings |
| `mockoon` | Mockoon environment JSON |
| `curl` | cURL command |
| `wsdl` | WSDL 1.1 service definition (generates SOAP mocks) |
**Examples:**
```bash
# Import from OpenAPI spec (auto-detected)
mockd import openapi.yaml
# Import from Postman collection
mockd import collection.json -f postman
# Import from Mockoon environment
mockd import environment.json -f mockoon
# Import from HAR file including static assets
mockd import recording.har --include-static
# Import from cURL command
mockd import "curl -X POST https://api.example.com/users -H 'Content-Type: application/json' -d '{\"name\": \"test\"}'"
# Preview import without saving
mockd import openapi.yaml --dry-run
# Replace all mocks with imported ones
mockd import mocks.yaml --replace
# Import into a specific workspace
mockd import openapi.yaml --workspace ws_abc123
```
***
### mockd export
[Section titled “mockd export”](#mockd-export)
Export current mocks to file.
```bash
mockd export [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | ----------------------------- | ----------------------- |
| `--output` | `-o` | Output file | stdout |
| `--name` | `-n` | Collection name | `exported-config` |
| `--format` | `-f` | Output format: mockd, openapi | `mockd` |
| `--version` | | Version tag for the export | |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
**Formats:**
| Format | Description |
| --------- | ------------------------------------------------------------- |
| `mockd` | Mockd native format (YAML/JSON) - recommended for portability |
| `openapi` | OpenAPI 3.x specification - for API documentation |
**Examples:**
```bash
# Export to stdout as YAML
mockd export
# Export to JSON file
mockd export -o mocks.json
# Export to YAML file
mockd export -o mocks.yaml
# Export as OpenAPI specification
mockd export -f openapi -o api.yaml
# Export with custom name
mockd export -n "My API Mocks" -o mocks.yaml
# Export mocks from a specific workspace
mockd export --workspace ws_abc123 -o mocks.yaml
```
***
### mockd convert
[Section titled “mockd convert”](#mockd-convert)
Convert recorded API traffic directly into `mockd` mock configurations. Reads recordings from disk (written by `mockd proxy start`) and outputs mock configurations that can be piped or imported with `mockd import`.
```bash
mockd convert [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------------- | ----- | ---------------------------------------------------------------- | -------- |
| `--session` | `-s` | Session name or directory | `latest` |
| `--file` | `-f` | Path to a specific recording file or directory | |
| `--recordings-dir` | | Base recordings directory override | |
| `--include-hosts` | | Comma-separated host patterns to include (e.g. `api.*.com`) | |
| `--path-filter` | | Glob pattern to filter paths (e.g., `/api/*`) | |
| `--method` | | Comma-separated HTTP methods (e.g., `GET,POST`) | |
| `--status` | | Status code filter (e.g., `2xx`, `200,201`) | |
| `--smart-match` | | Convert dynamic path segments like `/users/123` to `/users/{id}` | `false` |
| `--duplicates` | | Duplicate handling strategy: `first`, `last`, `all` | `first` |
| `--include-headers` | | Include request headers in mock matchers | `false` |
| `--check-sensitive` | | Check for sensitive data in recordings and show warnings | `true` |
| `--output` | `-o` | Output file path (default is stdout) | |
**Examples:**
```bash
# Convert the latest proxy recording session into mocks
mockd convert
# Convert a named session with smart path parameter matching
mockd convert --session stripe-api --smart-match
# Convert only specific host traffic, targeting only GET/POST methods
mockd convert --include-hosts "api.stripe.com" --method GET,POST
# Convert a specific recording JSON file
mockd convert --file ./my-recordings/rec_abc123.json
# Pipe converted JSON directly into the server using 'mockd import'
mockd convert --session my-api --smart-match | mockd import "curl -X POST -d @-"
```
***
## Logging Commands
[Section titled “Logging Commands”](#logging-commands)
### mockd logs
[Section titled “mockd logs”](#mockd-logs)
View request logs.
```bash
mockd logs [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | -------------------------------------------------------------------- | ----------------------- |
| `--protocol` | | Filter by protocol (http, grpc, mqtt, soap, graphql, websocket, sse) | |
| `--method` | `-m` | Filter by HTTP method | |
| `--path` | `-p` | Filter by path (substring match) | |
| `--matched` | | Show only matched requests | |
| `--unmatched` | | Show only unmatched requests | |
| `--limit` | `-n` | Number of entries to show | `20` |
| `--verbose` | | Show headers and body | |
| `--clear` | | Clear all logs | |
| `--follow` | `-f` | Stream logs in real-time (like tail -f) | |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
| `--json` | | Output in JSON format | |
**Examples:**
```bash
# Show recent logs
mockd logs
# Show last 50 entries
mockd logs -n 50
# Filter by method
mockd logs -m POST
# Filter by protocol
mockd logs --protocol grpc
# Show verbose output
mockd logs --verbose
# Stream logs in real-time
mockd logs --follow
# Clear logs
mockd logs --clear
# Show logs for a specific workspace
mockd logs --workspace ws_abc123
```
***
## Configuration Commands
[Section titled “Configuration Commands”](#configuration-commands)
### mockd config
[Section titled “mockd config”](#mockd-config)
Show effective configuration.
```bash
mockd config [flags]
```
**Flags:**
| Flag | Description |
| -------- | --------------------- |
| `--json` | Output in JSON format |
**Examples:**
```bash
mockd config
mockd config --json
```
***
### mockd doctor
[Section titled “mockd doctor”](#mockd-doctor)
Diagnose common setup issues and validate configuration.
```bash
mockd doctor [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| -------------- | ----- | ------------------------------- | ------- |
| `--config` | | Path to config file to validate | |
| `--port` | `-p` | Mock server port to check | `4280` |
| `--admin-port` | `-a` | Admin API port to check | `4290` |
**Examples:**
```bash
# Run all checks with defaults
mockd doctor
# Validate a specific config file
mockd doctor --config mocks.yaml
# Check custom ports
mockd doctor -p 3000 -a 3001
```
***
## Context Commands
[Section titled “Context Commands”](#context-commands)
### mockd context
[Section titled “mockd context”](#mockd-context)
Manage contexts (admin server + workspace pairs). Similar to kubectl contexts, allows quick switching between different mockd deployments (local, staging, CI, cloud, etc.).
```bash
mockd context [command]
```
**Subcommands:**
| Command | Description |
| --------------- | ----------------------------------------- |
| (no command) | Show current context |
| `show` | Show current context (same as no command) |
| `use ` | Switch to a different context |
| `add ` | Add a new context |
| `list` | List all contexts |
| `remove ` | Remove a context |
**Configuration:**
Contexts are stored in `~/.config/mockd/contexts.yaml`. A default “local” context pointing to `http://localhost:4290` is created automatically.
***
### mockd context show
[Section titled “mockd context show”](#mockd-context-show)
Show the current context.
```bash
mockd context
mockd context show
```
**Output includes:**
* Current context name
* Admin URL
* Workspace (if set)
* Description (if set)
* Environment variable overrides (if active)
***
### mockd context use
[Section titled “mockd context use”](#mockd-context-use)
Switch to a different context.
```bash
mockd context use
```
**Examples:**
```bash
# Switch to staging context
mockd context use staging
# Switch back to local
mockd context use local
```
***
### mockd context add
[Section titled “mockd context add”](#mockd-context-add)
Add a new context.
```bash
mockd context add [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------------ | ----- | ---------------------------------- | ------- |
| `--session` | | Clear specific session only | |
| `--recordings-dir` | | Base recordings directory override | |
| `--force` | `-f` | Skip confirmation | |
**Examples:**
```bash
mockd recordings clear
mockd recordings clear --force
```
***
Note
See [`mockd convert`](#mockd-convert) in the Import/Export section above for the full convert command reference.
***
## Stream Recordings Commands
[Section titled “Stream Recordings Commands”](#stream-recordings-commands)
### mockd stream-recordings
[Section titled “mockd stream-recordings”](#mockd-stream-recordings)
Manage WebSocket and SSE stream recordings.
```bash
mockd stream-recordings [flags]
```
**Subcommands:**
* `list, ls` - List all stream recordings
* `show, get` - Show details of a specific recording
* `delete, rm` - Delete a recording
* `export` - Export a recording to JSON
* `convert` - Convert a recording to mock config
* `stats` - Show storage statistics
* `vacuum` - Remove soft-deleted recordings
* `sessions` - List active recording sessions
***
#### mockd stream-recordings list
[Section titled “mockd stream-recordings list”](#mockd-stream-recordings-list)
List all WebSocket and SSE stream recordings.
```bash
mockd stream-recordings list [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------------- | -------------------------------------------------- | ----------- |
| `--protocol` | Filter by protocol (websocket, sse) | |
| `--path` | Filter by path prefix | |
| `--status` | Filter by status (complete, incomplete, recording) | |
| `--json` | Output as JSON | |
| `--limit` | Maximum number of recordings to show | `20` |
| `--offset` | Offset for pagination | |
| `--sort` | Sort by field: startTime, name, size | `startTime` |
| `--order` | Sort order: asc, desc | `desc` |
| `--include-deleted` | Include soft-deleted recordings | |
**Examples:**
```bash
# List all recordings
mockd stream-recordings list
# List only WebSocket recordings
mockd stream-recordings list --protocol websocket
# List as JSON
mockd stream-recordings list --json
# Paginate results
mockd stream-recordings list --limit 10 --offset 20
```
***
#### mockd stream-recordings show
[Section titled “mockd stream-recordings show”](#mockd-stream-recordings-show)
Show details of a specific stream recording.
```bash
mockd stream-recordings show [flags]
```
**Flags:**
| Flag | Description |
| -------- | -------------- |
| `--json` | Output as JSON |
**Examples:**
```bash
mockd stream-recordings show 01ABCDEF123456
mockd stream-recordings show 01ABCDEF123456 --json
```
***
#### mockd stream-recordings delete
[Section titled “mockd stream-recordings delete”](#mockd-stream-recordings-delete)
Delete a stream recording.
```bash
mockd stream-recordings delete [flags]
```
**Flags:**
| Flag | Short | Description |
| ------------- | ----- | ------------------------------------ |
| `--force` | `-f` | Skip confirmation |
| `--permanent` | | Permanently delete (not soft-delete) |
**Examples:**
```bash
mockd stream-recordings delete 01ABCDEF123456
mockd stream-recordings delete 01ABCDEF123456 --force
mockd stream-recordings delete 01ABCDEF123456 --permanent
```
***
#### mockd stream-recordings export
[Section titled “mockd stream-recordings export”](#mockd-stream-recordings-export)
Export a stream recording to JSON format.
```bash
mockd stream-recordings export [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ---------- | ----- | ---------------- | ------- |
| `--output` | `-o` | Output file path | stdout |
**Examples:**
```bash
mockd stream-recordings export 01ABCDEF123456
mockd stream-recordings export 01ABCDEF123456 -o recording.json
```
***
#### mockd stream-recordings convert
[Section titled “mockd stream-recordings convert”](#mockd-stream-recordings-convert)
Convert a stream recording to a mock configuration.
```bash
mockd stream-recordings convert [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------------- | ----- | --------------------------------------- | ------- |
| `--output` | `-o` | Output file path | stdout |
| `--simplify-timing` | | Normalize timing to reduce noise | |
| `--min-delay` | | Minimum delay to preserve in ms | `10` |
| `--max-delay` | | Maximum delay in ms | `5000` |
| `--include-client` | | Include client messages as expect steps | `true` |
| `--deduplicate` | | Remove consecutive duplicate messages | |
**Examples:**
```bash
mockd stream-recordings convert 01ABCDEF123456
mockd stream-recordings convert 01ABCDEF123456 --simplify-timing
mockd stream-recordings convert 01ABCDEF123456 -o scenario.json
```
***
#### mockd stream-recordings stats
[Section titled “mockd stream-recordings stats”](#mockd-stream-recordings-stats)
Show storage statistics for stream recordings.
```bash
mockd stream-recordings stats [flags]
```
**Flags:**
| Flag | Description |
| -------- | -------------- |
| `--json` | Output as JSON |
***
#### mockd stream-recordings vacuum
[Section titled “mockd stream-recordings vacuum”](#mockd-stream-recordings-vacuum)
Permanently remove soft-deleted recordings.
```bash
mockd stream-recordings vacuum [flags]
```
**Flags:**
| Flag | Short | Description |
| --------- | ----- | ----------------- |
| `--force` | `-f` | Skip confirmation |
**Examples:**
```bash
mockd stream-recordings vacuum
mockd stream-recordings vacuum --force
```
***
#### mockd stream-recordings sessions
[Section titled “mockd stream-recordings sessions”](#mockd-stream-recordings-sessions)
List active recording sessions.
```bash
mockd stream-recordings sessions [flags]
```
**Flags:**
| Flag | Description |
| -------- | -------------- |
| `--json` | Output as JSON |
***
## AI Commands
[Section titled “AI Commands”](#ai-commands)
### mockd generate
[Section titled “mockd generate”](#mockd-generate)
Generate mock configurations using AI.
```bash
mockd generate [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | ------------------------------------------- | ----------------------- |
| `--input` | `-i` | Input OpenAPI spec file | |
| `--prompt` | `-p` | Natural language description for generation | |
| `--output` | `-o` | Output file | stdout |
| `--ai` | | Enable AI-powered data generation | |
| `--provider` | | AI provider (openai, anthropic, ollama) | |
| `--model` | | AI model to use | |
| `--dry-run` | | Preview generation without saving | |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
**Environment Variables:**
| Variable | Description |
| ------------------- | ---------------------------- |
| `MOCKD_AI_PROVIDER` | Default AI provider |
| `MOCKD_AI_API_KEY` | API key for the provider |
| `MOCKD_AI_MODEL` | Default model |
| `MOCKD_AI_ENDPOINT` | Custom endpoint (for Ollama) |
**Examples:**
```bash
# Generate mocks from OpenAPI spec with AI enhancement
mockd generate --ai --input openapi.yaml -o mocks.yaml
# Generate mocks from natural language description
mockd generate --ai --prompt "user management API with CRUD operations"
# Generate mocks using specific provider
mockd generate --ai --provider openai --prompt "payment processing API"
# Preview what would be generated
mockd generate --ai --prompt "blog API" --dry-run
```
***
### mockd enhance
[Section titled “mockd enhance”](#mockd-enhance)
Enhance existing mocks with AI-generated response data.
```bash
mockd enhance [flags]
```
**Flags:**
| Flag | Description |
| ------------- | ----------------------------------------------------- |
| `--ai` | Enable AI-powered enhancement (required) |
| `--provider` | AI provider (openai, anthropic, ollama) |
| `--model` | AI model to use |
| `--admin-url` | Admin API base URL (default: ) |
**Environment Variables:**
| Variable | Description |
| ------------------- | ---------------------------- |
| `MOCKD_AI_PROVIDER` | Default AI provider |
| `MOCKD_AI_API_KEY` | API key for the provider |
| `MOCKD_AI_MODEL` | Default model |
| `MOCKD_AI_ENDPOINT` | Custom endpoint (for Ollama) |
**Examples:**
```bash
# Enhance all mocks with AI-generated data
mockd enhance --ai
# Use specific provider
mockd enhance --ai --provider anthropic
```
***
## GraphQL Commands
[Section titled “GraphQL Commands”](#graphql-commands)
### mockd graphql
[Section titled “mockd graphql”](#mockd-graphql)
Manage and test GraphQL endpoints.
```bash
mockd graphql [flags]
```
**Subcommands:**
* `validate` - Validate a GraphQL schema file
* `query` - Execute a query against a GraphQL endpoint
***
#### mockd graphql validate
[Section titled “mockd graphql validate”](#mockd-graphql-validate-1)
Validate a GraphQL schema file.
```bash
mockd graphql validate
```
**Arguments:**
| Argument | Description |
| ------------- | -------------------------------------------------- |
| `schema-file` | Path to the GraphQL schema file (.graphql or .gql) |
**Examples:**
```bash
# Validate a schema file
mockd graphql validate schema.graphql
# Validate with full path
mockd graphql validate ./schemas/api.graphql
```
***
#### mockd graphql query
[Section titled “mockd graphql query”](#mockd-graphql-query-1)
Execute a GraphQL query against an endpoint.
```bash
mockd graphql query [flags]
```
**Arguments:**
| Argument | Description |
| ---------- | ------------------------------------------------------------ |
| `endpoint` | GraphQL endpoint URL (e.g., ) |
| `query` | GraphQL query string or @filename |
**Flags:**
| Flag | Short | Description | Default |
| ------------- | ----- | -------------------------------------------- | ------- |
| `--variables` | `-v` | JSON string of variables | |
| `--operation` | `-o` | Operation name for multi-operation documents | |
| `--header` | `-H` | Additional headers (key:value,key2:value2) | |
| `--pretty` | | Pretty print output | `true` |
**Examples:**
```bash
# Simple query
mockd graphql query http://localhost:4280/graphql "{ users { id name } }"
# Query with variables
mockd graphql query http://localhost:4280/graphql \
"query GetUser($id: ID!) { user(id: $id) { name } }" \
-v '{"id": "123"}'
# Query from file
mockd graphql query http://localhost:4280/graphql @query.graphql
# With custom headers
mockd graphql query http://localhost:4280/graphql "{ me { name } }" \
-H "Authorization:Bearer token123"
```
***
## Chaos Engineering Commands
[Section titled “Chaos Engineering Commands”](#chaos-engineering-commands)
### mockd chaos
[Section titled “mockd chaos”](#mockd-chaos)
Manage chaos injection for fault testing.
```bash
mockd chaos [flags]
```
**Subcommands:**
* `enable` - Enable chaos injection
* `disable` - Disable chaos injection
* `status` - Show current chaos configuration
* `profiles` - List available chaos profiles
* `apply` - Apply a named chaos profile
***
#### mockd chaos enable
[Section titled “mockd chaos enable”](#mockd-chaos-enable)
Enable chaos injection on the running mock server.
```bash
mockd chaos enable [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| --------------- | ----- | --------------------------------------- | ----------------------- |
| `--admin-url` | | Admin API base URL | `http://localhost:4290` |
| `--latency` | `-l` | Add random latency (e.g., “10ms-100ms”) | |
| `--error-rate` | `-e` | Error rate (0.0-1.0) | |
| `--error-code` | | HTTP error code to return | `500` |
| `--path` | `-p` | Path pattern to apply chaos to (regex) | |
| `--probability` | | Probability of applying chaos | `1.0` |
**Examples:**
```bash
# Enable random latency
mockd chaos enable --latency "50ms-200ms"
# Enable error injection with 10% rate
mockd chaos enable --error-rate 0.1 --error-code 503
# Apply chaos only to specific paths
mockd chaos enable --latency "100ms-500ms" --path "/api/.*"
# Combine latency and errors
mockd chaos enable --latency "10ms-50ms" --error-rate 0.05
```
***
#### mockd chaos disable
[Section titled “mockd chaos disable”](#mockd-chaos-disable)
Disable chaos injection on the running mock server.
```bash
mockd chaos disable [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | ------------------ | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
**Examples:**
```bash
mockd chaos disable
```
***
#### mockd chaos status
[Section titled “mockd chaos status”](#mockd-chaos-status)
Show current chaos configuration.
```bash
mockd chaos status [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | --------------------- | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
| `--json` | Output in JSON format | |
**Examples:**
```bash
mockd chaos status
mockd chaos status --json
```
***
#### mockd chaos profiles
[Section titled “mockd chaos profiles”](#mockd-chaos-profiles)
List all available chaos profiles. Profiles are pre-built chaos configurations for common failure scenarios.
```bash
mockd chaos profiles [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | ------------------------------------------------------------- | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
| `--json` | Output in JSON format (includes full config for each profile) | |
**Examples:**
```bash
# List all profiles
mockd chaos profiles
# List with full configuration details
mockd chaos profiles --json
```
**Output:**
```plaintext
Available chaos profiles:
degraded Partially degraded service
dns-flaky Intermittent DNS resolution failures
flaky Unreliable service with random errors
mobile-3g Mobile 3G network conditions
offline Service completely down
overloaded Overloaded server under heavy load
rate-limited Rate-limited API
satellite Satellite internet simulation
slow-api Simulates slow upstream API
timeout Connection timeout simulation
Apply a profile with: mockd chaos apply
```
***
#### mockd chaos apply
[Section titled “mockd chaos apply”](#mockd-chaos-apply)
Apply a named chaos profile. This replaces the current chaos configuration with the profile’s settings.
```bash
mockd chaos apply [flags]
```
**Arguments:**
| Argument | Description |
| -------------- | --------------------------------------------------------------- |
| `profile-name` | Name of the chaos profile to apply (see `mockd chaos profiles`) |
**Flags:**
| Flag | Description | Default |
| ------------- | ------------------ | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
**Examples:**
```bash
# Simulate a flaky service (20% errors, 0-100ms latency)
mockd chaos apply flaky
# Simulate mobile network conditions (300-800ms, 50KB/s, 2% errors)
mockd chaos apply mobile-3g
# Take the service completely offline (100% 503 errors)
mockd chaos apply offline
# Disable when done
mockd chaos disable
```
***
## Verification Commands
[Section titled “Verification Commands”](#verification-commands)
### mockd verify
[Section titled “mockd verify”](#mockd-verify)
Verify mock call counts and inspect invocations. Useful for integration testing where you need to prove your code makes the correct API calls.
```bash
mockd verify [flags]
```
**Subcommands:**
| Command | Description |
| ------------- | ------------------------------------------------------------ |
| `status` | Show call count and last-called time for a mock |
| `check` | Assert that a mock was called the expected number of times |
| `invocations` | List recorded request details for a mock |
| `reset` | Clear verification data (call counts and invocation history) |
***
#### mockd verify status
[Section titled “mockd verify status”](#mockd-verify-status)
Show the call count and last-called timestamp for a specific mock.
```bash
mockd verify status [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | --------------------- | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
| `--json` | Output in JSON format | |
**Examples:**
```bash
# Check how many times a mock was called
mockd verify status http_abc123
# Output:
# Mock: http_abc123
# Call count: 5
# Last called: 2026-02-26 19:30:45
# JSON output
mockd verify status http_abc123 --json
```
***
#### mockd verify check
[Section titled “mockd verify check”](#mockd-verify-check)
Assert call count expectations for a mock. Returns exit code 0 on pass and exit code 1 on failure — suitable for CI scripts and test automation.
At least one assertion flag is required.
```bash
mockd verify check [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | --------------------------------------- | ----------------------- |
| `--exactly` | Assert mock was called exactly N times | |
| `--at-least` | Assert mock was called at least N times | |
| `--at-most` | Assert mock was called at most N times | |
| `--never` | Assert mock was never called | |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
| `--json` | Output in JSON format | |
**Examples:**
```bash
# Assert exactly 3 calls
mockd verify check http_abc123 --exactly 3
# PASS: called exactly 3 time(s) (called 3 time(s))
# Assert at least 1 call
mockd verify check http_abc123 --at-least 1
# PASS: called at least 1 time(s) (called 5 time(s))
# Assert never called (useful for negative testing)
mockd verify check http_abc123 --never
# FAIL: expected never called but was called 5 time(s)
# Assert a range (combine flags)
mockd verify check http_abc123 --at-least 1 --at-most 10
# Use in CI — non-zero exit code on failure
mockd verify check http_abc123 --exactly 1 || echo "Verification failed!"
```
***
#### mockd verify invocations
[Section titled “mockd verify invocations”](#mockd-verify-invocations)
List all recorded invocation details (method, path, timestamp, body) for a specific mock.
```bash
mockd verify invocations [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | --------------------- | ----------------------- |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
| `--json` | Output in JSON format | |
**Examples:**
```bash
# List all invocations for a mock
mockd verify invocations http_abc123
# Output:
# Mock: http_abc123 (3 invocation(s))
#
# [1] 19:30:45.123 GET /api/users at http_abc123
# [2] 19:31:02.456 POST /api/users at http_abc123
# Body: {"name":"Alice","email":"alice@example.com"}
# [3] 19:31:15.789 GET /api/users at http_abc123
# JSON output (full request details)
mockd verify invocations http_abc123 --json
```
***
#### mockd verify reset
[Section titled “mockd verify reset”](#mockd-verify-reset)
Clear verification data (call counts and invocation history) for a specific mock or all mocks. Use between test runs for isolation.
```bash
mockd verify reset [mock-id] [flags]
```
**Flags:**
| Flag | Description | Default |
| ------------- | ------------------------------------- | ----------------------- |
| `--all` | Reset verification data for all mocks | |
| `--admin-url` | Admin API base URL | `http://localhost:4290` |
| `--json` | Output in JSON format | |
**Examples:**
```bash
# Reset a specific mock
mockd verify reset http_abc123
# Verification data cleared for mock: http_abc123
# Reset all mocks (useful between test suites)
mockd verify reset --all
# All verification data cleared
```
***
## Tunnel Commands
[Section titled “Tunnel Commands”](#tunnel-commands)
### mockd tunnel
[Section titled “mockd tunnel”](#mockd-tunnel)
Start a local mock server + admin API + cloud tunnel in one shot. This is the easiest way to expose mocks to the internet — everything starts with a single command.
If no `--token` flag or `MOCKD_TOKEN` environment variable is set, an anonymous tunnel token is fetched automatically (2-hour session, 100MB bandwidth).
```bash
mockd tunnel [flags]
```
**Flags:**
| Flag | Short | Description | Default |
| -------------- | ----- | -------------------------------------------------------------- | ---------------- |
| `--port` | `-p` | HTTP server port | `4280` |
| `--admin-port` | | Admin API port | `4290` |
| `--config` | `-c` | Path to mock configuration file | |
| `--relay` | | Relay server address (host or host:port) | `relay.mockd.io` |
| `--token` | | Authentication token (or set `MOCKD_TOKEN` env var) | |
| `--subdomain` | `-s` | Requested subdomain (auto-assigned if empty) | |
| `--domain` | | Custom domain (must be verified) | |
| `--auth-token` | | Require this token for incoming requests | |
| `--auth-basic` | | Require Basic Auth for incoming requests (format: `user:pass`) | |
| `--allow-ips` | | Allow only these IPs (comma-separated CIDR or IP) | |
The default relay is `relay.mockd.io` on port 443. If the relay address does not include a port, port 443 is appended automatically.
**Authentication modes** (`--auth-token`, `--auth-basic`, `--allow-ips`) are mutually exclusive — use at most one per tunnel.
**Examples:**
```bash
# Start mock server + tunnel (anonymous token, zero config)
mockd tunnel
# Start with a config file
mockd tunnel --config mocks.yaml
# Request a custom subdomain
mockd tunnel --config mocks.yaml --subdomain my-api
# Use an authenticated token
mockd tunnel --token $MOCKD_TOKEN --config mocks.yaml
# Protect the tunnel with a bearer token
mockd tunnel --config mocks.yaml --auth-token secret123
# Protect with Basic Auth
mockd tunnel --config mocks.yaml --auth-basic admin:password
# Restrict by IP range
mockd tunnel --config mocks.yaml --allow-ips "10.0.0.0/8,192.168.1.0/24"
# Use a custom relay and port
mockd tunnel --relay relay.yourcompany.com:4433 --token $TOKEN
```
***
### mockd tunnel enable
[Section titled “mockd tunnel enable”](#mockd-tunnel-enable)
Enable a tunnel on an already-running engine, making its mocks publicly accessible. The engine must be reachable via the admin API.
```bash
mockd tunnel enable [flags]
```
**Flags:**
| Flag | Description | Default |
| ---------------------- | ---------------------------------------------- | ------- |
| `--engine` | Engine ID | `local` |
| `--mode` | Exposure mode: `all`, `selected`, `none` | `all` |
| `--subdomain` | Custom subdomain (auto-assigned if empty) | |
| `--domain` | Custom domain | |
| `--auth-token` | Require token for incoming requests | |
| `--auth-basic` | Require Basic Auth (`user:pass`) | |
| `--allow-ips` | Restrict by IP (comma-separated CIDRs) | |
| `--workspaces` | Expose only these workspaces (comma-separated) | |
| `--folders` | Expose only these folders (comma-separated) | |
| `--mocks` | Expose only these mock IDs (comma-separated) | |
| `--types` | Expose only these mock types (comma-separated) | |
| `--exclude-workspaces` | Exclude these workspaces (comma-separated) | |
| `--exclude-folders` | Exclude these folders (comma-separated) | |
| `--exclude-mocks` | Exclude these mock IDs (comma-separated) | |
**Examples:**
```bash
# Enable tunnel on the local engine (all mocks)
mockd tunnel enable
# Expose only HTTP mocks
mockd tunnel enable --types http
# Expose only specific workspaces
mockd tunnel enable --workspaces payments,users
# Expose all except certain folders
mockd tunnel enable --exclude-folders internal,debug
# Enable with authentication
mockd tunnel enable --auth-token my-secret-token
# Enable on a specific engine
mockd tunnel enable --engine my-engine-id
```
***
### mockd tunnel disable
[Section titled “mockd tunnel disable”](#mockd-tunnel-disable)
Disable the tunnel on an engine, removing public access.
```bash
mockd tunnel disable [flags]
```
**Flags:**
| Flag | Description | Default |
| ---------- | ----------- | ------- |
| `--engine` | Engine ID | `local` |
**Examples:**
```bash
# Disable tunnel on the local engine
mockd tunnel disable
# Disable on a specific engine
mockd tunnel disable --engine my-engine-id
```
***
### mockd tunnel status
[Section titled “mockd tunnel status”](#mockd-tunnel-status)
Show detailed tunnel status for an engine, including public URL, transport, session ID, and connection time.
```bash
mockd tunnel status [flags]
```
**Flags:**
| Flag | Description | Default |
| ---------- | ----------- | ------- |
| `--engine` | Engine ID | `local` |
**Examples:**
```bash
# Check tunnel status
mockd tunnel status
# Check status on a specific engine
mockd tunnel status --engine my-engine-id
```
***
### mockd tunnel stop
[Section titled “mockd tunnel stop”](#mockd-tunnel-stop)
Alias for `mockd tunnel disable`. Disables the tunnel on an engine.
```bash
mockd tunnel stop [flags]
```
**Flags:**
| Flag | Description | Default |
| ---------- | ----------- | ------- |
| `--engine` | Engine ID | `local` |
***
### mockd tunnel list
[Section titled “mockd tunnel list”](#mockd-tunnel-list)
List all active tunnels across all engines.
```bash
mockd tunnel list
```
Displays a table of active tunnels with engine ID, name, public URL, status, transport, and uptime.
**Examples:**
```bash
mockd tunnel list
```
***
### mockd tunnel preview
[Section titled “mockd tunnel preview”](#mockd-tunnel-preview)
Preview which mocks would be exposed through a tunnel without actually enabling it. Useful for verifying filter settings before going live.
```bash
mockd tunnel preview [flags]
```
**Flags:**
| Flag | Description | Default |
| ---------------------- | ---------------------------------------------- | ------- |
| `--engine` | Engine ID | `local` |
| `--mode` | Exposure mode: `all`, `selected`, `none` | `all` |
| `--workspaces` | Expose only these workspaces (comma-separated) | |
| `--folders` | Expose only these folders (comma-separated) | |
| `--mocks` | Expose only these mock IDs (comma-separated) | |
| `--types` | Expose only these mock types (comma-separated) | |
| `--exclude-workspaces` | Exclude these workspaces (comma-separated) | |
| `--exclude-folders` | Exclude these folders (comma-separated) | |
| `--exclude-mocks` | Exclude these mock IDs (comma-separated) | |
**Examples:**
```bash
# Preview all mocks that would be exposed
mockd tunnel preview
# Preview only HTTP mocks
mockd tunnel preview --types http
# Preview with exclusions
mockd tunnel preview --exclude-workspaces internal
```
***
## See Also
[Section titled “See Also”](#see-also)
* [Configuration Reference](/reference/configuration) - Config file format
* [Admin API Reference](/reference/admin-api) - Runtime management API
# Configuration Reference
> Complete reference for mockd configuration files, including all mock types, server settings, and validation options.
Complete reference for mockd configuration files.
## File Format
[Section titled “File Format”](#file-format)
mockd supports YAML and JSON configuration files. The `version` field is required.
```bash
mockd serve --config mocks.yaml
mockd serve --config mocks.json
```
## Top-Level Structure
[Section titled “Top-Level Structure”](#top-level-structure)
```yaml
version: "1.0"
mocks:
- id: string
name: string
type: http | websocket | graphql | grpc | mqtt | soap | oauth
enabled: boolean
http: { ... } # if type: http
websocket: { ... } # if type: websocket
graphql: { ... } # if type: graphql
grpc: { ... } # if type: grpc
mqtt: { ... } # if type: mqtt
soap: { ... } # if type: soap
oauth: { ... } # if type: oauth
serverConfig: { ... } # Optional server settings
statefulResources: [ ... ] # Optional CRUD resources
tables: [ ... ] # Optional stateful data tables
extend: [ ... ] # Optional mock-to-table bindings
imports: [ ... ] # Optional spec imports with namespacing
customOperations: [ ... ] # Optional multi-step operations
```
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ------------------------------------------------------------- |
| `version` | string | Yes | Config version (e.g., `"1.0"`) |
| `mocks` | array | Yes | Mock definitions |
| `serverConfig` | object | No | Server configuration |
| `statefulResources` | array | No | Stateful CRUD resources (low-level) |
| `tables` | map | No | Named data stores (pure data, no routing) |
| `extend` | array | No | Bindings from mocks to tables (action + table reference) |
| `imports` | array | No | Import external specs (OpenAPI, WSDL) with namespace prefixes |
| `customOperations` | array | No | Multi-step custom operations with expression evaluation |
Project Configuration Format
For multi-workspace setups using `mockd up`, see the project configuration format which adds `admins`, `engines`, and `workspaces` top-level sections. Run `mockd help config` for the full reference.
***
## Mock Definition
[Section titled “Mock Definition”](#mock-definition)
All mock types share common fields:
```yaml
mocks:
- id: unique-mock-id
name: "Human-readable name"
description: "Optional description"
type: http
enabled: true
parentId: "" # Folder ID (optional)
metaSortKey: 0 # Sort order (optional)
http: { ... } # Type-specific configuration
```
### Common Fields
[Section titled “Common Fields”](#common-fields)
| Field | Type | Required | Default | Description |
| ------------- | ------- | -------- | -------------- | -------------------------------------------------------------------------- |
| `id` | string | No | Auto-generated | Unique identifier |
| `type` | string | No | Inferred | Mock type: `http`, `websocket`, `graphql`, `grpc`, `mqtt`, `soap`, `oauth` |
| `name` | string | No | | Human-readable name |
| `description` | string | No | | Longer description |
| `enabled` | boolean | No | `true` | Whether mock is active |
| `parentId` | string | No | | Folder ID for organization |
| `metaSortKey` | number | No | | Manual ordering within folder |
| `workspaceId` | string | No | | Workspace this mock belongs to (set automatically by workspace context) |
***
## HTTP Mock
[Section titled “HTTP Mock”](#http-mock)
HTTP mocks match incoming requests and return configured responses.
```yaml
mocks:
- id: get-users
name: Get Users
type: http
enabled: true
http:
priority: 0
matcher:
method: GET
path: /api/users
headers:
Authorization: "Bearer *"
queryParams:
status: active
response:
statusCode: 200
headers:
Content-Type: application/json
body: '{"users": []}'
delayMs: 100
```
### HTTP Spec Fields
[Section titled “HTTP Spec Fields”](#http-spec-fields)
| Field | Type | Description |
| ------------ | ------- | -------------------------------------------------- |
| `priority` | integer | Match priority (higher = matches first) |
| `matcher` | object | Request matching criteria |
| `response` | object | Response definition |
| `sse` | object | Server-Sent Events config (instead of response) |
| `chunked` | object | Chunked transfer config (instead of response) |
| `validation` | object | Request validation ([see Validation](#validation)) |
### HTTP Matcher
[Section titled “HTTP Matcher”](#http-matcher)
| Field | Type | Description |
| -------------- | ------ | -------------------------------------------------------- |
| `method` | string | HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) |
| `path` | string | URL path (supports `{param}` syntax for path parameters) |
| `pathPattern` | string | Regex pattern for URL path |
| `headers` | map | Header matchers (exact match or glob patterns with `*`) |
| `queryParams` | map | Query parameter matchers (exact match) |
| `bodyContains` | string | Body must contain this string |
| `bodyEquals` | string | Body must equal this string exactly |
| `bodyPattern` | string | Body must match this regex pattern |
| `bodyJsonPath` | map | JSONPath matchers (path: expected value) |
| `mtls` | object | mTLS client certificate matching |
### Path Patterns
[Section titled “Path Patterns”](#path-patterns)
```yaml
# Exact match
path: /api/users
# Path parameters
path: /api/users/{id}
path: /api/{resource}/{id}
# Greedy path parameter (matches multiple segments)
path: /api/files/{path:.*}
# Regex pattern
pathPattern: "/api/users/[0-9]+"
```
### HTTP Response
[Section titled “HTTP Response”](#http-response)
| Field | Type | Default | Description |
| ------------ | ------- | ------- | ------------------------------------------------------- |
| `statusCode` | integer | `200` | HTTP status code |
| `headers` | map | `{}` | Response headers |
| `body` | string | `""` | Response body (supports templates) |
| `bodyFile` | string | | Load body from file path |
| `delayMs` | integer | `0` | Response delay in milliseconds |
| `seed` | integer | `0` | Deterministic seed for faker/random output (0 = random) |
### mTLS Matching
[Section titled “mTLS Matching”](#mtls-matching)
```yaml
matcher:
mtls:
cn: "client.example.com" # Common Name pattern
ou: "Engineering" # Organizational Unit pattern
o: "Example Corp" # Organization pattern
san:
dns: "*.example.com" # DNS SAN pattern
email: "*@example.com" # Email SAN pattern
ip: "10.0.0.*" # IP SAN pattern
```
### SSE (Server-Sent Events)
[Section titled “SSE (Server-Sent Events)”](#sse-server-sent-events)
```yaml
http:
matcher:
method: GET
path: /events
sse:
events:
- type: update
data: '{"status": "connected"}'
id: "1"
- type: update
data: '{"status": "processing"}'
delay: 1000
timing:
fixedDelay: 1000 # ms between events
initialDelay: 0 # ms before first event
lifecycle:
maxEvents: 10 # max events before closing
timeout: 60000 # connection timeout ms
keepaliveInterval: 15 # keepalive interval in seconds
resume:
enabled: true # support Last-Event-ID
bufferSize: 100 # events to buffer
```
### Chunked Transfer
[Section titled “Chunked Transfer”](#chunked-transfer)
```yaml
http:
matcher:
method: GET
path: /stream
chunked:
chunkSize: 1024 # bytes per chunk
chunkDelay: 100 # ms between chunks
data: "..." # data to stream
dataFile: ./large.json # or load from file
format: ndjson # optional: ndjson format
ndjsonItems: # for ndjson format
- {"id": 1}
- {"id": 2}
```
***
## WebSocket Mock
[Section titled “WebSocket Mock”](#websocket-mock)
WebSocket mocks handle bidirectional message communication.
```yaml
mocks:
- id: chat-ws
name: Chat WebSocket
type: websocket
enabled: true
websocket:
path: /ws/chat
subprotocols:
- chat
- json
requireSubprotocol: false
echoMode: true
maxMessageSize: 65536
idleTimeout: "5m"
maxConnections: 100
heartbeat:
enabled: true
interval: "30s"
timeout: "10s"
matchers:
- match:
type: exact
value: "ping"
response:
type: text
value: "pong"
- match:
type: json
path: "$.type"
value: "join"
response:
type: json
value:
type: "joined"
message: "Welcome!"
defaultResponse:
type: json
value:
type: "echo"
message: "{{message}}"
```
### WebSocket Spec Fields
[Section titled “WebSocket Spec Fields”](#websocket-spec-fields)
| Field | Type | Default | Description |
| -------------------- | ------- | -------- | ------------------------------------------ |
| `path` | string | Required | WebSocket upgrade path |
| `subprotocols` | array | `[]` | Supported subprotocols |
| `requireSubprotocol` | boolean | `false` | Require matching subprotocol |
| `echoMode` | boolean | `false` | Echo received messages |
| `maxMessageSize` | integer | `65536` | Max message size (bytes) |
| `idleTimeout` | string | | Connection idle timeout |
| `maxConnections` | integer | `0` | Max concurrent connections (0 = unlimited) |
| `heartbeat` | object | | Ping/pong keepalive config |
| `matchers` | array | `[]` | Message matching rules |
| `defaultResponse` | object | | Response when no matcher matches |
| `scenario` | object | | Scripted message sequence |
### WebSocket Match Criteria
[Section titled “WebSocket Match Criteria”](#websocket-match-criteria)
| Field | Type | Description |
| ------------- | ------ | ------------------------------------------------ |
| `type` | string | Match type: `exact`, `contains`, `regex`, `json` |
| `value` | string | Value to match |
| `path` | string | JSONPath for `json` type |
| `messageType` | string | Filter by message type: `text`, `binary` |
### WebSocket Message Response
[Section titled “WebSocket Message Response”](#websocket-message-response)
| Field | Type | Description |
| ------- | ------ | -------------------------------------------- |
| `type` | string | Response type: `text`, `json`, `binary` |
| `value` | any | Response content (string or object for json) |
| `delay` | string | Delay before sending (e.g., “100ms”) |
### WebSocket Heartbeat
[Section titled “WebSocket Heartbeat”](#websocket-heartbeat)
| Field | Type | Default | Description |
| ---------- | ------- | ------- | ---------------- |
| `enabled` | boolean | `false` | Enable ping/pong |
| `interval` | string | `"30s"` | Ping interval |
| `timeout` | string | `"10s"` | Pong timeout |
### WebSocket Scenario
[Section titled “WebSocket Scenario”](#websocket-scenario)
```yaml
websocket:
scenario:
name: "onboarding"
loop: false
resetOnReconnect: true
steps:
- type: send
message:
type: json
value: {"type": "welcome"}
- type: wait
duration: "1s"
- type: expect
match:
type: json
path: "$.type"
value: "ready"
timeout: "10s"
optional: false
```
***
## GraphQL Mock
[Section titled “GraphQL Mock”](#graphql-mock)
GraphQL mocks provide a full GraphQL API endpoint.
```yaml
mocks:
- id: graphql-api
name: GraphQL API
type: graphql
enabled: true
graphql:
path: /graphql
introspection: true
schema: |
type Query {
users: [User!]!
user(id: ID!): User
}
type User {
id: ID!
name: String!
email: String!
}
resolvers:
Query.users:
response:
- id: "1"
name: "Alice"
email: "alice@example.com"
Query.user:
response:
id: "1"
name: "Alice"
email: "alice@example.com"
```
### GraphQL Spec Fields
[Section titled “GraphQL Spec Fields”](#graphql-spec-fields)
| Field | Type | Default | Description |
| --------------- | ------- | -------- | ----------------------------- |
| `path` | string | Required | GraphQL endpoint path |
| `schema` | string | | Inline SDL schema |
| `schemaFile` | string | | Path to .graphql schema file |
| `introspection` | boolean | `false` | Enable introspection queries |
| `resolvers` | map | `{}` | Field resolver configurations |
| `subscriptions` | map | `{}` | Subscription configurations |
### GraphQL Resolvers
[Section titled “GraphQL Resolvers”](#graphql-resolvers)
Resolvers are keyed by `Type.field`:
```yaml
resolvers:
Query.users:
response:
- { id: "1", name: "Alice" }
- { id: "2", name: "Bob" }
delay: "100ms"
Query.user:
# Match specific arguments
match:
args:
id: "1"
response:
id: "1"
name: "Alice"
Mutation.createUser:
response:
id: "{{uuid}}"
name: "New User"
Query.error:
error:
message: "Something went wrong"
path: ["error"]
extensions:
code: "INTERNAL_ERROR"
```
***
## gRPC Mock
[Section titled “gRPC Mock”](#grpc-mock)
gRPC mocks provide a gRPC service endpoint.
```yaml
mocks:
- id: grpc-greeter
name: Greeter Service
type: grpc
enabled: true
grpc:
port: 50051
reflection: true
protoFile: |
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloStream (HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
services:
helloworld.Greeter:
methods:
SayHello:
response:
message: "Hello, World!"
SayHelloStream:
responses:
- message: "Hello 1"
- message: "Hello 2"
- message: "Hello 3"
streamDelay: "500ms"
```
### gRPC Spec Fields
[Section titled “gRPC Spec Fields”](#grpc-spec-fields)
| Field | Type | Default | Description |
| ------------- | ------- | -------- | ----------------------- |
| `port` | integer | Required | gRPC server port |
| `protoFile` | string | | Inline proto definition |
| `protoFiles` | array | | Paths to .proto files |
| `importPaths` | array | | Proto import paths |
| `reflection` | boolean | `false` | Enable gRPC reflection |
| `services` | map | `{}` | Service configurations |
### gRPC Method Config
[Section titled “gRPC Method Config”](#grpc-method-config)
```yaml
services:
package.Service:
methods:
MethodName:
response: # Single response
field: value
responses: # Multiple responses (streaming)
- { field: value1 }
- { field: value2 }
delay: "100ms" # Response delay
streamDelay: "50ms" # Delay between stream messages
match: # Request matching
metadata:
authorization: "Bearer *"
request:
field: expected_value
error:
code: "NOT_FOUND"
message: "Resource not found"
details:
type: "ErrorInfo"
```
***
## MQTT Mock
[Section titled “MQTT Mock”](#mqtt-mock)
MQTT mocks provide an MQTT broker.
```yaml
mocks:
- id: mqtt-broker
name: IoT MQTT Broker
type: mqtt
enabled: true
mqtt:
port: 1883
tls:
enabled: false
certFile: ./certs/mqtt.crt
keyFile: ./certs/mqtt.key
auth:
enabled: false
users:
- username: device
password: secret123
acl:
- topic: "sensors/#"
access: publish
- topic: "commands/#"
access: subscribe
topics:
- topic: sensors/temperature
qos: 1
retain: true
messages:
- payload: '{"temp": 22, "unit": "celsius"}'
interval: "5s"
repeat: true
- topic: commands/device/+
qos: 1
onPublish:
response:
payload: '{"status": "ack"}'
forward: responses/device
```
### MQTT Spec Fields
[Section titled “MQTT Spec Fields”](#mqtt-spec-fields)
| Field | Type | Default | Description |
| -------- | ------- | -------- | ---------------------------- |
| `port` | integer | Required | MQTT broker port |
| `tls` | object | | TLS configuration |
| `auth` | object | | Authentication configuration |
| `topics` | array | `[]` | Topic configurations |
### MQTT Topic Config
[Section titled “MQTT Topic Config”](#mqtt-topic-config)
| Field | Type | Default | Description |
| ------------------ | ------- | -------- | ---------------------------------------------- |
| `topic` | string | Required | Topic pattern (supports `+` and `#` wildcards) |
| `qos` | integer | `0` | Quality of Service (0, 1, 2) |
| `retain` | boolean | `false` | Retain last message |
| `messages` | array | | Messages to publish |
| `onPublish` | object | | Handler for received messages |
| `deviceSimulation` | object | | Simulate multiple devices |
### MQTT Message Config
[Section titled “MQTT Message Config”](#mqtt-message-config)
| Field | Type | Description |
| ---------- | ------- | ------------------------------------ |
| `payload` | string | Message payload (supports templates) |
| `delay` | string | Initial delay before sending |
| `interval` | string | Repeat interval |
| `repeat` | boolean | Whether to repeat |
***
## SOAP Mock
[Section titled “SOAP Mock”](#soap-mock)
SOAP mocks provide SOAP/WSDL service endpoints.
```yaml
mocks:
- id: soap-service
name: Calculator Service
type: soap
enabled: true
soap:
path: /soap/calculator
wsdlFile: ./calculator.wsdl # or inline with wsdl:
operations:
Add:
soapAction: "http://example.com/Add"
response: |
{{xpath://Add/a}}
delay: "50ms"
match:
xpath:
"//a": "10"
fault:
code: "Server.InvalidInput"
message: "Invalid input provided"
detail: "1001"
```
### SOAP Spec Fields
[Section titled “SOAP Spec Fields”](#soap-spec-fields)
| Field | Type | Description |
| ------------ | ------ | ------------------------ |
| `path` | string | SOAP endpoint path |
| `wsdl` | string | Inline WSDL definition |
| `wsdlFile` | string | Path to WSDL file |
| `operations` | map | Operation configurations |
### SOAP Operation Config
[Section titled “SOAP Operation Config”](#soap-operation-config)
| Field | Type | Description |
| ------------------ | ------ | --------------------------------------------------------------------------- |
| `soapAction` | string | SOAPAction header value |
| `response` | string | XML response body |
| `delay` | string | Response delay |
| `match` | object | XPath-based request matching |
| `fault` | object | SOAP fault response |
| `statefulResource` | string | Name of stateful resource for CRUD operations |
| `statefulAction` | string | CRUD action: `get`, `list`, `create`, `update`, `patch`, `delete`, `custom` |
> **Note:** When `statefulResource` is set, the operation gets its response from the stateful resource — `response` and `fault` fields are not required. `statefulResource` and `statefulAction` must be set together.
***
## Custom Operations
[Section titled “Custom Operations”](#custom-operations)
Custom operations compose reads, writes, and expression-evaluated transforms against stateful resources.
```yaml
version: "1.0"
customOperations:
- name: TransferFunds
consistency: atomic
steps:
- type: read
resource: accounts
id: "input.sourceId"
as: source
- type: read
resource: accounts
id: "input.destId"
as: dest
- type: update
resource: accounts
id: "input.sourceId"
set:
balance: "source.balance - input.amount"
- type: update
resource: accounts
id: "input.destId"
set:
balance: "dest.balance + input.amount"
response:
status: '"completed"'
```
### Custom Operation Fields
[Section titled “Custom Operation Fields”](#custom-operation-fields)
| Field | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |
| `name` | string | Yes | Unique operation name |
| `consistency` | string | No | Execution mode: `best_effort` (default) or `atomic` (rollback-on-failure, no isolation guarantees) |
| `steps` | array | Yes | Ordered sequence of steps |
| `response` | map | No | Field → expression map for building the result |
### Step Config
[Section titled “Step Config”](#step-config)
| Field | Type | Description |
| -------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `type` | string | Step type: `read`, `create`, `update`, `delete`, `set`, `list`, `validate` |
| `resource` | string | Stateful resource name (for read/create/update/delete/list) |
| `id` | string | Expression resolving to item ID (for read/update/delete) |
| `as` | string | Variable name to store the result (required for `read`/`list`, optional for `create`/`update`) |
| `set` | map | Field → expression map (for create/update) |
| `var` | string | Variable name (for set steps) |
| `value` | string | Expression value (for set steps) |
| `filter` | map | Field → expression map for filtering items (for list steps) |
| `condition` | string | Boolean expression (for validate steps — halts operation if false) |
| `errorMessage` | string | Error message returned when validate fails |
| `errorStatus` | integer | HTTP status code for validate failures (default: 400) |
Expressions use [expr-lang/expr](https://github.com/expr-lang/expr) syntax. The environment includes `input` (request data) and variables from prior steps (from `as` and `set.var`).
**String literals in expressions:** To set a field to a literal string value, wrap the string in inner quotes: `'"succeeded"'`. Without inner quotes (e.g., `"succeeded"`), expr-lang treats the value as a variable reference. See the [Custom Operations guide](/guides/stateful-mocking/#string-literals-in-expressions) for details and examples.
Use `mockd stateful custom validate --file ` to preflight custom operations before registering them. Add `--strict` to fail on warnings (for example, empty `set` maps). For stronger preflight checks, provide sample input and run `--check-expressions-runtime` with `--fixtures-file` to evaluate expressions without writing state.
***
## Server Configuration
[Section titled “Server Configuration”](#server-configuration)
Server settings can be included in the config file.
> **Note:** Port settings (`httpPort`, `httpsPort`, `adminPort`) from config files are currently overridden by CLI flags. Use `--port` and `--admin-port` flags to set ports:
>
> ```bash
> mockd serve --config myconfig.yaml --port 4280 --admin-port 4290
> ```
```yaml
version: "1.0"
serverConfig:
httpPort: 4280
httpsPort: 4283
adminPort: 4290
logRequests: true
maxLogEntries: 1000
maxBodySize: 10485760 # 10MB
readTimeout: 30 # seconds
writeTimeout: 30 # seconds
tls:
enabled: false
certFile: ./certs/server.crt
keyFile: ./certs/server.key
autoGenerateCert: true
mtls:
enabled: false
clientAuth: "require-and-verify"
caCertFile: ./certs/ca.crt
allowedCNs:
- "client.example.com"
mocks: [...]
```
### Server Config Fields
[Section titled “Server Config Fields”](#server-config-fields)
| Field | Type | Default | Description |
| ---------------- | ------- | ---------- | ----------------------------------------------- |
| `httpPort` | integer | `4280` | HTTP server port (0 = disabled) |
| `httpsPort` | integer | `0` | HTTPS server port (0 = disabled) |
| `adminPort` | integer | `4290` | Admin API port |
| `managementPort` | integer | `4281` | Engine management API port (internal) |
| `logRequests` | boolean | `true` | Enable request logging |
| `maxLogEntries` | integer | `1000` | Max log entries to retain |
| `maxBodySize` | integer | `10485760` | Max request body size (bytes) |
| `readTimeout` | integer | `30` | HTTP read timeout (seconds) |
| `writeTimeout` | integer | `30` | HTTP write timeout (seconds) |
| `maxConnections` | integer | `0` | Max concurrent HTTP connections (0 = unlimited) |
The `managementPort` is used for internal communication between the Admin API and the mock engine. In standalone mode, you typically don’t need to configure this.
### TLS Configuration
[Section titled “TLS Configuration”](#tls-configuration)
| Field | Type | Default | Description |
| ------------------ | ------- | ------- | ------------------------------ |
| `enabled` | boolean | `false` | Enable TLS/HTTPS |
| `certFile` | string | | Path to certificate file |
| `keyFile` | string | | Path to private key file |
| `autoGenerateCert` | boolean | `false` | Auto-generate self-signed cert |
### mTLS Configuration
[Section titled “mTLS Configuration”](#mtls-configuration)
| Field | Type | Default | Description |
| ------------- | ------- | -------- | ----------------------------- |
| `enabled` | boolean | `false` | Enable mTLS |
| `clientAuth` | string | `"none"` | Client auth mode |
| `caCertFile` | string | | CA certificate file |
| `caCertFiles` | array | | Multiple CA certificate files |
| `allowedCNs` | array | | Allowed client Common Names |
| `allowedOUs` | array | | Allowed Organizational Units |
Client auth modes:
* `none` - No client certificate requested
* `request` - Client certificate requested but not required
* `require` - Client certificate required but not verified
* `verify-if-given` - Verify client certificate if provided
* `require-and-verify` - Require and verify client certificate
### CORS Configuration
[Section titled “CORS Configuration”](#cors-configuration)
Configure Cross-Origin Resource Sharing for the mock server.
```yaml
serverConfig:
cors:
enabled: true
allowOrigins:
- "http://localhost:3000"
- "https://app.example.com"
allowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowHeaders:
- Content-Type
- Authorization
- X-Requested-With
exposeHeaders:
- X-Request-ID
allowCredentials: false
maxAge: 86400
```
| Field | Type | Default | Description |
| ------------------ | ------- | ----------------------------------------------------------------- | ---------------------------------------------- |
| `enabled` | boolean | `true` | Enable CORS handling |
| `allowOrigins` | array | `["http://localhost:*"]` | Allowed origins (use `["*"]` for any) |
| `allowMethods` | array | `[GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD]` | Allowed HTTP methods |
| `allowHeaders` | array | `[Content-Type, Authorization, X-Requested-With, Accept, Origin]` | Allowed request headers |
| `exposeHeaders` | array | `[]` | Headers browsers can access |
| `allowCredentials` | boolean | `false` | Allow credentials (cannot use with `*` origin) |
| `maxAge` | integer | `86400` | Preflight cache duration (seconds) |
**Default behavior:** When not configured, mockd allows requests from localhost origins only. This is secure for local development while preventing cross-origin attacks.
**Wildcard origins:**
```yaml
cors:
allowOrigins: ["*"] # Allow any origin (not recommended for production)
```
**Note:** When `allowCredentials: true`, you cannot use wildcard origins.
### Rate Limiting Configuration
[Section titled “Rate Limiting Configuration”](#rate-limiting-configuration)
Configure rate limiting for the mock server.
```yaml
serverConfig:
rateLimit:
enabled: true
requestsPerSecond: 1000
burstSize: 2000
trustedProxies:
- "10.0.0.0/8"
- "172.16.0.0/12"
```
| Field | Type | Default | Description |
| ------------------- | ------- | ------- | --------------------------------- |
| `enabled` | boolean | `false` | Enable rate limiting |
| `requestsPerSecond` | float | `1000` | Requests per second limit |
| `burstSize` | integer | `2000` | Maximum burst size (token bucket) |
| `trustedProxies` | array | `[]` | CIDR ranges for trusted proxies |
**How it works:** Rate limiting uses a token bucket algorithm. The bucket fills at `requestsPerSecond` rate up to `burstSize` tokens. Each request consumes one token.
**Trusted proxies:** When set, mockd trusts `X-Forwarded-For` headers from these IP ranges for accurate client IP detection.
**Example: Strict rate limiting for load testing:**
```yaml
serverConfig:
rateLimit:
enabled: true
requestsPerSecond: 100
burstSize: 150
```
### Chaos Configuration
[Section titled “Chaos Configuration”](#chaos-configuration)
Configure chaos injection in the config file. Chaos settings can also be managed at runtime via the CLI (`mockd chaos enable`) or Admin API (`PUT /chaos`).
```yaml
serverConfig:
chaos:
enabled: true
latency:
min: "50ms"
max: "200ms"
probability: 1.0
errorRate:
probability: 0.1
statusCodes: [500, 502, 503]
defaultCode: 503
```
For advanced path-scoped rules with stateful fault types:
```yaml
serverConfig:
chaos:
enabled: true
rules:
- pathPattern: "/api/payments/.*"
faults:
- type: circuit_breaker
probability: 1.0
circuitBreaker:
failureThreshold: 5
recoveryTimeout: "30s"
halfOpenRequests: 2
tripStatusCode: 503
- pathPattern: "/api/.*"
faults:
- type: latency
probability: 0.5
latency:
min: "50ms"
max: "200ms"
```
| Field | Type | Default | Description |
| ----------------------- | ------- | ------- | ------------------------------------ |
| `enabled` | boolean | `false` | Enable chaos injection |
| `latency` | object | | Global latency settings |
| `latency.min` | string | | Minimum latency (Go duration) |
| `latency.max` | string | | Maximum latency (Go duration) |
| `latency.probability` | float | `1.0` | Probability of applying latency |
| `errorRate` | object | | Global error injection settings |
| `errorRate.probability` | float | `0` | Probability of error response |
| `errorRate.statusCodes` | array | `[500]` | Status codes to randomly choose from |
| `errorRate.defaultCode` | integer | `500` | Default error status code |
| `rules` | array | | Path-scoped chaos rules |
| `rules[].pathPattern` | string | | Regex pattern to match request paths |
| `rules[].faults` | array | | Fault definitions for matched paths |
**Fault types:** `latency`, `error`, `timeout`, `corrupt_body`, `empty_response`, `slow_body`, `connection_reset`, `partial_response`, `circuit_breaker`, `retry_after`, `progressive_degradation`, `chunked_dribble`
See the [Chaos Engineering guide](/guides/chaos-engineering/) for detailed usage and examples.
***
## Tables
[Section titled “Tables”](#tables)
Tables are named data stores — pure in-memory collections with no routing or HTTP endpoints attached. Tables hold seed data and are referenced by [extend bindings](#extend-bindings) to wire mock endpoints to CRUD actions.
```yaml
version: "1.0"
tables:
- name: users
idField: id
seedData:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
- name: products
idField: sku
seedData:
- sku: "WIDGET-001"
name: "Blue Widget"
price: 29.99
mocks: []
```
### Table Fields
[Section titled “Table Fields”](#table-fields)
| Field | Type | Default | Description |
| --------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idField` | string | `"id"` | Field name for resource ID |
| `idStrategy` | string | `"uuid"` | ID generation strategy: `uuid` (36-char UUID v4), `prefix` (prefix + 16 hex chars), `ulid` (26-char time-sortable), `sequence` (auto-incrementing integer), `short` (16 hex chars) |
| `idPrefix` | string | `""` | Prefix for generated IDs (when `idStrategy: prefix`, e.g., `"cus_"`) |
| `parentField` | string | `""` | Foreign key field for sub-resource filtering by parent |
| `maxItems` | integer | `0` | Max items in the table (0 = unlimited) |
| `seedData` | array | `[]` | Initial data to load |
| `validation` | object | | Validation rules ([see Validation](#validation)) |
| `response` | object | | Response transform config ([see Response Transform](#response-transform)) |
| `relationships` | map | `{}` | Field-to-table mappings for `?expand[]` support |
Each table has a `name` field (e.g., `users`, `products`). Internally, tables are converted into `statefulResources` entries — but unlike the legacy `statefulResources` + `basePath` pattern, tables never auto-generate HTTP endpoints. All routing is explicit via `extend`.
### Response Transform
[Section titled “Response Transform”](#response-transform)
Tables and extend bindings support a `response` field that controls how stateful data is shaped before it’s returned to clients. Binding-level overrides replace (not merge with) the table default.
```yaml
tables:
- name: customers
response:
timestamps:
format: unix
fields:
createdAt: created
updatedAt: updated
fields:
inject: { object: customer, livemode: false }
hide: [updatedAt]
rename: { firstName: first_name }
wrapAsList:
items:
url: "/v1/customers/{{id}}/items"
list:
dataField: data
extraFields: { object: list, has_more: false }
metaFields: { total: total_count }
hideMeta: true
create:
status: 200
delete:
status: 200
preserve: true
body:
id: "{{item.id}}"
object: customer
deleted: true
errors:
wrap: error
fields: { message: message, type: type, code: code }
inject: { doc_url: "https://docs.example.com" }
typeMap: { NOT_FOUND: invalid_request_error }
codeMap: { NOT_FOUND: resource_missing }
```
#### ResponseTransform Fields
[Section titled “ResponseTransform Fields”](#responsetransform-fields)
| Field | Type | Description |
| ------------ | ------ | ----------------------------------------------------- |
| `timestamps` | object | Timestamp format and field renaming |
| `fields` | object | Field injection, hiding, renaming, and array wrapping |
| `list` | object | List envelope customization (HTTP-specific) |
| `create` | object | Create verb override (status code) |
| `delete` | object | Delete verb override (status, body, preserve) |
| `errors` | object | Error response format customization |
#### Timestamps
[Section titled “Timestamps”](#timestamps)
| Field | Type | Default | Description |
| -------- | ------ | ----------- | ---------------------------------------------------------------------------------------------------------------- |
| `format` | string | `"rfc3339"` | Output format: `unix` (epoch seconds), `iso8601` (RFC3339 string), `rfc3339` (no-op), `none` (remove timestamps) |
| `fields` | map | `{}` | Rename timestamp keys. Keys: `createdAt`, `updatedAt`. Values: output names. |
#### Fields
[Section titled “Fields”](#fields)
| Field | Type | Default | Description |
| ------------ | ----- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `inject` | map | `{}` | Static key-value pairs added to every item response |
| `hide` | array | `[]` | Field names to remove from responses (data still stored) |
| `rename` | map | `{}` | Key renames applied to responses (key: original, value: output) |
| `wrapAsList` | map | `{}` | Array fields to wrap in `{object: "list", data: [...], has_more: false}` envelopes. Value is a `ListWrapConfig` with optional `url` template. |
**ListWrapConfig:**
| Field | Type | Description |
| ----- | ------ | --------------------------------------------------------------------------------------------------- |
| `url` | string | URL template for the sub-resource list. Supports `{{fieldName}}` substitution from the parent item. |
#### List
[Section titled “List”](#list)
| Field | Type | Default | Description |
| ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dataField` | string | `"data"` | Key for the items array in the list envelope |
| `extraFields` | map | `{}` | Static fields on the list envelope (including `null` values). All values are passed through as-is except `has_more`, which is dynamically computed from pagination state. |
| `metaFields` | map | `{}` | Rename pagination meta keys: `total`, `limit`, `offset`, `count` |
| `hideMeta` | boolean | `false` | Omit pagination metadata entirely |
#### Create (VerbOverride)
[Section titled “Create (VerbOverride)”](#create-verboverride)
| Field | Type | Default | Description |
| -------- | ------- | ------- | ------------------------------------- |
| `status` | integer | `201` | HTTP status code for create responses |
#### Delete (VerbOverride)
[Section titled “Delete (VerbOverride)”](#delete-verboverride)
| Field | Type | Default | Description |
| ---------- | ------- | ------- | ----------------------------------------------------------------------------------------- |
| `status` | integer | `204` | HTTP status code for delete responses |
| `body` | map | `nil` | Response body template. Supports `{{item.fieldName}}` substitution from the deleted item. |
| `preserve` | boolean | `false` | Soft delete: return the configured response but keep the item in the store |
#### Errors (ErrorTransform)
[Section titled “Errors (ErrorTransform)”](#errors-errortransform)
| Field | Type | Default | Description |
| --------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------------------- |
| `wrap` | string | `""` | Nest the error object under this key (e.g., `"error"` produces `{"error":{...}}`) |
| `fields` | map | `{}` | Map mockd error fields (`message`, `code`, `type`, `resource`, `id`, `field`) to custom names |
| `inject` | map | `{}` | Static fields on every error response |
| `typeMap` | map | `{}` | Map error codes (`NOT_FOUND`, `CONFLICT`, `VALIDATION_ERROR`, `CAPACITY_EXCEEDED`, `INTERNAL_ERROR`) to custom type strings |
| `codeMap` | map | `{}` | Map error codes to custom code strings |
**Transform execution order:** rename > hide > wrapAsList > timestamps > inject. See the [Response Transforms guide](/guides/stateful-mocking/#response-transforms) for detailed examples and the full Stripe digital twin walkthrough.
***
## Extend Bindings
[Section titled “Extend Bindings”](#extend-bindings)
Extend bindings wire mock endpoints to tables. Each binding references a mock (by `id`), a table, and an action to perform.
```yaml
version: "1.0"
tables:
- name: users
seedData:
- id: "1"
name: "Alice"
mocks:
- id: list-users
type: http
http:
matcher:
method: GET
path: /api/users
response:
statusCode: 200
- id: create-user
type: http
http:
matcher:
method: POST
path: /api/users
response:
statusCode: 201
- id: get-user
type: http
http:
matcher:
method: GET
path: /api/users/{id}
response:
statusCode: 200
extend:
- mock: list-users
table: users
action: list
- mock: create-user
table: users
action: create
- mock: get-user
table: users
action: get
```
### Extend Binding Fields
[Section titled “Extend Binding Fields”](#extend-binding-fields)
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `mock` | string | Yes | ID of the mock to bind |
| `table` | string | Yes | Name of the table to operate on |
| `action` | string | Yes | CRUD action: `list`, `get`, `create`, `update`, `patch`, `delete`, `custom` |
| `operation` | string | No | Operation name (required when `action: custom`) |
| `response` | object | No | Response transform override for this binding ([see Response Transform](#response-transform)). Replaces (does not merge with) the table default. |
### Supported Actions
[Section titled “Supported Actions”](#supported-actions)
| Action | Description |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list` | List all items in the table |
| `get` | Get a single item by ID (extracted from path parameter) |
| `create` | Create a new item from the request body |
| `update` | Fully replace an item (PUT semantics — replaces all fields). Missing fields are removed from the stored item. |
| `patch` | Partially update an item (PATCH semantics — merges sent fields into existing item). Works with any HTTP method. Use this for POST-as-update endpoints (e.g., Stripe) where only fields present in the body are updated. |
| `delete` | Delete an item by ID |
| `custom` | Execute a named custom operation (requires `operation` field) |
### Custom Operations via Extend
[Section titled “Custom Operations via Extend”](#custom-operations-via-extend)
To trigger a custom operation from a mock endpoint, use `action: custom` with an `operation` field:
```yaml
extend:
- mock: transfer-endpoint
table: accounts
action: custom
operation: TransferFunds
```
***
## Imports
[Section titled “Imports”](#imports)
Imports load external API specifications (OpenAPI, WSDL) and generate mocks with a namespace prefix. This is useful for creating digital twins of third-party APIs.
```yaml
version: "1.0"
imports:
- path: ./stripe-openapi.yaml
as: stripe
format: openapi
tables:
- name: customers
seedData:
- id: "cus_001"
name: "Alice"
extend:
- mock: stripe.ListCustomers
table: customers
action: list
```
### Import Fields
[Section titled “Import Fields”](#import-fields)
| Field | Type | Required | Description |
| -------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `path` | string | Yes\* | Local file path to the spec (resolved relative to the config file). Exactly one of `path` or `url` must be set. |
| `url` | string | Yes\* | Remote URL to fetch the spec from. Exactly one of `path` or `url` must be set. |
| `as` | string | No | Namespace prefix for generated mock IDs (e.g., `stripe`). Imported mocks get `{as}.{operationId}`. If empty, the raw operationId is used. |
| `format` | string | No | Spec format (auto-detected if omitted): `openapi`, `wsdl` |
Imported mocks receive IDs prefixed with the namespace using dot notation (e.g., `stripe.ListCustomers`). You can then reference these IDs in `extend` bindings to wire them to your tables.
Endpoints that are NOT bound via `extend` remain as static schema-generated mocks — they return example responses from the spec without any stateful behavior. Use `mockd list` on a running server to discover all generated mock IDs and their operationIds.
***
## Stateful Resources
[Section titled “Stateful Resources”](#stateful-resources)
Stateful resources are the low-level internal representation of data stores. In most cases, you should use [tables](#tables) and [extend bindings](#extend-bindings) instead — they provide a cleaner separation between data and routing.
The `statefulResources` field is still supported for backward compatibility and for the CLI `mockd stateful add` workflow. Tables are converted into `statefulResources` entries internally.
```yaml
version: "1.0"
statefulResources:
- name: users
idField: id
parentField: ""
seedData:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
mocks: []
```
### Stateful Resource Fields
[Section titled “Stateful Resource Fields”](#stateful-resource-fields)
| Field | Type | Default | Description |
| ------------- | ------ | -------- | ------------------------------------------------ |
| `name` | string | Required | Resource name (e.g., “users”) |
| `idField` | string | `"id"` | Field name for resource ID |
| `parentField` | string | | Parent FK field for nested resources |
| `seedData` | array | `[]` | Initial data to load |
| `validation` | object | | Validation rules ([see Validation](#validation)) |
### Validation
[Section titled “Validation”](#validation)
Stateful resources and HTTP mocks support field-level request validation.
#### StatefulValidation
[Section titled “StatefulValidation”](#statefulvalidation)
| Field | Type | Default | Description |
| ------------ | ------- | ---------- | ----------------------------------------------- |
| `mode` | string | `"strict"` | Validation mode: `strict`, `warn`, `permissive` |
| `auto` | boolean | `false` | Auto-infer rules from seed data |
| `required` | array | `[]` | Required field names (shared) |
| `fields` | map | `{}` | Field validators (shared) |
| `pathParams` | map | `{}` | Path parameter validators |
| `onCreate` | object | | Create-specific validation |
| `onUpdate` | object | | Update-specific validation |
| `schema` | object | | Inline JSON Schema |
| `schemaRef` | string | | Path to JSON Schema file |
#### RequestValidation (for HTTP mocks)
[Section titled “RequestValidation (for HTTP mocks)”](#requestvalidation-for-http-mocks)
| Field | Type | Default | Description |
| ------------- | ------- | ---------- | ----------------------------------------------- |
| `mode` | string | `"strict"` | Validation mode: `strict`, `warn`, `permissive` |
| `failStatus` | integer | `400` | HTTP status code for failures |
| `required` | array | `[]` | Required field names |
| `fields` | map | `{}` | Field validators |
| `pathParams` | map | `{}` | Path parameter validators |
| `queryParams` | map | `{}` | Query parameter validators |
| `headers` | map | `{}` | Header validators |
| `schema` | object | | Inline JSON Schema |
| `schemaRef` | string | | Path to JSON Schema file |
#### FieldValidator
[Section titled “FieldValidator”](#fieldvalidator)
| Field | Type | Description |
| -------------- | ------- | ------------------------------------------------------------------------------ |
| `type` | string | Expected type: `string`, `number`, `integer`, `boolean`, `array`, `object` |
| `required` | boolean | Field must be present |
| `nullable` | boolean | Allow null values |
| `minLength` | integer | Minimum string length |
| `maxLength` | integer | Maximum string length |
| `pattern` | string | Regex pattern for strings |
| `format` | string | Format: `email`, `uuid`, `date`, `datetime`, `uri`, `ipv4`, `ipv6`, `hostname` |
| `min` | number | Minimum value (inclusive) |
| `max` | number | Maximum value (inclusive) |
| `exclusiveMin` | number | Minimum value (exclusive) |
| `exclusiveMax` | number | Maximum value (exclusive) |
| `minItems` | integer | Minimum array items |
| `maxItems` | integer | Maximum array items |
| `uniqueItems` | boolean | Array items must be unique |
| `items` | object | FieldValidator for array items |
| `enum` | array | Allowed values |
| `properties` | map | Nested object validators |
| `message` | string | Custom error message |
#### Nested Fields
[Section titled “Nested Fields”](#nested-fields)
Use dot notation for nested object fields:
```yaml
fields:
"address.city":
type: string
required: true
"address.zipCode":
type: string
pattern: "^[0-9]{5}$"
"items.sku":
type: string
required: true
```
For arrays, the field after the dot applies to each array item:
* `items.sku` validates the `sku` field in each item of the `items` array
See the [Validation Guide](/guides/validation) for comprehensive examples.
***
## Template Variables
[Section titled “Template Variables”](#template-variables)
Response bodies support template variables. Templates work in **all protocols** (HTTP, GraphQL, gRPC, SOAP, WebSocket, SSE, MQTT).
```yaml
body: |
{
"id": "{{request.pathParam.id}}",
"query": "{{request.query.search}}",
"header": "{{request.header.Authorization}}",
"body": {{request.body}},
"field": "{{jsonPath request.body '$.field'}}",
"timestamp": "{{now}}",
"uuid": "{{uuid}}",
"name": "{{faker.name}}",
"email": "{{faker.email}}",
"card": "{{faker.creditCard}}",
"random": {{randomInt 1 100}}
}
```
### Available Variables
[Section titled “Available Variables”](#available-variables)
| Variable | Description |
| ------------------------------------ | ------------------------------------------------------ |
| `{{request.method}}` | HTTP method |
| `{{request.path}}` | Request path |
| `{{request.url}}` | Full URL |
| `{{request.pathParam.name}}` | Path parameter value |
| `{{request.query.name}}` | Query parameter value |
| `{{request.header.Name}}` | Request header value |
| `{{request.body}}` | Full request body (raw) |
| `{{jsonPath request.body '$.path'}}` | JSONPath extraction |
| `{{now}}` | ISO 8601 timestamp |
| `{{timestamp}}` | Unix timestamp (seconds) |
| `{{timestamp.iso}}` | ISO timestamp (RFC3339Nano UTC) |
| `{{timestamp.unix_ms}}` | Unix timestamp (milliseconds) |
| `{{uuid}}` | Random UUID |
| `{{uuid.short}}` | Short random ID (hex) |
| `{{randomInt min max}}` | Random integer (alias for `random.int`) |
| `{{randomFloat min max}}` | Random float (alias for `random.float`) |
| `{{randomString length}}` | Random alphanumeric string (alias for `random.string`) |
| `{{sequence("name")}}` | Auto-incrementing counter |
| `{{upper value}}` | Uppercase string |
| `{{lower value}}` | Lowercase string |
| `{{default value fallback}}` | Default if empty |
### Faker Functions (35 types)
[Section titled “Faker Functions (35 types)”](#faker-functions-35-types)
Generate realistic sample data in response bodies. See the [Response Templating guide](/guides/response-templating/#faker-functions) for full details and example output.
| Category | Types |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Basic** | `name`, `firstName`, `lastName`, `email`, `phone`, `company`, `address`, `word`, `sentence`, `words`, `words(n)`, `boolean`, `uuid` |
| **Internet** | `ipv4`, `ipv6`, `macAddress`, `userAgent`, `url` |
| **Finance** | `creditCard`, `creditCardExp`, `cvv`, `currencyCode`, `currency`, `iban`, `price` |
| **Commerce** | `productName`, `color`, `hexColor` |
| **Identity** | `ssn`, `passport`, `jobTitle` |
| **Geo** | `latitude`, `longitude` |
| **Text** | `slug` |
| **Data** | `mimeType`, `fileExtension` |
Usage: `{{faker.name}}`, `{{faker.creditCard}}`, `{{faker.words(5)}}`, etc.
See `mockd help templating` for the complete reference.
***
## Complete Example
[Section titled “Complete Example”](#complete-example)
```yaml
version: "1.0"
serverConfig:
httpPort: 4280
adminPort: 4290
logRequests: true
mocks:
# HTTP mock
- id: health-check
name: Health Check
type: http
enabled: true
http:
matcher:
method: GET
path: /health
response:
statusCode: 200
body: '{"status": "ok"}'
# HTTP with path parameters
- id: get-user
name: Get User
type: http
enabled: true
http:
matcher:
method: GET
path: /api/users/{id}
response:
statusCode: 200
headers:
Content-Type: application/json
body: |
{
"id": "{{request.pathParam.id}}",
"name": "User {{request.pathParam.id}}"
}
# WebSocket
- id: ws-echo
name: Echo WebSocket
type: websocket
enabled: true
websocket:
path: /ws/echo
echoMode: true
# GraphQL
- id: graphql
name: GraphQL API
type: graphql
enabled: true
graphql:
path: /graphql
introspection: true
schema: |
type Query {
hello: String!
}
resolvers:
Query.hello:
response: "Hello, World!"
# Stateful CRUD via tables + extend
- id: list-posts
type: http
http:
matcher:
method: GET
path: /api/posts
response:
statusCode: 200
- id: create-post
type: http
http:
matcher:
method: POST
path: /api/posts
response:
statusCode: 201
- id: get-post
type: http
http:
matcher:
method: GET
path: /api/posts/{id}
response:
statusCode: 200
tables:
- name: posts
seedData:
- id: "1"
title: "First Post"
content: "Hello, World!"
extend:
- mock: list-posts
table: posts
action: list
- mock: create-post
table: posts
action: create
- mock: get-post
table: posts
action: get
```
## See Also
[Section titled “See Also”](#see-also)
* [CLI Reference](/reference/cli) - Command-line options
* [Request Matching](/guides/request-matching) - Matching patterns
* `mockd help config` - Built-in configuration help
* `mockd help templating` - Template variable reference
* `mockd init --template list` - Available templates
# JSON Schema Reference
> JSON Schema for mockd configuration validation, including editor setup and programmatic validation examples.
mockd provides a JSON Schema (Draft-07) for configuration validation. Use this schema with your editor for instant autocompletion and validation of `mockd.yaml` and `mockd.json` config files.
## Schema URL
[Section titled “Schema URL”](#schema-url)
```plaintext
https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json
```
The schema covers all 7 protocols (HTTP, GraphQL, gRPC, WebSocket, MQTT, SOAP, OAuth), stateful resources, tables, extend bindings, imports, custom operations, chaos config, and server settings.
## Editor Setup
[Section titled “Editor Setup”](#editor-setup)
### VS Code
[Section titled “VS Code”](#vs-code)
Add to your `.vscode/settings.json`:
```json
{
"json.schemas": [
{
"fileMatch": ["mockd.json", "mockd.yaml", "mockd.yml", "mocks.json", "mocks.yaml"],
"url": "https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json"
}
],
"yaml.schemas": {
"https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json": ["mockd.yaml", "mockd.yml", "mocks.yaml"]
}
}
```
Or add directly in your JSON config file:
```json
{
"$schema": "https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json",
"mocks": []
}
```
For YAML config files, add a schema comment at the top:
```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json
mocks:
- type: http
http:
matcher:
method: GET
path: /api/users
response:
statusCode: 200
body: '[]'
```
### JetBrains IDEs
[Section titled “JetBrains IDEs”](#jetbrains-ides)
1. Open Settings -> Languages & Frameworks -> Schemas and DTDs -> JSON Schema Mappings
2. Add new mapping with URL: `https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json`
3. Set file pattern: `mockd.json`, `mockd.yaml`, `mocks.json`, `mocks.yaml`
### Vim/Neovim (with coc.nvim or nvim-lspconfig)
[Section titled “Vim/Neovim (with coc.nvim or nvim-lspconfig)”](#vimneovim-with-cocnvim-or-nvim-lspconfig)
Add to `coc-settings.json`:
```json
{
"json.schemas": [
{
"fileMatch": ["mockd.json", "mocks.json"],
"url": "https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json"
}
]
}
```
## Schema Definition
[Section titled “Schema Definition”](#schema-definition)
### Root Schema
[Section titled “Root Schema”](#root-schema)
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "mockd Configuration",
"type": "object",
"properties": {
"version": { "type": "string" },
"name": { "type": "string" },
"server": { "$ref": "#/definitions/server" },
"mocks": {
"type": "array",
"items": { "$ref": "#/definitions/mock" }
},
"statefulResources": {
"type": "array",
"items": { "$ref": "#/definitions/statefulResource" }
},
"tables": {
"type": "array",
"items": { "$ref": "#/definitions/table" },
"description": "Stateful data tables (pure data stores, no routing — endpoints are bound via extend)"
},
"extend": {
"type": "array",
"items": { "$ref": "#/definitions/extendBinding" },
"description": "Bindings from mocks to tables"
},
"imports": {
"type": "array",
"items": { "$ref": "#/definitions/importSpec" },
"description": "External spec imports with namespacing"
}
}
}
```
### Server Definition
[Section titled “Server Definition”](#server-definition)
```json
{
"definitions": {
"server": {
"type": "object",
"properties": {
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"default": 4280
},
"host": {
"type": "string",
"default": "localhost"
},
"adminEnabled": {
"type": "boolean",
"default": true
},
"adminPort": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"default": 4290
},
"tls": { "$ref": "#/definitions/tls" },
"cors": { "$ref": "#/definitions/cors" }
}
}
}
}
```
### Mock Definition
[Section titled “Mock Definition”](#mock-definition)
A mock wraps protocol-specific config under a type key. The `id` and `type` fields are auto-generated if omitted in config files.
```json
{
"definitions": {
"mock": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique mock ID (auto-generated if omitted)"
},
"type": {
"type": "string",
"enum": ["http", "graphql", "grpc", "websocket", "mqtt", "soap", "oauth"],
"description": "Protocol type (inferred from spec field if omitted)"
},
"name": {
"type": "string",
"description": "Human-readable name for the mock"
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Whether this mock is active"
},
"priority": {
"type": "integer",
"default": 0,
"description": "Match priority (higher = matched first)"
},
"http": { "$ref": "#/definitions/httpSpec" },
"graphql": { "$ref": "#/definitions/graphqlSpec" },
"grpc": { "$ref": "#/definitions/grpcSpec" },
"websocket": { "$ref": "#/definitions/websocketSpec" },
"mqtt": { "$ref": "#/definitions/mqttSpec" },
"soap": { "$ref": "#/definitions/soapSpec" },
"oauth": { "$ref": "#/definitions/oauthSpec" }
}
},
"httpSpec": {
"type": "object",
"description": "HTTP mock specification",
"properties": {
"matcher": { "$ref": "#/definitions/requestMatcher" },
"response": { "$ref": "#/definitions/response" },
"priority": { "type": "integer", "default": 0 }
}
}
}
}
```
### Request Matcher Definition
[Section titled “Request Matcher Definition”](#request-matcher-definition)
```json
{
"definitions": {
"requestMatcher": {
"type": "object",
"properties": {
"method": {
"type": "string",
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
"description": "HTTP method to match"
},
"path": {
"type": "string",
"description": "URL path pattern (supports {param} syntax)"
},
"headers": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Header matchers (exact match or glob patterns with *)"
},
"queryParams": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Query parameter matchers (exact match)"
},
"bodyEquals": {
"type": "string",
"description": "Exact body match (full string comparison)"
},
"bodyContains": {
"type": "string",
"description": "Substring body match"
},
"bodyPattern": {
"type": "string",
"description": "Regex body match"
},
"bodyJsonPath": {
"type": "object",
"additionalProperties": {},
"description": "JSONPath condition matchers (e.g., {\"$.user.role\": \"admin\"})"
}
}
}
}
}
```
### Response Definition
[Section titled “Response Definition”](#response-definition)
```json
{
"definitions": {
"response": {
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"minimum": 100,
"maximum": 599,
"default": 200,
"description": "HTTP status code"
},
"headers": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Response headers"
},
"body": {
"description": "Response body (string, object, or array)"
},
"bodyFile": {
"type": "string",
"description": "Load body from file path"
},
"delayMs": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Response delay in milliseconds"
}
}
}
}
}
```
### Table Definition
[Section titled “Table Definition”](#table-definition)
Tables are defined as array items under the top-level `tables` key. Each table requires a `name` field.
```json
{
"definitions": {
"table": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Table name (e.g., 'customers')"
},
"idField": {
"type": "string",
"default": "id",
"description": "Field used as the unique identifier"
},
"idStrategy": {
"type": "string",
"enum": ["uuid", "prefix", "ulid", "sequence", "short"],
"description": "ID generation strategy for new items"
},
"idPrefix": {
"type": "string",
"description": "ID prefix when idStrategy is 'prefix' (e.g., 'cus_')"
},
"maxItems": {
"type": "integer",
"description": "Maximum items in the collection"
},
"parentField": {
"type": "string",
"description": "Foreign key field for nested resources"
},
"seedData": {
"type": "array",
"items": { "type": "object" },
"description": "Initial data to populate the table"
},
"response": {
"$ref": "#/definitions/responseTransform",
"description": "Default response transform for all bindings to this table"
},
"relationships": {
"type": "object",
"description": "Maps field names to related tables for ?expand[] support",
"additionalProperties": {
"type": "object",
"required": ["table"],
"properties": {
"table": { "type": "string", "description": "Target table name" },
"field": { "type": "string", "description": "Field in target table to match (defaults to target's idField)" }
}
}
}
}
}
}
}
```
### Extend Binding Definition
[Section titled “Extend Binding Definition”](#extend-binding-definition)
```json
{
"definitions": {
"extendBinding": {
"type": "object",
"required": ["mock", "table", "action"],
"properties": {
"mock": {
"type": "string",
"description": "ID of the mock to bind"
},
"table": {
"type": "string",
"description": "Name of the table to operate on"
},
"action": {
"type": "string",
"enum": ["list", "get", "create", "update", "patch", "delete", "custom"],
"description": "CRUD action to perform"
},
"operation": {
"type": "string",
"description": "Custom operation name (required when action is 'custom')"
}
}
}
}
}
```
### Import Definition
[Section titled “Import Definition”](#import-definition)
```json
{
"definitions": {
"importEntry": {
"type": "object",
"required": ["path"],
"properties": {
"path": {
"type": "string",
"description": "File path to the API spec (relative to config file)"
},
"url": {
"type": "string",
"description": "URL to fetch the API spec from (alternative to path)"
},
"as": {
"type": "string",
"description": "Namespace prefix for imported mocks (e.g., 'stripe')"
},
"format": {
"type": "string",
"enum": ["openapi", "swagger", "postman", "har", "wiremock", "wsdl", "mockoon", "curl", "yaml", "json"],
"description": "Spec format (auto-detected if omitted)"
}
}
}
}
}
```
### Stateful Resources Definition
[Section titled “Stateful Resources Definition”](#stateful-resources-definition)
```json
{
"definitions": {
"statefulResources": {
"type": "array",
"items": { "$ref": "#/definitions/statefulResource" }
},
"statefulResource": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Resource name (e.g., users, products)"
},
"idField": {
"type": "string",
"default": "id",
"description": "Field used as the unique identifier"
},
"parentField": {
"type": "string",
"default": "",
"description": "Optional parent field for nested resources"
},
"seedData": {
"type": "array",
"items": { "type": "object" },
"description": "Initial data to populate the resource"
}
}
}
}
}
```
> **Note:** In most cases, prefer using `tables` and `extend` over `statefulResources` directly. Tables provide a cleaner separation of data and routing. Resource definitions and seed data are persisted to the admin file store. Runtime data (CRUD operations) is in-memory only and resets to seed data on restart.
## Validation
[Section titled “Validation”](#validation)
### CLI Validation
[Section titled “CLI Validation”](#cli-validation)
```bash
mockd validate mocks.json
```
### Programmatic Validation
[Section titled “Programmatic Validation”](#programmatic-validation)
Using Node.js with ajv:
```javascript
const Ajv = require('ajv');
const schema = require('./mockd-schema.json');
const config = require('./mocks.json');
const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(config);
if (!valid) {
console.error(validate.errors);
}
```
Using Python with jsonschema:
```python
import json
from jsonschema import validate, ValidationError
with open('mockd-schema.json') as f:
schema = json.load(f)
with open('mocks.json') as f:
config = json.load(f)
try:
validate(instance=config, schema=schema)
print("Configuration is valid")
except ValidationError as e:
print(f"Validation error: {e.message}")
```
## Custom Schema Extensions
[Section titled “Custom Schema Extensions”](#custom-schema-extensions)
Add custom properties with `x-` prefix:
```json
{
"mocks": [
{
"x-team": "backend",
"x-version": "2.0",
"type": "http",
"http": {
"matcher": {"method": "GET", "path": "/api/data"},
"response": {"statusCode": 200, "body": "{}"}
}
}
]
}
```
Custom properties are ignored by mockd but preserved in the config.
## Full Schema File
[Section titled “Full Schema File”](#full-schema-file)
The complete schema is available at:
* **URL**: `https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json`
* **Repository**: `schema/mockd.schema.json`
## See Also
[Section titled “See Also”](#see-also)
* [Configuration Reference](/reference/configuration) - Config options
* [Quickstart](/getting-started/quickstart) - Getting started