See API Reference for a matrix of endpoints, auth requirements, and streaming behavior. The NodeTool server uses token-based authentication to secure all endpoints when deployed. For environment variable defaults and precedence, see the Configuration Guide.

Quick Start

The running server enforces authentication when it is in Supabase mode (both SUPABASE_URL and SUPABASE_KEY set). Without those, it runs in Local mode: loopback connections are trusted and run as user "1", while non-loopback requests are rejected.

# Start the server (Local mode — loopback trusted)
nodetool serve

# Enable Supabase mode to enforce auth on every request
export SUPABASE_URL=https://your-project.supabase.co
export SUPABASE_KEY=your-service-role-key
nodetool serve

The SERVER_AUTH_TOKEN / deployment.yaml token described below is generated and consumed by the @nodetool-ai/deploy tooling; see Authentication Modes for how the server itself selects its mode.


How It Works

Token Generation Priority

The server looks for a token in this order:

  1. Environment variable SERVER_AUTH_TOKEN
  2. Config file ~/.config/nodetool/deployment.yaml
  3. Auto-generate a new token and save it to the config file

Auto-Generation

On first run, if no token is found:

  1. A cryptographically secure 32-byte token is generated (crypto.randomBytes)
  2. The token is saved to ~/.config/nodetool/deployment.yaml
  3. File permissions are set to 0600 (owner read/write only)
  4. The full token is displayed in the console output

Token Location

Config file path:

  • Linux/macOS: ~/.config/nodetool/deployment.yaml
  • Windows: %APPDATA%\nodetool\deployment.yaml

File format:

server_auth_token: your-auto-generated-token-here

Using the Token

All API requests (except the public health endpoints /health, /ready, and /api/health) require authentication when the server is running in Supabase mode — that is, when both SUPABASE_URL and SUPABASE_KEY are set. When those are not set, the server runs in Local mode and accepts loopback connections without a token for convenience (see Localhost Trust); non-loopback requests are rejected with 401.

When authentication is enforced, send the Supabase JWT in the header:

# Get token from config file
TOKEN=$(cat ~/.config/nodetool/deployment.yaml | grep server_auth_token | cut -d' ' -f2)

# Use in requests
curl -H "Authorization: Bearer $TOKEN" http://localhost:7777/v1/models

Example Requests

Chat Completion:

curl -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -X POST http://localhost:7777/v1/chat/completions \
  -d '{
    "model": "llama3.2:latest",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

List Collections:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:7777/admin/collections

Upload File:

curl -H "Authorization: Bearer $TOKEN" \
  -X PUT http://localhost:7777/admin/storage/assets/image.png \
  --data-binary @image.png

Authentication Modes

The running server does not read an AUTH_PROVIDER variable to choose a strategy. It selects its mode from the presence of Supabase credentials:

  • Supabase mode — enabled when both SUPABASE_URL and SUPABASE_KEY are set. The server validates a Supabase JWT on every non-public request and enforces auth.
  • Local mode — the default when those are not set. Loopback connections bypass auth and run as user "1" (gated by NODETOOL_TRUST_LOCALHOST); non-loopback requests are rejected with 401.

AUTH_PROVIDER is only written into deployed-container environments by the @nodetool-ai/deploy tooling. The server itself never branches on it.

To enable Supabase mode:

export SUPABASE_URL=https://your-project.supabase.co
export SUPABASE_KEY=your-service-role-key

Behavior:

  • Both HTTP and WebSocket endpoints enforce auth in Supabase mode.
  • Clients send Authorization: Bearer <supabase_jwt>.
  • In Local mode, loopback connections default to user "1" and no token is required.

Supabase token verification results are cached in-process. The cache defaults to a 60-second TTL and at most 2000 entries; these are constructor options on the provider and are not configurable via environment variables.

WebSockets use the same Authorization header semantics as HTTP when auth is enforced. A ?api_key=<token> query parameter is also accepted as a fallback for WebSocket connections.


Architecture

The authentication system is split across two packages:

  • @nodetool-ai/auth – defines the abstract AuthProvider base class, token extraction from HTTP and WebSocket headers, and the AuthResult / TokenType types.
  • @nodetool-ai/deploy – provides the concrete token management functions: getServerAuthToken(), generateSecureToken(), verifyServerToken(), and config file I/O.
// @nodetool-ai/auth
abstract class AuthProvider {
  extractTokenFromHeaders(headers: Record<string, string> | Headers): string | null;
  extractTokenFromWs(headers, queryParams?): string | null;
  abstract verifyToken(token: string): Promise<AuthResult>;
}

// @nodetool-ai/deploy
function getServerAuthToken(): string;       // resolve token (env → config → generate)
function generateSecureToken(): string;       // crypto.randomBytes(32).toString("base64url")
function verifyServerToken(auth: string): Promise<"authenticated">;  // timing-safe compare
function loadAuthConfig(): Record<string, unknown>;
function saveAuthConfig(config: Record<string, unknown>): void;

