Adding Services
Step-by-Step Guide
1. Choose a Port
Select an available port from the appropriate category:
| Category | Port Range | Example |
|---|---|---|
| Document Processing | 5100-5199 | 5102, 5103, … |
| Voice & Audio | 5200-5299 | 5220, 5221, … |
| Correspondence | 5300-5399 | 5310, 5311, … |
| Text Processing | 5400-5499 | 5410, 5411, … |
| Events & Reporting | 5500-5599 | 5510, 5511, … |
| Heritage Projects | 5720-5799 | 5720, 5721, … |
Check existing ports in: {PLATFORM_DIR}/projects/registry.json
2. Add Route Configuration
Edit {PLATFORM_DIR}/config/routes.yaml:
- path: /myservice
port: 5150
name: My Service Name
domain: myservice.lettre.ai
description: Brief description of the serviceBackends on another machine, and a fallback
host: names the tailnet machine that runs the backend when it is not the tunnel machine (the editions run on openscience, 100.87.180.115; INQUA on mac-studio, 100.102.189.23). fallback_hosts: lists further machines that run the same backend on the same port:
- path: /kepler-gw
port: 5104
host: 100.87.180.115 # openscience: faster uplink for images and data
fallback_hosts: [100.102.189.23] # mac-studio runs the same editions supervisor
health_url: http://100.87.180.115:5104/The proxy probes every upstream (the health path of health_url on each host), forwards to the first healthy one in declared order, and fails over at once when a host refuses or does not answer the connect: a request that never reached a backend moves to the next host for any method, a request the backend may have started (read timeout) only for GET, HEAD and OPTIONS. A failed host is marked down until the next probe sees it. The answering host is in the X-Upstream response header (host:port), /_proxy/state lists upstreams with their health and the active_upstream of each route. Both proxies (mac-studio and openscience) read the same synced routes.yaml; on each machine the host that is itself becomes loopback. platform/scripts/editionsctl.py check probes every route on both proxies and on the public address.
3. Create Your Service
Your service must: - Listen on the configured port - Bind to 0.0.0.0 (all interfaces) - Serve on / (router strips the path prefix)
Example FastAPI service:
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello from My Service"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=5150)4. Create LaunchAgent (Optional)
For auto-start on login, create {HOME_DIR}/Library/LaunchAgents/com.myservice.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myservice</string>
<key>ProgramArguments</key>
<array>
<string>conda</string>
<string>run</string>
<string>-n</string>
<string>voicememo-py314</string>
<string>python</string>
<string>/path/to/your/server.py</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>Load with: launchctl load ~/Library/LaunchAgents/com.myservice.plist
5. Configure IONOS Domain
See IONOS Setup for configuring the subdomain.
6. Restart Router
# Kill existing router
pkill -f "ai-platform/router/server.py"
# Start new router (machine-portable)
cd {PLATFORM_DIR}
conda run -n voicememo-py314 python router/server.pyOr reload LaunchAgent:
launchctl unload ~/Library/LaunchAgents/com.ai-platform.router.plist
launchctl load ~/Library/LaunchAgents/com.ai-platform.router.plist7. Test
# Test via local router
curl http://localhost:5001/myservice/
# Test via ngrok
curl https://lettre.ngrok.dev/myservice/
# Test via IONOS domain
curl https://myservice.lettre.ai