jamal2drive/api-fault-lab

Deterministic fault injection for API clients: reproduce ambiguous outcomes, connection loss, duplicate dispatch, latency, and HTTP errors.

JavaScript

1

18 commits

updated Oct 9, 2026

See the code

See what people are saying

SourceMessageScoreDate

API Fault Lab

1

Oct 9, 2026

README

API Fault Lab

CI

Deterministic fault injection for API clients and integrations.

API Fault Lab is a small, zero-dependency reverse proxy that reproduces the failure modes ordinary mocks usually miss: ambiguous outcomes, connection resets, duplicate dispatch, deterministic latency, and injected HTTP errors.

It is built for local development, CI, sandboxes, and staging environments.

Project status: early-stage and actively maintained. Behavior and CLI details may evolve before 1.0, while failure semantics remain explicit and test-backed.

Safety: use API Fault Lab only with systems you control or are authorized to test. Some scenarios intentionally duplicate requests or hide successful responses and can create real side effects.

The failure this project cares about most

A production mutation can succeed while the caller sees only a network failure:

client              API Fault Lab              upstream
  | POST /orders           |                       |
  |----------------------->| POST /orders          |
  |                        |---------------------->|
  |                        |        201 Created    |
  |                        |<----------------------|
  |   connection dropped  X                       |
  |
  | caller saw failure, but the order may exist

That is not equivalent to "the request failed." It is an ambiguous outcome. API Fault Lab makes it easy to reproduce this state on demand.

Requirements

  • Node.js 20+
  • No runtime dependencies

60-second demo

git clone https://github.com/jamal2drive/api-fault-lab.git
cd api-fault-lab
npm test
npm run demo

Expected demo output:

Sending POST /orders through API Fault Lab...
Client result: network failure (no response received)
Upstream state: 1 order(s) exist
Meaning: the client observed failure, but the mutation succeeded.

Quick start

Start the included test API:

node examples/mock-api.js

Start API Fault Lab in another terminal:

node src/cli.js \
  --target http://127.0.0.1:9090 \
  --scenario accepted-response-lost \
  --match-method POST \
  --match-path /orders \
  --port 8787

Send a request through the proxy:

curl -i -X POST http://127.0.0.1:8787/orders \
  -H 'content-type: application/json' \
  -d '{"sku":"demo"}'

The upstream accepts the order, but the client connection is dropped before the response is delivered. Query the mock API directly to see that the order exists:

curl http://127.0.0.1:9090/orders

Scenarios

ScenarioWhat it simulatesUpstream reached?Client gets upstream response?
pass-throughControl / normal trafficYesYes
latencyDeterministic network/service delayYesYes, delayed
http-errorInjected 4xx/5xx without dispatchNoNo
reset-before-upstreamConnection loss before dispatchNoNo
accepted-response-lostMutation succeeds, response disappearsYes, onceNo
duplicate-upstreamAccidental duplicate dispatchYes, twiceFirst response

See docs/SCENARIOS.md for exact semantics.

Target only the request you care about

Faults can be restricted by HTTP method, exact URL path, and an exact request-header value. Selectors can be combined:

node src/cli.js \
  --target http://127.0.0.1:9090 \
  --scenario http-error \
  --match-method POST \
  --match-path /orders \
  --match-header 'x-fault-case: inject'

Header names are matched case-insensitively; header values are exact. Unmatched requests pass through normally.

Deterministic cadence

Use --every N to inject the fault on every Nth matching request:

node src/cli.js \
  --target http://127.0.0.1:9090 \
  --scenario http-error \
  --error-status 503 \
  --match-method POST \
  --match-path /orders \
  --every 3

The first two matching requests pass through; the third receives the injected fault; then the cycle repeats.

Structured logs

Each request emits one-line JSON events containing fields such as:

  • timestamp
  • request ID
  • global request number
  • matching-request number
  • method and path
  • whether the request matched the selector
  • active scenario
  • upstream status when known
  • duration

