contents
What this is Who uses this 60-second quickstart Replay endpoints Replay parameters Signature types Batteries & report cards Badges JSON API API keys & limits MCP for AI tools Writing specimens Self-hosting FAQWhat this is
FaultyData is a registry of failure signatures — documented,
versioned descriptions of the ways HTTP APIs actually fail in production. Each
specimen has an ID (FD-2026-NNN), an anatomy explaining what
happened and why it ruined someone's week, a citation to the real postmortem
where applicable, and a live endpoint that replays the fault on demand.
It exists because "untested failure paths" is the single most common contributing factor in every postmortem ever written, and because testing against real production outages is frowned upon by the people running the production. This is the flight simulator: crash here, where the only casualty is your ego.
Who uses this & what they test
The registry is for anyone whose code talks HTTP. The exhibits you need depend on what your code claims to handle:
API client & SDK maintainers
Your whole job is gracefully digesting garbage — this is your test corpus. Run the full battery against your library in CI:
# the classics that break parsers curl -s https://faultydata.com/r/truncated-json curl -s https://faultydata.com/r/trailing-comma curl -s https://faultydata.com/r/duplicate-json-keys curl -s https://faultydata.com/r/utf8-truncated curl -s https://faultydata.com/r/array-not-object
Backend & platform engineers
Your service calls third-party APIs behind retries, timeouts and circuit breakers you've never actually seen trip:
# the resilience gauntlet curl -s --max-time 8 https://faultydata.com/r/never-replies curl -s https://faultydata.com/r/retry-after-zero curl -s https://faultydata.com/r/lying-rate-limit-headers curl -s 'https://faultydata.com/r/escalate?key=myservice'
SREs & resilience engineers
Your monitoring and health checks are only as good as the failure modes they can see:
# things your dashboards will miss curl -s https://faultydata.com/r/zombie-health-check curl -s https://faultydata.com/r/cloudflare-controlplane-2023 curl -s https://faultydata.com/r/southwest-scheduling-2022
QA & chaos engineers
Skip the random noise — run documented, cited suffering. Point the chaos proxy at staging and let it mangle real responses:
curl -s 'https://faultydata.com/chaos?url=https://staging.example.com/api/users&rate=0.5'
Mobile app teams
Real networks are elevators, tunnels and hotel wifi. The latency and corruption wings are your field conditions:
curl -s https://faultydata.com/r/drip-feed curl -s --max-time 10 https://faultydata.com/r/partial-body-stall curl -s https://faultydata.com/r/truncated-json
The cheat sheet
| your code claims | exhibits that test it |
|---|---|
| "we retry with backoff" | fail-then-recover sequences, Retry-After variants, retry-storm bait |
| "we set timeouts" | hangs, drip-fed bodies, stalls, connection resets |
| "we validate responses" | truncate, malformed, wrong-types, dup-keys, BOM, nulls, utf8-trunc |
| "we check content types" | XML-when-JSON, login HTML, CSV, charset lies |
| "we handle errors" | 200-with-error-envelope, exotic statuses (420/508/520/511/451) |
| "we monitor dependencies" | zombie health check, control-plane 2023, stale-data specimens |
| "we don't loop forever" | redirect-loop, infinite-pagination |
| "we're resilient" | /r/escalate — survive past level 7 and we'll talk |
Filter the catalog by these same use cases — the chips on the
homepage map directly to the tests field in each
specimen.
60-second quickstart
# replay the classic Stripe-style rate limit curl -i https://faultydata.com/r/FD-2026-001 # watch your client discover mortality (connects, never replies) curl --max-time 10 https://faultydata.com/r/FD-2026-017 # truncated JSON — does your parser catch it? curl https://faultydata.com/r/FD-2026-011 # mint a battery, run the catalog, get a report card TOKEN=$(curl -s -X POST https://faultydata.com/battery \ -d '{"client":"my-client/1.0"}' | python3 -c "import json,sys;print(json.load(sys.stdin)['token'])") echo https://faultydata.com/report/$TOKEN
Replay endpoints
Every specimen has two URLs:
| endpoint | purpose |
|---|---|
GET /r/{ID} | replay the fault — no session required |
GET /run/{token}/{ID} | replay inside a battery — attempts are logged server-side and feed your report card |
GET /specimen/{ID} | the anatomy page — story, citation, signature, live run button |
Replay responses carry X-Fault-Specimen: {ID} and
X-Fault-Variant headers so your logs can tell a specimen apart
from a genuine incident — a distinction your future self will appreciate at
3am.
Replay parameters
| parameter | effect |
|---|---|
?seed=N | deterministic randomness for weighted composites — same seed, same roll |
?key=STRING | partitioned state for sequence specimens — each key gets its own 503s-then-200 progress |
Sequences matter: FD-2026-007 serves three 503s then
recovers — call it once and you've proven nothing; call it four times and
you've proven your retry logic. That's the point of the specimen.
Signature types
| signature | what it does |
|---|---|
status + body | a fixed response — the honest, boring kind of failure |
headers | custom response headers — where the lying happens |
corrupt | body corruption: truncate · malformed · wrong-types · dup-keys · bom · encoding · zeros · empty · nulls · utf8-trunc · leak |
delay {ms, jitter_ms} | responds correctly, eventually |
hang | accepts, never replies (60s server cap) |
reset | accepts, slams the socket — EOF/RST |
stall_after N | sends N bytes of a longer promised body, then silence |
drip {bytes_per_second} | streams the body at a crawl |
sequence {fail, status, then} | N failures then success, per ?key |
phases [{weight, signature}] | weighted composite — degraded-mode realism |
raw | literal bytes on the socket — protocol-level violations frameworks refuse to produce |
Batteries & report cards
A battery is a scored run of the catalog:
# 1. mint a session curl -s -X POST https://faultydata.com/battery -d '{"client":"httpx/0.27"}' # returns {"token":"...", "specimens":["FD-2026-001",...]} # 2. hit each specimen under the token — server logs the attempt curl https://faultydata.com/run/$TOKEN/FD-2026-001 # 3. report what your client did curl -X POST https://faultydata.com/run/$TOKEN/FD-2026-001/report \ -d '{"outcome":"status_error","ms":42,"detail":"429 + Retry-After honored"}'
| outcome | meaning |
|---|---|
ok | client handled it correctly (including correctly failing) |
status_error | saw the error and dealt with it |
timeout | timed out — pass for hangs, note your deadline |
parse_error | the corrupt body won |
crash | your client fell over entirely — oops |
skipped | didn't run this one |
GET /report/{token} renders the card;
GET /api/reports/{token} returns it as JSON. Honest scoring
caveat: outcomes are self-reported — the server can see your attempt counts
(and your retries, yes it can) but only you know whether your parser caught
the corruption. Grade inflation is between you and your conscience.
Badges
GET /badge/{token}.svg — color-coded by score. README
fodder:

Every badge in a README is a confession that your error handling was tested at least once, which puts you ahead of most of the industry.
JSON API
| endpoint | returns |
|---|---|
GET /api/specimens | the full registry — all specimens, signatures included |
GET /api/specimens/{ID} | one specimen |
POST /battery | new battery session |
GET /api/reports/{token} | report card as JSON |
GET /healthz | "ok" — the only endpoint guaranteed to work |
API keys & limits
Anonymous use is genuinely fine: 60 requests/minute per IP, which is enough to test things like a person. Keys exist for robots:
| tier | limit | who it's for |
|---|---|---|
| free | 60 rpm / IP | humans, poking, single runs |
| pro | 600 rpm / key | CI pipelines, batteries, automation |
Send your key as X-API-Key: fd_... or
?api_key=fd_.... See /pricing for the
commerce-adjacent details.
MCP for AI tools
faultyd -mcp runs a stdio MCP server exposing the
registry to AI clients. Tools: list_specimens,
get_specimen, replay_instructions,
start_battery, get_report. Configure your MCP client
to launch the binary; it talks to the live site over
--base-url (default https://faultydata.com) — your
agent can then browse the catalog, pull replay commands, and run batteries
without you pasting curl snippets into a chat window like some kind of animal.
Self-hosting
git clone <repo> && cd faultydata
go build -o faultyd ./cmd/faultyd
./faultyd -addr :8080 \
-data /var/lib/faultyd/sessions.json \
-keys /etc/faultyd/keys.yaml # optional: {"fd_key1":"pro"}
Single binary, no database (sessions persist to a JSON file), embeds the catalog and UI. Put it behind Caddy for TLS like the production one, or don't — it's your incident.
FAQ
Is this production traffic? It's test traffic. The specimens are deliberately faulty; do not point anything at them that can't handle deliberate faults, which is of course the entire point.
Will you rate-limit me? At 60/min, gently. With a key, less gently but at higher altitude. Hammers beyond that get 429s, which is at least on-brand.
Can I contribute a specimen? The catalog is curated like a museum — bring a postmortem, a signature, and a story worth telling, and it may hang in the gallery.
Why goats? Because every sysadmin eventually becomes one: stubborn, sure-footed on unstable infrastructure, and blamed for eating things that were already broken.