openapi: 3.1.0
info:
  title: catastrophic.io
  summary: A chaos engineering scratchpad — public HTTP endpoints that deliberately misbehave.
  description: |
    Catastrophic.io is a collection of public endpoints designed to misbehave
    in specific, documented ways. Use them to test how your HTTP clients
    handle slow responses, weird status codes, broken bodies, lying headers,
    intermittent failures, and other adversity.

    Every chaos response includes `X-Chaos-*` headers so monitoring clients
    can distinguish intentional chaos from real failures.

    All endpoints accept GET unless noted. Unknown paths fall through to the
    static catalog at the same domain.
  version: 1.0.0
  contact:
    url: https://catastrophic.io
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://chaos.catastrophic.io
    description: Production

# All endpoints are intentionally public — no authentication required.
# The empty array satisfies tooling that expects an explicit security
# declaration (e.g. Redocly's `security-defined` rule).
security: []

tags:
  - name: latency
    description: Delayed and slowly-streaming responses.
  - name: status
    description: Arbitrary HTTP status codes.
  - name: payload
    description: Bodies that misbehave or lie about their size or type.
  - name: redirects
    description: Redirect chains.
  - name: headers
    description: Header floods and contradictory caching directives.
  - name: rate-limiting
    description: 429 responses with configurable Retry-After.
  - name: fault-injection
    description: Random failures at a configurable probability.
  - name: inspection
    description: Request reflection for debugging.
  - name: auth
    description: WWW-Authenticate challenges and 401 handling.
  - name: cors
    description: Broken or missing CORS headers.
  - name: agent
    description: Semantically wrong responses that pass structural validation.
  - name: discovery
    description: Mutated .well-known discovery documents.
  - name: pagination
    description: Paginated responses whose metadata lies, whose pages overlap, or whose Link headers point to the wrong place.
  - name: method
    description: Method-handling chaos — Allow header lies, 405s without Allow, OPTIONS that contradicts reality.

