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
{
"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
{
"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.
{
"tunnelType": "http",
"subdomain": "myapp"
}{
"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.
{
"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.
{
"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
{ "type": "Control", "token": "sk_live_..." }{ "type": "AuthSuccess", "tunnel": { "Http": { "subdomain": "myapp" } } }{ "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:
{ "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.