Token verification uses crypto.timingSafeEqual to prevent timing attacks.

Note: @nodetool-ai/auth also ships a StaticTokenProvider that reads STATIC_AUTH_TOKEN / STATIC_AUTH_TOKENS. This is a distinct mechanism from the deploy-package SERVER_AUTH_TOKEN flow, and the StaticTokenProvider is not wired into the running websocket server — the server uses only the Supabase / Local selection described in Authentication Modes.


Environment Variable Override

You can override the auto-generated token by setting the environment variable:

# Set your own token
export SERVER_AUTH_TOKEN="my-custom-secure-token"

# Start server (will use this token instead)
nodetool serve

This is useful for:

  • Docker deployments with secrets
  • CI/CD pipelines
  • Multiple server instances with shared token

Docker Deployment

Using Auto-Generated Token

Mount the config directory as a volume:

docker run -v ~/.config/nodetool:/root/.config/nodetool \
  -p 7777:7777 \
  nodetool-server

The token persists across container restarts.

Using Environment Variable

# Generate token
TOKEN=$(openssl rand -base64 32)

# Run with token
docker run -e SERVER_AUTH_TOKEN="$TOKEN" \
  -p 7777:7777 \
  nodetool-server

Docker Compose

version: '3.8'
services:
  server:
    image: nodetool-server
    ports:
      - "7777:7777"
    volumes:
      # Mount config to persist token
      - ./config:/root/.config/nodetool
    # OR use environment variable
    environment:
      - SERVER_AUTH_TOKEN=${SERVER_AUTH_TOKEN}

Retrieving Your Token

From Console Output

The full token is displayed when the server starts:

======================================================================
AUTHENTICATION
======================================================================
Status: ENABLED (all endpoints require authentication)
Token: abc12345...xyz9
Source: Auto-generated and saved to /home/user/.config/nodetool/deployment.yaml

   Authorization: Bearer abc12345-full-token-here-xyz9
======================================================================

From Config File

# Linux/macOS
cat ~/.config/nodetool/deployment.yaml

# Windows PowerShell
Get-Content $env:APPDATA\nodetool\deployment.yaml

Programmatically

import { getServerAuthToken, getTokenSource } from "@nodetool-ai/deploy";

const token = getServerAuthToken();
const source = getTokenSource();  // "environment" | "config" | "generated"
console.log(`Token (${source}): ${token}`);

Security Best Practices

1. Protect the Config File

The config file contains your authentication token. It’s automatically set to 0600 permissions, but ensure it’s not:

  • Committed to version control
  • Shared publicly
  • Accessible by other users
# Verify permissions (should be -rw-------)
ls -la ~/.config/nodetool/deployment.yaml

# Fix if needed
chmod 600 ~/.config/nodetool/deployment.yaml

2. Rotate Tokens Regularly

# Delete the config file
rm ~/.config/nodetool/deployment.yaml

# Restart server (new token will be generated)
nodetool serve

Or set a new environment variable:

export SERVER_AUTH_TOKEN="$(openssl rand -base64 32)"

3. Use Different Tokens per Environment

# Development
export SERVER_AUTH_TOKEN="dev-token"

# Staging
export SERVER_AUTH_TOKEN="staging-token"

# Production
export SERVER_AUTH_TOKEN="$(openssl rand -base64 32)"

4. Use HTTPS in Production

Always deploy with TLS/SSL:

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://localhost:7777;
    }
}

Public Endpoints

These endpoints do not require authentication:

  • GET /health - Health check
  • GET /ready - Readiness check
  • GET /api/health - API health check

This allows load balancers and monitoring systems to check service status without authentication.


Error Responses

401 Unauthorized

In Supabase mode, requests without a valid token receive a 401 with an error field:

Missing token:

{
  "error": "Unauthorized"
}

Invalid / rejected token:

{
  "error": "<reason from the auth provider>"
}

Non-loopback request when running in Local mode:

{
  "error": "Remote access requires authentication"
}

Troubleshooting

Token Not Working

# Start server and check token source in output
nodetool serve
# Look for "Source: ..." in the output

# Verify token in config
cat ~/.config/nodetool/deployment.yaml

# Check environment variable
echo $SERVER_AUTH_TOKEN

Can’t Find Config File

# Check default location
ls -la ~/.config/nodetool/

# Create directory if missing
mkdir -p ~/.config/nodetool

# Restart server to generate token
nodetool serve

Token Not Persisting (Docker)

# Mount config directory as volume
docker run -v nodetool-config:/root/.config/nodetool \
  -p 7777:7777 \
  nodetool-server

# Or use environment variable
docker run -e SERVER_AUTH_TOKEN="your-token" \
  -p 7777:7777 \
  nodetool-server

API Client Examples

TypeScript / JavaScript

const TOKEN = "your-token-here";
const BASE_URL = "http://localhost:7777";

