Mock Server — Fake API Responses
Your app calls an API that is not ready yet, is slow, or costs money per call. You want it to answer with a fixed response, and you want to see what your app sent. The mock server does both. You describe a response once: method and path in, status, headers and body out. Your app calls Buggregator instead of the real API.
Every call is also captured as an HTTP dump, so you see the request and the response that was sent next to your logs and exceptions.
Available since server v2.6.0. The Mock server page needs frontend 1.35.0, which is bundled with v2.6.0.
Use cases
- API not ready yet — the frontend team builds against
/mock/users/{id}while the backend is still in progress. - Error handling — make a rule that answers
500or429and check how your app reacts. - Slow responses — add a delay to test timeouts and loading states.
- CI — put the rules in
buggregator.yaml, so every test run gets the same answers. - Realistic data — a template rule copies the id from the path and fills in fake names, emails and prices for each request.
How it works
Point your client at http://127.0.0.1:8000/mock/. Write rule paths without the /mock prefix.
For example, the rule GET /users/{id} answers GET http://127.0.0.1:8000/mock/users/42.
curl http://127.0.0.1:8000/mock/users/42
# {"id": 1, "name": "Jane"}In Docker Compose, replace
127.0.0.1with the Buggregator service name (e.g.,buggregator).
The mock server lives only under /mock/. A rule can never hide SDK ingestion, /api/... or the UI.
The mock server is part of the HTTP dumps module. If you turn that module off (modules.http-dump: false), /mock/ is off too.
Which rule answers
Buggregator checks the rules in order. The first enabled rule that matches answers.
- Rules from
buggregator.yamlcome first, in file order. They are read-only. - Rules made in the UI or through the API come next, in the order you set.
A rule matches when all of these fit:
| Condition | Values |
|---|---|
| Method | GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, or ANY (the default) |
| Path | see the path forms below |
| Project | empty means every project; otherwise it must be the request's project |
| Enabled | a disabled rule never answers |
Path forms
| Form | Example rule | Matches | Does not match |
|---|---|---|---|
| Exact | /users/me | /mock/users/me | /mock/users/42 |
| One segment | /users/{id} | /mock/users/42 | /mock/users/42/orders |
| The rest | /files/* | /mock/files, /mock/files/a/b.png | /mock/images/a.png |
*is allowed only as the whole last segment.- A trailing slash is ignored. The query string is ignored.
No rule matched
The mock server answers 404 with a JSON error:
{"error": "no mock rule matched GET /users/42"}The request is still captured. In the request details you see the rules whose path fits but that missed by one condition, and why: disabled, method or project. Example: you made POST /orders but the app sent GET. The details then show that rule with the reason method.
Projects
A request belongs to the default project unless it says otherwise. Two ways to set a project:
# Header
curl http://127.0.0.1:8000/mock/users/42 -H 'X-Buggregator-Project: shop'
# Basic Auth with the user name "mock" (or "http-dump")
curl http://mock:shop@127.0.0.1:8000/mock/users/42Other Basic Auth credentials are left alone: they belong to the API you mock.
What every response carries
Mock bodies are your own content, served from the same address as the Buggregator UI. To keep the UI safe, every mock response gets two headers that a rule cannot remove:
Content-Security-Policy: sandbox
X-Content-Type-Options: nosniffA Set-Cookie header from a rule stays inside the mock server. Buggregator removes its Domain and puts its Path under /mock. Example: sid=1; Path=/; Domain=example.com is sent as sid=1; Path=/mock/.
CORS preflight
A browser may send an OPTIONS request before the real one (a CORS preflight). An OPTIONS rule answers it. Without such a rule, Buggregator answers 204 with the CORS headers, and the request is captured as unmatched.
Limits
| Field | Limit |
|---|---|
status | 100–599, default 200 |
| delay | up to 30 seconds |
body | up to 1 MB |
| header names | no spaces, no : |
| stored rules | at most 500 per project; rule 501 gets 422 with errors.project |
The Mock server page
Open Mock server in the sidebar.
- Rules on the left, in match order. Rules from
buggregator.yamlare markedyaml. You can open them and duplicate them as editable rules, but not change them. - Your own rules can be switched on and off, moved up and down, and edited.
- Requests on the right: calls to
/mock/of the current project, live. - Click a request to see what was sent and what came back. For an unmatched request, the page lists the rules that almost matched and offers to create a rule from that request.
Rules in buggregator.yaml
Rules in the config file suit CI and shared setups: everyone gets the same answers.
mocks:
- name: user # optional
method: GET # GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS or ANY (default)
path: /users/{id}
status: 200 # default 200
headers: { Content-Type: application/json }
body: '{"id": 1, "name": "Jane"}'
delay: 150ms # optional, a Go duration, at most 30s
project: default # optional; empty = every project- These rules are always enabled and read-only in the UI.
- An invalid rule is skipped at startup, with a warning in the log.
- A
delaythat is not a duration (e.g.150without a unit) is ignored, with a warning.
See Server configuration for where the file goes.
Import an OpenAPI spec
Have an OpenAPI 3.0 or 3.1 spec? Import it, and every operation becomes a rule. Example: a spec with GET /users/{id} and POST /orders gives two rules, and /mock/v1/users/42 answers at once.
How a rule is built:
- One rule per operation, all in one group. The rule's name is the
operationId, else thesummary, else the method and the full path, base path included:GET /v1/users/{id}. - Status: the lowest
2xxresponse of the operation. - Body: the spec's
example, as is. No example: a template built from the schema, with fake data. A root field named like a path parameter echoes it (idfor the last one). - Path: the path of
servers[0].urlgoes in front.https://api.example.com/v1+/users/{id}→/v1/users/{id}. Setbase_pathto change it;""means no prefix. - Import again into the same group, and the group's old rules are replaced.
- Local
$refonly.oneOfandanyOftake the first option. Swagger 2.0 is refused: convert it to OpenAPI 3.
The rules are ordinary rules: edit them afterwards like any other.
Import a spec:
curl -X POST http://127.0.0.1:8000/api/mocks/import/openapi \
-H 'Content-Type: application/json' \
-d "$(jq -n --rawfile spec shop.yaml '{spec: $spec}')"With this shop.yaml:
openapi: 3.0.3
info: { title: Shop API, version: "1.0" }
servers:
- url: https://api.example.com/v1
paths:
/users/{id}:
get:
parameters:
- { name: id, in: path, required: true, schema: { type: integer } }
responses:
"200":
description: A user
content:
application/json:
schema:
type: object
properties:
id: { type: integer }
name: { type: string }
email: { type: string, format: email }
status: { type: string, enum: [active, banned] }
/orders:
post:
responses:
"201":
description: Created
content:
application/json:
example: { id: 1001, status: pending }The answer lists the rules (cut here) and the operations it skipped:
{"group": "openapi:Shop API", "title": "Shop API",
"rules": [
{"name": "GET /v1/users/{id}", "method": "GET", "path": "/v1/users/{id}", "status": 200, "template": true,
"body": "{\n \"id\": {{ int (path \"id\") }},\n \"name\": {{ fake \"name\" | json }},\n \"email\": {{ fake \"email\" | json }},\n \"status\": {{ pick \"active\" \"banned\" | json }}\n}", …},
{"name": "POST /v1/orders", "method": "POST", "path": "/v1/orders", "status": 201, "template": false,
"body": "{\n \"id\": 1001,\n \"status\": \"pending\"\n}", …}],
"skipped": []}Then:
curl http://127.0.0.1:8000/mock/v1/users/42
# {"id": 42, "name": "Alden Vaughn", "email": "troy-hart@ramirez.net", "status": "active"}
curl -X POST http://127.0.0.1:8000/mock/v1/orders
# {"id": 1001, "status": "pending"} (status 201)(Bodies come indented; shown here on one line.)
Request fields:
| Field | What it does |
|---|---|
spec | The spec text, YAML or JSON |
url | Or the spec's address: http/https, at most 5 MB, downloaded within 10 s |
project | The rules' project; empty = every project. A project that does not exist yet is fine: it is created by its first event |
group | The group name; default openapi:<info.title> |
base_path | The prefix for every path; default: the path of servers[0].url; "" = none |
Answer fields: group, title, rules (the created rules), skipped (a list of method, path, reason). An operation is skipped, for example, when one import builds more than 500 rules (reason: over 500 rules).
A project holds at most 500 stored rules. When the import would take it over, the whole import is refused and nothing is added: 422, errors.project: the import adds 2 rules, the project has 499; at most 500 rules per project.
Errors are 422, the field in errors:
| Case | errors |
|---|---|
neither spec nor url | {"spec": "give the spec text or its url"} |
| both | {"spec": "give spec or url, not both"} |
| Swagger 2.0 | {"spec": "Swagger 2.0 is not supported; convert it to OpenAPI 3"} |
| not OpenAPI | {"spec": "not an OpenAPI 3 document (no openapi: 3.x)"} |
url not http(s) | {"url": "url must be http or https"} |
| download failed | {"url": "download: raw.githubusercontent.com answered 404"} |
Delete a group (the name URL-encoded):
curl -X DELETE 'http://127.0.0.1:8000/api/mocks/groups/openapi%3AShop%20API'
# {"deleted":2}A spec in buggregator.yaml
mocks_openapi:
- file: ./shop.yaml
project: shop # optional; empty = every project
group: shop-api # optional; default openapi:<info.title>
base_path: "" # optional; "" = no prefixThe spec is read at startup; the log says mock: OpenAPI spec loaded with the file, group and rule count. These rules are read-only, like other config rules ("source": "config", ids config-1, …): changing or deleting one gets 409.
AI agents import with the MCP tool mock_import_openapi and delete a group with mock_group_delete, see MCP tools.
REST API
All routes need the same login as the rest of /api when SSO is on. The mock server itself (/mock/...) is always public: the clients of the mocked API call it.
A rule looks like this:
{
"id": "f5171e15-2599-4c33-949b-7f7ecc05ab20",
"project": "",
"name": "user",
"method": "GET",
"path": "/users/{id}",
"status": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\"id\": 1}",
"delay_ms": 100,
"template": false,
"seed": 0,
"group": "",
"enabled": true,
"position": 0,
"source": "api",
"read_only": false,
"created_at": "2026-11-06T18:30:09Z",
"updated_at": "2026-11-06T18:30:09Z"
}Rules from the config file have "source": "config", "read_only": true and ids config-1, config-2, …
| Method | Path | Body | Answer |
|---|---|---|---|
GET | /api/mocks | — | {"data": [rule, …]} in match order |
POST | /api/mocks | rule fields; path is required | 201 and the rule, added at the end |
GET | /api/mocks/{id} | — | the rule, or 404 |
PUT | /api/mocks/{id} | rule fields (replaces the rule, keeps its position) | the rule; 409 for a config rule |
DELETE | /api/mocks/{id} | — | {"status": true}; 409 for a config rule |
POST | /api/mocks/order | {"ids": ["…", "…"]} | {"data": [rule, …]}; listed rules first, the rest keep their order; an unknown or config rule id gets 404 |
GET | /api/mocks/requests?limit=50&project=&rule_id= | — | captured mock requests, newest first; limit 1–500, default 50 |
POST | /api/mocks/import/openapi | {"spec" or "url", "project", "group", "base_path"} | {"group", "title", "rules", "skipped"}, see OpenAPI import |
DELETE | /api/mocks/groups/{group} | — | {"deleted": n}: the group's stored rules are gone |
Defaults on create: method ANY, status 200, enabled true, template false, seed 0.
Template rules (template, seed), POST /api/mocks/preview and GET /api/mocks/helpers: Response templates.
Errors are {"message", "code"}. A rule that fails validation gets 422 with the bad fields:
{"message": "invalid mock rule: status: must be between 100 and 599", "code": 422,
"errors": {"status": "must be between 100 and 599"}}Example — create a rule:
curl -X POST http://127.0.0.1:8000/api/mocks \
-H 'Content-Type: application/json' \
-d '{"method": "POST", "path": "/orders", "status": 201, "body": "{\"id\": 7}", "delay_ms": 200}'The captured request
A mock request is a normal http-dump event. Its payload has an extra mock field with what was sent:
"mock": {
"matched": true,
"rule_id": "f5171e15-…", "rule_name": "user",
"status": 200,
"headers": {"Content-Type": "application/json", "Content-Security-Policy": "sandbox", "X-Content-Type-Options": "nosniff"},
"body": "{\"id\": 1}", "body_size": 9, "body_truncated": false,
"delay_ms": 100
}bodykeeps the first 64 KB;body_truncatedsays when it was cut.- For an unmatched request,
matchedisfalse,rule_idandrule_namearenull, andnearlists up to 5 rules that missed by one condition:[{"rule_id", "rule_name", "reason"}]. - A template that failed adds
template_errorwith the error text;statusis then500. - Ordinary HTTP dumps have no
mockfield.