HTTP endpoints that misbehave on purpose.
Point your client code, integration tests, or curious colleagues at 59 public endpoints
on chaos.catastrophic.io, across 14 categories of adversity —
latency, malformed payloads, broken CORS, lying Retry-After headers, and other flavors of wrong.
See how your stack handles it before production does.
14 flavors of wrong
Every category covers a real failure mode your HTTP client should handle gracefully. They probably don't.
Agent chaos
Semantically wrong responses that pass structural validation. Tests whether agents validate content, not just shape.
Authentication
401 challenges with configurable WWW-Authenticate schemes.
CORS
Broken or missing CORS headers, including preflight failures.
Delivery
Responses where the wire-level delivery of the body lies — short bodies, early closes, Content-Length mismatches.
Discovery chaos
Meta-endpoints that serve mutated versions of standard .well-known discovery schemas.
Fault injection
Random failures at configurable probability.
Format chaos
Common-Internet-file-format misbehaviour: bytes that look like the right format but break in specific structural ways.
Headers & caching
Responses with misleading, excessive, or contradictory headers.
Latency
Slow responses, dripping streams, and delayed behavior.
Method handling
Servers that lie about which HTTP methods they support, or violate RFC 9110 method-response rules.
Pagination
Paginated responses whose metadata lies, whose pages overlap, or whose Link headers point to the wrong place.
Rate limiting
429 responses with configurable Retry-After headers.
Redirects
Redirect chains of configurable depth.
Status codes
Arbitrary HTTP statuses on demand.
Three quick requests
Quickest way to confirm your client handles adversity. Each one fails (or succeeds-suspiciously) in a documented way.
# 2 second delay before the response arrives
curl -i https://catastrophic.io/slow?ms=2000
# arbitrary status code — try anything from 100 to 599
curl -i https://catastrophic.io/status/503
# 30% chance of failure, otherwise 200
curl -i 'https://catastrophic.io/flaky?p=0.3'
import requests
# 2 second delay before the response arrives
print(requests.get('https://catastrophic.io/slow?ms=2000').status_code)
# arbitrary status code — try anything from 100 to 599
print(requests.get('https://catastrophic.io/status/503').status_code)
# 30% chance of failure, otherwise 200
print(requests.get('https://catastrophic.io/flaky', params={'p': 0.3}).status_code)
// 2 second delay before the response arrives
await fetch('https://catastrophic.io/slow?ms=2000');
// arbitrary status code — try anything from 100 to 599
console.log((await fetch('https://catastrophic.io/status/503')).status);
// 30% chance of failure, otherwise 200
console.log((await fetch('https://catastrophic.io/flaky?p=0.3')).status);
package main
import (
"fmt"
"net/http"
)
func main() {
// 2 second delay before the response arrives
http.Get("https://catastrophic.io/slow?ms=2000")
// arbitrary status code — try anything from 100 to 599
r, _ := http.Get("https://catastrophic.io/status/503")
fmt.Println(r.StatusCode)
// 30% chance of failure, otherwise 200
r, _ = http.Get("https://catastrophic.io/flaky?p=0.3")
fmt.Println(r.StatusCode)
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
fn main() -> Result<(), Box> {
// 2 second delay before the response arrives
reqwest::blocking::get("https://catastrophic.io/slow?ms=2000")?;
// arbitrary status code — try anything from 100 to 599
let r = reqwest::blocking::get("https://catastrophic.io/status/503")?;
println!("{}", r.status());
// 30% chance of failure, otherwise 200
println!("{}", reqwest::blocking::get("https://catastrophic.io/flaky?p=0.3")?.status());
Ok(())
}
import java.net.URI;
import java.net.http.*;
HttpClient c = HttpClient.newHttpClient();
var discard = HttpResponse.BodyHandlers.discarding();
// 2 second delay before the response arrives
c.send(HttpRequest.newBuilder(URI.create("https://catastrophic.io/slow?ms=2000")).build(), discard);
// arbitrary status code — try anything from 100 to 599
System.out.println(c.send(HttpRequest.newBuilder(URI.create("https://catastrophic.io/status/503")).build(), discard).statusCode());
// 30% chance of failure, otherwise 200
System.out.println(c.send(HttpRequest.newBuilder(URI.create("https://catastrophic.io/flaky?p=0.3")).build(), discard).statusCode());
using var c = new HttpClient();
// 2 second delay before the response arrives
await c.GetAsync("https://catastrophic.io/slow?ms=2000");
// arbitrary status code — try anything from 100 to 599
var r = await c.GetAsync("https://catastrophic.io/status/503");
Console.WriteLine((int)r.StatusCode);
// 30% chance of failure, otherwise 200
Console.WriteLine((int)(await c.GetAsync("https://catastrophic.io/flaky?p=0.3")).StatusCode);
require 'net/http'
# 2 second delay before the response arrives
Net::HTTP.get_response(URI('https://catastrophic.io/slow?ms=2000'))
# arbitrary status code — try anything from 100 to 599
puts Net::HTTP.get_response(URI('https://catastrophic.io/status/503')).code
# 30% chance of failure, otherwise 200
puts Net::HTTP.get_response(URI('https://catastrophic.io/flaky?p=0.3')).code
# 2 second delay before the response arrives
Invoke-WebRequest 'https://catastrophic.io/slow?ms=2000'
# arbitrary status code — wrap in try/catch for non-2xx
try { Invoke-WebRequest 'https://catastrophic.io/status/503' } catch { $_.Exception.Response.StatusCode.value__ }
# 30% chance of failure, otherwise 200
try { Invoke-WebRequest 'https://catastrophic.io/flaky?p=0.3' } catch { 'failed (chaos)' }