const headers = {
  "Authorization": `Bearer ${TOKEN}`,
  "Content-Type": "application/json",
};

// List models
const models = await fetch(`${BASE_URL}/v1/models`, { headers });
console.log(await models.json());

// Chat completion
const chat = await fetch(`${BASE_URL}/v1/chat/completions`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    model: "llama3.2:latest",
    messages: [{ role: "user", content: "Hello!" }],
  }),
});
console.log(await chat.json());

Python

import requests

TOKEN = "your-token-here"
BASE_URL = "http://localhost:7777"

headers = {"Authorization": f"Bearer {TOKEN}"}

# List models
response = requests.get(f"{BASE_URL}/v1/models", headers=headers)
print(response.json())

# Chat completion
response = requests.post(
    f"{BASE_URL}/v1/chat/completions",
    headers={**headers, "Content-Type": "application/json"},
    json={
        "model": "llama3.2:latest",
        "messages": [{"role": "user", "content": "Hello!"}]
    }
)
print(response.json())

Shell Script

#!/bin/bash

TOKEN=$(cat ~/.config/nodetool/deployment.yaml | grep server_auth_token | awk '{print $2}')
BASE_URL="http://localhost:7777"

# List models
curl -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/v1/models"

# Chat completion
curl -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -X POST "$BASE_URL/v1/chat/completions" \
  -d '{
    "model": "llama3.2:latest",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'

Advanced Configuration

Custom Config Location

import { loadAuthConfig, saveAuthConfig, generateSecureToken } from "@nodetool-ai/deploy";

// Load current config
const config = loadAuthConfig();

// Generate and save a new token
config["server_auth_token"] = generateSecureToken();
saveAuthConfig(config);

Localhost Trust and Reverse Proxies

By default the server bypasses authentication for connections from loopback (127.0.0.1/::1) and runs them as user 1. This is convenient for the desktop app and local development, but dangerous behind a reverse proxy, container network, or SSH tunnel, where the proxy itself connects from loopback — a blanket bypass would silently disable auth in exactly the deployment that needs it.

To make this safe:

  • The loopback bypass is gated by NODETOOL_TRUST_LOCALHOST. It defaults off whenever auth is enforced (Supabase mode) and on otherwise. Set it explicitly (true/false) to override.
  • X-Forwarded-For is trusted only for proxies listed in NODETOOL_TRUSTED_PROXIES (comma-separated IPs/CIDRs). When unset, the header is ignored and the unspoofable socket peer address is used to identify the client, so a remote client cannot forge a loopback origin.
# Behind an nginx reverse proxy on the same host, enforcing Supabase auth:
export SUPABASE_URL=... SUPABASE_KEY=...
export NODETOOL_TRUSTED_PROXIES=127.0.0.1   # trust the local proxy's XFF
# NODETOOL_TRUST_LOCALHOST stays off — clients must present a valid token.

Local mode in Docker

Docker NATs a published-port connection to the bridge gateway (e.g. 172.17.0.1), so in Local mode the request never arrives from loopback and the NODETOOL_TRUST_LOCALHOST bypass can’t fire — the UI loads (static assets and /api/config are public) but every API/WebSocket call returns 401 "Remote access requires authentication".

NODETOOL_TRUST_LOCAL_NETWORKS (comma-separated source CIDRs) restores the single-local-user model by trusting those sources as user "1" without a token. It is honored only in Local mode — in Supabase mode it is ignored so every request must present a valid token.

⚠️ This bypasses authentication

Every source IP in NODETOOL_TRUST_LOCAL_NETWORKS is trusted as admin user "1" with no password — full access to your workflows, files, stored secrets, and API keys. Anyone who can reach the published port from a listed range gets that access.

  • 172.16.0.0/12 — the Docker bridge range. Host and LAN clients reach the app through the port mapping, but nothing off the bridge is trusted. Use this.
  • 0.0.0.0/0 — trusts every source, the whole internet if the port is reachable. Never use this on a public IP. Only on a network you fully control (private LAN / VPN), ideally with the port firewalled.
  • Exposing NodeTool to the internet or untrusted users? Do not widen this list — enable Supabase auth so every request needs a real login.
# Docker self-host, single user, no login (safe on a private LAN):
NODETOOL_TRUST_LOCAL_NETWORKS=172.16.0.0/12   # the Docker bridge range

Security Hardening

  • Production: enable Supabase mode by setting SUPABASE_URL and SUPABASE_KEY, terminate TLS in front of all non-public endpoints, and rotate Supabase service-role keys via your secrets manager.
  • Localhost trust: keep NODETOOL_TRUST_LOCALHOST off in any reverse-proxied or containerized deployment, and list only your real proxies in NODETOOL_TRUSTED_PROXIES.
  • Staging: keep asset buckets private or signed, and run workflows in subprocess or Docker isolation.
  • Development: restrict Local mode to isolated machines and avoid storing real secrets in .env.development.

See Security Hardening for detailed checklists across dev, staging, and production.