{
  "role": "secondary",
  "routes": [
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/health",
      "description": "Liveness, role, origin wiring and server clock",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST",
        "PUT",
        "DELETE",
        "PATCH",
        "HEAD",
        "OPTIONS"
      ],
      "pattern": "/api/echo",
      "description": "Echo the exact request the server received, cookies and headers included",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "*"
      ],
      "pattern": "/api/echo/*",
      "description": "Echo, reachable at any sub-path",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/time",
      "description": "Server wall clock, for device clock-skew measurement",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/geo",
      "description": "Client IP and platform geo hints, for geo-fencing sanity checks",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/_routes",
      "description": "List every registered endpoint",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/set",
      "description": "Set one cookie with fully-specified attributes",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/set-matrix",
      "description": "Set the whole SameSite matrix in one response (free cookie-name mode)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/set-many",
      "description": "Set N cookies, to probe the per-domain cookie limit",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/set-big",
      "description": "Set one large cookie, to probe the per-cookie size limit",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/clear",
      "description": "Delete named cookies",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/clear-seen",
      "description": "Delete every cookie visible on this request",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/list",
      "description": "Report which cookies arrived, with the request-context headers",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/set-then-redirect",
      "description": "Set a cookie then 3xx elsewhere, to test cookies across a top-level navigation",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/cookie/post-target",
      "description": "Landing page for a cross-site top-level POST",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/cookie/landing",
      "description": "Generic landing page reporting what arrived",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cookie/frame",
      "description": "HTML page for cross-site iframe third-party cookie testing",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/clear-site-data",
      "description": "Emit Clear-Site-Data and let the client verify whether it was honoured",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/status/:code",
      "description": "Return an arbitrary HTTP status code",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/redirect/chain",
      "description": "An n-hop redirect chain",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/redirect/loop",
      "description": "Redirect loop, to observe the engine redirect limit",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/redirect/cross",
      "description": "Redirect across to the other origin, optionally bouncing back",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/redirect/meta",
      "description": "Meta-refresh redirect with a configurable delay",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/redirect/js",
      "description": "JS redirect via href / replace / assign",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/redirect/scheme",
      "description": "Attempt an external/custom scheme and classify what happened",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/redirect/preserve",
      "description": "Redirect that should preserve method and body (307/308)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST",
        "PUT"
      ],
      "pattern": "/api/redirect/preserve-landing",
      "description": "Landing endpoint reporting the method and body it received",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/redirect/slow",
      "description": "Redirect after a deliberate server delay",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST",
        "PUT"
      ],
      "pattern": "/api/redirect/:code",
      "description": "One redirect with a chosen 3xx status",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/session/start",
      "description": "Issue a signed session cookie (session or persistent)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/session/whoami",
      "description": "Report the verified age and state of the session cookie",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/session/stamp",
      "description": "Set a dated reference marker before an app restart",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/session/end",
      "description": "Clear the session and the stamp",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/auth/login",
      "description": "Mock login form (GET renders, POST authenticates)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "pattern": "/api/auth/login",
      "description": "Authenticate and issue a session cookie",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/auth/protected",
      "description": "Session-protected resource (401 without a valid session)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/auth/basic/:nonce",
      "description": "HTTP Basic auth challenge under a per-run realm, to test the native credential dialog",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/auth/basic",
      "description": "HTTP Basic auth challenge (shared realm; prefer /api/auth/basic/:nonce)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/oauth/authorize",
      "description": "Mock OAuth authorize + consent, hosted cross-site",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/oauth/token",
      "description": "Exchange a mock authorization code for a session",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/oauth/popup-callback",
      "description": "Popup OAuth callback that reports via window.opener",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/slow",
      "description": "Respond after a deliberate delay, to find the engine request timeout",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/large",
      "description": "Large line-numbered body with an accurate Content-Length",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/trickle",
      "description": "Slow buffered response with chunk markers (not real streaming - see comment)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/bytes",
      "description": "Exactly n bytes of a known pattern, with a checksum header",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cache/etag",
      "description": "Stable ETag, with a real 304 when If-None-Match matches",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cache/maxage",
      "description": "Cacheable response with max-age and a server timestamp (no no-store, on purpose)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cache/vary",
      "description": "Vary: User-Agent, to check cache-key handling",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/range",
      "description": "Accept-Ranges with 206 Partial Content and 416 when unsatisfiable",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cors/simple",
      "description": "Plain cross-origin GET with CORS allowed",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "PUT",
        "POST"
      ],
      "pattern": "/api/cors/preflight",
      "description": "Requires a CORS preflight (PUT plus the x-wv-test header)",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cors/credentialed",
      "description": "Credentialed cross-origin GET, reporting which cookies arrived",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/cors/denied",
      "description": "Sends no CORS headers at all, so a real CORS failure can be observed",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/csp/enforced",
      "description": "HTML page with a strict enforced CSP that must block an external probe script",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "pattern": "/api/csp/report",
      "description": "Accept a CSP violation report and echo back what arrived",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/frame-options",
      "description": "Page sent with X-Frame-Options: DENY",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/frame-allowed",
      "description": "Page sent with frame-ancestors *, the control for the X-Frame-Options test",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/mixed-content",
      "description": "HTTPS page referencing http:// subresources, to see blocked vs upgraded vs allowed",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/mixed-content/pixel.gif",
      "description": "1x1 GIF, the http:// subresource for the mixed-content test",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/permissions-policy",
      "description": "Page carrying a restrictive Permissions-Policy, to check enforcement",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/headers/tls",
      "description": "Report whatever TLS/protocol information the platform exposed in headers",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/compress",
      "description": "Highly compressible body plus the Accept-Encoding the client sent",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/pdf",
      "description": "Valid one-page PDF as an attachment - the deposit receipt path",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/inline-pdf",
      "description": "The same PDF with Content-Disposition: inline - should render, not download",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/csv",
      "description": "CSV attachment - the account statement export path",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/octet",
      "description": "application/octet-stream attachment - the generic binary path",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/no-filename",
      "description": "Attachment with no filename - see what the shell names it",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/utf8-name",
      "description": "RFC 5987 filename* with non-ASCII characters, which shells mangle",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/cross-origin",
      "description": "Page linking to a download on the peer origin",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/download/page",
      "description": "Link / download-attribute / Blob-URL / data-URL download mechanisms",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "pattern": "/api/upload",
      "description": "Accept multipart/form-data and report each part name, filename, type and byte length",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/upload/page",
      "description": "The five KYC file inputs, with picker-never-opened vs cancelled detection",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/upload/limits",
      "description": "Report the configured maximum request body size",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/print/page",
      "description": "Call window.print() and classify what happened",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/sse",
      "description": "A finite but correctly framed SSE stream; continues from Last-Event-ID",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/sse/slow",
      "description": "SSE stream delayed before the first byte, for connect-timeout behaviour",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/longpoll",
      "description": "Long poll with a signed cursor that measures the gap between polls",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/ws/info",
      "description": "Whether this deployment can serve WebSocket, and the wss:// URL to use",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/stream/countdown",
      "description": "Issue a signed bet-close deadline the client can time against",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/stream/countdown/check",
      "description": "Verify a countdown token and report true remaining time and client drift",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/stream/page",
      "description": "EventSource monitor page: missed ids, reconnects and background gap",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "pattern": "/api/psp/create",
      "description": "Create a mock payment; detects a double-submitted deposit by nonce",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/3ds/iframe",
      "description": "3-D Secure challenge for a cross-site iframe; reports third-party cookie survival",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/3ds/redirect",
      "description": "3-D Secure challenge returning by top-level cross-site navigation",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/3ds/popup",
      "description": "3-D Secure challenge opened with window.open; reports via window.opener",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/3ds/popup-done",
      "description": "Popup 3-D Secure callback that reports via window.opener and closes",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/3ds/app",
      "description": "3-D Secure handoff to a bank app, reusing /api/redirect/scheme",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/status",
      "description": "Payment status derived from token age: pending, then confirmed",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "pattern": "/api/psp/confirm",
      "description": "Force a payment to confirmed by re-issuing it backdated past the settle window",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/fail",
      "description": "Declined outcome with a retry path",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET",
        "POST"
      ],
      "pattern": "/api/psp/return",
      "description": "Merchant landing after a payment; reports whether the session cookie survived",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/receipt",
      "description": "Tiny valid PDF deposit receipt, sent as an attachment",
      "roles": [
        "primary",
        "secondary"
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "pattern": "/api/psp/card-form",
      "description": "Card form measuring autofill tokens and keyboard occlusion of the deposit CTA",
      "roles": [
        "primary",
        "secondary"
      ]
    }
  ]
}