Proxy Mode
Proxy mode forwards requests to real servers and optionally records responses for playback. This is useful for creating mocks from real API behavior.
Basic Proxy
Forward all requests to a backend server:
{
"port": 4545,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "https://api.example.com"
}
}]
}]
}
Proxy Modes
proxyAlways
Always forward requests; record a new stub for each unique request:
{
"proxy": {
"to": "https://api.example.com",
"mode": "proxyAlways"
}
}
Use when: Building a comprehensive mock from varied requests.
proxyOnce
Forward the first request, then replay the recorded response:
{
"proxy": {
"to": "https://api.example.com",
"mode": "proxyOnce"
}
}
Use when: Recording a fixed set of responses for offline testing.
proxyTransparent
Forward without recording (pure reverse proxy):
{
"proxy": {
"to": "https://api.example.com",
"mode": "proxyTransparent"
}
}
Use when: Acting as a transparent proxy without mocking.
Predicate Generators
Control how requests are matched when generating stubs:
{
"proxy": {
"to": "https://api.example.com",
"predicateGenerators": [{
"matches": {
"method": true,
"path": true,
"query": true
}
}]
}
}
Available Fields
| Field | Description |
|---|---|
method | Match HTTP method |
path | Match request path |
query | Match query parameters |
headers | Match request headers |
body | Match request body |
Selective Matching
Match only specific aspects:
{
"predicateGenerators": [{
"matches": {
"method": true,
"path": true
}
}]
}
This generates stubs that match method and path, ignoring query and body.
Case Sensitivity
{
"predicateGenerators": [{
"matches": { "path": true },
"caseSensitive": true
}]
}
Generation Failures
A predicate generator can also be an inject function that builds predicates in JavaScript. If that generation fails — the script throws, returns something other than a predicate array, the script pool is unavailable, or it exceeds the script timeout — Rift does not record a stub. Recording a stub with the empty/partial predicate list would produce a match-all stub that shadows every future request, so the failure is surfaced instead of hidden:
- no stub is recorded for that request (the proxied response is still returned to the client), and
- the proxied response carries an
x-rift-generator-errorheader whose value names the failure —script-error,invalid-output,pool-failure,timeout, ortask-panic.
A generator that legitimately returns an empty array ([]) is not a failure — it records a match-all stub as before. Only genuine generation failures skip recording and set the header.
Recording Workflow
Step 1: Create Recording Proxy
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{
"port": 4545,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "https://api.example.com",
"mode": "proxyOnce",
"predicateGenerators": [{
"matches": { "method": true, "path": true, "query": true }
}]
}
}]
}]
}'
Step 2: Run Your Tests
# Run application tests against the proxy
npm test
# or
pytest
Step 3: Export Recorded Stubs
curl http://localhost:2525/imposters/4545?replayable=true > recorded.json
Step 4: Use Recorded Mocks
# Delete proxy imposter
curl -X DELETE http://localhost:2525/imposters/4545
# Load recorded mocks
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d @recorded.json
Flat Response Form
Rift accepts a stub response in flat form — statusCode, headers, and body at the top level of the response object, with no is wrapper — and serves it identically to the wrapped form. This matters when replaying recorded or externally-generated mocks that emit flat responses:
// Flat form (no "is" wrapper) — accepted and served as a 200 with the body
{ "statusCode": 200, "body": "recorded" }
// Equivalent to the canonical wrapped form
{ "is": { "statusCode": 200, "body": "recorded" } }
statusCode defaults to 200 when omitted. If both a top-level field and an explicit is are present, is takes precedence.
Modifying Proxied Responses
Behaviors on the proxy response
A _behaviors (or behaviors) block on a proxy response runs on the upstream’s response before anything is recorded, as in Mountebank. The client, the proxy recording and the stub predicateGenerators generates all get the transformed response:
{
"proxy": { "to": "https://api.example.com", "mode": "proxyOnce" },
"_behaviors": {
"decorate": "function(request, response) { response.headers['X-Proxied-By'] = 'Rift'; }"
}
}
- The generated stub holds the transformed body, not the behaviors, so a replay does not run them again. It carries only what
addWaitBehaviorandaddDecorateBehavioradd. - A
proxyOncereplay served from the recording is likewise not transformed a second time. - A
waitdelays the live response, after the upstream’s latency is measured, so it is not counted in whataddWaitBehaviorrecords. - If a behavior fails, nothing is recorded and the next matching request goes upstream again. The failed response is served as usual: lenient by default (with the
x-rift-<behavior>-errorheader), or a500understrictBehaviors(a504withx-rift-script-timeoutwhen adecorateruns out of time). Recording it would replay a response the configuration did not ask for. - A body that is not UTF-8 reaches the behaviors base64-encoded, as a
_mode: "binary"body does, and is decoded before it is served. - The upstream’s
content-lengthis dropped when behaviors run, since they can change the body’s length; the served length is computed from the body.
addDecorateBehavior
Add a decorate behavior to the stub the proxy generates. It runs when that stub replays, not on the live proxied response, as in Mountebank:
{
"proxy": {
"to": "https://api.example.com",
"addDecorateBehavior": "function(request, response) { \
response.headers['X-Proxied-By'] = 'Rift'; \
return response; \
}"
}
}
addWaitBehavior
Record the upstream’s observed latency on the generated stub, so replay reproduces it as a wait behavior. It is a boolean (default false), as in Mountebank, and does not delay the proxied response itself:
{
"proxy": {
"to": "https://api.example.com",
"addWaitBehavior": true
}
}
Like predicateGenerators, enabling it causes a stub to be generated from the proxied response. The live proxied response also carries an x-rift-proxy-latency: <ms> header when it is set.
HTTPS Proxy
Proxy to HTTPS Backend
{
"proxy": {
"to": "https://secure-api.example.com"
}
}
The origin’s certificate is verified against the operating system trust store, and the connection offers http/1.1 only. A TLS failure on this hop (an untrusted issuer, a name mismatch) answers the client 502 with an x-rift-proxy-error: true header and a Proxy error: … body naming the upstream. The TLS cause itself, e.g. invalid peer certificate: UnknownIssuer, goes to the server log only.
Trusting a Private CA
Rift verifies outbound TLS against the operating system trust store. An origin issued by an internal CA — a corporate API gateway — needs that CA supplied:
rift --upstream-ca-file /etc/rift/corp-ca.pem
# or
RIFT_UPSTREAM_CA_FILE=/etc/rift/corp-ca.pem rift
The anchor is appended to the OS store, so public origins keep working. The file may hold several certificates, and a missing or unusable one stops startup. This applies to every outbound call Rift makes: proxy stub upstreams, --configfile https://…, and (on the standalone binary) the intercept listener’s WebSocket passthrough. Embedders pass upstreamCaFile / upstreamCaPem to rift_serve_admin instead. For a worked example, see TLS/HTTPS → Trusting a Private CA.
SSL_CERT_FILE/SSL_CERT_DIRare also honoured, but they replace the trust store rather than adding to it — pointingSSL_CERT_FILEat a lone private CA silently drops every public root. Use--upstream-ca-fileunless you are supplying a complete bundle.
Skipping Verification (development only)
rift --upstream-tls-skip-verify # or RIFT_UPSTREAM_TLS_SKIP_VERIFY=true
Accepts any certificate and logs a warning. Prefer --upstream-ca-file: a recording proxy with verification disabled will faithfully record MITM’d traffic.
key,cert,passphraseandcipherson aproxyresponse are accepted for Mountebank compatibility and are not honoured — they are dropped on load and do not appear when the imposter is read back. Rift cannot present a client certificate to an upstream, and its outbound trust is process-wide, configured by the two flags above.
Header Manipulation
Inject Headers
Add headers to proxied requests:
{
"proxy": {
"to": "https://api.example.com",
"injectHeaders": {
"X-Forwarded-By": "Rift",
"Authorization": "Bearer token123"
}
}
}
Path Rewriting
Modify the request path before forwarding to the backend:
{
"proxy": {
"to": "https://api.example.com",
"pathRewrite": {
"from": "/api/v2",
"to": "/api/v1"
}
}
}
Use Cases
Version Migration:
Route v2 API calls to v1 backend during migration:
{
"port": 4545,
"protocol": "http",
"stubs": [{
"predicates": [{ "startsWith": { "path": "/api/v2" } }],
"responses": [{
"proxy": {
"to": "https://legacy-api.example.com",
"pathRewrite": {
"from": "/api/v2",
"to": "/api/v1"
}
}
}]
}]
}
Strip Prefix:
Remove a prefix from paths:
{
"proxy": {
"to": "https://backend.internal",
"pathRewrite": {
"from": "/gateway/service",
"to": ""
}
}
}
Request to /gateway/service/users → forwards to /users
Combining Proxy with Stubs
Mix static stubs with proxy fallback:
{
"port": 4545,
"protocol": "http",
"stubs": [
{
"predicates": [{ "equals": { "path": "/mocked" } }],
"responses": [{
"is": { "statusCode": 200, "body": "Mocked response" }
}]
},
{
"responses": [{
"proxy": {
"to": "https://api.example.com",
"mode": "proxyTransparent"
}
}]
}
]
}
Requests to /mocked return the static response; all others proxy to the real API.
Example: API Gateway Pattern
Create a proxy that records and allows overriding specific endpoints:
{
"port": 4545,
"protocol": "http",
"stubs": [
{
"predicates": [{ "equals": { "path": "/health" } }],
"responses": [{ "is": { "statusCode": 200, "body": "OK" } }]
},
{
"predicates": [{ "equals": { "path": "/api/feature-flag" } }],
"responses": [{ "is": { "body": { "enabled": true } } }]
},
{
"responses": [{
"proxy": {
"to": "https://api.example.com",
"mode": "proxyOnce",
"predicateGenerators": [{
"matches": { "method": true, "path": true }
}]
}
}]
}
]
}
Best Practices
- Start with proxyOnce - Record responses for consistent tests
- Use predicate generators wisely - Too specific = too many stubs
- Export regularly - Save recorded mocks to version control
- Clean up sensitive data - Review recorded responses for secrets
- Use proxyTransparent for debugging - See actual API responses