### Set up the development environment Source: https://docs.routstr.com/contributing/setup Runs the setup command that installs uv, creates a virtual environment, installs all dependencies and dev tools, and installs the project in editable mode. ```bash make setup ``` -------------------------------- ### Verify installation Source: https://docs.routstr.com/contributing/setup Verifies the installation by checking dependencies, running unit tests, and starting the development server. ```bash # Check dependencies make check-deps # Run unit tests make test-unit # Start development server make dev ``` -------------------------------- ### Start production Docker Compose Source: https://docs.routstr.com/provider/deployment Starts the production Docker Compose setup that builds the UI, runs the Python node, and starts Tor for anonymous access via a .onion address. ```bash docker compose up -d ``` -------------------------------- ### Manual integration test setup with Docker Compose Source: https://docs.routstr.com/contributing/testing Manually starts required Docker services (Cashu mint, mock OpenAI, Nostr relay), runs integration tests with local services enabled, and then stops the containers. This sequence is used for Docker-mode integration testing. ```bash docker-compose -f compose.testing.yml up -d USE_LOCAL_SERVICES=1 pytest tests/integration/ -v docker-compose -f compose.testing.yml down -v ``` -------------------------------- ### Example .env configuration for Routstr node Source: https://docs.routstr.com/provider/configuration Create this .env file in the project root to bootstrap initial configuration and store sensitive secrets. ROUTSTR_SECRET_KEY is optional; if unset, the node generates a key on first start and prints it once. Set ADMIN_PASSWORD in .env before first start to seed the initial admin password instead of generating one. ```dotenv # Encrypts node secrets at rest. Optional — if unset, the node generates a key on # first start and prints it once (back it up). Set it to manage the key yourself # (recommended in production). See "Secrets at Rest" below. ROUTSTR_SECRET_KEY= # Node Identity NAME="My AI Node" DESCRIPTION="Fast access to models" # Lightning Payouts RECEIVE_LN_ADDRESS=yourname@wallet.com ``` -------------------------------- ### Start the node with Docker Compose Source: https://docs.routstr.com/provider/quickstart Run this command to start the Routstr node, UI, and optional services like Tor. Requires Docker and a configured .env file. ```bash docker compose up -d ``` -------------------------------- ### Create environment file Source: https://docs.routstr.com/contributing/setup Creates the environment file from the example template. ```bash cp .env.example .env ``` -------------------------------- ### Install project in editable mode Source: https://docs.routstr.com/contributing/setup Run these commands to install the project in editable mode, which is required to avoid import errors. ```bash # Ensure project is installed in editable mode uv sync uv pip install -e . ``` -------------------------------- ### Build from source (optional) Source: https://docs.routstr.com/provider/quickstart If you cloned the repository, build the images yourself before starting. This is an alternative to using pre-built images. ```bash docker compose build docker compose up -d ``` -------------------------------- ### Makefile commands for development Source: https://docs.routstr.com/contributing/setup Key Makefile commands for development. Use make help to list all commands, make setup for initial setup, make dev to run the dev server, make test to run all tests, make lint and make format for code style, make type-check for type checking, make clean to remove temporary files, and make docker-build to build the Docker image. ```makefile make help # Show all commands make setup # Initial setup make dev # Run dev server make test # Run all tests make lint # Check code style make format # Fix code style make type-check # Check types make clean # Clean temp files make docker-build # Build Docker image ``` -------------------------------- ### Python SDK client setup Source: https://docs.routstr.com/api/overview Initialize the OpenAI Python client with a custom base URL for Routstr. ```python from openai import OpenAI client = OpenAI( api_key="sk-...", base_url="https://your-node.com/v1" ) ``` -------------------------------- ### Run development server Source: https://docs.routstr.com/contributing/setup Starts the development server with auto-reload. The server runs at http://localhost:8000 and reloads on code changes. ```bash make dev # Server starts at http://localhost:8000 # Auto-reloads on code changes ``` -------------------------------- ### Docker Compose setup for Routstr Source: https://docs.routstr.com/provider/deployment Defines the Routstr service with persistent storage and environment variables, then starts it in detached mode. The compose file sets the database URL and log level. ```yaml version: '3.8' services: routstr: image: 9qeklajc/routstr:latest container_name: routstr restart: unless-stopped ports: - "8000:8000" volumes: - routstr-data:/app/data environment: DATABASE_URL: "sqlite:////app/data/routstr.db" LOG_LEVEL: "info" volumes: routstr-data: ``` ```bash docker compose up -d ``` -------------------------------- ### Initialize OpenAI SDK with Routstr base URL Source: https://docs.routstr.com/client/integration Minimal initialization of the OpenAI SDK pointing at Routstr. Use any provider's URL in place of the example. ```python client = OpenAI(base_url="https://api.routstr.com/v1", api_key="sk-...") # or any provider's URL ``` -------------------------------- ### Refund response example Source: https://docs.routstr.com/client/payments Example response from the refund endpoint, containing a Cashu token and the refunded amount in millisatoshis. ```json { "token": "cashuAeyJ0b2tlbiI6W3sibWludCI6...", "msats": "450000" } ``` -------------------------------- ### JavaScript/TypeScript SDK client setup Source: https://docs.routstr.com/api/overview Initialize the OpenAI JavaScript/TypeScript client with a custom base URL for Routstr. ```javascript import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: 'sk-...', baseURL: 'https://your-node.com/v1' }); ``` -------------------------------- ### GET /v1/models Source: https://docs.routstr.com/api/endpoints Get available models and pricing. ```APIDOC ## GET /v1/models ### Description Get available models and pricing. ### Method GET ### Endpoint /v1/models ### Response #### Success Response (200) - **object** (string) - Always "list". - **data** (array) - List of model objects. - **id** (string) - Model identifier. - **object** (string) - Always "model". - **created** (integer) - Unix timestamp of creation. - **owned_by** (string) - Owner of the model. - **permission** (array) - Permissions array. - **root** (string) - Root model identifier. - **parent** (null) - Parent model. - **pricing** (object) - Pricing information. - **prompt** (number) - Price per unit for prompt tokens. - **completion** (number) - Price per unit for completion tokens. - **unit** (string) - Unit of pricing (e.g., "1k tokens"). #### Response Example ```json { "object": "list", "data": [ { "id": "gpt-3.5-turbo", "object": "model", "created": 1677610602, "owned_by": "openai", "permission": [...], "root": "gpt-3.5-turbo", "parent": null, "pricing": { "prompt": 0.001, "completion": 0.002, "unit": "1k tokens" } } ] } ``` ``` -------------------------------- ### PPQ history record example Source: https://docs.routstr.com/tinfoil-direct-integration Example record returned by the PPQ query history endpoint. Contains timestamp, model, input/output counts, price in USD, query type, and query source. ```json { "timestamp": "...", "model": "private/gpt-oss-120b", "input_count": 74, "output_count": 3, "price_in_usd": 0.00006250875, "query_type": "chat_completion", "query_source": "api" } ``` -------------------------------- ### PPQ cost header example Source: https://docs.routstr.com/tinfoil-direct-integration Example of a PPQ cost header that should be returned outside the encrypted response body for blind-relay exact billing. ```text X-PPQ-Cost-USD: 0.00006250875 ``` -------------------------------- ### Integration test for wallet topup Source: https://docs.routstr.com/contributing/testing Integration test example for wallet top-up. It uses the `authenticated_client`, `testmint_wallet`, and `db_snapshot` fixtures from `tests/integration/conftest.py`. The test mints a Cashu token, posts it to `/v1/wallet/topup`, and verifies the response status and database snapshot diff. ```python import pytest from httpx import AsyncClient @pytest.mark.integration @pytest.mark.asyncio async def test_wallet_topup( authenticated_client: AsyncClient, testmint_wallet: object, db_snapshot: object, ) -> None: await db_snapshot.capture() token = await testmint_wallet.mint_tokens(1000) response = await authenticated_client.post( "/v1/wallet/topup", params={"cashu_token": token} ) assert response.status_code == 200 diff = await db_snapshot.diff() assert len(diff["api_keys"]["modified"]) == 1 ``` -------------------------------- ### Multi-Key Authentication with Environment Variables Source: https://docs.routstr.com/api/authentication Use multiple API keys for different purposes, such as production, development, and analytics. The example shows retrieving keys from environment variables and selecting the appropriate one based on the environment. ```python # Production key for main app PROD_KEY = os.getenv("ROUTSTR_PROD_KEY") # Development key for testing DEV_KEY = os.getenv("ROUTSTR_DEV_KEY") # Analytics key with restricted permissions ANALYTICS_KEY = os.getenv("ROUTSTR_ANALYTICS_KEY") # Choose key based on environment api_key = PROD_KEY if is_production() else DEV_KEY ``` -------------------------------- ### TinfoilAI chat completion example Source: https://docs.routstr.com/tinfoil-direct-integration Example using Tinfoil's OpenAI-compatible client. Creates a chat completion with the 'llama3-3-70b' model and logs the response content and usage. ```typescript import { TinfoilAI } from "tinfoil"; const client = new TinfoilAI({ apiKey: process.env.TINFOIL_API_KEY, transport: "ehbp", }); const res = await client.chat.completions.create({ model: "llama3-3-70b", messages: [{ role: "user", content: "Hello" }], }); console.log(res.choices[0].message.content); console.log(res.usage); ``` -------------------------------- ### Get node info with GET /v1/info Source: https://docs.routstr.com/api/overview Retrieve node metadata by sending a GET request to /v1/info. Supported models and pricing are available at /v1/models, and upstream provider path discovery is at /v1/models/paths. ```http GET /v1/info ``` -------------------------------- ### GET /v1/balance/info Source: https://docs.routstr.com/api/endpoints Get current balance and consumption data for an API key. ```APIDOC ## GET /v1/balance/info ### Description Get current balance and consumption data for an API key. ### Method GET ### Endpoint /v1/balance/info ### Parameters #### Headers - **Authorization** (string) - Required - Bearer token with the API key. ### Response #### Success Response (200) - **api_key** (string) - The API key. - **balance** (number) - The spendable balance used by request admission. - **reserved** (number) - Reserved amount. - **total_requests** (number) - Total number of requests made. - **total_spent** (number) - Total amount spent. - **validity_date** (string or null) - Validity date of the key, or null. #### Response Example { "api_key": "sk-abc...", "balance": 8500000, "reserved": 0, "total_requests": 42, "total_spent": 1500000, "validity_date": null } ``` -------------------------------- ### Run all-in-one Docker image Source: https://docs.routstr.com/provider/deployment Starts the all-in-one Routstr container with a persistent volume and SQLite database. Access the API and admin dashboard at http://localhost:8000. ```bash docker run -d \ --name routstr \ -p 8000:8000 \ -v routstr-data:/app/data \ -e DATABASE_URL="sqlite:////app/data/routstr.db" \ 9qeklajc/routstr:latest ``` -------------------------------- ### Makefile development commands Source: https://docs.routstr.com/contributing/code-structure Provides Makefile targets for setup, development server, testing, and linting. Use these commands to manage the project's common development tasks. ```makefile # Setup commands setup: uv sync uv pip install -e . # Development server dev: fastapi dev routstr --host 0.0.0.0 # Testing test: uv run pytest # Code quality lint: uv run ruff check . ``` -------------------------------- ### GET /redoc Source: https://docs.routstr.com/api/overview Provides ReDoc interactive documentation for the Routstr API. ```APIDOC ## GET /redoc ### Description Provides ReDoc interactive documentation for the Routstr API. ### Method GET ### Endpoint /redoc ### Response #### Success Response (200) - **Content-Type** (string) - Response format - **Content-Length** (integer) - Response size - **X-Request-ID** (string) - Unique request identifier - **X-Cashu** (string) - Change token (when request used `X-Cashu` header) #### Response Example (HTML content for ReDoc) ``` -------------------------------- ### Stream chat completion with OpenAI SDK (Python) Source: https://docs.routstr.com/client/integration Full Python example using the official OpenAI SDK with Routstr. Initializes the client with a session key, then streams a chat completion and prints the delta content. ```python from openai import OpenAI # 1. Initialize with Routstr URL and your funded key client = OpenAI( base_url="https://api.routstr.com/v1", api_key="sk-7f8e9d..." ) # 2. Call the API normally response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello!"}], stream=True ) for chunk in response: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") ``` -------------------------------- ### GET /docs Source: https://docs.routstr.com/api/overview Provides interactive Swagger UI documentation for the Routstr API. ```APIDOC ## GET /docs ### Description Provides interactive Swagger UI documentation for the Routstr API. ### Method GET ### Endpoint /docs ### Response #### Success Response (200) - **Content-Type** (string) - Response format - **Content-Length** (integer) - Response size - **X-Request-ID** (string) - Unique request identifier - **X-Cashu** (string) - Change token (when request used `X-Cashu` header) #### Response Example (HTML content for Swagger UI) ``` -------------------------------- ### GET /v1/balance/create Source: https://docs.routstr.com/client/payments Creates a new balance by importing a Cashu token. The token is claimed and an API key with the corresponding balance is issued. ```APIDOC ## GET /v1/balance/create ### Description Creates a new balance by importing a Cashu token. The token is claimed and an API key with the corresponding balance is issued. ### Method GET ### Endpoint /v1/balance/create ### Parameters #### Query Parameters - **initial_balance_token** (string) - Required - The Cashu token to import as the initial balance. ### Request Example GET /v1/balance/create?initial_balance_token=cashuA... ### Response #### Success Response (200) - **api_key** (string) - The newly issued API key (sk-...) with the imported balance. #### Response Example { "api_key": "sk-..." } ``` -------------------------------- ### Streaming response example (SSE) Source: https://docs.routstr.com/api/overview Shows the format of streaming responses using Server-Sent Events. Each data line is a chunk, ending with a [DONE] marker. ```text data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]} data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":" there"},"finish_reason":null}]} data: [DONE] ``` -------------------------------- ### Get Key Information (GET /v1/balance/info) Source: https://docs.routstr.com/api/endpoints Get current balance and consumption data for an API key. The response includes the api_key, spendable balance, reserved amount, total_requests, total_spent, and validity_date. The balance field is the spendable balance used by request admission. ```http GET /v1/balance/info Authorization: Bearer sk-... ``` ```json { "api_key": "sk-abc...", "balance": 8500000, "reserved": 0, "total_requests": 42, "total_spent": 1500000, "validity_date": null } ``` -------------------------------- ### Create Completion response Source: https://docs.routstr.com/api/endpoints Example JSON response from the Create Completion endpoint, including generated text, finish reason, and usage token counts. ```json { "id": "cmpl-123", "object": "text_completion", "created": 1677652288, "model": "gpt-3.5-turbo-instruct", "choices": [{ "text": " in a faraway land, there lived a brave knight...", "index": 0, "logprobs": null, "finish_reason": "length" }], "usage": { "prompt_tokens": 4, "completion_tokens": 50, "total_tokens": 54 } } ``` -------------------------------- ### Prepare .env configuration Source: https://docs.routstr.com/provider/quickstart Create a .env file in the project root with these secrets. ROUTSTR_SECRET_KEY is optional; if unset, the node generates a key on first start and prints it once. The admin password and Nostr identity are generated and stored encrypted in the database, not in .env. ```plaintext # Encrypts node secrets at rest. Optional — if unset, the node generates a key on # first start and prints it once (back it up). ROUTSTR_SECRET_KEY= # Node Identity NAME="My AI Node" DESCRIPTION="Fast access to models" # Lightning Payouts RECEIVE_LN_ADDRESS=yourname@wallet.com ``` -------------------------------- ### Check Balance (GET /v1/wallet/balance) Source: https://docs.routstr.com/api/endpoints Get the current wallet balance. The response includes the balance, currency (sat), and reserved amount. ```http GET /v1/wallet/balance Authorization: Bearer sk-... ``` ```json { "balance": 8500, "currency": "sat", "reserved": 0 } ``` -------------------------------- ### Private usage metrics header example Source: https://docs.routstr.com/tinfoil-direct-integration Example of a private usage metrics header that should be returned outside the encrypted response body for blind-relay exact billing. ```text X-Private-Usage-Metrics: input=74,output=3 ``` -------------------------------- ### Create chat completion with OpenAI SDK (Node.js) Source: https://docs.routstr.com/client/integration Node.js example using the OpenAI SDK with Routstr. Accepts either a session key or a raw Cashu token as the API key, then creates a chat completion and logs the first choice. ```javascript import OpenAI from 'openai'; // You can use a session key OR a raw Cashu token directly const openai = new OpenAI({ baseURL: 'https://api.routstr.com/v1', apiKey: 'cashuA3s8jKx9...', // or 'sk-7f8e9d...' }); async function main() { const completion = await openai.chat.completions.create({ messages: [{ role: 'user', content: 'Say this is a test' }], model: 'gpt-3.5-turbo', }); console.log(completion.choices[0]); } main(); ``` -------------------------------- ### Create Completion request body Source: https://docs.routstr.com/api/endpoints Example JSON request body for the Create Completion endpoint. Uses model gpt-3.5-turbo-instruct with a prompt, max_tokens, and temperature. ```json { "model": "gpt-3.5-turbo-instruct", "prompt": "Once upon a time", "max_tokens": 50, "temperature": 0.7 } ``` -------------------------------- ### Tinfoil usage metrics header example Source: https://docs.routstr.com/tinfoil-direct-integration Example of a Tinfoil usage metrics header that should be returned outside the encrypted response body for blind-relay exact billing. ```text X-Tinfoil-Usage-Metrics: prompt=74,completion=3,total=77 ``` -------------------------------- ### List Models - GET /v1/models Source: https://docs.routstr.com/api/endpoints Lists available models and pricing. The response includes model metadata and per-model pricing in USD per 1k tokens. ```http GET /v1/models ``` ```json { "object": "list", "data": [ { "id": "gpt-3.5-turbo", "object": "model", "created": 1677610602, "owned_by": "openai", "permission": [...], "root": "gpt-3.5-turbo", "parent": null, "pricing": { "prompt": 0.001, "completion": 0.002, "unit": "1k tokens" } } ] } ``` -------------------------------- ### Environment Variables - ROUTSTR_API_KEY and ROUTSTR_BASE_URL Source: https://docs.routstr.com/api/authentication Shows how to store the API key and base URL in a .env file and load them in code using os.getenv. This follows the security best practice of keeping keys out of version control. The example uses the ROUTSTR_API_KEY and ROUTSTR_BASE_URL environment variables. ```bash # .env file ROUTSTR_API_KEY=sk-1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p ROUTSTR_BASE_URL=https://your-node.com/v1 # Usage in code import os api_key = os.getenv("ROUTSTR_API_KEY") ``` -------------------------------- ### Provider Info - GET /v1/providers/{provider_name} Source: https://docs.routstr.com/api/endpoints Gets details for a specific provider by name. The response includes display name, description, models with pricing, endpoints, features, and status. ```http GET /v1/providers/{provider_name} ``` ```json { "name": "openai", "display_name": "OpenAI", "description": "Official OpenAI API", "models": [ { "id": "gpt-4", "name": "GPT-4", "context_window": 8192, "pricing": { "prompt": 0.03, "completion": 0.06, "unit": "1k tokens" } } ], "endpoints": ["chat/completions", "completions", "embeddings"], "features": ["streaming", "function_calling"], "status": "active" } ``` -------------------------------- ### GET /admin/api/settings Source: https://docs.routstr.com/api/endpoints Retrieves the current application settings. Sensitive values may be redacted. ```APIDOC ## GET /admin/api/settings ### Description Returns the current application settings. Sensitive values may be redacted. ### Method GET ### Endpoint /admin/api/settings ### Response #### Success Response (200) - **settings** (object) - The current application settings, with sensitive values possibly redacted. #### Response Example { "settings": {} } ``` -------------------------------- ### PPQ test request response Source: https://docs.routstr.com/tinfoil-direct-integration Example response from a test request to 'private/gpt-oss-120b'. Shows token counts and the computed price in USD. ```json { "input_count": 74, "output_count": 3, "price_in_usd": 0.00006250875 } ``` -------------------------------- ### Structured logging setup in routstr/core/logging.py Source: https://docs.routstr.com/contributing/code-structure Configures JSON structured logging and defines a filter to attach request IDs to log records. ```python def setup_logging(): """Configure JSON structured logging""" class RequestIdFilter(logging.Filter): """Attach request ID to log records.""" ``` -------------------------------- ### GET /openapi.json Source: https://docs.routstr.com/api/overview Retrieves the complete OpenAPI 3.0 specification for the Routstr API. ```APIDOC ## GET /openapi.json ### Description Retrieves the complete OpenAPI 3.0 specification for the Routstr API. ### Method GET ### Endpoint /openapi.json ### Response #### Success Response (200) - **Content-Type** (string) - Response format - **Content-Length** (integer) - Response size - **X-Request-ID** (string) - Unique request identifier - **X-Cashu** (string) - Change token (when request used `X-Cashu` header) #### Response Example { "openapi": "3.0.0", "info": { "title": "Routstr API", "version": "1.0.0" } } ``` -------------------------------- ### Authenticate with API key as query parameter (GET /v1/models) Source: https://docs.routstr.com/api/authentication Pass the API key as a query parameter for tools that don't support headers. Warning: query parameters may be logged; use headers when possible. ```http GET /v1/models?api_key=sk-1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p ``` -------------------------------- ### Add a database model: Transaction (SQLModel) Source: https://docs.routstr.com/contributing/setup Define a new database model in routstr/core/db.py using SQLModel. The example shows a Transaction table with fields for id, api_key_id, amount, timestamp, and description. After defining the model, create and apply a migration with make db-migrate and make db-upgrade. ```python class Transaction(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) api_key_id: int = Field(foreign_key="apikey.id") amount: int # millisatoshis timestamp: datetime = Field(default_factory=datetime.utcnow) description: str ``` -------------------------------- ### Pre-configure Routstr with .env file Source: https://docs.routstr.com/provider/deployment Pre-configure Routstr using an .env file referenced by the compose file. The .env example sets upstream, node identity, Lightning address, and database path; leaving ROUTSTR_SECRET_KEY unset generates a key stored as routstr_secret.key next to the database on the same volume. ```yaml services: routstr: image: ghcr.io/routstr/proxy:latest env_file: - .env volumes: - ./data:/app/data ``` ```dotenv UPSTREAM_BASE_URL=https://api.openai.com/v1 UPSTREAM_API_KEY=sk-proj-... # Keep the database (and the key file generated beside it) on the mounted volume. DATABASE_URL=sqlite:////app/data/routstr.db # Encrypts node secrets at rest. Optional — if unset, a key is generated next to # your database (on the same volume) and its file is named once for backup. Set # it explicitly to manage the key yourself. ROUTSTR_SECRET_KEY= NAME=My Provider Node DESCRIPTION=Fast GPT-4 access via Lightning RECEIVE_LN_ADDRESS=me@walletofsatoshi.com ``` -------------------------------- ### Add a new endpoint: GET /v1/stats Source: https://docs.routstr.com/contributing/setup Add a new endpoint by defining a route in the appropriate router module. The example shows a GET /v1/stats endpoint that depends on get_current_user and returns a StatsResponse. The implementation is left as a placeholder. ```python # In routstr/core/main.py or appropriate router @app.get("/v1/stats") async def get_stats( user: User = Depends(get_current_user) ) -> StatsResponse: """Get usage statistics for the current user.""" # Implementation pass ``` -------------------------------- ### Integration test runner script Source: https://docs.routstr.com/contributing/testing Runs integration tests using the provided runner script, which handles Docker services setup. This is the recommended way to run integration tests in Docker mode. ```bash ./tests/run_integration.py ``` -------------------------------- ### GET PPQ models endpoint Source: https://docs.routstr.com/tinfoil-direct-integration Retrieves all PPQ models, including private ones. Filter the returned list for model IDs starting with 'private/' to see private pricing. ```http GET https://api.ppq.ai/v1/models?type=all ``` -------------------------------- ### Write a test using AAA pattern: test_balance_deduction Source: https://docs.routstr.com/contributing/setup Write a unit test following the AAA (Arrange, Act, Assert) pattern. The example tests that deduct_balance correctly reduces the balance from 1000 to 900 and returns success with the deducted amount. ```python async def test_balance_deduction(): # Arrange api_key = await create_test_api_key(balance=1000) # Act result = await deduct_balance(api_key.key, amount=100) # Assert assert result.success assert result.new_balance == 900 assert result.deducted == 100 ``` -------------------------------- ### Pre-configure Routstr with environment variables Source: https://docs.routstr.com/provider/deployment Pre-configure Routstr using environment variables in the compose file. Set upstream, node identity, Lightning withdrawal address, and database path; the admin password is generated on first start unless set as a legacy seed. ```yaml services: routstr: image: ghcr.io/routstr/proxy:latest environment: # Pre-configure upstream (optional) - UPSTREAM_BASE_URL=https://api.openai.com/v1 - UPSTREAM_API_KEY=sk-proj-... # The admin password is generated and logged once on first start; set # ADMIN_PASSWORD here only as a legacy seed for an existing deployment. # Node identity - NAME=My Provider Node - DESCRIPTION=Fast GPT-4 access via Lightning # Lightning withdrawals - RECEIVE_LN_ADDRESS=me@walletofsatoshi.com # Keep the database (and the key file generated beside it) on the volume. - DATABASE_URL=sqlite:////app/data/routstr.db volumes: - ./data:/app/data ``` -------------------------------- ### Repository structure for routstr-core Source: https://docs.routstr.com/contributing/setup Shows the top-level layout of the routstr-core repository, including the main Python package, the Next.js admin dashboard, tests, migrations, scripts, docs, examples, and root configuration files. ```text routstr-core/ ├── routstr/ # Main package │ ├── __init__.py │ ├── algorithm.py # Provider selection algorithms │ ├── auth.py # Authentication logic │ ├── balance.py # Balance management API │ ├── discovery.py # Nostr discovery │ ├── lightning.py # Lightning invoice handling │ ├── nip91.py # Node announcement implementation │ ├── proxy.py # Request proxying │ ├── wallet.py # Cashu wallet integration │ │ │ ├── core/ # Core modules │ │ ├── admin.py # Admin dashboard API │ │ ├── db.py # Database models (SQLModel) │ │ ├── exceptions.py # Custom exceptions │ │ ├── log_manager.py # Log management │ │ ├── logging.py # Logging setup │ │ ├── main.py # FastAPI app entry │ │ ├── middleware.py # HTTP middleware │ │ └── settings.py # Configuration │ │ │ ├── payment/ # Payment processing │ │ ├── cost_calculation.py │ │ ├── helpers.py │ │ ├── lnurl.py # LNURL support │ │ ├── models.py # Model pricing │ │ └── price.py # BTC/USD rates │ │ │ └── upstream/ # Upstream providers │ ├── base.py # Base provider class │ ├── helpers.py # Shared utilities │ ├── openai.py # OpenAI │ ├── anthropic.py # Anthropic │ ├── gemini.py # Google Gemini │ ├── openrouter.py # OpenRouter │ └── ... # More providers │ ├── ui/ # Admin dashboard (Next.js) │ ├── app/ # Next.js app router │ │ ├── page.tsx # Landing page │ │ ├── balances/ # Balance management │ │ ├── logs/ # Request logs viewer │ │ ├── model/ # Model configuration │ │ ├── providers/ # Upstream providers │ │ ├── settings/ # Node settings │ │ └── transactions/ # Transaction history │ ├── components/ # React components │ │ ├── ui/ # shadcn/ui primitives │ │ ├── landing/ # Landing page components │ │ └── settings/ # Settings components │ └── lib/ # Utilities & API client │ ├── api/ # Backend API client │ ├── auth/ # Auth context │ └── hooks/ # React hooks │ ├── tests/ # Test suite ├── migrations/ # Alembic migrations ├── scripts/ # Utility scripts ├── docs/ # Documentation ├── examples/ # Usage examples │ ├── Makefile # Dev commands ├── pyproject.toml # Project config └── compose.yml # Docker setup ``` -------------------------------- ### Get Tor hidden service address (Docker) Source: https://docs.routstr.com/provider/tor Retrieves the .onion address generated by the Tor container in the Docker setup. The output is a long address ending in .onion, which Routstr automatically detects and uses for the /v1/info endpoint and Nostr announcements. ```bash docker exec tor cat /var/lib/tor/hidden_service/hostname ``` -------------------------------- ### GET /v1/wallet/balance Source: https://docs.routstr.com/api/endpoints Get current wallet balance. ```APIDOC ## GET /v1/wallet/balance ### Description Get current wallet balance. ### Method GET ### Endpoint /v1/wallet/balance ### Parameters #### Headers - **Authorization** (string) - Required - Bearer token with the API key. ### Response #### Success Response (200) - **balance** (number) - The current wallet balance. - **currency** (string) - The currency of the balance (e.g., sat). - **reserved** (number) - Reserved amount. #### Response Example { "balance": 8500, "currency": "sat", "reserved": 0 } ``` -------------------------------- ### Connect to Routstr .onion via SOCKS5 (Python) Source: https://docs.routstr.com/provider/tor Python client example for connecting to a Routstr .onion address. It uses the OpenAI SDK with a custom HTTP client that routes traffic through a SOCKS5 proxy (default Tor port 9050). Replace youraddress.onion and the API key with your own values. ```python import httpx from openai import OpenAI client = OpenAI( base_url="http://youraddress.onion/v1", api_key="sk-...", http_client=httpx.Client(proxy="socks5://127.0.0.1:9050") ) ``` -------------------------------- ### GET /v1/providers/{provider_name} Source: https://docs.routstr.com/api/endpoints Gets specific provider details. ```APIDOC ## GET /v1/providers/{provider_name} ### Description Gets specific provider details. ### Method GET ### Endpoint /v1/providers/{provider_name} ### Parameters #### Path Parameters - **provider_name** (string) - Required - The name of the provider. ### Response #### Success Response (200) - **name** (string) - Provider name. - **display_name** (string) - Display name. - **description** (string) - Description. - **models** (array) - List of models. - **id** (string) - Model ID. - **name** (string) - Model name. - **context_window** (integer) - Context window size. - **pricing** (object) - Pricing details. - **prompt** (number) - Prompt price. - **completion** (number) - Completion price. - **unit** (string) - Pricing unit. - **endpoints** (array of strings) - List of endpoint paths. - **features** (array of strings) - List of features. - **status** (string) - Provider status. #### Response Example { "name": "openai", "display_name": "OpenAI", "description": "Official OpenAI API", "models": [ { "id": "gpt-4", "name": "GPT-4", "context_window": 8192, "pricing": { "prompt": 0.03, "completion": 0.06, "unit": "1k tokens" } } ], "endpoints": ["chat/completions", "completions", "embeddings"], "features": ["streaming", "function_calling"], "status": "active" } ``` -------------------------------- ### Run production-like server Source: https://docs.routstr.com/contributing/setup Runs the server in a production-like environment. ```bash make run ``` -------------------------------- ### Configure environment variables Source: https://docs.routstr.com/contributing/setup Minimal environment configuration for development. UPSTREAM_API_KEY is optional for mock testing; DATABASE_URL uses SQLite with aiosqlite. ```bash # Minimum required for development UPSTREAM_BASE_URL=https://api.openai.com/v1 UPSTREAM_API_KEY=your-api-key # Optional for mock testing ADMIN_PASSWORD=development-password DATABASE_URL=sqlite+aiosqlite:///dev.db ``` -------------------------------- ### GET /v1/models/paths Source: https://docs.routstr.com/api/endpoints Get the selectable upstream routes for each advertised model. This endpoint is discovery-only; request-side selection will be added separately. ```APIDOC ## GET /v1/models/paths ### Description Get the selectable upstream routes for each advertised model. This endpoint is discovery-only; request-side selection will be added separately. ### Method GET ### Endpoint /v1/models/paths ### Response #### Success Response (200) - **data** (array) - List of model path objects. - **id** (string) - Model identifier. - **paths** (array) - List of path objects. - **path** (string) - Opaque, percent-encoded selector. Clients must store and return it unchanged. - **provider** (object) - Provider information. - **id** (integer) - Provider ID. - **slug** (string) - Provider slug. - **type** (string) - Provider type. - **endpoint** (object|null) - Endpoint information. - **tag** (string) - Machine-readable endpoint tag. - **name** (string) - Display name of the endpoint. - **updated_at** (integer) - Unix timestamp of last update. #### Response Example ```json { "data": [ { "id": "anthropic/claude-sonnet-4", "paths": [ { "path": "url=https%3A%2F%2Fapi.anthropic.com%2Fv1&provider-id=12&model-id=anthropic%2Fclaude-sonnet-4", "provider": {"id": 12, "slug": "anthropic-primary", "type": "anthropic"}, "endpoint": null }, { "path": "url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1&provider-id=42&model-id=anthropic%2Fclaude-sonnet-4&endpoint=google-vertex%2Fus", "provider": {"id": 42, "slug": "openrouter-main", "type": "openrouter"}, "endpoint": {"tag": "google-vertex/us", "name": "Google"} } ] } ], "updated_at": 1753500000 } ``` ``` -------------------------------- ### Async context managers for resources Source: https://docs.routstr.com/contributing/code-structure Demonstrates async context managers for resource management, such as HTTP clients and database transactions. Use these patterns to ensure proper cleanup and atomicity. ```python async with httpx.AsyncClient() as client: response = await client.get(url) async with database.transaction(): # Atomic operations ``` -------------------------------- ### Run and build the admin dashboard UI with pnpm Source: https://docs.routstr.com/contributing/setup Commands to set up and run the admin dashboard UI locally with pnpm. Use `pnpm dev` for development at http://localhost:3000 and `pnpm build` for a production build. ```bash # Development cd ui pnpm install pnpm dev # http://localhost:3000 # Build for production pnpm build ``` -------------------------------- ### Get Settings - GET /admin/api/settings Source: https://docs.routstr.com/api/endpoints Retrieves the current application settings. Sensitive values may be redacted. Requires the Admin cookie with the configured admin_password. ```http GET /admin/api/settings ``` -------------------------------- ### Configure FastAPI app with middleware and routers in routstr/core/main.py Source: https://docs.routstr.com/contributing/code-structure Sets up the FastAPI application with version, lifespan, middleware, and all routers. The ellipsis in the CORSMiddleware call indicates omitted configuration. ```python # FastAPI application setup app = FastAPI(version=__version__, lifespan=lifespan) # Middleware registration app.add_middleware(CORSMiddleware, ...) app.add_middleware(LoggingMiddleware) # Router inclusion app.include_router(models_router) app.include_router(admin_router) app.include_router(balance_router) app.include_router(deprecated_wallet_router) app.include_router(providers_router) app.include_router(proxy_router) ``` -------------------------------- ### Make targets for running tests Source: https://docs.routstr.com/contributing/testing Lists the Make targets available for different test suites: all tests, unit, integration with mocks or Docker, fast tests, performance tests, and coverage. Use these commands for typical test execution. ```bash # Run all tests (unit + integration with mocks) make test # Unit tests only make test-unit # Integration tests with mocks (fast) make test-integration # Integration tests with Docker services make test-integration-docker # Fast tests only (skip slow and Docker tests) make test-fast # Performance tests make test-performance # Coverage make test-coverage ``` -------------------------------- ### GET /v1/providers Source: https://docs.routstr.com/api/endpoints Lists available upstream providers. ```APIDOC ## GET /v1/providers ### Description Lists available upstream providers. ### Method GET ### Endpoint /v1/providers ### Response #### Success Response (200) - **providers** (array) - List of providers. - **name** (string) - Provider name. - **models** (array of strings) - List of model identifiers. - **endpoints** (array of strings) - List of endpoint paths. - **status** (string) - Provider status (e.g., "active"). #### Response Example { "providers": [ { "name": "openai", "models": ["gpt-4", "gpt-3.5-turbo"], "endpoints": ["chat/completions", "completions"], "status": "active" } ] } ``` -------------------------------- ### GET /v1/wallet/transactions Source: https://docs.routstr.com/api/authentication Retrieves the transaction history for the authenticated key. ```APIDOC ## GET /v1/wallet/transactions ### Description View transaction history for the authenticated key. ### Method GET ### Endpoint /v1/wallet/transactions ### Parameters #### Headers - **Authorization** (string) - Required - Bearer token (e.g., `Bearer sk-...`) #### Query Parameters - **limit** (number) - Optional - Maximum number of transactions to return (e.g., `10`) ### Response #### Success Response (200) - **transactions** (array) - List of transactions - **id** (string) - Transaction identifier - **type** (string) - Type of transaction (e.g., `usage`, `deposit`) - **amount** (number) - Amount (negative for usage, positive for deposit) - **balance_after** (number) - Balance after transaction - **description** (string) - Description of transaction - **timestamp** (string) - Timestamp of transaction - **has_more** (boolean) - Whether more transactions are available - **total** (number) - Total number of transactions #### Response Example ```json { "transactions": [ { "id": "txn_123", "type": "usage", "amount": -154, "balance_after": 8546, "description": "gpt-3.5-turbo: 50 prompt + 150 completion tokens", "timestamp": "2024-01-01T12:34:56Z" }, { "id": "txn_122", "type": "deposit", "amount": 10000, "balance_after": 10000, "description": "Initial deposit", "timestamp": "2024-01-01T00:00:00Z" } ], "has_more": false, "total": 2 } ``` ``` -------------------------------- ### POST /v1/wallet/topup Source: https://docs.routstr.com/api/endpoints Add funds to existing wallet. ```APIDOC ## POST /v1/wallet/topup ### Description Add funds to existing wallet. ### Method POST ### Endpoint /v1/wallet/topup ### Parameters #### Headers - **Authorization** (string) - Required - Bearer token with the API key. #### Request Body - **cashu_token** (string) - Required - The Cashu token to add. ### Request Example { "cashu_token": "cashuAeyJ0..." } ### Response #### Success Response (200) - **balance** (number) - The new wallet balance. - **amount_added** (number) - The amount added. - **currency** (string) - The currency of the balance. #### Response Example { "balance": 18500, "amount_added": 10000, "currency": "sat" } ``` -------------------------------- ### Get OpenAPI specification Source: https://docs.routstr.com/api/overview Retrieves the complete OpenAPI 3.0 specification. ```http GET /openapi.json ``` -------------------------------- ### Verify the node is running Source: https://docs.routstr.com/provider/quickstart Check the node's info endpoint to confirm it is up and responding. ```bash curl http://localhost:8000/v1/info ``` -------------------------------- ### Interactive documentation endpoints Source: https://docs.routstr.com/api/overview Access Swagger UI and ReDoc for interactive API documentation. ```http GET /docs # Swagger UI GET /redoc # ReDoc ```