Review findings on 84b0246:
HIGH — the quote was computed from the incoming header while the forwarding
sanitizer may strip it: forward_with_trailer removes every header a Connection
field nominates, so `Connection: X-Tinfoil-Max-Output-Tokens` with cap=8 was
quoted at 8 while the enclave received no cap at all, leaving output unbounded
against a smaller hold. enforced_completion_cap now runs the real
_strip_hop_by_hop_headers over the request headers and additionally refuses when
any Connection field nominates the cap (case-insensitive, comma-split); either
signal yields no quote and the full envelope. Provider target headers are merged
after that strip and must never re-add the cap (Tinfoil's adds only usage
metrics), documented at the hook.
MEDIUM — _model_token_window treated top_provider.max_completion_tokens as the
total window. That value is the output share of the window, not the window: a
100k-context model with a 10k completion cap quoted 13_000 msats while a valid
30k prompt + 1k output bills 34_000. The window is now top_provider.context_length
then model.context_length; a completion-only limit is never a context and an
unknown window keeps the full envelope.
Regression coverage runs the real sanitizer rather than only the forwarding mock:
Connection nomination (exact, case-insensitive, unrelated-token preservation),
end-to-end full-envelope reserve under nomination, and the window cases
(completion-only top_provider -> 100k window; context_length fallback -> 50k).
Still disabled: EHBP_OUTPUT_TOKEN_RESERVATION_ENABLED = False and no features
advertised, pending an upstream request-total guarantee.
Tests: 74 dedicated cases, 194 across the touched suites; ruff/mypy clean.
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/
- Client Guide: https://docs.routstr.com/client/introduction/
- Browse providers and models: https://routstr.com/providers
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)
Start a provider node with the guided setup. The default assumes you already have
a subdomain with HTTPS and a reverse proxy forwarding to 127.0.0.1:8000, so the
node publishes itself on Nostr as soon as it starts. Without a public URL yet, use
--private and publish later. Agents: read llms.txt first, and rotate
the bootstrap admin password before publishing.
git clone https://github.com/Routstr/routstr-core.git
cd routstr-core
# production: check out a reviewed release tag
# Phase 0: DNS + TLS + reverse proxy -> 127.0.0.1:8000
# Phase 1: start (public is the default)
python3 scripts/node_setup.py start \
--public-url https://node.example \
--ln-address you@wallet.com
# Phase 2: back up before configuring
# routstr_secret.key, keys.db (+ -wal/-shm), .wallet/ and .env
docker compose -f compose.node.yml exec routstr \
/.venv/bin/python scripts/reveal_nsec.py
# Phase 3: dashboard - rotate the admin password, add an upstream, set pricing
docker compose -f compose.node.yml logs routstr | grep -i admin
# Phase 4: verify
python3 scripts/node_setup.py check --public-url https://node.example
compose.node.ymlbinds the API to127.0.0.1only, starts no Tor service, and starts with analytics off. Your reverse proxy fronts it.- Private mode (
python3 scripts/node_setup.py start --private) keeps the node loopback-only and publishes nothing until you set a public URL later. - Port 8000 taken? Pass
--port 18080and point the proxy at it. - Use
docker compose -f compose.node.yml ...for every later Compose command. Plaindocker compose upselects the different public/Tor stack. startalso mints a long-lived CLI token and writes~/.routstr/config.json(mode0600; an existing config for another node is left untouched) so the Routstr CLI can operate the node (routstr instruct,routstr providers list, ...). The token is full node admin; revoke it in Settings → CLI Tokens when done (--no-cli-tokenskips creation).
See the provider quickstart and the deployment guide for publishing, backups and updates.
Development
make setup
cp .env.example .env
fastapi run routstr