paths:
  /slow:
    get:
      operationId: slow
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/latency/slow/
      tags: [latency]
      summary: Sleep then return 200
      description: Sleeps for the specified number of milliseconds before responding. The full response arrives at once after the delay elapses.
      parameters:
        - name: ms
          in: query
          description: Delay in milliseconds.
          schema:
            type: integer
            minimum: 0
            maximum: 15000
            default: 1000
      responses:
        '200':
          description: Returned after the configured delay.
          headers:
            X-Chaos-Delayed-Ms:
              description: Actual delay applied, in milliseconds.
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /slow }
                  delayed_ms: { type: integer }
                  max_ms: { type: integer }

  /status/{code}:
    get:
      operationId: statusCode
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/status-codes/status-code/
      tags: [status]
      summary: Return an arbitrary HTTP status code
      description: Returns the requested status code. Codes 204 and 304 return no body per RFC 9110. Code 101 (and other protocol-switching codes) is rejected with 400.
      parameters:
        - name: code
          in: path
          required: true
          description: HTTP status code to return. Clamped to 100–599.
          schema:
            type: integer
            minimum: 100
            maximum: 599
      responses:
        '400':
          description: Returned when the requested code cannot legally be returned by the platform (e.g. 101).
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  error: { type: string }
                  requested: { type: integer }
                  reason: { type: string }
        default:
          description: The requested status code, with a JSON body describing the response (empty for 204/304).
          headers:
            X-Chaos-Status:
              description: The status code returned.
              schema: { type: string }

  /truncate:
    get:
      operationId: truncate
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/delivery/truncate/
      tags: [payload]
      summary: Send Content-Length for full body, cut at N bytes
      description: |
        Sends a `Content-Length` header claiming the full payload size, but the response stream closes early at N bytes. The payload is a deterministic ASCII pattern (`0123456789` repeating) so clients can verify exactly how many bytes they received versus what was promised.
      parameters:
        - name: at
          in: query
          description: Byte position where the stream is truncated.
          schema:
            type: integer
            minimum: 1
            maximum: 1000000
            default: 512
      responses:
        '200':
          description: Truncated body with lying Content-Length.
          headers:
            Content-Length:
              description: Promised body size (larger than what is actually delivered).
              schema: { type: string }
            X-Chaos-Truncated-At:
              schema: { type: string }
            X-Chaos-Promised-Bytes:
              schema: { type: string }
            X-Chaos-Actual-Bytes:
              schema: { type: string }
          content:
            text/plain:
              schema: { type: string }

  /json:
    parameters:
      - name: mode
        in: query
        description: Which kind of JSON flaw to send. `all` (default) bundles three flavors of wrong in one payload (the original behavior); the others isolate a single failure mode.
        schema:
          type: string
          enum: [all, missing-brace, unquoted-key, trailing-comma]
          default: all
    get:
      operationId: jsonChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/json/
      tags: [payload]
      summary: Invalid JSON with application/json Content-Type
      description: "Returns a syntactically invalid JSON body with `Content-Type: application/json`. Tests client JSON parsing error handling. Use `?mode=` to test a specific failure mode in isolation."
      responses:
        '200':
          description: Invalid JSON body.
          headers:
            X-Chaos-Payload:
              schema: { type: string, const: json-chaos }
            X-Chaos-Json-Mode:
              schema: { type: string }
          content:
            application/json:
              schema: { type: string, description: Not actually valid JSON. }

  /wrong-content-type:
    parameters:
      - name: type
        in: query
        description: Which kind of body/Content-Type mismatch to send. `json-as-html` (default) sends a JSON body labelled `text/html`. `missing` omits the Content-Type header entirely.
        schema:
          type: string
          enum: [json-as-html, xml-as-json, html-as-json, json-as-png, missing]
          default: json-as-html
    get:
      operationId: wrongContentType
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/headers-and-caching/wrong-content-type/
      tags: [payload]
      summary: Body labelled with a non-matching Content-Type
      description: "Returns a body whose declared `Content-Type` does not match its actual format. Tests whether clients parse by declared type or by content sniffing. `X-Chaos-Body-Format` exposes the body's real format so a client can detect the mismatch programmatically."
      responses:
        '200':
          description: Body with a mismatched (or missing) Content-Type header.
          headers:
            X-Chaos-Payload:
              schema: { type: string, const: wrong-content-type }
            X-Chaos-Type-Mode:
              schema: { type: string }
            X-Chaos-Body-Format:
              schema: { type: string }
            X-Chaos-Actual-Format:
              schema: { type: string, description: "Deprecated alias for X-Chaos-Body-Format." }
          content:
            text/html:
              schema: { type: string }

  /redirect-chain:
    get:
      operationId: redirectChain
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/redirects/redirect-chain/
      tags: [redirects]
      summary: Redirect N times before resolving to 200
      description: Each hop redirects to `/redirect-chain?n=N-1`. When `n=0`, returns 200. Tests redirect-following limits, loop detection, and history tracking.
      parameters:
        - name: n
          in: query
          description: Number of redirect hops remaining.
          schema:
            type: integer
            minimum: 0
            maximum: 20
            default: 3
      responses:
        '200':
          description: Final hop in the chain.
          headers:
            X-Chaos-Redirect-Chain:
              schema: { type: string, const: resolved }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  message: { type: string }
                  hops_completed: { type: boolean }
        '302':
          description: Intermediate hop. Location points to the next URL in the chain.
          headers:
            Location:
              schema: { type: string }

  /header-flood:
    get:
      operationId: headerFlood
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/headers-and-caching/header-flood/
      tags: [headers]
      summary: Return N custom response headers
      description: Returns N synthetic `X-Chaos-Header-NNN` headers alongside the normal response. Tests header size limits and parser behavior.
      parameters:
        - name: n
          in: query
          description: Number of custom headers to include.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: Response with many custom headers.
          headers:
            X-Chaos-Header-Count:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  header_count: { type: integer }
                  max: { type: integer }

  /cache-confused:
    parameters:
      - name: mode
        in: query
        description: |
          Which contradiction to send. `all` (default) bundles three at once. `store-vs-age` = `no-store, max-age=300`. `cache-vs-immutable` = `no-cache, immutable`. `private-vs-shared` = `private, s-maxage=3600`.
        schema:
          type: string
          enum: [all, store-vs-age, cache-vs-immutable, private-vs-shared]
          default: all
    get:
      operationId: cacheConfused
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/headers-and-caching/cache-confused/
      tags: [headers]
      summary: Contradictory Cache-Control directives
      description: |
        Sends a `Cache-Control` header combining mutually contradictory directives. Tests whether clients, proxies, and CDNs resolve conflicts consistently per RFC 9111. Use `?mode=` to isolate a single contradiction.
      responses:
        '200':
          description: Response with a contradictory Cache-Control header.
          headers:
            Cache-Control:
              schema: { type: string }
            X-Chaos-Payload:
              schema: { type: string, const: cache-confused }
            X-Chaos-Cache-Mode:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  mode: { type: string }
                  directives_sent:
                    type: array
                    items: { type: string }

  /drip:
    get:
      operationId: drip
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/latency/drip/
      tags: [latency]
      summary: Stream the body slowly in chunks
      description: |
        Headers arrive immediately, but the body trickles in chunks of `chunk` bytes with `interval` ms between each chunk. Unlike `/slow`, the connection stays open and data dribbles in over time.

        Total stream time is `(bytes / chunk) * interval` ms. Keep this under ~25 seconds to stay inside the platform's wall-clock limit.
      parameters:
        - name: bytes
          in: query
          description: Total bytes to stream.
          schema:
            type: integer
            minimum: 1
            maximum: 100000
            default: 1000
        - name: interval
          in: query
          description: Milliseconds between chunks.
          schema:
            type: integer
            minimum: 10
            maximum: 5000
            default: 200
        - name: chunk
          in: query
          description: Bytes per chunk.
          schema:
            type: integer
            minimum: 1
            maximum: 4096
            default: 100
      responses:
        '200':
          description: Streamed body of the configured size.
          headers:
            X-Chaos-Drip-Total-Bytes:
              schema: { type: string }
            X-Chaos-Drip-Interval-Ms:
              schema: { type: string }
            X-Chaos-Drip-Chunk-Bytes:
              schema: { type: string }
          content:
            text/plain:
              schema: { type: string }

  /rate-limit:
    get:
      operationId: rateLimit
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/rate-limiting/rate-limit/
      tags: [rate-limiting]
      summary: Always return 429 with Retry-After
      description: |
        Always returns 429 Too Many Requests. When `lie=true`, the `Retry-After` header is set to `1` regardless of the requested value; the body still reports the real requested value so clients can detect the lie.
      parameters:
        - name: retry_after
          in: query
          description: Retry-After value in seconds.
          schema:
            type: integer
            minimum: 1
            maximum: 3600
            default: 60
        - name: lie
          in: query
          description: If true, send Retry-After=1 but report the real value in the body.
          schema:
            type: boolean
            default: false
      responses:
        '429':
          description: Rate limited.
          headers:
            Retry-After:
              schema: { type: string }
            X-RateLimit-Limit:
              schema: { type: string }
            X-RateLimit-Remaining:
              schema: { type: string }
            X-RateLimit-Reset:
              schema: { type: string }
            X-Chaos-Rate-Limit-Lied:
              schema: { type: string, enum: ['true', 'false'] }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  retry_after_seconds: { type: integer }
                  header_retry_after: { type: integer }
                  lied: { type: boolean }

  /flaky:
    get:
      operationId: flaky
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/fault-injection/flaky/
      tags: [fault-injection]
      summary: Random failure at configurable probability
      description: |
        Returns the configured failure status with probability `p`, otherwise 200. The `X-Chaos-Flaky-Failed` header indicates the outcome so monitoring clients can distinguish a chaos failure from a real one.

        Set `p=0` to verify retry wiring without triggering failures. Set `p=1` to test the full failure path on every request.
      parameters:
        - name: p
          in: query
          description: Failure probability.
          schema:
            type: number
            minimum: 0
            maximum: 1
            default: 0.5
        - name: status
          in: query
          description: Status code returned on failure.
          schema:
            type: integer
            minimum: 100
            maximum: 599
            default: 500
      responses:
        default:
          description: Either 200 or the configured failure status.
          headers:
            X-Chaos-Flaky-Failed:
              schema: { type: string, enum: ['true', 'false'] }
            X-Chaos-Flaky-Probability:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  failed: { type: boolean }
                  probability: { type: number }
                  status: { type: integer }

  /random:
    get:
      operationId: random
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/fault-injection/random/
      tags: [fault-injection]
      summary: Dispatch to a random chaos endpoint
      description: |
        Selects one chaos endpoint at random and dispatches the request to its handler. The selected path (with any query parameters used) is reported in the `X-Chaos-Random-From` response header so failing clients can pin down which scenario tripped them.

        Streaming endpoints (`/slow`, `/drip`, `/partial-stream`) are excluded so each pick returns promptly. Useful for soak-testing retry, timeout, and circuit-breaker logic across the whole catalog from a single URL.
      responses:
        default:
          description: Whatever the selected endpoint returned.
          headers:
            X-Chaos-Random-From:
              description: The path (with query parameters, if any) that was selected.
              schema: { type: string }

  /echo:
    parameters: []
    get:
      operationId: echoGet
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/echo/
      tags: [inspection]
      summary: Reflect the request as JSON
      description: Returns the request method, URL, path, query parameters, headers, and body as JSON. If the body is valid JSON it is parsed and included as a nested object; otherwise it is included as a raw string.
      responses:
        '200':
          $ref: '#/components/responses/EchoResponse'
    post:
      operationId: echoPost
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/echo/
      tags: [inspection]
      summary: Reflect the request as JSON (POST)
      description: Same as GET; the body is included in the response.
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
          text/plain:
            schema: { type: string }
      responses:
        '200':
          $ref: '#/components/responses/EchoResponse'
    put:
      operationId: echoPut
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/echo/
      tags: [inspection]
      summary: Reflect the request as JSON (PUT)
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
      responses:
        '200':
          $ref: '#/components/responses/EchoResponse'
    patch:
      operationId: echoPatch
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/echo/
      tags: [inspection]
      summary: Reflect the request as JSON (PATCH)
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
      responses:
        '200':
          $ref: '#/components/responses/EchoResponse'
    delete:
      operationId: echoDelete
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/echo/
      tags: [inspection]
      summary: Reflect the request as JSON (DELETE)
      responses:
        '200':
          $ref: '#/components/responses/EchoResponse'

  /auth-required:
    get:
      operationId: authRequired
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/auth/auth-required/
      tags: [auth]
      summary: 401 with WWW-Authenticate challenge
      description: |
        Returns 401 with a `WWW-Authenticate` challenge when no `Authorization` header is present. If any `Authorization` header is sent — regardless of its value — returns 200. This lets clients exercise the full challenge-response cycle without real credentials.
      parameters:
        - name: scheme
          in: query
          description: Challenge scheme. The `none` value returns 401 with no `WWW-Authenticate` header.
          schema:
            type: string
            enum: [basic, bearer, digest, none]
            default: basic
        - name: realm
          in: query
          description: Realm value for the challenge. Sanitized to safe characters and capped at 64 chars.
          schema:
            type: string
            default: catastrophic.io
        - name: Authorization
          in: header
          required: false
          description: Send any value to get a 200 instead of a 401.
          schema:
            type: string
      responses:
        '200':
          description: Returned when any Authorization header is present.
          headers:
            X-Chaos-Auth-Accepted:
              schema: { type: string, const: 'true' }
            X-Chaos-Auth-Scheme:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /auth-required }
                  authenticated: { type: boolean, const: true }
                  scheme: { type: string }
                  received_authorization: { type: string }
        '401':
          description: Returned when no Authorization header is present.
          headers:
            WWW-Authenticate:
              description: Present unless scheme=none.
              schema: { type: string }
            X-Chaos-Auth-Scheme:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /auth-required }
                  authenticated: { type: boolean, const: false }
                  scheme: { type: string }
                  challenge:
                    oneOf:
                      - { type: string }
                      - { type: 'null' }
                  hint: { type: string }

  /cors-broken:
    parameters:
      - name: mode
        in: query
        description: Brokenness mode.
        schema:
          type: string
          enum: [missing, wildcard-credentials, wrong-origin, preflight-deny]
          default: missing
    get:
      operationId: corsBrokenGet
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/cors/cors-broken/
      tags: [cors]
      summary: Send broken or missing CORS headers
      description: |
        Returns 200 with CORS headers (or none) according to the chosen mode. Browsers will block cross-origin reads from all modes — that is the point. Direct (non-browser) clients see the headers but do not enforce CORS.
      responses:
        '200':
          description: The actual response. CORS headers vary by mode.
          headers:
            Access-Control-Allow-Origin:
              description: Present in wildcard-credentials, wrong-origin, and preflight-deny modes.
              schema: { type: string }
            Access-Control-Allow-Credentials:
              description: Only present in wildcard-credentials mode.
              schema: { type: string }
            Access-Control-Allow-Methods:
              schema: { type: string }
            X-Chaos-Cors-Mode:
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /cors-broken }
                  mode: { type: string }
                  request_origin: { type: string }
                  cors_headers_sent:
                    type: array
                    items: { type: string }
    options:
      operationId: corsBrokenPreflight
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/cors/cors-broken/
      tags: [cors]
      summary: Handle CORS preflight
      description: |
        Returns 204 in most modes. In `preflight-deny` mode, returns 403 and the actual request is blocked by the browser before it ever fires.
      responses:
        '204':
          description: Preflight succeeded (CORS headers vary by mode).
          headers:
            Access-Control-Allow-Origin:
              schema: { type: string }
            Access-Control-Allow-Methods:
              schema: { type: string }
            X-Chaos-Cors-Mode:
              schema: { type: string }
        '403':
          description: Preflight denied (only in preflight-deny mode).
          headers:
            X-Chaos-Cors-Mode:
              schema: { type: string }

  /semantic-drift:
    get:
      operationId: semanticDrift
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/agent/semantic-drift/
      tags: [agent]
      summary: Structurally-valid JSON whose values lie about reality
      description: |
        Returns a JSON response matching the shape of a well-known discovery
        schema (openid-configuration, webfinger, jwks, or agent-card), where
        every URL points at a non-existent resource and every claimed
        capability is fabricated. Passes structural validators; fails any
        consumer that resolves the values.
      parameters:
        - name: schema
          in: query
          description: Which schema shape to return.
          schema:
            type: string
            enum: [openid-configuration, oauth-authorization-server, webfinger, jwks, host-meta, agent-card]
            default: openid-configuration
        - name: ai
          in: query
          description: |
            If `true`, the response body is generated fresh on every call by an edge LLM (Llama-3.1-8b) — same shape, different invented values each time. If `false` (default), the response is the deterministic hardcoded drift.

            AI mode falls back to the deterministic drift automatically when the inference backend is unavailable, the shared daily budget is exhausted (`ai_state >= 2`), the model errors, or returns unparseable JSON. The `X-Chaos-Ai-Source` response header indicates which path served the request (`ai` or `fallback`); on fallback, `X-Chaos-Ai-Fallback-Reason` explains why.

            AI calls add 100ms–10s of latency; first-call-after-cold-load is the slow case.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Structurally-valid drifted response. Source indicated by X-Chaos-Ai-Source header.
          headers:
            X-Chaos-Schema:
              schema: { type: string }
            X-Chaos-Drift:
              schema: { type: string, const: semantic }
            X-Chaos-Hint:
              schema: { type: string }
            X-Chaos-Ai-Source:
              description: Present only when `ai=true`. Either `ai` (model-generated) or `fallback` (deterministic).
              schema: { type: string, enum: [ai, fallback] }
            X-Chaos-Ai-Model:
              description: Present only on successful AI generation.
              schema: { type: string }
            X-Chaos-Ai-Latency-Ms:
              description: AI invocation duration. Present when AI was attempted.
              schema: { type: string }
            X-Chaos-Ai-Fallback-Reason:
              description: Present only on fallback. Values include `ai-binding-missing`, `budget-exhausted`, `ai-error`, `invalid-json`, `no-prompt-for-schema`.
              schema: { type: string }
          content:
            application/json:
              schema: { type: object }
            application/jrd+json:
              schema: { type: object }
            application/jwk-set+json:
              schema: { type: object }
        '400':
          description: Returned when the requested schema is not one of the supported values.
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  error: { type: string }
                  requested: { type: string }
                  valid:
                    type: array
                    items: { type: string }

  /retry-mismatch:
    get:
      operationId: retryMismatch
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/agent/retry-mismatch/
      tags: [agent]
      summary: Different response on retry within 60s
      description: |
        Token-keyed retry chaos. The first call for a given `id` returns version 1 of the response; any subsequent call for the same `id` within a 60-second window returns version 2 — same response shape, different data. Tests whether retry logic silently picks up changed data without noticing the shift.

        State is per-edge (in-memory cache) and expires after 60 seconds.
        Use any string as `id` — uuids, request IDs, anything stable across the retry.
      parameters:
        - name: id
          in: query
          required: true
          description: Client-supplied identity token (1–128 chars). State is tracked per id.
          schema:
            type: string
            minLength: 1
            maxLength: 128
      responses:
        '200':
          description: |
            First call returns version-1 data; subsequent calls within 60s for the same id return version-2 data. Check `X-Chaos-Retry-Replayed` (`true`/`false`) and `data.version` (1 or 2) to disambiguate.
          headers:
            X-Chaos-Retry-Attempt:
              schema: { type: string, enum: ['1', '2+'] }
            X-Chaos-Retry-Replayed:
              schema: { type: string, enum: ['true', 'false'] }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /retry-mismatch }
                  id: { type: string }
                  attempt: { type: string }
                  replayed: { type: boolean }
                  data:
                    type: object
                    properties:
                      items: { type: array, items: { type: string } }
                      version: { type: integer, enum: [1, 2] }
                      generated_at: { type: string, format: date-time }
        '400':
          description: Returned when `id` is missing or longer than 128 chars.
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  error: { type: string }
                  hint: { type: string }

  /partial-stream:
    get:
      operationId: partialStream
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/delivery/partial-stream/
      tags: [agent]
      summary: Cleanly close the stream mid-JSON
      description: |
        Sends `Content-Type: application/json` and a JSON fragment, then closes the stream after the configured byte count. No `Content-Length` is sent — the HTTP layer reports success and the client sees a well-formed-but-incomplete body. Tests partial-parse recovery and timeout handling.
      parameters:
        - name: bytes
          in: query
          description: How many bytes of the JSON fragment to send before closing.
          schema:
            type: integer
            minimum: 1
            maximum: 16384
            default: 128
      responses:
        '200':
          description: Incomplete JSON body. HTTP layer is healthy; the payload is not.
          headers:
            X-Chaos-Partial-Stream-Bytes:
              schema: { type: string }
            X-Chaos-Hint:
              schema: { type: string }
          content:
            application/json:
              schema: { type: string, description: Not actually valid JSON — body is cut off mid-object. }

  /.well-known/chaos:
    get:
      operationId: wellKnownChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/discovery/well-known-chaos/
      tags: [discovery]
      summary: Mutated .well-known discovery document
      description: |
        Parametric chaos for well-known discovery schemas. Schema (openid-configuration, webfinger, jwks, agent-card) × mode (semantic, missing-fields, wrong-types). Response is served with the correct Content-Type per schema.
      parameters:
        - name: schema
          in: query
          description: Which schema shape to base the response on.
          schema:
            type: string
            enum: [openid-configuration, oauth-authorization-server, webfinger, jwks, host-meta, agent-card]
            default: openid-configuration
        - name: mode
          in: query
          description: |
            How to misbehave.
            `semantic` keeps the shape valid but URLs unreachable;
            `missing-fields` strips required fields;
            `wrong-types` shape-shifts a field type.
          schema:
            type: string
            enum: [semantic, missing-fields, wrong-types]
            default: semantic
        - name: ai
          in: query
          description: |
            If `true`, the source data is generated fresh on every call by an edge LLM (Llama-3.1-8b), then the requested `mode` transformation is applied on top. So `ai=true&mode=missing-fields` returns an AI-invented agent or issuer with required fields then stripped.

            Falls back to the deterministic source when the inference backend is missing, the shared daily budget is exhausted, the model errors, or returns unparseable JSON. The `X-Chaos-Ai-Source` response header indicates which path served; on fallback, `X-Chaos-Ai-Fallback-Reason` explains why.

            Adds 100ms–10s of latency per call.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Drifted discovery document.
          headers:
            X-Chaos-Schema:
              schema: { type: string }
            X-Chaos-Drift:
              schema: { type: string }
            X-Chaos-Ai-Source:
              description: Present only when `ai=true`. Either `ai` or `fallback`.
              schema: { type: string, enum: [ai, fallback] }
            X-Chaos-Ai-Model:
              description: Present only on successful AI generation.
              schema: { type: string }
            X-Chaos-Ai-Latency-Ms:
              description: AI invocation duration. Present when AI was attempted.
              schema: { type: string }
            X-Chaos-Ai-Fallback-Reason:
              description: Present only on fallback. Codes include `ai-binding-missing`, `budget-exhausted`, `ai-error`, `invalid-json`, `no-prompt-for-schema`.
              schema: { type: string }
          content:
            application/json:
              schema: { type: object }
            application/jrd+json:
              schema: { type: object }
            application/jwk-set+json:
              schema: { type: object }
        '400':
          description: Returned when schema or mode is not one of the supported values.
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  error: { type: string }
                  requested: { type: string }
                  valid:
                    type: array
                    items: { type: string }

  # The conflicting-discovery sibling group (/.well-known/openid-configuration,
  # /.well-known/oauth-authorization-server, /.well-known/agent-card.json) moved
  # to bots.catastrophic.io. See https://bots.catastrophic.io/openapi.yaml.

  /cookie:
    get:
      operationId: cookieChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/headers-and-caching/cookie/
      tags: [headers]
      summary: Malformed Set-Cookie headers
      description: |
        Returns one or more Set-Cookie headers crafted to be silently dropped, contradict themselves, or exceed practical browser limits. Tests whether your code assumes Set-Cookie succeeded or actually verifies session establishment on the next request.
      parameters:
        - name: mode
          in: query
          description: |
            `samesite-none-no-secure` (default) — SameSite=None without Secure; modern browsers reject silently.
            `expiry-contradiction` — past Expires with far-future Max-Age; RFC 6265 says Max-Age wins but not every client knows that.
            `multiple-same-name` — three Set-Cookie headers with the same name; browsers usually keep the last.
            `bad-path` — cookie scoped to a path the response was not served from; clients silently drop it.
            `huge-value` — ~8KB value, over the 4KB practical limit; browsers and proxies drop the entire header.
            `host-prefix-violation` — `__Host-` cookie with Domain= set, violating RFC 6265bis §4.1.3.2; browsers reject silently.
            `secure-prefix-violation` — `__Secure-` cookie without the Secure attribute, violating RFC 6265bis §4.1.3.1; browsers reject silently.
            `comma-folded` — two cookies joined in one Set-Cookie value with a comma; RFC 6265 §4.1 forbids combining Set-Cookie headers this way — intermediaries that fold anyway break both cookies.
            `domain-overreach` — Domain=catastrophic.io from chaos.catastrophic.io; RFC 6265 §5.3.6 requires host-match.
            `partitioned-no-secure` — Partitioned (CHIPS) without Secure; CHIPS spec requires Secure; browsers with CHIPS support reject silently.
          schema:
            type: string
            enum:
              - samesite-none-no-secure
              - expiry-contradiction
              - multiple-same-name
              - bad-path
              - huge-value
              - host-prefix-violation
              - secure-prefix-violation
              - comma-folded
              - domain-overreach
              - partitioned-no-secure
            default: samesite-none-no-secure
      responses:
        '200':
          description: Always 200.
          headers:
            X-Chaos-Cookie-Mode:
              schema: { type: string }
            Set-Cookie:
              description: One or more cookie strings, varying by mode.
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /cookie }
                  mode: { type: string }
                  note: { type: string }
                  set_cookie_lines:
                    type: array
                    items: { type: string }

  /etag:
    get:
      operationId: etagChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/headers-and-caching/etag/
      tags: [headers]
      summary: ETag and Last-Modified headers that break conditional requests
      description: |
        Returns ETag and Last-Modified headers that violate RFC 9110 in specific ways. Tests caches, CDNs, and clients that rely on validators.
      parameters:
        - name: mode
          in: query
          description: |
            `random-etag` (default) — different ETag every call for identical content.
            `stable-etag-mutated-body` — same ETag, body changes.
            `weak-strong-conflict` — both strong and weak ETag headers.
            `unquoted` — ETag without required quotes.
            `future-last-modified` — Last-Modified in 2099.
          schema:
            type: string
            enum: [random-etag, stable-etag-mutated-body, weak-strong-conflict, unquoted, future-last-modified]
            default: random-etag
      responses:
        '200':
          description: Always 200.
          headers:
            X-Chaos-Etag-Mode:
              schema: { type: string }
            ETag:
              schema: { type: string }
            Last-Modified:
              description: Present only in `future-last-modified` mode.
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /etag-chaos }
                  mode: { type: string }
                  note: { type: string }
                  body: { type: object }

  /pagination:
    get:
      operationId: paginationChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/pagination/pagination/
      tags: [pagination]
      summary: Paginated collections whose metadata lies
      description: |
        Returns paginated collections where `total_items` lies, pages overlap, `Link` headers loop back to page 1, or returned size drifts from requested size. Walk pages 1–5 with the same mode to see the chaos accumulate.
      parameters:
        - name: mode
          in: query
          description: |
            `lying-total` (default) — claims 10 pages, only pages 1–3 return items.
            `overlapping-pages` — consecutive pages share 5 items.
            `link-loop` — Link rel=next points back to page 1.
            `size-drift` — returned size does not match requested size.
          schema:
            type: string
            enum: [lying-total, overlapping-pages, link-loop, size-drift]
            default: lying-total
        - name: page
          in: query
          description: Page number, 1-indexed.
          schema:
            type: integer
            minimum: 1
            maximum: 20
            default: 1
        - name: size
          in: query
          description: Requested items per page. Some modes ignore this.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
      responses:
        '200':
          description: Always 200.
          headers:
            X-Chaos-Pagination-Mode:
              schema: { type: string }
            Link:
              description: Present in `link-loop` mode. Contains rel="next" and rel="last" links.
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /pagination-chaos }
                  mode: { type: string }
                  note: { type: string }
                  page: { type: integer }
                  requested_size: { type: integer }
                  returned_size: { type: integer }
                  total_items: { type: integer }
                  total_pages: { type: integer }
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: integer }
                        name: { type: string }

  /compression:
    get:
      operationId: compressionChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/headers-and-caching/compression/
      tags: [headers]
      summary: Lying Content-Encoding or unannounced compression
      description: |
        Returns responses where `Content-Encoding` lies about the body's actual compression state, or where the body is gzip-compressed but the header doesn't say so. Tests how clients, caches, and proxies handle disagreement between encoding metadata and bytes.

        All modes set `Cache-Control: no-transform` so the Cloudflare edge does not transparently re-encode the response. Note: clients that do NOT send `Accept-Encoding: gzip` will have the lying `Content-Encoding` header stripped by content negotiation at the edge.
      parameters:
        - name: mode
          in: query
          description: |
            `gzip-header-plain-body` (default) — Content-Encoding: gzip on a plain JSON body.
            `gzip-body-no-header` — body is actually gzipped; no Content-Encoding header.
            `wrong-encoding-name` — Content-Encoding: superzip, an unknown encoding.
            `chained-claim` — Content-Encoding: gzip, br on a plain body.
          schema:
            type: string
            enum: [gzip-header-plain-body, gzip-body-no-header, wrong-encoding-name, chained-claim]
            default: gzip-header-plain-body
      responses:
        '200':
          description: Always 200.
          headers:
            X-Chaos-Compression-Mode:
              schema: { type: string }
            Content-Encoding:
              description: Present (and lying) in all modes except `gzip-body-no-header`.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: 'no-transform' }
          content:
            application/json:
              schema:
                type: object
                description: Plain JSON body, except in `gzip-body-no-header` mode where the body is real gzip bytes.

  /auth:
    get:
      operationId: authChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/auth/auth/
      tags: [auth]
      summary: Malformed WWW-Authenticate challenges
      description: |
        Returns 401 with `WWW-Authenticate` challenges that contradict each other, omit required attributes, violate quoting rules, or use undefined schemes. Tests how auth clients cope when the challenge itself is broken.
      parameters:
        - name: mode
          in: query
          description: |
            `multiple-challenges` (default) — three contradictory challenges (Basic + Bearer + Digest) with different realms.
            `malformed-digest` — Digest scheme missing required nonce, qop, opaque.
            `unquoted-realm` — realm value without RFC 9110 quoted-string wrapping.
            `nonexistent-scheme` — challenge uses an undefined auth scheme name.
          schema:
            type: string
            enum: [multiple-challenges, malformed-digest, unquoted-realm, nonexistent-scheme]
            default: multiple-challenges
      responses:
        '401':
          description: Always 401.
          headers:
            X-Chaos-Auth-Mode:
              schema: { type: string }
            WWW-Authenticate:
              description: One or more challenge strings, varying by mode.
              schema: { type: string }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string, const: /auth-chaos }
                  mode: { type: string }
                  note: { type: string }
                  challenges:
                    type: array
                    items: { type: string }

  /method:
    get:
      operationId: methodChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/method-handling/method/
      tags: [method]
      summary: Servers that lie about which methods they support
      description: |
        An endpoint that claims via Allow or OPTIONS to support GET, POST, PUT, PATCH, DELETE, OPTIONS but actually handles only GET. The chaos surfaces when non-GET methods are sent. GET always returns 200 with metadata describing the mode so the try-it experience still shows context without firing the non-GET methods.
      parameters:
        - name: mode
          in: query
          description: |
            `lying-allow` (default) — Allow header claims six methods; only GET works.
            `silent-on-wrong-method` — non-GET returns 200 with empty body.
            `405-without-allow` — 405 with no Allow header (RFC 9110 violation).
            `options-mismatch` — OPTIONS advertises six methods; only GET actually works.
          schema:
            type: string
            enum: [lying-allow, silent-on-wrong-method, 405-without-allow, options-mismatch]
            default: lying-allow
      responses:
        '200':
          description: GET returns 200 with metadata.
          headers:
            X-Chaos-Method-Mode:
              schema: { type: string }
            X-Chaos-Method-Claimed:
              schema: { type: string }
            X-Chaos-Method-Actual:
              schema: { type: string }
            Allow:
              description: Present in `lying-allow` and `options-mismatch` modes.
              schema: { type: string }
          content:
            application/json:
              schema: { type: object }
        '204':
          description: OPTIONS responses use 204 No Content.
        '405':
          description: Non-GET methods in modes where they return Method Not Allowed.
          headers:
            X-Chaos-Method-Mode:
              schema: { type: string }
            Allow:
              description: Present in `lying-allow` and `options-mismatch` modes; absent (RFC violation) in `405-without-allow`.
              schema: { type: string }
          content:
            application/json:
              schema: { type: object }
  /image:
    get:
      operationId: imageChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/image/
      tags: [payload]
      summary: Image responses whose metadata lies about the bytes
      description: |
        Returns an image-shaped body where the declared MIME type, the magic bytes, the embedded IHDR dimensions, or the IDAT chunk framing disagrees with the actual content. Tests how image-processing pipelines handle the case where wire-level metadata and pixel data tell different stories. The `X-Chaos-Image-Note` header explains the lie in plain text. `Cache-Control: no-transform` is set so the Cloudflare edge does not re-encode or content-sniff the response and mask the chaos.
      parameters:
        - name: mode
          in: query
          description: |
            `mime-mismatch` (default) — real PNG bytes served with `Content-Type: image/jpeg`.
            `magic-byte-lie` — PNG signature prefix on a JPEG body; magic-number sniffers say PNG, decoders fail past byte 8.
            `wrong-dimensions` — valid 1x1 PNG whose IHDR claims 4000x4000 (CRC is correct). `X-Chaos-Image-Claimed-Dimensions` and `X-Chaos-Image-Actual-Dimensions` headers carry the contradiction.
            `truncated-png` — IHDR + an IDAT chunk that declares 100 bytes but ships ~9, with no IEND.
          schema:
            type: string
            enum: [mime-mismatch, magic-byte-lie, wrong-dimensions, truncated-png]
            default: mime-mismatch
      responses:
        '200':
          description: Image-shaped body whose metadata lies. Body Content-Type varies by mode (image/png or image/jpeg).
          headers:
            X-Chaos-Image-Mode:
              schema: { type: string }
            X-Chaos-Image-Note:
              description: Plain-text explanation of the lie under test.
              schema: { type: string }
            X-Chaos-Image-Claimed-Dimensions:
              description: Present only in `wrong-dimensions` mode.
              schema: { type: string }
            X-Chaos-Image-Actual-Dimensions:
              description: Present only in `wrong-dimensions` mode.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            image/png:
              schema: { type: string, format: binary }
            image/jpeg:
              schema: { type: string, format: binary }

  /jsonl:
    get:
      operationId: jsonlChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/jsonl/
      tags: [payload]
      summary: JSON Lines streams with stream-level violations
      description: |
        Returns a JSON Lines / NDJSON stream with a deliberate stream-level flaw. Targets ML pipelines, log aggregators, and AI chat exports — anywhere line-delimited JSON is consumed. `Cache-Control: no-transform` is set so the Cloudflare edge does not rewrite the body. `X-Chaos-Jsonl-Note` explains the flaw in plain text.
      parameters:
        - name: mode
          in: query
          description: |
            `schema-drift` (default) — records 1–3 use one shape, records 4–5 use a different shape; schema-aware pipelines reject or null-coerce.
            `blank-lines` — empty lines interleaved between records; strict per-line parsers raise on the empty string.
            `partial-final-line` — final record truncated mid-object with no trailing newline; streaming parsers raise on EOF mid-record.
            `bom-start` — UTF-8 BOM at start of stream; strict per-line parsers raise on the first record only.
          schema:
            type: string
            enum: [schema-drift, blank-lines, partial-final-line, bom-start]
            default: schema-drift
      responses:
        '200':
          description: NDJSON stream with the chosen flaw.
          headers:
            X-Chaos-Jsonl-Mode:
              schema: { type: string }
            X-Chaos-Jsonl-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            application/x-ndjson:
              schema: { type: string }

  /svg:
    get:
      operationId: svgChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/svg/
      tags: [payload]
      summary: SVG documents with structural and rendering chaos
      description: |
        Returns SVG with a deliberate flaw. SVG is an XML+rendering hybrid so chaos modes span both worlds: malformed XML, external-resource references, recursive <use> chains, non-conformant namespaces. `Cache-Control: no-transform` is set so the Cloudflare edge does not rewrite the body. `X-Chaos-Svg-Note` explains the flaw in plain text.
      parameters:
        - name: mode
          in: query
          description: |
            `mismatched-tags` (default) — `<g>` closes before its `<rect>` child closes; XML parsers reject, lenient renderers recover.
            `external-image-ref` — embedded `<image href="http://nonexistent.invalid/...">`; processors that resolve external refs hang or fail.
            `circular-use` — two `<use>` elements reference each other; non-conformant renderers may recurse.
            `bad-namespace` — xmlns declares a non-SVG namespace; namespace-aware processors refuse to render.
          schema:
            type: string
            enum: [mismatched-tags, external-image-ref, circular-use, bad-namespace]
            default: mismatched-tags
      responses:
        '200':
          description: SVG body with the chosen flaw.
          headers:
            X-Chaos-Svg-Mode:
              schema: { type: string }
            X-Chaos-Svg-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            image/svg+xml:
              schema: { type: string }

  /csv:
    get:
      operationId: csvChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/csv/
      tags: [payload]
      summary: CSV with RFC 4180 corner-case violations
      description: |
        Returns CSV with a deliberate RFC 4180 violation. Targets data pipelines, BI tools, and ML preprocessing — anywhere CSV is the interchange format and parsers vary in strictness. `Cache-Control: no-transform` is set so the Cloudflare edge does not rewrite the body. `X-Chaos-Csv-Note` explains the flaw in plain text.
      parameters:
        - name: mode
          in: query
          description: |
            `unquoted-commas` (default) — a field contains a comma but isn't quoted; the row appears to have 4 columns instead of 3.
            `embedded-newlines` — a row contains a literal CRLF inside an unquoted field; naive line-splitters break mid-row.
            `bom-mismatch` — UTF-8 BOM at the start of the stream; strict parsers include the BOM bytes in cell[0] of the header row.
            `ragged-columns` — header declares 3 columns but rows carry 2 and 4; strict parsers raise, lenient ones pad or misalign silently.
          schema:
            type: string
            enum: [unquoted-commas, embedded-newlines, bom-mismatch, ragged-columns]
            default: unquoted-commas
      responses:
        '200':
          description: CSV body with the chosen RFC 4180 violation.
          headers:
            X-Chaos-Csv-Mode:
              schema: { type: string }
            X-Chaos-Csv-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            text/csv:
              schema: { type: string }

  /xml:
    get:
      operationId: xmlChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/xml/
      tags: [payload]
      summary: XML documents with parser-attack or well-formedness flaws
      description: |
        Returns XML with a deliberate structural flaw. Targets SOAP, B2B, RSS/sitemap, and config-file ingestion pipelines. Default mode is visible misnesting on a bare GET. `Cache-Control: no-transform` is set so the Cloudflare edge does not rewrite the body. `X-Chaos-Xml-Note` explains the flaw in plain text.
      parameters:
        - name: mode
          in: query
          description: |
            `mismatched-tags` (default) — `<alpha><beta></alpha></beta>`; every conformant parser rejects.
            `billion-laughs` — three levels of self-similar entity expansion (~3KB expanded); hardened parsers reject, vulnerable ones expand.
            `xxe-external` — external entity referencing nonexistent.invalid (RFC 6761 reserved); parsers with external-entity resolution hang or fail.
            `encoding-mismatch` — declaration claims utf-8 but body bytes are CP1252.
          schema:
            type: string
            enum: [mismatched-tags, billion-laughs, xxe-external, encoding-mismatch]
            default: mismatched-tags
      responses:
        '200':
          description: XML body with the chosen flaw.
          headers:
            X-Chaos-Xml-Mode:
              schema: { type: string }
            X-Chaos-Xml-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            application/xml:
              schema: { type: string }

  /zip:
    get:
      operationId: zipChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/zip/
      tags: [payload]
      summary: ZIP archives with one deliberate structural lie
      description: |
        Returns a single-entry STORED ZIP archive where one structural field disagrees with the bytes. Targets archive extractors, package managers, document-format parsers (DOCX/XLSX/PPTX are ZIPs under the hood), and security scanners. `Cache-Control: no-transform` is set so the Cloudflare edge does not rewrite the body. `X-Chaos-Zip-Note` explains the flaw in plain text. The famously dangerous zip-slip (path-traversal payload) and zip-bomb (resource-exhaustion payload) shapes are deliberately not served.
      parameters:
        - name: mode
          in: query
          description: |
            `bad-crc` (default) — LFH and CDH declare CRC32 0xDEADBEEF; the body's real CRC differs. Strict extractors reject; lenient ones extract anyway.
            `central-dir-mismatch` — local file header names `alpha.txt`, central directory names `beta.txt` for the same offset and body. CD-driven tools and LFH-scanning tools disagree about archive contents.
            `zip64-lie` — LFH/CDH sizes use the 0xFFFFFFFF ZIP64 sentinel; the ZIP64 Extended Information extra field claims 8 GiB. Actual body is ~40 bytes.
            `truncated` — LFH and body intact; the central directory is cut off mid-record and no EOCD is emitted. EOCD-first parsers fail; LFH-scanning parsers can still recover the file.
          schema:
            type: string
            enum: [bad-crc, central-dir-mismatch, zip64-lie, truncated]
            default: bad-crc
      responses:
        '200':
          description: ZIP-shaped body whose structure contains one deliberate lie.
          headers:
            X-Chaos-Zip-Mode:
              schema: { type: string }
            X-Chaos-Zip-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            X-Chaos-Zip-Claimed-Size:
              description: Present only in `zip64-lie` mode.
              schema: { type: string }
            X-Chaos-Zip-Actual-Size:
              description: Present only in `zip64-lie` mode.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            application/zip:
              schema: { type: string, format: binary }

  /ooxml:
    get:
      operationId: ooxmlChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/ooxml/
      tags: [payload]
      summary: Office Open XML (DOCX/XLSX/PPTX) packages with one structural lie
      description: |
        Returns a DOCX, XLSX, or PPTX container where one structural part of the OOXML package disagrees with the rest. Targets document-processing pipelines, e-discovery, document-conversion services, and OOXML parsers in general. Reuses the STORED ZIP framing from `/zip-chaos` — the container itself is always a valid ZIP. `Cache-Control: no-transform` is set so the Cloudflare edge does not rewrite the body. `X-Chaos-Ooxml-Note` explains the flaw in plain text. Macro-enabled containers, encrypted OOXML, and embedded OLE objects are deliberately not served.
      parameters:
        - name: format
          in: query
          description: |
            `docx` (default) — Word document.
            `xlsx` — Excel workbook.
            `pptx` — PowerPoint presentation. Includes the full slide-master / slide-layout / theme triangle required for conformant readers.
          schema:
            type: string
            enum: [docx, xlsx, pptx]
            default: docx
        - name: mode
          in: query
          description: |
            `wrong-content-type` (default) — `[Content_Types].xml` declares the main part with another OOXML format's content type. Type-trusting parsers fail; content-sniffing parsers succeed.
            `missing-part` — root `.rels` references the main document part; the ZIP has the part at a typo'd path. Conformant readers report corruption.
            `dangling-relationship` — main part's `.rels` declares an image relationship to `media/image1.png` that isn't in the package. Renderers vary (placeholder vs hard fail).
            `format-confusion` — `[Content_Types].xml` declares main parts for all three OOXML formats; only the current format's tree is present.
          schema:
            type: string
            enum: [wrong-content-type, missing-part, dangling-relationship, format-confusion]
            default: wrong-content-type
      responses:
        '200':
          description: OOXML package whose structure contains one deliberate lie. Body Content-Type varies by `format`.
          headers:
            X-Chaos-Ooxml-Format:
              schema: { type: string }
            X-Chaos-Ooxml-Mode:
              schema: { type: string }
            X-Chaos-Ooxml-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Content-Disposition:
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            application/vnd.openxmlformats-officedocument.wordprocessingml.document:
              schema: { type: string, format: binary }
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema: { type: string, format: binary }
            application/vnd.openxmlformats-officedocument.presentationml.presentation:
              schema: { type: string, format: binary }

  /pdf:
    get:
      operationId: pdfChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/pdf/
      tags: [payload]
      summary: PDF documents with one deliberate structural lie
      description: |
        Returns a PDF where one element of the object graph deliberately lies. Targets document-ingestion pipelines, security scanners, and PDF parsers. Built at request time using traditional cross-reference table format (PDF 1.4) — all chaos lives in text fields. `Cache-Control: no-transform` prevents the Cloudflare edge from rewriting the body. `X-Chaos-Pdf-Note` explains the flaw in plain text. Recursive page trees, working-exploit JavaScript, and font/glyph corruption are deliberately not served.
      parameters:
        - name: mode
          in: query
          description: |
            `bad-xref` (default) — xref byte offsets shifted +100 bytes; object lookup by offset fails. Strict readers fail to resolve any object; lenient readers (most browsers, modern Acrobat) may recover by scanning for `obj` markers.
            `page-count-lie` — /Count declares 100 pages; only one page object exists in the file.
            `encrypted-ghost` — trailer /Encrypt references object 9 which is not defined anywhere in the file.
            `javascript-action` — Catalog OpenAction executes app.alert("pdf-chaos") on open; JS-enabled readers pop an alert, JS-disabled readers open silently, AV scanners may flag any JS presence.
          schema:
            type: string
            enum: [bad-xref, page-count-lie, encrypted-ghost, javascript-action]
            default: bad-xref
      responses:
        '200':
          description: PDF-shaped body whose structure contains one deliberate lie.
          headers:
            X-Chaos-Pdf-Mode:
              schema: { type: string }
            X-Chaos-Pdf-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            application/pdf:
              schema: { type: string, format: binary }

  /html:
    get:
      operationId: htmlChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/html/
      tags: [payload]
      summary: HTML documents with deliberate structural flaws
      description: |
        Returns an HTML document with a deliberate structural flaw. Useful for browser-parser testing, scraper robustness, and ML preprocessing of HTML. Default mode produces visible misnesting on a bare GET. `Cache-Control: no-transform` is set so the Cloudflare edge does not sanitise or re-encode the broken markup on the way out. The `X-Chaos-Html-Note` header explains the flaw in plain text.
      parameters:
        - name: mode
          in: query
          description: |
            `mismatched-tags` (default) — `<div><span></div></span>`; HTML5 parsers auto-correct, strict XML parsers reject.
            `meta-charset-conflict` — declares `<meta charset="utf-8">` but body bytes are CP1252 (smart quotes, en/em-dash, euro, ellipsis); utf-8 parsers replace or fail.
            `doctype-mismatch` — XHTML 1.0 Strict doctype with HTML5 syntax (unclosed `<br>` etc.).
            `script-without-end` — `<script>` opens and never closes; browsers swallow the rest of the document into the script body.
          schema:
            type: string
            enum: [mismatched-tags, meta-charset-conflict, doctype-mismatch, script-without-end]
            default: mismatched-tags
      responses:
        '200':
          description: HTML body with the chosen structural flaw.
          headers:
            X-Chaos-Html-Mode:
              schema: { type: string }
            X-Chaos-Html-Note:
              description: Plain-text explanation of the flaw under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-transform }
          content:
            text/html:
              schema: { type: string }

  /problem-details:
    get:
      operationId: problemDetailsChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/problem-details/
      tags: [payload]
      summary: RFC 9457 Problem Details objects with spec violations
      description: |
        Returns an RFC 9457 Problem Details envelope with one spec-level violation. Targets error-handling code in REST clients, SDKs, and API gateways that parse or dispatch on Problem Details fields. Default mode omits the required `type` field; clients that branch on `type` receive undefined. `X-Chaos-Problem-Details-Note` explains the violation in plain text.
      parameters:
        - name: mode
          in: query
          description: |
            `drop-required` (default) — `type` field omitted; RFC 9457 §3 requires it; clients that branch on type for error routing receive undefined.
            `status-type-shift` — `status` is the string "404" instead of integer 404; typed SDKs that deserialize into an int field error.
            `instance-malformed` — `instance` is the bare word "not-a-uri"; clients that dereference it get a URL parse error.
            `content-type-lie` — body is valid Problem Details JSON but Content-Type is application/json; clients that dispatch on the media type miss the error envelope.
          schema:
            type: string
            enum: [drop-required, status-type-shift, instance-malformed, content-type-lie]
            default: drop-required
      responses:
        '200':
          description: Problem Details envelope with the chosen spec violation.
          headers:
            X-Chaos-Problem-Details-Mode:
              schema: { type: string }
            X-Chaos-Problem-Details-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/problem+json:
              schema: { type: object }

  /jsonrpc:
    get:
      operationId: jsonRpcChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/jsonrpc/
      tags: [payload]
      summary: JSON-RPC 2.0 responses with spec-level violations
      description: |
        Returns a JSON-RPC 2.0 response with one spec-level violation. Used by MCP, Ethereum clients, language servers, and many internal RPC systems — all of which parse the envelope strictly. Default mode omits the required `jsonrpc` version field; strict clients reject or fall back to 1.x parsing rules.
      parameters:
        - name: mode
          in: query
          description: |
            `version-missing` (default) — `jsonrpc` field absent; signals a 1.x response with a different envelope; strict clients reject.
            `id-mismatch` — response `id` is 99 but the request id was 1; clients correlating by id fail to match.
            `result-and-error` — both `result` and `error` present; JSON-RPC 2.0 §5 says they are mutually exclusive.
            `error-code-invalid` — `error.code` is -32001, inside the reserved range but not a standard code.
          schema:
            type: string
            enum: [version-missing, id-mismatch, result-and-error, error-code-invalid]
            default: version-missing
      responses:
        '200':
          description: JSON-RPC 2.0 envelope with the chosen spec violation.
          headers:
            X-Chaos-Jsonrpc-Mode:
              schema: { type: string }
            X-Chaos-Jsonrpc-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/json:
              schema: { type: object }

  /jsonapi:
    get:
      operationId: jsonApiChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/jsonapi/
      tags: [payload]
      summary: JSON:API responses with spec-level violations
      description: |
        Returns a JSON:API response served as `application/vnd.api+json` with one spec-level violation. Targets JSON:API client libraries and serializers. Default mode returns `data` as an array when a single resource is implied; clients that call `response.data.attributes` receive undefined.
      parameters:
        - name: mode
          in: query
          description: |
            `data-shape-confusion` (default) — `data` is an array when a single resource is implied; clients that call response.data.attributes receive undefined.
            `included-orphan` — `included` contains a resource not referenced by any relationship in `data`; JSON:API §5 requires all included resources to be linked.
            `missing-id` — resource object has no `id`; JSON:API §3.1 requires it for server-assigned IDs.
            `type-missing` — resource object has no `type`; JSON:API §3.1 requires it; clients that dispatch on type throw or fall through.
          schema:
            type: string
            enum: [data-shape-confusion, included-orphan, missing-id, type-missing]
            default: data-shape-confusion
      responses:
        '200':
          description: JSON:API envelope with the chosen spec violation.
          headers:
            X-Chaos-Jsonapi-Mode:
              schema: { type: string }
            X-Chaos-Jsonapi-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/vnd.api+json:
              schema: { type: object }

  /oauth-token:
    get:
      operationId: oauthTokenChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/oauth-token/
      tags: [payload]
      summary: RFC 6749 token responses with field-level violations
      description: |
        Returns an OAuth 2.0 token response per RFC 6749 §5.1 with one field-level violation. OAuth SDKs and libraries consume these responses and often have weak type enforcement. Default mode returns `expires_in` as the string "3600" instead of integer 3600; typed SDKs error and expiry arithmetic produces NaN. `Cache-Control: no-store` matches real token endpoint behaviour.
      parameters:
        - name: mode
          in: query
          description: |
            `expires-in-type-shift` (default) — `expires_in` is the string "3600" instead of integer 3600; RFC 6749 §5.1 requires a number.
            `scope-delimiter-wrong` — `scope` uses comma separators instead of the space separators required by RFC 6749 §3.3.
            `token-type-nonstandard` — `token_type` is "token" instead of "Bearer"; clients that exact-match fail to attach Authorization headers.
            `id-token-without-scope` — `id_token` present but `scope` omits "openid"; OIDC Core §3.1.3.3 requires the openid scope.
          schema:
            type: string
            enum: [expires-in-type-shift, scope-delimiter-wrong, token-type-nonstandard, id-token-without-scope]
            default: expires-in-type-shift
      responses:
        '200':
          description: OAuth token response with the chosen field-level violation.
          headers:
            X-Chaos-Oauth-Token-Mode:
              schema: { type: string }
            X-Chaos-Oauth-Token-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
            Cache-Control:
              schema: { type: string, const: no-store }
          content:
            application/json:
              schema: { type: object }

  /cloudevents:
    get:
      operationId: cloudEventsChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/cloudevents/
      tags: [payload]
      summary: CloudEvents 1.0 envelopes with spec violations
      description: |
        Returns a CloudEvents 1.0 envelope served as `application/cloudevents+json` with one spec-level violation. Used by serverless platforms, event grids, and message buses — all of which validate the envelope before dispatching to handlers. Default mode claims `specversion` "0.3" instead of "1.0"; consumers that validate version before dispatching reject or apply 0.3 parsing rules.
      parameters:
        - name: mode
          in: query
          description: |
            `specversion-lie` (default) — `specversion` is "0.3" instead of "1.0"; consumers that validate version before dispatching reject the envelope.
            `type-as-number` — `type` is integer 42; CloudEvents §3.1 requires a non-empty string; consumers that substring-match on type throw a type error.
            `datacontenttype-mismatch` — `datacontenttype` says application/json but `data` is a bare string; consumers call JSON.parse on non-JSON and throw.
            `data-base64-confusion` — both `data` and `data_base64` present; CloudEvents §3.1 says they are mutually exclusive.
          schema:
            type: string
            enum: [specversion-lie, type-as-number, datacontenttype-mismatch, data-base64-confusion]
            default: specversion-lie
      responses:
        '200':
          description: CloudEvents 1.0 envelope with the chosen spec violation.
          headers:
            X-Chaos-Cloudevents-Mode:
              schema: { type: string }
            X-Chaos-Cloudevents-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/cloudevents+json:
              schema: { type: object }

  /geojson:
    get:
      operationId: geoJsonChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/geojson/
      tags: [payload]
      summary: GeoJSON documents with RFC 7946 violations
      description: |
        Returns a GeoJSON Feature served as `application/geo+json` with one RFC 7946 violation. Targets GIS clients, mapping libraries, and spatial data pipelines. Default mode returns a Polygon whose ring does not close; strict validators reject, lenient renderers auto-close and silently accept the server-side bug.
      parameters:
        - name: mode
          in: query
          description: |
            `polygon-not-closed` (default) — Polygon ring last position differs from first; RFC 7946 §3.1.6 requires an identical closing position.
            `coordinates-swapped` — coordinates are in [latitude, longitude] order instead of GeoJSON's required [longitude, latitude]; geometry is syntactically valid so parsers accept it and place points in the wrong hemisphere.
            `type-coords-mismatch` — `type` is "Point" but `coordinates` is an array of rings; strict validators reject, lenient renderers produce NaN coordinates.
            `properties-missing` — `properties` member absent from the Feature; RFC 7946 §3.2 requires it even if null.
          schema:
            type: string
            enum: [polygon-not-closed, coordinates-swapped, type-coords-mismatch, properties-missing]
            default: polygon-not-closed
      responses:
        '200':
          description: GeoJSON Feature with the chosen RFC 7946 violation.
          headers:
            X-Chaos-Geojson-Mode:
              schema: { type: string }
            X-Chaos-Geojson-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/geo+json:
              schema: { type: object }

  /schema-org:
    get:
      operationId: schemaOrgChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/schema-org/
      tags: [payload]
      summary: Schema.org / JSON-LD documents with vocabulary and context violations
      description: |
        Returns a JSON-LD document served as `application/ld+json` with one Schema.org spec violation. Targets structured-data extractors, rich-result generators, and JSON-LD processors that crawl or ingest semantic markup. Default mode points `@context` at a non-existent IRI so processors that dereference get a 404 and those that cache by IRI fail to match the vocabulary.
      parameters:
        - name: mode
          in: query
          description: |
            `context-lie` (default) — `@context` is "https://schema.example.invalid"; JSON-LD processors that dereference the context get a 404.
            `type-unknown` — `@type` is "ChaosEntity", not in any Schema.org vocabulary; rich-result renderers reject.
            `missing-required` — Recipe without `name` or `recipeIngredient`; search crawlers silently drop the rich result.
            `context-array-conflict` — second `@context` entry remaps "name" to a custom IRI, shadowing schema.org's definition.
          schema:
            type: string
            enum: [context-lie, type-unknown, missing-required, context-array-conflict]
            default: context-lie
      responses:
        '200':
          description: JSON-LD document with the chosen Schema.org violation.
          headers:
            X-Chaos-Schema-Org-Mode:
              schema: { type: string }
            X-Chaos-Schema-Org-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/ld+json:
              schema: { type: object }

  /jwt-payload:
    get:
      operationId: jwtPayloadChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/jwt-payload/
      tags: [payload]
      summary: JWT payload (claims) with RFC 7519 violations
      description: |
        Returns a JSON object representing a JWT claim set with one RFC 7519 violation. Distinct from `/jwt-chaos` (token-validation flaws) and `/auth-chaos` (challenge framing) — this tests the claim set that libraries deserialize after signature verification. Default mode sends `exp` as a string instead of a NumericDate integer.
      parameters:
        - name: mode
          in: query
          description: |
            `exp-as-string` (default) — `exp` is a string instead of a NumericDate integer per RFC 7519 §4.1.4.
            `iss-malformed` — `iss` is a bare word, not a URI; validators that require URI format reject.
            `aud-mixed-types` — `aud` array contains string, integer, and boolean; RFC 7519 §4.1.3 requires StringOrURI.
            `reserved-claim-collision` — `sub` is an object instead of a string principal identifier.
          schema:
            type: string
            enum: [exp-as-string, iss-malformed, aud-mixed-types, reserved-claim-collision]
            default: exp-as-string
      responses:
        '200':
          description: JWT claim set with the chosen RFC 7519 violation.
          headers:
            X-Chaos-Jwt-Payload-Mode:
              schema: { type: string }
            X-Chaos-Jwt-Payload-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/json:
              schema: { type: object }

  /activitystreams:
    get:
      operationId: activityStreamsChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/activitystreams/
      tags: [payload]
      summary: ActivityStreams 2.0 / ActivityPub objects with spec violations
      description: |
        Returns an ActivityStreams 2.0 object served as `application/activity+json` with one ActivityPub spec violation. Consumed by Mastodon, Misskey, and other Fediverse servers during federation. Default mode omits `@context`; servers that check for it reject the activity or fail to expand JSON-LD terms.
      parameters:
        - name: mode
          in: query
          description: |
            `context-missing` (default) — `@context` absent; ActivityPub §3.1 requires it on every networked object.
            `type-not-vocabulary` — `type` is "ChaosPost", not in the AS2 vocabulary.
            `actor-not-uri` — `actor` is a bare string; ActivityPub §4.1 requires a dereferenceable URI for key verification.
            `object-shape-variance` — Like activity with `object` as a bare string instead of a URI or embedded object.
          schema:
            type: string
            enum: [context-missing, type-not-vocabulary, actor-not-uri, object-shape-variance]
            default: context-missing
      responses:
        '200':
          description: ActivityStreams 2.0 object with the chosen spec violation.
          headers:
            X-Chaos-Activitystreams-Mode:
              schema: { type: string }
            X-Chaos-Activitystreams-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/activity+json:
              schema: { type: object }

  /web-annotation:
    get:
      operationId: webAnnotationChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/web-annotation/
      tags: [payload]
      summary: W3C Web Annotation objects with spec violations
      description: |
        Returns a W3C Web Annotation object served as `application/ld+json` (with `anno.jsonld` profile) with one spec violation. Targets annotation tools, highlighting libraries, and knowledge-management systems that store and replay annotations. Default mode omits the required `id` field.
      parameters:
        - name: mode
          in: query
          description: |
            `id-missing` (default) — `id` field absent; W3C Web Annotation §3.1 requires it for persistence and deduplication.
            `body-as-string` — `body` is a plain string instead of a TextualBody object or URI.
            `motivation-invalid` — `motivation` is "chaos", not in the W3C motivation vocabulary.
            `target-shape-variance` — `target` is a Specific Resource object instead of a URI string.
          schema:
            type: string
            enum: [id-missing, body-as-string, motivation-invalid, target-shape-variance]
            default: id-missing
      responses:
        '200':
          description: Web Annotation with the chosen W3C spec violation.
          headers:
            X-Chaos-Web-Annotation-Mode:
              schema: { type: string }
            X-Chaos-Web-Annotation-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/ld+json:
              schema: { type: object }

  /sse:
    get:
      operationId: sseChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/sse/
      tags: [payload]
      summary: Server-Sent Events with WHATWG EventSource violations
      description: |
        Returns a `text/event-stream` response with one WHATWG EventSource spec violation. Default mode sends payload lines with no `data:` prefix, so conformant clients deliver empty event.data to their handlers and the stream looks silent.
      parameters:
        - name: mode
          in: query
          description: |
            `missing-data-prefix` (default) — payload lines have no `data:` prefix; conformant clients ignore unrecognized field names.
            `event-without-listener` — all events use named `event:` fields; `EventSource.onmessage` never fires.
            `id-discontinuity` — event ids go backwards; Last-Event-ID reconnect gets an unpredictable cursor.
            `crlf-mix` — line endings alternate CRLF and LF; parsers that normalize only one style misparse field boundaries.
          schema:
            type: string
            enum: [missing-data-prefix, event-without-listener, id-discontinuity, crlf-mix]
            default: missing-data-prefix
      responses:
        '200':
          description: SSE stream with the chosen violation.
          headers:
            X-Chaos-Sse-Mode:
              schema: { type: string }
          content:
            text/event-stream:
              schema: { type: string }

  /multipart:
    get:
      operationId: multipartChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/multipart/
      tags: [payload]
      summary: multipart/form-data with RFC 7578 violations
      description: |
        Returns a `multipart/form-data` response with one RFC 7578 spec violation. Default mode declares one boundary in the Content-Type and uses a different one in the body, so strict parsers find no opening delimiter.
      parameters:
        - name: mode
          in: query
          description: |
            `boundary-mismatch` (default) — Content-Type boundary does not match body delimiter.
            `trailing-crlf-confusion` — closing boundary `--xxx--` has no trailing CRLF; strict parsers treat the stream as unterminated.
            `disposition-name-injection` — two `name=` parameters on one Content-Disposition; RFC 7578 leaves precedence undefined.
            `nested-multipart` — inner part uses Content-Type: multipart/mixed; most form-data parsers do not recurse.
          schema:
            type: string
            enum: [boundary-mismatch, trailing-crlf-confusion, disposition-name-injection, nested-multipart]
            default: boundary-mismatch
      responses:
        '200':
          description: multipart/form-data response with the chosen violation.
          headers:
            X-Chaos-Multipart-Mode:
              schema: { type: string }
            X-Chaos-Multipart-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            multipart/form-data:
              schema: { type: string }

  /toml:
    get:
      operationId: tomlChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/toml/
      tags: [payload]
      summary: TOML documents with TOML 1.0 violations
      description: |
        Returns an `application/toml` document with one TOML 1.0 violation or parser-ambiguity pattern. Default mode mixes int, string, and float in a single array; many parsers accept silently and downstream schema validators fail.
      parameters:
        - name: mode
          in: query
          description: |
            `mixed-array-types` (default) — array mixes int, string, and float; TOML 1.0 forbids mixed types in arrays.
            `unquoted-datetime` — datetime value is unquoted and not in RFC 3339 format.
            `duplicate-table` — the same table is defined twice.
            `invalid-nesting` — a table is defined after it has been used as a simple key.
            `datetime-offset-lie` — offset `+99:00` is structurally valid syntax but semantically impossible.
            `dotted-key-table-collision` — table is defined, then a dotted key redefines it; TOML 1.0 forbids this.
            `trailing-comma-inline-table` — inline table has a trailing comma; TOML 1.0 forbids this (unlike arrays).
          schema:
            type: string
            enum: [mixed-array-types, unquoted-datetime, duplicate-table, invalid-nesting, datetime-offset-lie, dotted-key-table-collision, trailing-comma-inline-table]
            default: mixed-array-types
      responses:
        '200':
          description: TOML document with the chosen violation.
          headers:
            X-Chaos-Toml-Mode:
              schema: { type: string }
            X-Chaos-Toml-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/toml:
              schema: { type: string }

  /yaml:
    get:
      operationId: yamlChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/yaml/
      tags: [payload]
      summary: YAML documents with YAML 1.1 vs 1.2 ambiguity and parser adversity
      description: |
        Returns an `application/yaml` document exploiting the YAML 1.1 vs 1.2 split and parser ambiguity. Default sends the Norway problem: bare `NO/YES/ON/OFF` scalars that YAML 1.1 parsers coerce to booleans while YAML 1.2 parsers leave as strings.
      parameters:
        - name: mode
          in: query
          description: |
            `norway-problem` (default) — bare `NO/YES/ON/OFF` scalars; YAML 1.1 vs 1.2 parsers disagree on the type.
            `anchor-cycle` — recursive anchor reference; parsers without cycle detection exhaust stack or heap.
            `duplicate-keys` — a key appears twice; spec behavior is application-defined.
            `tag-lie` — explicit `!!int` tag applied to a non-integer string.
          schema:
            type: string
            enum: [norway-problem, anchor-cycle, duplicate-keys, tag-lie]
            default: norway-problem
      responses:
        '200':
          description: YAML document with the chosen violation.
          headers:
            X-Chaos-Yaml-Mode:
              schema: { type: string }
            X-Chaos-Yaml-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/yaml:
              schema: { type: string }

  /json-feed:
    get:
      operationId: jsonFeedChaos
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/chaos/endpoints/format/json-feed/
      tags: [payload]
      summary: JSON Feed documents with semantic schema violations
      description: |
        Returns a JSON Feed 1.1 document served as `application/feed+json` with one semantic schema violation that parses as JSON but breaks the spec. Targets feed readers and aggregators. Default mode sends `version` as the bare string "1.1" instead of the required full URL "https://jsonfeed.org/version/1.1".
      parameters:
        - name: mode
          in: query
          description: |
            `version-mismatch` (default) — `version` is "1.1" instead of the full URL "https://jsonfeed.org/version/1.1" per JSON Feed §2.
            `items-id-not-unique` — two items share the same id; aggregators that dedupe on id collapse both entries.
            `item-url-malformed` — an item's url is a relative path; JSON Feed §4.1.2 requires absolute URLs.
            `feed-url-wrong` — feed_url points to a different host; subscription dedup and refresh routing target the wrong place.
          schema:
            type: string
            enum: [version-mismatch, items-id-not-unique, item-url-malformed, feed-url-wrong]
            default: version-mismatch
      responses:
        '200':
          description: JSON Feed document with the chosen spec violation.
          headers:
            X-Chaos-Json-Feed-Mode:
              schema: { type: string }
            X-Chaos-Json-Feed-Note:
              description: Plain-text explanation of the violation under test.
              schema: { type: string }
          content:
            application/feed+json:
              schema: { type: object }

components:
  responses:
    EchoResponse:
      description: The reflected request.
      content:
        application/json:
          schema:
            type: object
            properties:
              endpoint: { type: string, const: /echo }
              method: { type: string }
              url: { type: string }
              path: { type: string }
              query:
                type: object
                additionalProperties: { type: string }
              headers:
                type: object
                additionalProperties: { type: string }
              body:
                description: Parsed JSON if the body was valid JSON, otherwise the raw string, otherwise null.
                oneOf:
                  - { type: object }
                  - { type: array }
                  - { type: string }
                  - { type: 'null' }