Successful forwarded responses also include:

x-api-fault-lab: <scenario>
x-api-fault-lab-request-id: <uuid>

CLI

api-fault-lab --target <url> [options]

--target <url>          Upstream API base URL (required)
--port <number>         Local proxy port (default: 8787)
--scenario <name>       Fault scenario (default: pass-through)
--latency-ms <number>   Delay for latency scenario (default: 1500)
--error-status <number> Status for http-error scenario (default: 503)
--every <number>        Inject on every Nth matching request (default: 1)
--match-method <method> Only inject for this HTTP method, e.g. POST
--match-path <path>     Only inject for this exact URL path, e.g. /orders
--match-header <n:v>    Only inject when this request header exactly matches
--list-scenarios        Print scenario names and exit
--version               Print version and exit
--help                  Show help

Why deterministic instead of random chaos?

This project is meant to make a hard failure repeatable enough to become a test. Random fault injection is valuable for other kinds of resilience work, but it is deliberately not the default here.

Non-goals

API Fault Lab is not intended to be:

  • a production reverse proxy
  • a load-testing framework
  • a general network emulator
  • an application-specific mock server
  • a system that decides whether a business operation truly succeeded

It injects controlled failures; your application remains responsible for recovery, idempotency, state verification, and domain correctness.

Development

npm test
npm run check
npm run demo

CI runs the test suite on supported Node.js versions.

Roadmap

Near-term ideas are tracked in ROADMAP.md. Contributions are welcome, especially small deterministic scenarios with precise semantics and tests.

Responsible use

Read SECURITY.md before using destructive scenarios. Keep duplicate-dispatch and response-loss tests limited to local, disposable, sandbox, or explicitly authorized systems.

Maintainer

API Fault Lab is maintained by @jamal2drive. See MAINTAINERS.md for project-maintenance responsibilities.

License

MIT

api-testing
failure-testing
fault-injection
idempotency
nodejs
reliability
resilience
reverse-proxy

jamal2drive/api-fault-lab

Deterministic fault injection for API clients: reproduce ambiguous outcomes, connection loss, duplicate dispatch, latency, and HTTP errors.

JavaScript

1

18 commits

updated Oct 9, 2026

See the code

See what people are saying

SourceMessageScoreDate

API Fault Lab

1

Oct 9, 2026

README

API Fault Lab

CI

Deterministic fault injection for API clients and integrations.

API Fault Lab is a small, zero-dependency reverse proxy that reproduces the failure modes ordinary mocks usually miss: ambiguous outcomes, connection resets, duplicate dispatch, deterministic latency, and injected HTTP errors.

It is built for local development, CI, sandboxes, and staging environments.

Project status: early-stage and actively maintained. Behavior and CLI details may evolve before 1.0, while failure semantics remain explicit and test-backed.

Safety: use API Fault Lab only with systems you control or are authorized to test. Some scenarios intentionally duplicate requests or hide successful responses and can create real side effects.

The failure this project cares about most

A production mutation can succeed while the caller sees only a network failure:

client              API Fault Lab              upstream
  | POST /orders           |                       |
  |----------------------->| POST /orders          |
  |                        |---------------------->|
  |                        |        201 Created    |
  |                        |<----------------------|
  |   connection dropped  X                       |
  |
  | caller saw failure, but the order may exist

That is not equivalent to "the request failed." It is an ambiguous outcome. API Fault Lab makes it easy to reproduce this state on demand.

Requirements

  • Node.js 20+
  • No runtime dependencies

60-second demo

git clone https://github.com/jamal2drive/api-fault-lab.git
cd api-fault-lab
npm test
npm run demo

Expected demo output:

Sending POST /orders through API Fault Lab...
Client result: network failure (no response received)
Upstream state: 1 order(s) exist
Meaning: the client observed failure, but the mutation succeeded.

Quick start

Start the included test API:

node examples/mock-api.js

Start API Fault Lab in another terminal:

node src/cli.js \
  --target http://127.0.0.1:9090 \
  --scenario accepted-response-lost \
  --match-method POST \
  --match-path /orders \
  --port 8787

Send a request through the proxy:

curl -i -X POST http://127.0.0.1:8787/orders \
  -H 'content-type: application/json' \
  -d '{"sku":"demo"}'

The upstream accepts the order, but the client connection is dropped before the response is delivered. Query the mock API directly to see that the order exists:

curl http://127.0.0.1:9090/orders

Scenarios

ScenarioWhat it simulatesUpstream reached?Client gets upstream response?
pass-throughControl / normal trafficYesYes
latencyDeterministic network/service delayYesYes, delayed
http-errorInjected 4xx/5xx without dispatchNoNo
reset-before-upstreamConnection loss before dispatchNoNo
accepted-response-lostMutation succeeds, response disappearsYes, onceNo
duplicate-upstreamAccidental duplicate dispatchYes, twiceFirst response

See docs/SCENARIOS.md for exact semantics.

Target only the request you care about

Faults can be restricted by HTTP method, exact URL path, and an exact request-header value. Selectors can be combined:

node src/cli.js \
  --target http://127.0.0.1:9090 \
  --scenario http-error \
  --match-method POST \
  --match-path /orders \
  --match-header 'x-fault-case: inject'

Header names are matched case-insensitively; header values are exact. Unmatched requests pass through normally.

Deterministic cadence

Use --every N to inject the fault on every Nth matching request:

node src/cli.js \
  --target http://127.0.0.1:9090 \
  --scenario http-error \
  --error-status 503 \
  --match-method POST \
  --match-path /orders \
  --every 3

The first two matching requests pass through; the third receives the injected fault; then the cycle repeats.

Structured logs

Each request emits one-line JSON events containing fields such as:

  • timestamp
  • request ID
  • global request number
  • matching-request number
  • method and path
  • whether the request matched the selector
  • active scenario
  • upstream status when known
  • duration

Successful forwarded responses also include:

x-api-fault-lab: <scenario>
x-api-fault-lab-request-id: <uuid>

CLI

api-fault-lab --target <url> [options]

--target <url>          Upstream API base URL (required)
--port <number>         Local proxy port (default: 8787)
--scenario <name>       Fault scenario (default: pass-through)
--latency-ms <number>   Delay for latency scenario (default: 1500)
--error-status <number> Status for http-error scenario (default: 503)
--every <number>        Inject on every Nth matching request (default: 1)
--match-method <method> Only inject for this HTTP method, e.g. POST
--match-path <path>     Only inject for this exact URL path, e.g. /orders
--match-header <n:v>    Only inject when this request header exactly matches
--list-scenarios        Print scenario names and exit
--version               Print version and exit
--help                  Show help

Why deterministic instead of random chaos?

This project is meant to make a hard failure repeatable enough to become a test. Random fault injection is valuable for other kinds of resilience work, but it is deliberately not the default here.

Non-goals

API Fault Lab is not intended to be:

  • a production reverse proxy
  • a load-testing framework
  • a general network emulator
  • an application-specific mock server
  • a system that decides whether a business operation truly succeeded

It injects controlled failures; your application remains responsible for recovery, idempotency, state verification, and domain correctness.

Development

npm test
npm run check
npm run demo

CI runs the test suite on supported Node.js versions.

Roadmap

Near-term ideas are tracked in ROADMAP.md. Contributions are welcome, especially small deterministic scenarios with precise semantics and tests.

Responsible use

Read SECURITY.md before using destructive scenarios. Keep duplicate-dispatch and response-loss tests limited to local, disposable, sandbox, or explicitly authorized systems.

Maintainer

API Fault Lab is maintained by @jamal2drive. See MAINTAINERS.md for project-maintenance responsibilities.

License

MIT

api-testing
failure-testing
fault-injection
idempotency
nodejs
reliability
resilience
reverse-proxy