HTTP API

The REST API lives at https://dashboard.router.direct/api and is the same API the dashboard UI uses. Sessions are cookie-based; tunnels are managed under the authenticated user.

Authentication

Signup and login set an HTTP-only session cookie (router_session) backed by Redis with a 7-day expiry. Tunnel endpoints read the cookie to identify the user.

POST /api/auth/signup

request
{
  "email": "you@example.com",
  "password": "at-least-8-chars"
}

Returns { "success": true, "email": "you@example.com" } and sets the session cookie. Errors: 400 (missing fields, short password, invalid email, email already registered), 429 (rate limited — 3 attempts per 10 minutes per IP).

POST /api/auth/login

request
{
  "email": "you@example.com",
  "password": "your-password"
}

Same response shape as signup. Errors: 400 (invalid credentials), 429 (5 attempts per 5 minutes per IP).

POST /api/auth/logout

Clears the session cookie. No body required.

Tunnels

POST /api/tunnels

Creates a tunnel and returns its API key.

HTTP tunnel request
{
  "tunnelType": "http",
  "subdomain": "myapp"
}
TCP tunnel request
{
  "tunnelType": "tcp"
}
  • tunnelType: "http" or "tcp".
  • subdomain: required for HTTP tunnels; must match [a-z0-9]([a-z0-9-]{0,61}[a-z0-9])? and not be reserved.
  • TCP tunnels receive a random free port in the 10000–20000 range.
response
{
  "success": true,
  "tunnel": {
    "id": "...",
    "userId": "...",
    "apiKey": "sk_live_...",
    "tunnelType": "http",
    "subdomain": "myapp",
    "tcpPort": null,
    "createdAt": "..."
  }
}

Errors: 400 (invalid type, missing subdomain, invalid or reserved subdomain, subdomain already taken), 401 (not logged in), 500.

GET /api/tunnels

Lists the authenticated user's tunnels, newest first: { "tunnels": [...] }.

DELETE /api/tunnels/:id

Deletes a tunnel. The Redis key is removed first (instant revocation at the relay), then the PostgreSQL record. Errors: 401, 403 (not yours), 404.

GET /api/version

Public — no auth. Returns the latest CLI version and download URLs.

response
{
  "version": "1.0.0",
  "download_urls": {
    "aarch64-apple-darwin":      "https://router.direct/bin/router-client-macos-arm64",
    "x86_64-apple-darwin":       "https://router.direct/bin/router-client-macos-x86_64",
    "x86_64-unknown-linux-gnu":  "https://router.direct/bin/router-client-linux-x86_64",
    "x86_64-pc-windows-msvc":    "https://router.direct/bin/router-client-windows.exe"
  },
  "changelog": "..."
}

Relay wire protocol

The CLI talks to the relay over raw TCP with newline-free JSON objects, TLS-encrypted, on 77.42.121.237:2333 (TLS server name router.direct). You normally never touch this — the CLI implements it — but it's documented here for completeness.

Control connection

client → relay
{ "type": "Control", "token": "sk_live_..." }
relay → client (success)
{ "type": "AuthSuccess", "tunnel": { "Http": { "subdomain": "myapp" } } }
relay → client (failure)
{ "type": "AuthFailed", "reason": "Invalid API key" }

On success the tunnel config is one of {"Http":{"subdomain":"…"}}, {"Tcp":{"port":14090}}, or {"Udp":{"port":51820}}. The relay then sends Ping every 30 seconds (answer with {"type":"Pong"}) and NewConnection for each inbound visitor:

incoming connection
{ "type": "NewConnection", "request_id": "550e8400-e29b-41d4-a716-446655440000" }

Data connection

For each request_id the client opens a second TLS connection to the relay, sends { "type": "Data", "request_id": "…" }, and the relay pipes bytes bidirectionally between the public socket and that connection. The data connection must originate from the same IP as the control connection. UDP tunnels instead keep one persistent data channel carrying length-prefixed frames of [peer-address, payload] pairs.

ImportantSend each JSON message as a single write; the relay reads complete objects from the stream and may batch several into one TCP segment.