Paperclip Deployment Engineerandthefux 9d0cd41e5a fix(upstream): report upstream 5xx as 424 + UPSTREAM_UNAVAILABLE, not node-down
CORE-UPSTREAM-5XX-NOT-NODE-DOWN

An upstream-attributable failure (provider 5xx, EHBP timeout, transport error
after a Cashu token was redeemed) was forwarded to the caller as the
provider's own 5xx. Callers read that as *this node* being down: they marked
the node unhealthy, dropped it from rotation, or refused to retry an upstream
blip that the node had already retried across candidates.

The node is healthy in those cases — it accepted the request, authenticated
it, reserved payment, tried every candidate, and reverted the reservation.
That is now visible from the response alone:

  status            424 (UPSTREAM_ERROR_STATUS)
  error.code        UPSTREAM_UNAVAILABLE
  header            X-Routstr-Error-Scope: upstream
  error.upstream_status / error.details.upstream_status
                    the provider's own status, preserved

Applied in routstr/upstream/base.py (forward_upstream_error_response and the
post-redemption x-cashu paths for both chat-completions and responses),
routstr/payment/helpers.py (create_error_response / create_upstream_error_response),
routstr/proxy.py (424 is retryable across candidates on the bearer, EHBP and
unauthenticated-GET loops), routstr/upstream/ehbp.py and
routstr/upstream/tinfoil.py (attestation host). New module
routstr/core/error_scope.py holds the contract constants and the mapping.

Deliberately unchanged:
  * rate limits keep 429 + UPSTREAM_RATE_LIMIT (including a rate limit wrapped
    in a provider 5xx envelope: status 429, code unchanged) — the retry hint is
    worth more than the status class;
  * provider-side 4xx passes through unchanged;
  * genuine node faults stay 500 and carry no scope header (UpstreamError gained
    scope=, set to "node" on internal-exception paths) so "node broken" is still
    distinguishable from "upstream broken".

The new status is exported through CORS (x-routstr-error-scope) so browser
clients can read the attribution.

Tests: 15 stale assertions of the old 5xx contract updated (renames keep their
intent: refund still happens, bodies still redacted, pinned requests still do
not fall back), plus new acceptance coverage for 424 + scope header +
upstream_status on the provider, bearer, x-cashu, /v1/messages, EHBP and
unauthenticated-GET paths, node faults staying 500 with no header, and failover
past a 424 to a healthy candidate returning 200.

Docs: docs/api/errors.md (status table, new "Upstream attribution" section,
upstream error examples, retry list now includes 424), docs/api/overview.md
and docs/api/endpoints.md.

Full unit suite: 1649 passed, 1 skipped.
2026-09-23 15:16:44 +00:00
2026-09-16 13:15:32 +02:00
2025-10-22 12:18:00 +08:00
2026-08-06 22:58:44 +02:00
2025-06-09 23:46:19 +02:00
2025-08-12 15:02:22 -03:00
2025-04-08 21:42:32 +08:00
2025-08-01 22:31:18 -03:00

Routstr Payment Proxy

License Stars Issues Release

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

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, the recommended way to start Routstr Core is to clone the repository at the latest release and run it with Docker Compose:

  1. Clone the latest release:

    git clone https://github.com/Routstr/routstr-core.git
    cd routstr-core
    git checkout v0.4.7   # current release — see https://github.com/Routstr/routstr-core/releases/latest
    

    Docker Compose builds the node and the admin dashboard from source, so there is no image to pull.

  2. Prepare your .env:

    cp .env.example .env
    

    Then edit it with your details:

    # 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"
    RECEIVE_LN_ADDRESS=yourname@wallet.com
    

    Your Nostr identity (nsec) is not set in .env — configure it from the admin UI after first start, where it's stored encrypted in the database. (NSEC in .env is still read once as a legacy seed for existing deployments.)

    If 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())"
    
  3. Start the services:

    docker compose up -d
    

    The first start builds both images (the dashboard build takes a few minutes).

  4. Get your admin password: On first start the node generates an admin password and logs it once with the /admin URL. 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.)

  5. 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
S
Description
Routstr is a decentralized protocol for permissionless, private, and censorship-resistant AI inference.
Readme GPL-3.0
33 MiB
Languages
Python 79.8%
TypeScript 18.3%
HTML 1.2%
JavaScript 0.2%
Makefile 0.2%
Other 0.3%