contents
Conventions Catalog Replay Auto-fuck Batteries Reports Badges Meta Errors & limits Full examplesConventions
Everything is GET or POST over HTTPS.
Request bodies are JSON. Responses are JSON with one deliberate exception:
the replay endpoints, whose entire job is to be wrong in a specified
way. If /r/{id} returns a 418 with a body that shouldn't exist,
that is not a bug in our bug database. It is the exhibit.
Every replay response carries:
X-Fault-Specimen: FD-2026-017 X-Fault-Variant: hang
so your logs can distinguish "we hit a specimen" from "someone deployed on a Friday".
Authentication: optional. Anonymous requests are limited
per-IP; send X-API-Key: fd_... or ?api_key=fd_...
for the pro tier. See pricing.
Catalog
GET /api/specimens
The whole registry. Signatures included — you could, in principle, implement the entire service from this one response, which tells you where the moat actually is: not the format, the curation.
curl -s https://faultydata.com/api/specimens | jq '.[0]'
{
"id": "FD-2026-061",
"title": "Memory leak served in responses (Cloudbleed, Feb 2017)",
"category": "corruption",
"severity": "fatal",
"tests": ["parsing", "monitoring"],
"citation": {
"source": "Cloudflare — incident report on memory leak...",
"url": "https://blog.cloudflare.com/incident-report-...",
"date": "2017-02-23"
},
"anatomy": "A buffer overrun in Cloudflare's HTML parser...",
"tags": ["real-world", "security", "memory-leak"],
"signature": { "status": 200, "body": "...", "corrupt": "leak" }
}
| field | notes |
|---|---|
severity | fatal | severe | moderate | trivial — drives "most fucked" ordering |
tests | what your client is being examined on: retry-logic, timeouts, parsing, headers, auth, semantics, monitoring, loops |
category | status | latency | corruption | sequence | composite | headers | redirect |
citation | present on real-world specimens — the postmortem it models |
GET /api/specimens/{id}
One specimen. 404s with {"error":"not found"} —
a genuine 404, not a specimen. Check the X-Fault-Specimen
header if you're ever unsure which kind of broken you're looking at.
Replay
GET /r/{id}
Replays the fault described by the specimen. The response is deliberately wrong in exactly the documented way. Query params:
| param | effect |
|---|---|
?seed=N | deterministic variant selection for weighted composites |
?key=S | isolated sequence state — each key gets its own fail-then-recover progress |
curl -i https://faultydata.com/r/FD-2026-001
HTTP/2 429
retry-after: 30
x-fault-specimen: FD-2026-001
{"error":{"type":"rate_limit_error",...}}
GET /run/{token}/{id}
Same replay, inside a battery. The attempt is logged
server-side (including retry count — yes, we see your hammering) and feeds
the report card. Same query params as /r/.
Auto-fuck
For when picking a specimen by hand is too much effort — automation for things that were previously only manually fucked.
GET /r/auto
Deals a random exhibit from the deck. Every request is a
surprise; X-Fault-Auto tells you which specimen drew the short
straw.
| param | effect |
|---|---|
?severity=S | restrict the deck — trivial | moderate | severe | fatal |
?seed=N | deterministic draw — same seed, same specimen |
curl -i https://faultydata.com/r/auto HTTP/2 451 x-fault-auto: FD-2026-067
GET /r/escalate?key=name
Punishes a client key with worsening exhibits the longer it
survives: calm for the first three draws, unpleasant through seven, then
severe/fatal for as long as it keeps coming back.
X-Fault-Level reports how deep in the hole you are.
for i in $(seq 1 20); do
curl -s -o /dev/null -w '%{http_code}\n' \
'https://faultydata.com/r/escalate?key=my-client'
done
ANY /chaos?url=…
The chaos proxy: it fetches a real upstream and then fucks with it. Point your client at us, we point ourselves at your staging API, and somewhere in the middle the response gets mangled.
| param | effect |
|---|---|
?url= | required — the http(s) upstream to fetch (private/loopback/link-local targets refused; we are not an SSRF oracle) |
?rate=F | probability the response gets mangled, 0–1 (default 0.5) |
?seed=N | deterministic fault selection |
Faults injected: wrong Content-Type, truncated body, malformed
tail, synthetic 5xx, empty 200, latency tax, embedded NULs.
X-Chaos-Fault names the mechanism,
X-Chaos-Fault-Specimen cites the registry exhibit it models
(e.g. truncate → FD-2026-011), and a
Link: rel="describedby" header points at the specimen page —
even chaos stays cross-referenced to canon.
curl -i 'https://faultydata.com/chaos?url=https://api.staging.example.com/users&rate=0.8' HTTP/2 200 x-chaos-fault: truncate x-chaos-upstream-status: 200
Batteries
POST /battery
curl -X POST https://faultydata.com/battery \
-H 'content-type: application/json' \
-d '{"client":"httpx/0.27"}'
Returns:
{
"token": "9f3c...20hex",
"client": "httpx/0.27",
"specimens": ["FD-2026-001", "FD-2026-002", ...],
"report": "/report/9f3c...20hex"
}
| field | notes |
|---|---|
client (in) | free-form name for the thing under test; appears on the report card |
token | the battery session id — embeds in all /run/ and /report/ URLs |
specimens | the full battery list, most-fucked first |
GET /run/{token}/auto · GET /run/{token}/escalate
Auto-fuck inside a battery. /auto deals a random
exhibit (same ?severity/?seed params);
/escalate climbs the punishment ladder per battery — keep
drawing and watch X-Fault-Level deepen until the client dies.
Every draw is logged against the report card under the specimen it landed
on, so the card records which exhibit actually bit you.
POST /run/{token}/{id}/report
curl -X POST https://faultydata.com/run/$TOKEN/FD-2026-017/report \
-H 'content-type: application/json' \
-d '{"outcome":"timeout","ms":5002,"detail":"max-time fired as expected"}'
| field | notes |
|---|---|
outcome | ok | status_error | timeout | parse_error | crash | skipped |
ms | elapsed ms — optional but it makes the card honest |
detail | free-text, shown on the report |
Reports
GET /report/{token}
The report card — HTML. Grade letter, score, pass count, and per-specimen verdicts including server-observed attempt counts.
GET /api/reports/{token}
{
"token": "9f3c...",
"client": "httpx/0.27",
"score": 87,
"grade": "B+",
"results": [
{"id":"FD-2026-001","outcome":"status_error","ms":42,"attempts":1},
{"id":"FD-2026-017","outcome":"timeout","ms":5002,"attempts":1}
]
}
Badges
GET /badge/{token}.svg

