diff --git a/reference/http/api.md b/reference/http/api.md
index 3bc9d321..babe8a96 100644
--- a/reference/http/api.md
+++ b/reference/http/api.md
@@ -78,17 +78,53 @@ A `Request` object is passed to HTTP middleware handlers and direct static REST
### Properties
-| Property | Type | Description |
-| ---------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `url` | string | The request target (path + query string), e.g. `/path?query=string` |
-| `method` | string | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, etc. |
-| `headers` | [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) | Request headers |
-| `pathname` | string | Path portion of the URL, without query string |
-| `protocol` | string | `http` or `https` |
-| `data` | any | Deserialized body, based on `Content-Type` header |
-| `ip` | string | Remote IP address of the client (or last proxy) |
-| `host` | string | Host from the request headers |
-| `session` | object | Current cookie-based session (a `Table` record instance). Update with `request.session.update({ key: value })`. A cookie is set automatically the first time a session is updated or a login occurs. |
+| Property | Type | Description |
+| ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `url` | string | The request target (path + query string), e.g. `/path?query=string` |
+| `method` | string | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, etc. |
+| `headers` | [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) | Request headers |
+| `pathname` | string | Path portion of the URL, without query string |
+| `protocol` | string | `http` or `https`. On the Node runtime, behind a TLS-terminating proxy that forwards the PROXY v2 SSL TLV, this reflects the client's original scheme even though the proxy-to-Harper hop is plaintext; a proxy forwarding only address info (no SSL TLV) still reads as `http`. Bun and uWebSockets derive this from `X-Forwarded-Proto`/the listener's secure flag instead, independent of the SSL TLV. |
+| `data` | any | Deserialized body, based on `Content-Type` header |
+| `ip` | string | Remote IP address of the client (or last proxy) |
+| `host` | string | Host from the request headers |
+| `session` | object | Current cookie-based session (a `Table` record instance). Update with `request.session.update({ key: value })`. A cookie is set automatically the first time a session is updated or a login occurs. |
+| `connectionInfo` | object \| undefined | TLS facts a fronting proxy forwarded over PROXY protocol v2 — negotiated ALPN, SNI, TLS version/cipher, the client's JA3/JA4 fingerprint, and the client's mTLS certificate chain (presented, not necessarily verified — see below). `undefined` for a direct connection or one without a v2 header. See [Connection info](#connection-info). |
+
+### Connection info
+
+
+
+When Harper runs behind a TLS-terminating proxy (for example, Harper Fabric's Symphony edge), the TLS handshake happens at the proxy, so facts about the client's TLS connection — its fingerprint, negotiated protocol, and any client certificate — are otherwise invisible to your application. Harper Fabric's proxy-protocol Unix domain socket mirrors decode this from a forwarded PROXY protocol v2 header and expose it on `request.connectionInfo`; a PROXY v2 header sent to an ordinary `http.port`/`securePort` listener isn't decoded (and isn't valid HTTP, so the connection fails).
+
+```ts
+interface ConnectionInfo {
+ alpn?: string; // negotiated ALPN protocol, e.g. "h2" — raw, unvalidated
+ authority?: string; // SNI hostname from the ClientHello — raw, unvalidated
+ tls?: {
+ version?: string; // e.g. "TLSv1.3"
+ cipher?: string; // negotiated cipher suite
+ verified?: boolean; // a client certificate was presented and the proxy verified it
+ };
+ ja3?: string; // JA3 fingerprint (32-char MD5 hex) — raw, unvalidated
+ ja4?: string; // JA4 fingerprint — raw, unvalidated
+ clientCertChain?: Buffer[]; // client certificate chain (DER, leaf first) — presented, not necessarily verified; check tls.verified
+}
+```
+
+`request.connectionInfo` is `undefined` unless a PROXY v2 header carrying TLS facts was decoded; each field is present only when the proxy actually forwarded it. On the Bun and uWebSockets runtimes it is always `undefined`. `alpn`, `authority`, `ja3`, and `ja4` are string-decoded from the TLV payload with no grammar or length validation beyond the header's own bounds, so validate them before placing them in response headers, logs, cache keys, or other security-sensitive contexts. Reading the client's JA4 fingerprint — e.g. to make a bot or abuse decision — is simply:
+
+```javascript
+class Origin {
+ get(request) {
+ const ja4 = request.connectionInfo?.ja4;
+ if (ja4 && abuseList.has(ja4)) return new Response('blocked', { status: 403 });
+ return fetch(request);
+ }
+}
+```
+
+**Trust and mTLS.** `connectionInfo` is populated only from the trusted proxy-protocol channel (Harper Fabric's per-worker Unix domain socket, writable only by the local proxy) — never from a request header, so the channel itself cannot be forged by a client (its _contents_ are client-controlled, per above). `clientCertChain` and `tls.verified` are independent signals, not one another's proof: a chain is attached whenever the client presented a certificate, whether or not the proxy verified it, and `tls.verified` can be `true` while `clientCertChain` is absent (a proxy may verify a certificate but omit an oversized chain). Treating chain presence alone as proof of a verified client accepts unverified certificates. For gating on a verified mTLS client, use `request.authorized` — a chain-gated boolean the proxy layer populates with the same semantics as a directly-terminated TLS connection — rather than `connectionInfo.tls.verified` or `clientCertChain` presence alone. Like `connectionInfo`, this is Node-runtime only: `request.authorized` is always `undefined` on Bun and uWebSockets.
### Methods