Three fixes to how a node provisions its own master key when the operator sets no ROUTSTR_SECRET_KEY: - Publish the key atomically. It is written to a same-directory temp file, fsynced, then os.link-ed into place and the directory fsynced. os.link publishes the complete file in one step, so a crash mid-write can no longer strand an empty key at the final path that a later boot would read as corrupt and then fail to decrypt every secret under. os.link also refuses to clobber, so a racing worker that generated first keeps ownership and the loser adopts its key. - Tighten loose permissions on read. A key file that is group/other-readable is repaired to 0600 rather than trusted, keeping an upgrading node booting. - Stop printing the key value. The one-time notice names the file to back up and shouts the backup imperative, but no longer echoes the key itself, which would leak it into captured stdout / aggregated container logs; the durable 0600 file is the recovery path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Routstr Payment Proxy
Routstr is a decentralized protocol for permissionless, private, and censorship-resistant AI inference. It combines Nostr for discovery and Cashu for private Bitcoin micropayments.
This repo contains Routstr Core: a FastAPI-based reverse proxy that sits in front of OpenAI-compatible APIs and handles pay-per-request billing.
Start Here
- Overview: https://docs.routstr.com/overview/
- Provider Guide: https://docs.routstr.com/provider/quickstart/
- User Guide: https://docs.routstr.com/user-guide/introduction/
Basic Usage
If you are a user/developer, you just point an OpenAI-compatible SDK at a Routstr node and pay with a Cashu token.
OpenAI SDK
from openai import OpenAI
client = OpenAI(
base_url="https://api.routstr.com/v1",
api_key="cashuBo2FteCJodHRwczovL21...",
)
response = client.chat.completions.create(
model="gpt-5-nano",
messages=[{"role": "user", "content": "hello"}],
)
print(response.choices[0].message.content)
cURL
curl https://api.routstr.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-cashu: cashuBo2FteCJodHRwczovL21..." \
-d '{
"model": "gpt-5-nano",
"messages": [{"role": "user", "content": "hello"}]
}'
Quick Start (Docker)
If you are a node runner, start a Routstr Core instance using Docker Compose:
-
Prepare your
.env:# Optional: encrypts node secrets at rest. If unset, the node generates a key # on first start, writes it to routstr_secret.key, and prints it once — back # up that file. Set it explicitly to manage the key yourself (recommended in # production). ROUTSTR_SECRET_KEY=<generated-key> NAME="My AI Node" DESCRIPTION="Fast access to models" NSEC=yournsec RECEIVE_LN_ADDRESS=yourname@wallet.comIf you don't set one, a key is generated and printed on first start — save it somewhere safe (losing it makes previously encrypted secrets unreadable). To supply your own, generate it once and keep it stable:
uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" -
Start the services:
docker compose up -d -
Get your admin password: On first start the node generates an admin password and logs it once with the
/adminURL. Read it from the logs:docker compose logs routstr | grep -i admin(Lost it? Reset with
docker compose exec routstr /.venv/bin/python scripts/reset_admin_password.py --regenerate.) -
Configure: Open http://localhost:8000/admin/ to connect your AI providers and set pricing.
For full instructions, see the Provider Quick Start Guide.
Development
make setup
cp .env.example .env
fastapi run routstr