Service
packages/service is the core of Routerly. It is a Fastify HTTP server that handles authentication, intelligent model routing, provider dispatch, cost accounting, and budget enforcement.
The CLI and the dashboard both communicate with the running service over HTTP — there is no other inter-process communication.
Startup Sequence
When the service starts it performs the following steps in order:
initConfigDirs— Creates~/.routerly/config/and~/.routerly/data/if they do not exist.loadSecret— Reads the AES-256 encryption key from~/.routerly/config/secret, generating and persisting it automatically if missing. API keys are encrypted with this secret at rest.readConfig('settings')— Loadssettings.json. Missing file → defaults are written and used.buildServer— Registers Fastify plugins, routes, and middleware in this order:- CORS (
@fastify/cors) - Dashboard static files (
@fastify/staticat/dashboard, only ifdashboardEnabled: true) - Management API routes (
/api/*) - Auth guard plugin (validates
Bearer sk-rt-*tokens for/v1/*routes) - LLM Proxy routes (
/v1/*) - Root redirect (
/→/dashboard/) - Health check (
/health)
- CORS (
server.listen— Binds tohost:portfrom settings (defaults:0.0.0.0:3000).
Running Manually
The service is normally managed as a background daemon by the installer. You can also start it directly:
# From the monorepo root (development)
npm run dev --workspace packages/service
# Standalone binary (after installation)
~/.routerly/app/routerly-service
# Specify a custom config/data directory
ROUTERLY_HOME=/opt/routerly ~/.routerly/app/routerly-service
Environment variables override settings.json — see Environment Variables for the full list.
Health Check
The service exposes a lightweight health endpoint at GET /health:
curl http://localhost:3000/health
{
"status": "ok",
"version": "0.1.5",
"timestamp": "2026-03-27T12:00:00.000Z"
}
Returns HTTP 200 when the process is up and accepting connections. This endpoint does not require authentication and is suitable for load balancer and container health probes.
Configuration Storage
All state is stored as JSON files on disk — there is no external database. The base directory defaults to ~/.routerly/ and can be overridden with $ROUTERLY_HOME.
~/.routerly/
├── config/
│ ├── settings.json # Port, log level, dashboard toggle, timeout, public URL
│ ├── models.json # Registered LLM models (API keys AES-256 encrypted)
│ ├── projects.json # Projects, routing policies, tokens, members, budgets
│ ├── users.json # Dashboard users (passwords bcrypt-hashed)
│ ├── roles.json # Custom RBAC role definitions
│ └── secret # AES-256 encryption key (auto-generated, never commit)
└── data/
└── usage.json # Append-only call records (tokens, cost, latency, outcome)
All writes to config files use a file lock (proper-lockfile) to prevent concurrent corruption. Missing files are auto-created with their defaults on first read.
Process Signals
| Signal | Behaviour |
|---|---|
SIGTERM | Graceful shutdown — Fastify drains in-flight requests before exiting |
SIGINT | Same as SIGTERM (Ctrl-C in a terminal) |
SIGHUP | Not specially handled — restart the process to reload config |
Config changes made via the CLI or dashboard API take effect immediately (the service re-reads files on each request) without a restart, except for port and host which require a restart.
Logging
The service uses pino via Fastify's built-in logger.
NODE_ENV | Format | Default level |
|---|---|---|
| development | pretty-printed with colours (pino-pretty) | info |
| production | JSON (one object per line) | info |
Change the log level in settings.json, via the dashboard (Settings → General → Log Level), or with the ROUTERLY_LOG_LEVEL environment variable.
Related
- Service — HTTP Endpoints — all routes the service exposes
- Service — Routing Engine — how model selection works
- Service — Provider Adapters — how requests are forwarded to providers
- Reference — Configuration Files
- Reference — Environment Variables