Hoody Tunnel
Section titled “Hoody Tunnel”The Hoody Tunnel kit service exposes HTTP endpoints for inspecting and managing active tunnel sessions, bindings, and streams inside a container. It also serves the multiplexed WebSocket control plane that clients connect to in order to register EXPOSE and PULL bindings. Use these endpoints to monitor tunnel activity, scrape Prometheus metrics, or terminate rogue sessions from the control plane.
All endpoints are served from the tunnel kit’s container hostname: https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com. Replace {projectId} and {containerId} with the 24-character hexadecimal project and container identifiers, and {serverName} with the server node label (for example node-us).
Health and Metrics
Section titled “Health and Metrics”GET /api/v1/tunnel/health
Section titled “GET /api/v1/tunnel/health”Returns kit health status including runtime version, memory usage, open file descriptor count, process ID, and client user agent. No authentication is required.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/healthimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.health.check();{ "status": "ok", "service": "hoody-tunnel", "built": "2026-01-15T08:42:11Z", "started": "2026-01-20T14:03:55Z", "memory": { "rss": 41943040, "heap": 20971520 }, "fds": 128, "pid": 421, "ip": "10.0.4.21", "userAgent": "hoody-tunnel/1.4.2"}GET /api/v1/tunnel/metrics
Section titled “GET /api/v1/tunnel/metrics”Returns Prometheus text-format metrics covering active sessions, active bindings, and FD permit budget.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/metricsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.getMetrics();# HELP hoody_tunnel_active_sessions Number of active tunnel sessions# TYPE hoody_tunnel_active_sessions gaugehoody_tunnel_active_sessions 3
# HELP hoody_tunnel_active_bindings Number of active bindings# TYPE hoody_tunnel_active_bindings gaugehoody_tunnel_active_bindings 5
# HELP hoody_tunnel_fd_permits Available FD permits# TYPE hoody_tunnel_fd_permits gaugehoody_tunnel_fd_permits 32768Active Sessions
Section titled “Active Sessions”GET /api/v1/tunnel/sessions
Section titled “GET /api/v1/tunnel/sessions”Returns all active tunnel sessions with their bindings, stream counts, and protocol version.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/sessionsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.listSessions();{ "sessions": [ { "sessionId": "sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE", "peerAddr": "203.0.113.42:54312", "isV2": true, "connectionsGranted": 12, "activeStreams": 3, "maxStreams": 256, "bindings": [ { "bindId": 0, "kind": "EXPOSE", "mode": "tcp", "containerPort": 8080 }, { "bindId": 1, "kind": "PULL", "mode": "tcp", "containerPort": 5432, "bindAddr": "127.0.0.1" } ] } ], "total": 1}DELETE /api/v1/tunnel/sessions/{session_id}
Section titled “DELETE /api/v1/tunnel/sessions/{session_id}”Signals the tunnel kit to close the named session. Live sessions receive a GOAWAY(0x0001, "closed by admin") frame sent directly on the WebSocket and are force-closed after grace_ms elapses. Admin kills are non-resumable (orphan parking is skipped).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
session_id | path | string | Yes | Session ID as returned by GET /sessions |
grace_ms | query | integer | No | GOAWAY drain budget in ms (0-5000, default 50) |
curl -X DELETE \ 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/sessions/sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE?grace_ms=200'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.killSession('sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE');// or with a custom drain budgetawait client.tunnel.killSession('sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE', { grace_ms: 200 });{ "sessionId": "sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE", "status": "closing"}{ "error": "grace_ms must be between 0 and 5000"}{ "error": "session not found"}Bindings and Tunnels
Section titled “Bindings and Tunnels”GET /api/v1/tunnel/bindings
Section titled “GET /api/v1/tunnel/bindings”Returns all active EXPOSE and PULL bindings across every session, with the owning session, port, kind, mode, and bind ID.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/bindingsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.listBindings();{ "bindings": [ { "sessionId": "sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE", "bindId": 0, "port": 8080, "kind": "EXPOSE", "mode": "tcp" }, { "sessionId": "sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE", "bindId": 1, "port": 5432, "kind": "PULL", "mode": "tcp", "bindAddr": "127.0.0.1" } ], "total": 2}GET /api/v1/tunnel/tunnels
Section titled “GET /api/v1/tunnel/tunnels”Returns a unified view of all active tunnel sessions, including expose and pull bindings, stream counts, orphan count, and FD budget status.
This endpoint takes no parameters.
curl https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/tunnelsimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.listTunnels();{ "fdPermitsAvailable": 32768, "orphanedSessions": 0, "totalBindings": 2, "totalStreams": 7, "sessions": [ { "sessionId": "sess_01HMZ8X3KQ9F2VNA7B4YTRWPCE", "peerAddr": "203.0.113.42:54312", "protocol": "hoody-tunnel.v2", "connectionsGranted": 12, "activeStreams": 3, "exposeBindings": [ { "bindId": 0, "containerPort": 8080 } ], "pullBindings": [ { "bindId": 1, "containerPort": 5432, "bindAddr": "127.0.0.1" } ] } ]}WebSocket Control Plane
Section titled “WebSocket Control Plane”GET /api/v1/tunnel/connect
Section titled “GET /api/v1/tunnel/connect”WebSocket upgrade endpoint for the multiplexed tunnel session. Clients MUST request subprotocol hoody-tunnel.v1 or hoody-tunnel.v2 and send a HELLO frame as the first binary message. The response header x-hoody-tunnel-versions lists the supported subprotocol versions when the request is rejected.
This endpoint takes no parameters.
curl -i \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Protocol: hoody-tunnel.v1" \ https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com/api/v1/tunnel/connectimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-tunnel-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.tunnel.tunnelConnect();The server returns a 101 Switching Protocols response. The HTTP response body is empty; subsequent communication happens over the WebSocket using the hoody-tunnel.v1 or hoody-tunnel.v2 wire protocol.
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Protocol: hoody-tunnel.v1The server rejects the upgrade with a 400 Bad Request response when the subprotocol header is missing or unsupported. The response includes an x-hoody-tunnel-versions header listing the supported versions. The HTTP response body is empty.
HTTP/1.1 400 Bad Requestx-hoody-tunnel-versions: hoody-tunnel.v1, hoody-tunnel.v2