Imposters
An imposter is a mock server that listens on a specific port and responds to requests based on configured stubs.
Creating an Imposter
Basic HTTP Imposter
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{
"port": 4545,
"protocol": "http",
"name": "My Service Mock",
"stubs": [{
"responses": [{
"is": { "statusCode": 200, "body": "Hello" }
}]
}]
}'
HTTPS Imposter
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{
"port": 4546,
"protocol": "https",
"name": "Secure Service Mock",
"key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
"cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"stubs": [{
"responses": [{
"is": { "statusCode": 200, "body": "Secure Hello" }
}]
}]
}'
Imposter Configuration
| Field | Type | Required | Description |
|---|---|---|---|
port | number | No | Port to listen on (auto-assigned if omitted) |
protocol | string | No | http or https (default: http) |
name | string | No | Human-readable name |
stubs | array | No | Request/response mappings |
defaultResponse | object | No | Response when no stub matches |
recordRequests | boolean | No | Store requests for verification |
allowCORS | boolean | No | Enable CORS headers and handle preflight requests |
service_name | string | No | Service identifier for documentation |
service_info | object | No | Additional service metadata |
key | string | HTTPS only | PEM-encoded private key |
cert | string | HTTPS only | PEM-encoded certificate |
mutualAuth | boolean | No | Require client certificate |
HTTP/2 and h2c
Rift auto-negotiates the HTTP version — you don’t configure it per imposter:
- HTTPS imposters advertise
h2andhttp/1.1via TLS ALPN, so an HTTP/2-capable client gets HTTP/2 and everything else falls back to HTTP/1.1. - Plain HTTP imposters accept h2c (cleartext HTTP/2 via prior-knowledge) alongside HTTP/1 — the listener detects the HTTP/2 preface and upgrades automatically.
This is on by default and backward-compatible with HTTP/1 clients. Two things force HTTP/1-only:
- an imposter that uses any TCP fault (
_rift.fault.tcpor a top-levelfault), because a connection-level abort is incompatible with HTTP/2 multiplexing; and - setting the
RIFT_DISABLE_HTTP2environment variable (truthy:1/true/yes/on), which forces every listener — HTTP and HTTPS, imposter, admin, and metrics — down to HTTP/1.
Auto-Port Assignment
If you omit the port field, Rift will automatically assign an available port from the dynamic range (49152-65535):
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{
"protocol": "http",
"stubs": [{
"responses": [{ "is": { "statusCode": 200 } }]
}]
}'
# Response includes the assigned port:
{
"port": 49152,
"protocol": "http",
...
}
Stubs
Each stub contains predicates (matching rules) and responses:
{
"stubs": [
{
"predicates": [
{ "equals": { "method": "GET", "path": "/api/users" } }
],
"responses": [
{ "is": { "statusCode": 200, "body": "[]" } }
]
}
]
}
Stub Configuration
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Unique identifier (Rift extension) |
predicates | array | No | Conditions to match requests |
responses | array | Yes | Responses to return |
scenarioName | string | No | Identifier for test scenarios |
The id field is a Rift extension that allows you to identify stubs by name rather than index:
{
"stubs": [{
"id": "get-user-success",
"predicates": [{ "equals": { "path": "/users/123" } }],
"responses": [{ "is": { "statusCode": 200, "body": "{\"id\": 123}" } }]
}]
}
The scenarioName field is useful for organizing stubs into logical groups for testing:
{
"stubs": [{
"scenarioName": "UserService-GetUser-Success",
"predicates": [{ "equals": { "path": "/users/123" } }],
"responses": [{ "is": { "statusCode": 200, "body": "{\"id\": 123}" } }]
}]
}
Multiple Responses (Round-Robin)
When a stub has multiple responses, they cycle through:
{
"stubs": [{
"predicates": [{ "equals": { "path": "/flip" } }],
"responses": [
{ "is": { "body": "heads" } },
{ "is": { "body": "tails" } }
]
}]
}
First request returns “heads”, second returns “tails”, third returns “heads”, etc.
Default Response
Configure a fallback response when no stub matches:
{
"port": 4545,
"protocol": "http",
"defaultResponse": {
"statusCode": 404,
"headers": { "Content-Type": "application/json" },
"body": { "error": "Not Found" }
},
"stubs": [...]
}
Recording Requests
Enable request recording for verification in tests:
{
"port": 4545,
"protocol": "http",
"recordRequests": true,
"stubs": [...]
}
Retrieve recorded requests:
curl http://localhost:2525/imposters/4545
# Response includes:
{
"requests": [
{
"method": "GET",
"path": "/api/users",
"headers": {...},
"body": "",
"timestamp": "2024-01-15T10:30:00.000Z"
}
]
}
Binary Request Bodies
A request body that is not valid UTF-8 (protobuf, gzip, an image upload) cannot be recorded as text without destroying it. Such a body is recorded base64-encoded and marked with _mode: "binary", mirroring how binary response bodies are represented:
{
"requests": [
{
"method": "POST",
"path": "/api/upload",
"body": "iVBORw0KGgoAAAANSUhEUg==",
"_mode": "binary",
"timestamp": "2024-01-15T10:30:00.000Z"
}
]
}
The body is standard base64 (with padding). _mode is absent for a normal text/JSON body, so recordings of text traffic are unchanged — check for it rather than assuming it is present.
Scripts see the same distinction: ctx.request.body carries the base64 string and ctx.request.isBinary is true. Where rift cannot determine the encoding (the decorate and predicate-inject paths do not carry it), isBinary is absent rather than false — so a script can tell “text” apart from “unknown” instead of being told something untrue.
Managing Imposters
List All Imposters
curl http://localhost:2525/imposters
# Response:
{
"imposters": [
{ "port": 4545, "protocol": "http", "name": "User Service" },
{ "port": 4546, "protocol": "https", "name": "Payment Service" }
]
}
Get Imposter Details
curl http://localhost:2525/imposters/4545
# Response includes full configuration and recorded requests
Delete Single Imposter
curl -X DELETE http://localhost:2525/imposters/4545
Delete All Imposters
curl -X DELETE http://localhost:2525/imposters
Loading from Configuration File
JSON Format
Create imposters.json:
{
"imposters": [
{
"port": 4545,
"protocol": "http",
"stubs": [...]
},
{
"port": 4546,
"protocol": "http",
"stubs": [...]
}
]
}
Load on startup:
docker run -v $(pwd)/imposters.json:/imposters.json \
zainalpour/rift-proxy:latest --configfile /imposters.json
EJS Templates
Use EJS for dynamic configuration:
{
"imposters": [
{
"port": "<%= port || 4545 %>",
"protocol": "http",
"stubs": [...]
}
]
}
Stub Matching Behavior
First-Match-Wins
Stubs are evaluated in order. The first stub whose predicates match is used:
{
"stubs": [
{
"predicates": [{ "startsWith": { "path": "/api" } }],
"responses": [{ "is": { "body": "general" } }]
},
{
"predicates": [{ "equals": { "path": "/api/users" } }],
"responses": [{ "is": { "body": "specific" } }]
}
]
}
In this example, /api/users returns “general” because the first stub matches. To get “specific”, swap the stub order.
Empty Predicates (Catch-All)
A stub with empty predicates matches all requests:
{
"stubs": [
{ "predicates": [], "responses": [{ "is": { "body": "catch all" } }] }
]
}
Warning: A catch-all stub shadows all subsequent stubs. Place catch-all stubs last.
Index-Based Operations
Stub indexes shift when stubs are added or removed:
Before: [Stub0, Stub1, Stub2] (indexes 0, 1, 2)
Delete index 0:
After: [Stub1, Stub2] (indexes 0, 1)
Rift Stub Analysis (Rift Extension)
Rift provides optional warnings for common stub configuration issues. These warnings appear in the API response under _rift.warnings:
curl http://localhost:2525/imposters/4545
# Response includes:
{
"port": 4545,
"stubs": [...],
"_rift": {
"warnings": [
{
"warningType": "catch_all_not_last",
"message": "Catch-all stub at index 0 will shadow 2 stub(s) after it",
"stubIndex": 0
}
]
}
}
Warning Types
| Type | Description |
|---|---|
duplicate_id | Multiple stubs have the same ID |
exact_duplicate | Stub predicates are identical to another stub |
potentially_shadowed | Stub may be unreachable due to earlier stub |
catch_all | Stub with empty predicates matches all requests |
catch_all_not_last | Catch-all stub is not at the end of the list |
Note: Mountebank does NOT provide overlap detection. These warnings are a Rift extension.
Imposter Error Responses
These are errors served on the imposter port — distinct from the admin API’s error responses (see API Reference). Every one of them serves the same JSON envelope as the admin plane, with Content-Type: application/json:
{ "errors": [ { "code": "...", "type": "...", "message": "..." } ] }
Branch on type — it is always a stable symbolic slug. code is legacy: the HTTP status as a string on most doors, a slug on a few, frozen for backward compatibility. See Error Responses for the full type list.
| Status | When |
|---|---|
400 | The request body could not be read to completion — a client transmission failure mid-stream. The underlying cause is logged server-side. |
413 | The request body exceeded the configured size cap. |
500 | An internal failure while building the response — for example a stub, inject, or upstream header value that is not a legal HTTP header. |
504 | A script exceeded its deadline. See Scripting and Debug Mode. |
The 400/413 split matters when you are debugging a flaky client: 413 means the body was too big, 400 means the connection failed to deliver the body it promised. Earlier releases answered 413 for both, which reported every mid-stream network failure as “body too large” (#694).
Two doors deliberately do not use the envelope: the Unknown fault diagnostic (a plain-text fault-injection marker) and the last-resort internal error emitted when the envelope itself cannot be constructed.
Best Practices
- Use meaningful names - Makes debugging easier
- Order stubs specifically - More specific predicates first
- Enable recording in tests - Verify expected requests
- Use default responses - Clear error messages for unmatched requests
- Separate imposters by service - One imposter per external dependency
- Place catch-all stubs last - Avoid accidentally shadowing specific stubs
- Use stub IDs (Rift) - Easier to track and manage stubs by name