SVG, color-coded by score. Green means your error handling survived the museum. Red means it didn't, and now everyone can see.
Meta
| endpoint | returns |
|---|---|
GET /healthz | ok — the one endpoint guaranteed healthy |
GET /.well-known/security.txt | contact for security reports |
Errors & rate limits
| status | when |
|---|---|
404 | genuinely not found (check X-Fault-Specimen — its absence means this one is real) |
400 | malformed report body — invalid JSON or unknown outcome |
429 | you exceeded the tier limit — 60/min anonymous, 600/min with a key |
Rate-limit responses include Retry-After. If you
ignore it you'll hit FD-2026-001 for real, which would be poetic.
Full examples
Python — run one specimen with httpx
import httpx
try:
r = httpx.get("https://faultydata.com/r/FD-2026-017", timeout=5)
print("survived:", r.status_code)
except httpx.TimeoutException:
print("correctly gave up waiting for a socket that never replies")
Node — fetch with a deadline
const res = await fetch("https://faultydata.com/r/FD-2026-016",
{ signal: AbortSignal.timeout(3000) });
console.log(res.status, res.headers.get("x-fault-specimen"));
Go — battery loop
token := mustPOST("/battery", `{"client":"go-http/1.0"}`)
for _, id := range specimens(token) {
ctx, cancel := context.WithTimeout(bg, 5*time.Second)
outcome := classify(do(ctx, "/run/"+token+"/"+id))
mustPOST("/run/"+token+"/"+id+"/report", outcome)
cancel()
}