diff --git a/README.md b/README.md index a0834551..78cf9404 100644 --- a/README.md +++ b/README.md @@ -1,256 +1,73 @@ # Routstr Payment Proxy -Routstr is a FastAPI-based reverse proxy that sits in front of any OpenAI-compatible API. It handles pay-per-request billing using the [Cashu](https://cashu.space/) eCash protocol on Bitcoin and tracks usage in a local SQL database. +[![License](https://img.shields.io/github/license/routstr/routstr-core?style=flat-square)](LICENSE) +[![Stars](https://img.shields.io/github/stars/routstr/routstr-core?style=flat-square)](https://github.com/routstr/routstr-core/stargazers) +[![Issues](https://img.shields.io/github/issues/routstr/routstr-core?style=flat-square)](https://github.com/routstr/routstr-core/issues) +[![Release](https://img.shields.io/github/v/release/routstr/routstr-core?style=flat-square)](https://github.com/routstr/routstr-core/releases) -The server exposes the same endpoints as the upstream API and deducts sats from user accounts for each call. Pricing can be static or model-specific by loading `models.json` (falls back to `models.example.json`). +Routstr is a decentralized protocol for permissionless, private, and censorship-resistant AI inference. It combines Nostr for discovery and Cashu for private Bitcoin micropayments. -## How It Works +This repo contains Routstr Core: a FastAPI-based reverse proxy that sits in front of OpenAI-compatible APIs and handles pay-per-request billing. -The proxy implements a seamless eCash payment flow that maintains compatibility with existing OpenAI clients while enabling Bitcoin micropayments: +## Start Here -```mermaid -sequenceDiagram - participant Client - participant Proxy as Routstr Proxy - participant DB as Database - participant Upstream as OpenAI API - participant Wallet as Cashu Wallet +- **Overview**: +- **Provider Guide**: +- **User Guide**: - Client->>Proxy: API Request + eCash Token - Proxy->>Wallet: Validate & Redeem Token - Wallet-->>Proxy: Token Value (sats) - Proxy->>DB: Store/Update Balance - Proxy->>Upstream: Forward API Request - Upstream-->>Proxy: API Response + Usage Data - Proxy->>DB: Deduct Actual Request Cost - Proxy->>DB: Update Final Balance - Proxy-->>Client: API Response +## 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 + +```python +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) ``` -## Features +### cURL -- **Cashu Wallet Integration** – Accept Lightning payments and redeem eCash tokens before forwarding requests -- **API Key Management** – Hashed keys stored in SQLite with balance tracking and optional expiry/refund address -- **Model-Based Pricing** – Convert USD prices in `models.json` to sats using live BTC/USD rates -- **Admin Dashboard** – Simple HTML interface at `/admin/` to view balances and API keys -- **Discovery** – Fetch available providers from Nostr relays using NIP-91 protocol -- **NIP-91 Auto-Announcement** – Automatically announce this provider to Nostr relays when NSEC is provided -- **Docker Support** – Provided `Dockerfile` and `compose.yml` for running with an optional Tor hidden service +```bash +curl http://localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "x-cashu: cashuBo2FteCJodHRwczovL21..." \ + -d '{ + "model": "gpt-5-nano", + "messages": [{"role": "user", "content": "hello"}] + }' +``` -## Getting Started +## Quick Start (Docker) -### Running the proxy using Docker +If you are a node runner, start a Routstr Core instance and configure upstream access in the dashboard. ```bash docker run -d \ ---name routstr-proxy \ --p 8000:8000 \ --e UPSTREAM_BASE_URL=https://api.openai.com/v1 \ --e UPSTREAM_API_KEY=your-openai-api-key \ -ghcr.io/routstr/proxy:latest + --name routstr-proxy \ + -p 8000:8000 \ + ghcr.io/routstr/proxy:latest ``` -### Development Requirements - -- Python 3.11+ -- [uv](https://github.com/astral-sh/uv) package manager (used in development) - -### Installation - -```bash -uv sync # install dependencies -``` - -Create a `.env` file based on `.env.example` and fill in the required values: +## Development ```bash +make setup cp .env.example .env -``` - -### Running Locally - -```bash fastapi run routstr --host 0.0.0.0 --port 8000 ``` -The service forwards requests to `UPSTREAM_BASE_URL`. Supply the upstream API key via the `UPSTREAM_API_KEY` environment variable if required. - -### Docker - -```bash -docker compose up --build -``` - -This builds the image and also starts a Tor container exposing the API as a hidden service. - -## Environment Variables - -The most common settings are shown below. See `.env.example` for the full list. - -### Core Settings - -- `UPSTREAM_BASE_URL` – URL of the OpenAI-compatible service -- `UPSTREAM_API_KEY` – API key for the upstream service (optional) -- `FIXED_PRICING` – Set to `true` to use a fixed per-request price; `false` (default) uses model pricing from `models.json` -- `ADMIN_PASSWORD` – Password for the `/admin/` dashboard -- `CASHU_MINTS` – Comma-separated list of Cashu mint URLs -- `NAME` – Name of the proxy -- `DESCRIPTION` – Description of the proxy -- `NPUB` – Nostr public key of the proxy -- `HTTP_URL` – Public-facing URL of the proxy -- `ONION_URL` – Tor hidden service URL of the proxy -- `NEXT_PUBLIC_API_URL` - UI Configuration for Next.js frontend (proxy URL, default: 'http://127.0.0.1:8000' ) - -## Database Migrations - -The application uses Alembic for database schema management and **automatically runs migrations on startup**. This ensures your database is always up-to-date when deploying new versions. - -### Automatic Migrations in Production - -When the FastAPI application starts, it automatically: - -1. Runs all pending database migrations -2. Updates the schema to the latest version -3. Logs the migration status - -This means you don't need to manually run migrations when deploying - just restart the application and migrations will be applied automatically. - -### Manual Migration Commands - -For development or troubleshooting, you can use these Makefile commands: - -```bash -make db-upgrade # Apply all pending migrations -make db-downgrade # Downgrade one migration -make db-current # Show current migration revision -make db-history # Show migration history -make db-migrate # Auto-generate new migration from model changes -make db-revision # Create empty migration file -make db-heads # Show current migration heads -make db-clean # Clean migration cache files -``` - -### Creating New Migrations - -When you modify SQLModel models: - -```bash -# Auto-generate a migration from model changes -make db-migrate -# Enter a descriptive message when prompted - -# Review the generated migration file in migrations/versions/ -# Edit if needed, then test with: -make db-upgrade -``` - -## Admin UI - -Routstr includes a modern Next.js admin dashboard that's served directly from the Python backend as static files - no separate Node.js server required. - -### Building the UI - -```bash -make ui-build -``` - -This compiles the Next.js application into static HTML, CSS, and JavaScript files in `ui/out/`. - -### Accessing the Dashboard - -Once built, the UI is automatically served by the FastAPI backend: - -- **Dashboard**: `http://localhost:8000/` -- **Login**: `http://localhost:8000/login` -- **Models Management**: `http://localhost:8000/model` -- **Providers Management**: `http://localhost:8000/providers` -- **Settings**: `http://localhost:8000/settings` - -The dashboard provides: - -- Real-time wallet balance monitoring -- Model pricing configuration -- Upstream provider management -- Transaction history -- System settings - -**Authentication**: Use the `ADMIN_PASSWORD` environment variable to access the dashboard. - -## Withdrawing Balance - -Go to the admin dashboard at `http://localhost:8000/` and login with your `ADMIN_PASSWORD` to withdraw your balance as a Cashu token. - -## Example Client - -`example.py` shows how to use the proxy with the official OpenAI client: - -```bash -CASHU_TOKEN= python example.py -``` - -The script sends streaming chat completions and pays for each request using the provided token. - -## Running Tests - -```bash -uv run pytest -``` - -The tests create a temporary SQLite database and mock the Cashu wallet. See `tests/README.md` for more details. - -## Future Features - -### Nut-24 Header Support (Coming Soon) - -We're implementing support for the Cashu Nut-24 specification, which will enable per-request token exchange with automatic change handling: - -```mermaid -graph TD - A["Client Request
x-cashu: token"] --> B[Proxy Validates Token] - B --> C{Token ≥ Minimum Amount?} - C -->|No| F[Return 402 Payment Required] - C -->|Yes| D[Calculate Request Cost] - D --> E[Process Request] - E --> G[Forward to Upstream API] - G --> H[Receive API Response] - H --> I[Calculate Change] - I --> J["Return Response
x-cashu: change_token"] - F --> K[End] - J --> K -``` - -**Key Benefits:** - -- **Per-Request Payments** – Send exact tokens for each API call -- **Automatic Change** – Receive change tokens in response headers -- **No Pre-funding** – No need to maintain account balances -- **Precise Billing** – Pay only for actual usage with msat-level precision -- **Minimum Amount Protection** – Proxy enforces minimum token value to prevent dust attacks - -**Header Format:** - -- **Request**: `x-cashu: ` – Token to spend for this request (must meet minimum amount) -- **Response**: `x-cashu: ` – Change token if payment exceeds cost - -**Implementation Note:** -The proxy should implement either a dedicated endpoint to communicate minimum eCash requirements per request, or extend the existing `models.json` to include minimum token amounts per model. This allows clients to autonomously determine the appropriate token amount to send with each request. - -**Compatible Clients:** - -To use this feature, you'll need a client that handles both OpenAI API calls and eCash header management. The following clients provide seamless integration: - -- **[routstr-chat](https://github.com/routstr/routstr-chat)** – chat app for the routstr network -- **[otrta-client](https://github.com/routstr/otrta-client)** – rust web app for the routstr network - -clients automatically: - -- **Handle eCash Headers** – Add `x-cashu` tokens to requests and process change tokens -- **Manage Wallets** – Maintain your Cashu wallet -- **Configure Proxy** – Set Routstr proxy endpoints -- **Top-up Balances** – Automatically request ecash when tokens run low and redeem ecash tokens - -This approach eliminates the need for account management while maintaining the security and privacy benefits of eCash payments. - ## License -This project is licensed under the terms of the GPLv3. See the `LICENSE` file for the full license text. +GPLv3. See `LICENSE`. diff --git a/docs/advanced/custom-pricing.md b/docs/advanced/custom-pricing.md deleted file mode 100644 index 27d1f153..00000000 --- a/docs/advanced/custom-pricing.md +++ /dev/null @@ -1,567 +0,0 @@ -# Custom Pricing - -This guide covers advanced pricing strategies and customization options for Routstr Core. - -## Pricing Models Overview - -Routstr supports three pricing models: - -1. **Fixed Pricing** - Simple per-request fee -2. **Token-Based Pricing** - Charge per input/output token -3. **Model-Based Pricing** - Dynamic pricing from models.json - -## Model-Based Pricing - -### Configuration - -Enable model-based pricing (default behavior): - -```bash -# .env -FIXED_PRICING=false -MODELS_PATH=/app/config/models.json -EXCHANGE_FEE=1.005 # 0.5% exchange fee -UPSTREAM_PROVIDER_FEE=1.05 # 5% provider margin -``` - -### Custom Models File - -Create a `models.json` with your pricing: - -```json -{ - "models": [ - { - "id": "gpt-4", - "name": "GPT-4", - "description": "Advanced reasoning model", - "context_length": 8192, - "pricing": { - "prompt": "0.03", // USD per 1K tokens - "completion": "0.06", // USD per 1K tokens - "request": "0.0001", // Fixed per-request fee - "image": "0", // For multimodal models - "web_search": "0.005", // Additional features - "internal_reasoning": "0.01" - }, - "supported_features": [ - "function_calling", - "vision", - "json_mode" - ], - "deprecation_date": null, - "replacement_model": null - }, - { - "id": "custom-model", - "name": "Custom Fine-tuned Model", - "pricing": { - "prompt": "0.001", - "completion": "0.002", - "request": "0.00005" - }, - "minimum_charge": "0.0001" // Minimum charge per request - } - ], - "default_pricing": { - "prompt": "0.002", - "completion": "0.002", - "request": "0" - } -} -``` - -### Dynamic Price Updates - -Automatically fetch prices from providers: - -```python -# scripts/update_prices.py -import asyncio -import httpx -import json - -async def fetch_openrouter_models(): - """Fetch current model pricing from OpenRouter.""" - async with httpx.AsyncClient() as client: - response = await client.get( - "https://openrouter.ai/api/v1/models" - ) - return response.json() - -async def update_models_json(): - """Update local models.json with latest prices.""" - data = await fetch_openrouter_models() - - models = [] - for model in data['data']: - models.append({ - "id": model['id'], - "name": model['name'], - "pricing": { - "prompt": model['pricing']['prompt'], - "completion": model['pricing']['completion'], - "request": model['pricing'].get('request', '0') - }, - "context_length": model.get('context_length', 4096) - }) - - with open('models.json', 'w') as f: - json.dump({"models": models}, f, indent=2) - -# Run periodically -if __name__ == "__main__": - asyncio.run(update_models_json()) -``` - -## Token-Based Pricing - -### Configuration - -Set up token-based pricing overrides: - -```bash -# .env -FIXED_PRICING=false # use model pricing -FIXED_COST_PER_REQUEST=1 # optional base fee -FIXED_PER_1K_INPUT_TOKENS=5 # optional override -FIXED_PER_1K_OUTPUT_TOKENS=15 # optional override -``` - -### Custom Token Counting - -Override default token counting: - -```python -from tiktoken import encoding_for_model - -class CustomTokenCounter: - def __init__(self): - self.encodings = {} - - def count_tokens( - self, - text: str, - model: str - ) -> int: - """Custom token counting logic.""" - # Cache encodings - if model not in self.encodings: - try: - self.encodings[model] = encoding_for_model(model) - except: - # Fallback encoding - self.encodings[model] = encoding_for_model("gpt-3.5-turbo") - - encoding = self.encodings[model] - - # Special handling for certain content - if text.startswith("```"): - # Code blocks might need special handling - tokens = encoding.encode(text) - return len(tokens) * 1.1 # 10% markup for code - - return len(encoding.encode(text)) -``` - -## Advanced Pricing Strategies - -### Time-Based Pricing - -Implement peak/off-peak pricing: - -```python -from datetime import datetime -import pytz - -class TimeBased PricingStrategy: - def __init__(self): - self.timezone = pytz.timezone('US/Eastern') - self.peak_hours = [(9, 17)] # 9 AM - 5 PM - self.peak_multiplier = 1.5 - self.weekend_discount = 0.8 - - def get_price_multiplier(self) -> float: - """Calculate price multiplier based on time.""" - now = datetime.now(self.timezone) - - # Weekend discount - if now.weekday() >= 5: # Saturday or Sunday - return self.weekend_discount - - # Peak hours surcharge - hour = now.hour - for start, end in self.peak_hours: - if start <= hour < end: - return self.peak_multiplier - - # Off-peak standard pricing - return 1.0 - - def apply_to_cost(self, base_cost: int) -> int: - """Apply time-based pricing to cost.""" - multiplier = self.get_price_multiplier() - return int(base_cost * multiplier) -``` - -### Model-Specific Surcharges - -Add custom fees for specific models: - -```python -class ModelSurchargeStrategy: - def __init__(self): - self.surcharges = { - "gpt-4-turbo": 1.1, # 10% premium - "claude-3-opus": 1.15, # 15% premium - "dall-e-3-hd": 1.25, # 25% premium for HD - } - - self.discounts = { - "gpt-3.5-turbo": 0.95, # 5% discount - "deprecated-model": 0.8, # 20% discount - } - - def get_model_multiplier(self, model: str) -> float: - """Get price multiplier for model.""" - if model in self.surcharges: - return self.surcharges[model] - elif model in self.discounts: - return self.discounts[model] - return 1.0 -``` - -### Geographic Pricing - -Adjust pricing based on client location: - -```python -import geoip2.database - -class GeographicPricingStrategy: - def __init__(self): - self.reader = geoip2.database.Reader('GeoLite2-Country.mmdb') - self.country_multipliers = { - 'US': 1.0, - 'GB': 1.0, - 'DE': 1.0, - 'IN': 0.7, # 30% discount - 'BR': 0.8, # 20% discount - 'NG': 0.6, # 40% discount - } - self.default_multiplier = 0.9 - - def get_country_multiplier(self, ip_address: str) -> float: - """Get price multiplier based on country.""" - try: - response = self.reader.country(ip_address) - country_code = response.country.iso_code - return self.country_multipliers.get( - country_code, - self.default_multiplier - ) - except: - return 1.0 # Default pricing if lookup fails -``` - -## Cost Calculation Pipeline - -### Implementing Custom Calculator - -```python -from abc import ABC, abstractmethod - -class CostCalculator(ABC): - @abstractmethod - async def calculate( - self, - request_data: dict, - usage_data: dict, - context: dict - ) -> CostResult: - pass - -class CompositeCostCalculator(CostCalculator): - """Combine multiple pricing strategies.""" - - def __init__(self): - self.strategies = [ - BaseCostCalculator(), - TimeBasedPricingStrategy(), - ModelSurchargeStrategy(), - GeographicPricingStrategy() - ] - - async def calculate( - self, - request_data: dict, - usage_data: dict, - context: dict - ) -> CostResult: - # Start with base cost - base_cost = await self.strategies[0].calculate( - request_data, usage_data, context - ) - - # Apply each strategy - final_cost = base_cost.total_msats - breakdown = {"base": base_cost.total_msats} - - for strategy in self.strategies[1:]: - multiplier = await strategy.get_multiplier(context) - adjustment = final_cost * (multiplier - 1) - final_cost += adjustment - breakdown[strategy.__class__.__name__] = adjustment - - return CostResult( - total_msats=int(final_cost), - breakdown=breakdown - ) -``` - -### Integration with Routstr - -```python -# In routstr/payment/cost_calculation.py -async def calculate_request_cost( - model: str, - prompt_tokens: int, - completion_tokens: int, - request_type: str, - context: dict -) -> CostData: - """Enhanced cost calculation with custom strategies.""" - - # Use custom calculator if configured - if os.getenv("USE_CUSTOM_PRICING", "false").lower() == "true": - calculator = CompositeCostCalculator() - result = await calculator.calculate( - request_data={ - "model": model, - "type": request_type - }, - usage_data={ - "prompt_tokens": prompt_tokens, - "completion_tokens": completion_tokens - }, - context=context - ) - return result - - # Fall back to standard calculation - return standard_calculate_cost(...) -``` - -## Monitoring Pricing - -### Price Analytics - -Track pricing effectiveness: - -```python -class PricingAnalytics: - async def analyze_pricing( - self, - start_date: datetime, - end_date: datetime - ): - """Analyze pricing performance.""" - # Average cost per request by model - model_costs = await self.get_average_costs_by_model( - start_date, end_date - ) - - # Revenue by pricing strategy - strategy_revenue = await self.get_revenue_by_strategy( - start_date, end_date - ) - - # Price elasticity - elasticity = await self.calculate_price_elasticity() - - return { - "model_costs": model_costs, - "strategy_revenue": strategy_revenue, - "price_elasticity": elasticity, - "recommendations": self.generate_recommendations( - model_costs, elasticity - ) - } -``` - -### A/B Testing Prices - -Test different pricing strategies: - -```python -class PricingExperiment: - def __init__(self): - self.experiments = { - "exp_001": { - "name": "10% discount test", - "group_a": {"multiplier": 1.0}, - "group_b": {"multiplier": 0.9}, - "allocation": 0.5 # 50/50 split - } - } - - def assign_group(self, api_key_id: int) -> str: - """Assign API key to experiment group.""" - # Consistent assignment based on key ID - import hashlib - hash_value = int(hashlib.md5( - str(api_key_id).encode() - ).hexdigest()[:8], 16) - - return "group_b" if (hash_value % 100) < 50 else "group_a" - - def get_experiment_multiplier( - self, - api_key_id: int, - experiment_id: str - ) -> float: - """Get price multiplier for experiment.""" - experiment = self.experiments.get(experiment_id) - if not experiment: - return 1.0 - - group = self.assign_group(api_key_id) - return experiment[group]["multiplier"] -``` - -## Configuration Examples - -### Enterprise Pricing - -```json -{ - "models": [ - { - "id": "gpt-4-enterprise", - "name": "GPT-4 Enterprise", - "pricing": { - "prompt": "0.02", - "completion": "0.04" - }, - "minimum_commitment": "1000", // $1000/month minimum - "sla": { - "uptime": "99.9%", - "support_response": "1 hour", - "dedicated_capacity": true - } - } - ], - "enterprise_features": { - "priority_queue": true, - "custom_models": true, - "audit_logs": true, - "sso": true - } -} -``` - -### Budget-Friendly Options - -```json -{ - "models": [ - { - "id": "gpt-3.5-turbo-budget", - "name": "GPT-3.5 Turbo Budget", - "pricing": { - "prompt": "0.0005", - "completion": "0.001" - }, - "restrictions": { - "max_tokens_per_request": 1000, - "requests_per_minute": 10, - "peak_hours_blocked": true - } - } - ], - "prepaid_packages": [ - { - "name": "Starter Pack", - "price_usd": 10, - "tokens_included": 10000000, - "expires_days": 30 - } - ] -} -``` - -## Troubleshooting - -### Price Calculation Issues - -```python -# Debug pricing -async def debug_price_calculation( - model: str, - tokens: dict, - api_key_id: int -): - """Debug price calculation step by step.""" - print(f"Model: {model}") - print(f"Tokens: {tokens}") - - # Base price - base_price = get_model_price(model) - print(f"Base price: {base_price}") - - # Token cost - token_cost = calculate_token_cost(base_price, tokens) - print(f"Token cost: {token_cost}") - - # Strategies - strategies = get_active_strategies() - for strategy in strategies: - multiplier = await strategy.get_multiplier(api_key_id) - print(f"{strategy.name}: {multiplier}x") - - # Final cost - final_cost = apply_all_strategies(token_cost, api_key_id) - print(f"Final cost: {final_cost} msats") - - return final_cost -``` - -### Common Issues - -1. **Prices Not Updating** - - Check `MODELS_PATH` is correct - - Verify file permissions - - Check background task logs - -2. **Wrong Currency Conversion** - - Verify BTC/USD rate source - - Check `EXCHANGE_FEE` setting - - Monitor rate update frequency - -3. **Discounts Not Applied** - - Verify strategy configuration - - Check API key metadata - - Review transaction history - -## Best Practices - -1. **Transparent Pricing** - - Publish pricing clearly - - Show cost breakdowns - - Notify of price changes - -2. **Fair Pricing** - - Regular competitive analysis - - Consider user feedback - - Offer budget options - -3. **Performance** - - Cache price calculations - - Optimize database queries - - Monitor calculation time - -## Next Steps - -- [Migrations](migrations.md) - Database migration guide -- [API Endpoints](../api/endpoints.md) - Pricing endpoints -- [Monitoring](../user-guide/admin-dashboard.md) - Track pricing metrics diff --git a/docs/advanced/migrations.md b/docs/advanced/migrations.md deleted file mode 100644 index 8af63101..00000000 --- a/docs/advanced/migrations.md +++ /dev/null @@ -1,646 +0,0 @@ -# Database Migrations - -This guide covers database schema management using Alembic migrations in Routstr Core. - -## Overview - -Routstr uses Alembic for database migrations with these features: - -- **Automatic migrations** on startup -- **Version control** for schema changes -- **Rollback capability** for safety -- **Support for multiple databases** (SQLite, PostgreSQL) - -## Automatic Migrations - -### Startup Behavior - -Migrations run automatically when Routstr starts: - -```python -# In routstr/core/main.py -@asynccontextmanager -async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: - logger.info("Running database migrations") - run_migrations() # Automatic migration - await init_db() # Initialize connection pool - # ... rest of startup -``` - -This ensures: - -- ✅ Database is always up-to-date -- ✅ No manual migration steps in production -- ✅ Zero-downtime deployments -- ✅ Backwards compatibility - -### Migration Safety - -Migrations are designed to be safe: - -- Idempotent (can run multiple times) -- Non-destructive by default -- Tested before release -- Reversible when possible - -## Creating Migrations - -### Auto-generating from Models - -After modifying SQLModel classes: - -```bash -# Generate migration from model changes -make db-migrate - -# You'll be prompted for a description -Enter migration message: Add user preferences table - -# Review generated file -cat migrations/versions/xxxx_add_user_preferences_table.py -``` - -### Manual Migrations - -For complex changes, create manually: - -```bash -# Create empty migration -alembic revision -m "Complex data transformation" - -# Edit the generated file -vim migrations/versions/xxxx_complex_data_transformation.py -``` - -### Migration Template - -```python -"""Add user preferences table - -Revision ID: a1b2c3d4e5f6 -Revises: f6e5d4c3b2a1 -Create Date: 2024-01-15 10:30:00.123456 - -""" -from alembic import op -import sqlalchemy as sa -import sqlmodel - -# revision identifiers -revision = 'a1b2c3d4e5f6' -down_revision = 'f6e5d4c3b2a1' -branch_labels = None -depends_on = None - -def upgrade() -> None: - """Apply migration.""" - # Create new table - op.create_table( - 'userpreferences', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('api_key_id', sa.Integer(), nullable=False), - sa.Column('theme', sa.String(), nullable=True), - sa.Column('notifications_enabled', sa.Boolean(), default=True), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.ForeignKeyConstraint(['api_key_id'], ['apikey.id'], ) - ) - - # Create index - op.create_index( - 'ix_userpreferences_api_key_id', - 'userpreferences', - ['api_key_id'] - ) - -def downgrade() -> None: - """Revert migration.""" - op.drop_index('ix_userpreferences_api_key_id', table_name='userpreferences') - op.drop_table('userpreferences') -``` - -## Common Migration Patterns - -### Adding Columns - -Add column with default value: - -```python -def upgrade(): - # Add nullable column first - op.add_column( - 'apikey', - sa.Column('last_rotation', sa.DateTime(), nullable=True) - ) - - # Populate existing rows - connection = op.get_bind() - connection.execute( - "UPDATE apikey SET last_rotation = created_at WHERE last_rotation IS NULL" - ) - - # Make non-nullable if needed - op.alter_column('apikey', 'last_rotation', nullable=False) -``` - -### Renaming Columns - -Safe column rename: - -```python -def upgrade(): - # SQLite doesn't support ALTER COLUMN, so we need a workaround - with op.batch_alter_table('apikey') as batch_op: - batch_op.alter_column('old_name', new_column_name='new_name') -``` - -### Adding Indexes - -Performance-improving indexes: - -```python -def upgrade(): - # Single column index - op.create_index( - 'ix_transaction_timestamp', - 'transaction', - ['timestamp'] - ) - - # Composite index - op.create_index( - 'ix_transaction_key_time', - 'transaction', - ['api_key_id', 'timestamp'] - ) - - # Partial index (PostgreSQL only) - op.create_index( - 'ix_apikey_active', - 'apikey', - ['balance'], - postgresql_where='balance > 0' - ) -``` - -### Data Migrations - -Transform existing data: - -```python -def upgrade(): - # Add new column - op.add_column( - 'apikey', - sa.Column('key_type', sa.String(), nullable=True) - ) - - # Migrate data - connection = op.get_bind() - result = connection.execute('SELECT id, metadata FROM apikey') - - for row in result: - key_type = 'premium' if row.metadata.get('premium') else 'standard' - connection.execute( - f"UPDATE apikey SET key_type = '{key_type}' WHERE id = {row.id}" - ) - - # Make column non-nullable - op.alter_column('apikey', 'key_type', nullable=False) -``` - -### Enum Types - -Add enum column: - -```python -from enum import Enum - -class KeyStatus(str, Enum): - ACTIVE = "active" - SUSPENDED = "suspended" - EXPIRED = "expired" - -def upgrade(): - # Create enum type (PostgreSQL) - key_status_enum = sa.Enum(KeyStatus, name='keystatus') - key_status_enum.create(op.get_bind(), checkfirst=True) - - # Add column - op.add_column( - 'apikey', - sa.Column( - 'status', - key_status_enum, - nullable=False, - server_default='active' - ) - ) -``` - -## Database-Specific Considerations - -### SQLite Limitations - -SQLite has limitations requiring workarounds: - -```python -def upgrade(): - # SQLite doesn't support ALTER COLUMN directly - # Use batch_alter_table for compatibility - with op.batch_alter_table('apikey') as batch_op: - batch_op.alter_column( - 'balance', - type_=sa.BigInteger(), # Change from Integer - existing_type=sa.Integer() - ) -``` - -### PostgreSQL Features - -Leverage PostgreSQL-specific features: - -```python -def upgrade(): - # Use JSONB for better performance - op.add_column( - 'apikey', - sa.Column('metadata', sa.JSON().with_variant( - sa.dialects.postgresql.JSONB(), 'postgresql' - )) - ) - - # Add GIN index for JSONB queries - op.create_index( - 'ix_apikey_metadata', - 'apikey', - ['metadata'], - postgresql_using='gin' - ) - - # Add check constraint - op.create_check_constraint( - 'ck_apikey_balance_positive', - 'apikey', - 'balance >= 0' - ) -``` - -## Migration Commands - -### Running Migrations - -```bash -# Apply all pending migrations -make db-upgrade - -# Upgrade to specific revision -alembic upgrade a1b2c3d4e5f6 - -# Upgrade one revision -alembic upgrade +1 -``` - -### Checking Status - -```bash -# Show current revision -make db-current -# Output: a1b2c3d4e5f6 (head) - -# Show migration history -make db-history -# Output: -# a1b2c3d4e5f6 -> b2c3d4e5f6a7 (head), Add user preferences -# f6e5d4c3b2a1 -> a1b2c3d4e5f6, Add indexes -# e5d4c3b2a1f6 -> f6e5d4c3b2a1, Initial schema -``` - -### Rolling Back - -```bash -# Rollback one migration -make db-downgrade - -# Rollback to specific revision -alembic downgrade f6e5d4c3b2a1 - -# Rollback all (dangerous!) -alembic downgrade base -``` - -## Testing Migrations - -### Unit Testing - -Test migrations in isolation: - -```python -import pytest -from alembic import command -from alembic.config import Config -from sqlalchemy import create_engine, inspect - -def test_migration_add_user_preferences(): - """Test user preferences migration.""" - # Create test database - engine = create_engine("sqlite:///:memory:") - - # Run migrations up to previous version - alembic_cfg = Config("alembic.ini") - alembic_cfg.set_main_option("sqlalchemy.url", str(engine.url)) - command.upgrade(alembic_cfg, "f6e5d4c3b2a1") - - # Verify state before migration - inspector = inspect(engine) - tables = inspector.get_table_names() - assert "userpreferences" not in tables - - # Run target migration - command.upgrade(alembic_cfg, "a1b2c3d4e5f6") - - # Verify state after migration - inspector = inspect(engine) - tables = inspector.get_table_names() - assert "userpreferences" in tables - - # Check columns - columns = {col['name'] for col in inspector.get_columns('userpreferences')} - assert columns == {'id', 'api_key_id', 'theme', 'notifications_enabled', 'created_at'} - - # Test downgrade - command.downgrade(alembic_cfg, "f6e5d4c3b2a1") - inspector = inspect(engine) - tables = inspector.get_table_names() - assert "userpreferences" not in tables -``` - -### Integration Testing - -Test with real data: - -```python -async def test_migration_with_data(): - """Test migration preserves existing data.""" - # Setup test database with data - async with test_engine.begin() as conn: - # Insert test data - await conn.execute( - "INSERT INTO apikey (key_hash, balance) VALUES ('test', 1000)" - ) - - # Run migration - run_migrations() - - # Verify data integrity - async with test_engine.connect() as conn: - result = await conn.execute("SELECT * FROM apikey WHERE key_hash = 'test'") - row = result.first() - assert row.balance == 1000 - assert row.key_type == 'standard' # New column with default -``` - -## Production Deployment - -### Zero-Downtime Migrations - -Strategy for seamless updates: - -1. **Make migrations backwards compatible** - - ```python - # Good: Add nullable column - op.add_column('apikey', sa.Column('new_field', sa.String(), nullable=True)) - - # Bad: Drop column immediately - # op.drop_column('apikey', 'old_field') - ``` - -2. **Deploy in phases** - - ```bash - # Phase 1: Deploy code that works with both schemas - # Phase 2: Run migration - # Phase 3: Deploy code that requires new schema - # Phase 4: Clean up deprecated columns - ``` - -3. **Use feature flags** - - ```python - if feature_enabled('use_new_schema'): - # Use new column - query = select(APIKey.new_field) - else: - # Use old column - query = select(APIKey.old_field) - ``` - -### Migration Monitoring - -Track migration execution: - -```python -# Add to migration -def upgrade(): - start_time = time.time() - logger.info(f"Starting migration {revision}") - - try: - # Migration logic here - op.create_table(...) - - duration = time.time() - start_time - logger.info(f"Migration {revision} completed in {duration:.2f}s") - except Exception as e: - logger.error(f"Migration {revision} failed: {e}") - raise -``` - -### Backup Before Migration - -Always backup before major changes: - -```bash -#!/bin/bash -# backup_before_migration.sh - -# Backup database -if [[ "$DATABASE_URL" == *"sqlite"* ]]; then - cp database.db "backup_$(date +%Y%m%d_%H%M%S).db" -else - pg_dump $DATABASE_URL > "backup_$(date +%Y%m%d_%H%M%S).sql" -fi - -# Run migration -alembic upgrade head - -# Verify -alembic current -``` - -## Troubleshooting - -### Common Issues - -**Migration Conflicts** - -```bash -# Multiple heads detected -alembic heads -# a1b2c3d4e5f6 (head) -# b2c3d4e5f6a7 (head) - -# Merge heads -alembic merge -m "Merge migrations" a1b2c3 b2c3d4 -``` - -**Failed Migration** - -```python -# Add rollback logic -def upgrade(): - try: - op.create_table(...) - except Exception as e: - # Clean up partial changes - op.drop_table('partial_table', checkfirst=True) - raise - -def downgrade(): - # Ensure clean rollback - op.drop_table('new_table', checkfirst=True) -``` - -**Lock Timeout** - -```python -# Add timeout handling -def upgrade(): - connection = op.get_bind() - - # Set timeout (PostgreSQL) - connection.execute("SET lock_timeout = '10s'") - - try: - op.add_column(...) - except OperationalError as e: - if 'lock timeout' in str(e): - logger.error("Migration failed due to lock timeout") - raise -``` - -### Recovery Procedures - -If migration fails in production: - -1. **Check current state** - - ```bash - alembic current - alembic history - ``` - -2. **Manual rollback if needed** - - ```sql - -- Check migration table - SELECT * FROM alembic_version; - - -- Force version if necessary - UPDATE alembic_version SET version_num = 'previous_version'; - ``` - -3. **Fix and retry** - - ```bash - # Fix migration file - vim migrations/versions/problematic_migration.py - - # Retry - alembic upgrade head - ``` - -## Best Practices - -### Migration Guidelines - -1. **Keep migrations small and focused** - - One logical change per migration - - Easier to review and rollback - -2. **Test migrations thoroughly** - - Test upgrade and downgrade - - Test with production-like data - - Test database-specific features - -3. **Document breaking changes** - - ```python - """BREAKING: Change balance column type - - This migration requires application update. - Deploy order: - 1. Update application to handle both int and bigint - 2. Run this migration - 3. Update application to use only bigint - """ - ``` - -4. **Make migrations idempotent** - - ```python - def upgrade(): - # Check if column exists - inspector = inspect(op.get_bind()) - columns = [col['name'] for col in inspector.get_columns('apikey')] - - if 'new_column' not in columns: - op.add_column( - 'apikey', - sa.Column('new_column', sa.String()) - ) - ``` - -### Performance Considerations - -1. **Add indexes concurrently (PostgreSQL)** - - ```python - def upgrade(): - # Create index without locking table - op.create_index( - 'ix_large_table_column', - 'large_table', - ['column'], - postgresql_concurrently=True - ) - ``` - -2. **Batch large updates** - - ```python - def upgrade(): - connection = op.get_bind() - - # Process in batches - batch_size = 1000 - offset = 0 - - while True: - result = connection.execute( - f"UPDATE apikey SET processed = true " - f"WHERE id IN (SELECT id FROM apikey WHERE processed = false LIMIT {batch_size})" - ) - - if result.rowcount == 0: - break - - offset += batch_size - time.sleep(0.1) # Prevent overload - ``` - -## Next Steps - -- [Testing Guide](../contributing/testing.md) - Testing migrations -- [Deployment](../getting-started/docker.md) - Production deployment diff --git a/docs/advanced/nostr.md b/docs/advanced/nostr.md deleted file mode 100644 index dd0fecaf..00000000 --- a/docs/advanced/nostr.md +++ /dev/null @@ -1,598 +0,0 @@ -# Nostr Discovery - -Routstr Core integrates with Nostr (Notes and Other Stuff Transmitted by Relays) for decentralized provider discovery. This enables users to find Routstr nodes without relying on centralized directories. - -## Overview - -Nostr integration provides: - -- **Decentralized Discovery**: Find providers through relay network -- **Cryptographic Identity**: Providers identified by public keys -- **Real-time Updates**: Live provider status and pricing -- **Censorship Resistance**: No central point of control - -## How It Works - -```mermaid -graph LR - A[Routstr Node] --> B[Nostr Relay] - B --> C[Nostr Relay] - B --> D[Nostr Relay] - - E[User Client] --> B - E --> C - E --> D - - B --> F[Provider List] - C --> F - D --> F -``` - -Providers announce themselves by publishing signed events to Nostr relays. Clients can query these relays to discover available providers. - -## Provider Configuration - -### Setting Up Nostr Identity - -1. **Generate Nostr Keys** - - ```bash - # Using nostril or similar tool - nostril --generate-keypair - - # Output: - # Private key (nsec): nsec1abc... - # Public key (npub): npub1xyz... - ``` - -2. **Configure Environment** - - ```bash - # .env - NPUB=npub1xyz... # Your public key - NSEC=nsec1abc... # Your private key (keep secret!) - NAME=Lightning AI Gateway - DESCRIPTION=Fast and reliable AI API with Bitcoin payments - HTTP_URL=https://api.lightning-ai.com - ONION_URL=http://lightningai.onion - ``` - -### Publishing to Nostr - -Routstr automatically publishes provider information to configured relays: - -```python -# Published event structure (NIP-89) -{ - "kind": 31990, # Application handler event - "pubkey": "your_public_key", - "content": { - "name": "Lightning AI Gateway", - "description": "Fast and reliable AI API", - "endpoints": { - "http": "https://api.lightning-ai.com", - "onion": "http://lightningai.onion" - }, - "models": ["gpt-3.5-turbo", "gpt-4", "claude-3"], - "pricing": { - "gpt-3.5-turbo": { - "prompt_sats_per_1k": 3, - "completion_sats_per_1k": 4 - } - }, - "cashu_mints": [ - "https://mint.minibits.cash/Bitcoin" - ] - }, - "tags": [ - ["d", "routstr"], - ["t", "ai-api"], - ["t", "bitcoin"], - ["p", "payment-proxy"] - ] -} -``` - -### Relay Configuration - -Configure which relays to publish to: - -```python -# Default relays -DEFAULT_RELAYS = [ - "wss://relay.damus.io", - "wss://relay.nostr.band", - "wss://relay.routstr.com", - "wss://nos.lol" -] - -# Custom relay configuration -RELAYS=wss://relay1.com,wss://relay2.com -``` - -## Client Discovery - -### Using the Discovery Endpoint - -Find providers through the API: - -```bash -GET /v1/providers - -Response: -{ - "providers": [ - { - "name": "Lightning AI Gateway", - "npub": "npub1xyz...", - "description": "Fast and reliable AI API", - "endpoints": { - "http": "https://api.lightning-ai.com", - "onion": "http://lightningai.onion" - }, - "models": ["gpt-3.5-turbo", "gpt-4"], - "pricing": { - "gpt-3.5-turbo": { - "prompt_sats_per_1k": 3, - "completion_sats_per_1k": 4 - } - }, - "last_seen": "2024-01-01T12:00:00Z", - "reliability_score": 0.99 - } - ] -} -``` - -### Direct Nostr Queries - -Query Nostr relays directly: - -```python -import json -import websocket - -def discover_providers(relay_url: str): - """Discover Routstr providers from Nostr relay.""" - ws = websocket.create_connection(relay_url) - - # Subscribe to provider events - subscription = { - "kinds": [31990], - "tags": { - "d": ["routstr"] - } - } - - ws.send(json.dumps(["REQ", "sub1", subscription])) - - providers = [] - while True: - response = json.loads(ws.recv()) - if response[0] == "EVENT": - event = response[2] - providers.append(parse_provider_event(event)) - elif response[0] == "EOSE": # End of stored events - break - - ws.close() - return providers -``` - -### JavaScript/TypeScript - -```typescript -import { SimplePool } from 'nostr-tools'; - -async function discoverProviders(): Promise { - const pool = new SimplePool(); - const relays = [ - 'wss://relay.damus.io', - 'wss://relay.nostr.band' - ]; - - const filter = { - kinds: [31990], - '#d': ['routstr'] - }; - - const events = await pool.list(relays, [filter]); - - return events.map(event => ({ - name: event.content.name, - npub: nip19.npubEncode(event.pubkey), - url: event.content.endpoints.http, - models: event.content.models, - pricing: event.content.pricing - })); -} -``` - -## Provider Ranking - -### Reliability Scoring - -Providers are ranked based on: - -```python -class ProviderScore: - def calculate(self, provider: Provider) -> float: - score = 1.0 - - # Uptime (based on recent checks) - uptime_ratio = provider.successful_pings / provider.total_pings - score *= uptime_ratio - - # Response time - if provider.avg_response_time < 500: # ms - score *= 1.0 - elif provider.avg_response_time < 1000: - score *= 0.9 - else: - score *= 0.7 - - # Model availability - model_score = len(provider.models) / 10 # Max 10 models - score *= min(1.0, 0.5 + model_score * 0.5) - - # Price competitiveness - if provider.is_cheapest_for_any_model(): - score *= 1.1 - - return min(1.0, score) -``` - -### Provider Selection - -Choose optimal provider: - -```python -def select_provider( - providers: list[Provider], - model: str, - requirements: dict -) -> Provider: - """Select best provider for requirements.""" - - # Filter by model availability - candidates = [p for p in providers if model in p.models] - - # Filter by requirements - if requirements.get('tor_required'): - candidates = [p for p in candidates if p.onion_url] - - if requirements.get('max_price_per_1k'): - max_price = requirements['max_price_per_1k'] - candidates = [ - p for p in candidates - if p.pricing[model]['prompt_sats_per_1k'] <= max_price - ] - - # Sort by score - candidates.sort(key=lambda p: p.reliability_score, reverse=True) - - return candidates[0] if candidates else None -``` - -## Publishing Updates - -### Automatic Updates - -Routstr publishes updates when: - -- Node starts up -- Configuration changes -- Models are added/removed -- Pricing updates - -### Manual Publishing - -Force publish current state: - -```python -async def publish_provider_info(): - """Manually publish provider information.""" - event = create_provider_event( - name=os.getenv("NAME"), - description=os.getenv("DESCRIPTION"), - models=get_available_models(), - pricing=get_current_pricing() - ) - - await publish_to_relays(event, RELAYS) -``` - -### Event Lifecycle - -```python -# Publish every 6 hours -@periodic_task(hours=6) -async def update_nostr_presence(): - """Keep provider information fresh.""" - try: - await publish_provider_info() - logger.info("Updated Nostr presence") - except Exception as e: - logger.error(f"Failed to update Nostr: {e}") - -# Delete on shutdown -async def remove_nostr_presence(): - """Remove provider from discovery.""" - deletion_event = create_deletion_event() - await publish_to_relays(deletion_event, RELAYS) -``` - -## Security Considerations - -### Key Management - -1. **Secure Storage** - - ```python - # Never log private keys - SENSITIVE_VARS = ['NSEC', 'ADMIN_PASSWORD'] - - def sanitize_env(env_dict: dict) -> dict: - return { - k: '***' if k in SENSITIVE_VARS else v - for k, v in env_dict.items() - } - ``` - -2. **Key Rotation** - - ```bash - # Generate new keys - nostril --generate-keypair - - # Update configuration - # Publish transition event - # Update all references - ``` - -### Event Validation - -Verify provider events: - -```python -def validate_provider_event(event: dict) -> bool: - """Validate provider announcement.""" - # Check signature - if not verify_signature(event): - return False - - # Check required fields - required = ['name', 'endpoints', 'models', 'pricing'] - content = json.loads(event['content']) - if not all(field in content for field in required): - return False - - # Verify endpoints are reachable - if not await check_endpoints(content['endpoints']): - return False - - return True -``` - -### Relay Security - -Choose relays carefully: - -```python -TRUSTED_RELAYS = { - 'wss://relay.damus.io': { - 'operator': 'Damus', - 'reputation': 'high', - 'filters_spam': True - }, - 'wss://relay.nostr.band': { - 'operator': 'Nostr.Band', - 'reputation': 'high', - 'paid_tier': True - } -} -``` - -## Advanced Features - -### Multi-Relay Broadcasting - -Ensure wide distribution: - -```python -async def broadcast_to_relays(event: dict, relays: list[str]): - """Broadcast event to multiple relays.""" - tasks = [] - for relay in relays: - task = asyncio.create_task( - publish_to_relay(event, relay) - ) - tasks.append(task) - - results = await asyncio.gather(*tasks, return_exceptions=True) - - successful = sum(1 for r in results if not isinstance(r, Exception)) - logger.info(f"Published to {successful}/{len(relays)} relays") -``` - -### Provider Metadata - -Extended metadata in events: - -```json -{ - "kind": 31990, - "content": { - "name": "Lightning AI", - "description": "Enterprise AI API", - "metadata": { - "established": "2024-01-01", - "total_requests": 1000000, - "average_response_ms": 250, - "supported_features": [ - "streaming", - "function_calling", - "vision", - "embeddings" - ], - "certifications": ["SOC2", "GDPR"], - "contact": { - "nostr": "npub1contact...", - "email": "support@lightning-ai.com" - } - } - } -} -``` - -### Discovery Filters - -Advanced filtering options: - -```python -# Find providers with specific features -GET /v1/providers?features=streaming,vision&max_price=5&min_reliability=0.95 - -# Response includes filtered results -{ - "providers": [...], - "filters_applied": { - "features": ["streaming", "vision"], - "max_price_sats_per_1k": 5, - "min_reliability": 0.95 - }, - "total_providers": 50, - "matching_providers": 12 -} -``` - -## Monitoring - -### Discovery Metrics - -Track discovery performance: - -```python -class DiscoveryMetrics: - def __init__(self): - self.relay_health = {} - self.provider_count = 0 - self.query_latency = [] - - async def check_relay_health(self, relay_url: str): - """Monitor relay connectivity.""" - start = time.time() - try: - await connect_to_relay(relay_url) - latency = time.time() - start - self.relay_health[relay_url] = { - 'status': 'healthy', - 'latency_ms': latency * 1000 - } - except Exception as e: - self.relay_health[relay_url] = { - 'status': 'unhealthy', - 'error': str(e) - } -``` - -### Provider Monitoring - -```python -@periodic_task(minutes=5) -async def monitor_providers(): - """Check provider health.""" - providers = await discover_providers() - - for provider in providers: - try: - # Test endpoint - response = await test_provider_endpoint(provider.http_url) - - # Update metrics - await update_provider_metrics( - provider.npub, - success=response.status_code == 200, - response_time=response.elapsed - ) - except Exception as e: - logger.warning(f"Provider {provider.name} check failed: {e}") -``` - -## Troubleshooting - -### No Providers Found - -```python -# Debug discovery issues -async def debug_discovery(): - """Diagnose discovery problems.""" - issues = [] - - # Check relay connectivity - for relay in RELAYS: - if not await can_connect_to_relay(relay): - issues.append(f"Cannot connect to {relay}") - - # Check event publishing - if not await verify_own_events_visible(): - issues.append("Own events not visible on relays") - - # Check filters - if len(await get_all_provider_events()) == 0: - issues.append("No provider events on any relay") - - return issues -``` - -### Relay Connection Issues - -```bash -# Test relay connection -wscat -c wss://relay.damus.io - -# Send subscription -["REQ","test",{"kinds":[31990],"#d":["routstr"]}] -``` - -## Best Practices - -### For Providers - -1. **Consistent Identity** - - Use same npub across services - - Maintain profile metadata - - Verify identity on multiple platforms - -2. **Regular Updates** - - Publish status every few hours - - Update pricing promptly - - Remove stale information - -3. **Relay Diversity** - - Publish to 5+ relays - - Include regional relays - - Monitor relay health - -### For Clients - -1. **Verify Providers** - - Check multiple relays - - Verify endpoints work - - Monitor reliability over time - -2. **Cache Discovery** - - Cache provider list - - Refresh periodically - - Handle stale data gracefully - -3. **Fallback Options** - - Keep backup providers - - Handle discovery failures - - Support manual configuration - -## Next Steps - -- [Tor Support](tor.md) - Anonymous provider access -- [Custom Pricing](custom-pricing.md) - Dynamic pricing strategies -- [API Reference](../api/endpoints.md) - Discovery API details diff --git a/docs/advanced/tor.md b/docs/advanced/tor.md deleted file mode 100644 index d6d0719c..00000000 --- a/docs/advanced/tor.md +++ /dev/null @@ -1,530 +0,0 @@ -# Tor Support - -Routstr Core includes built-in support for Tor hidden services, enabling anonymous access to your API and enhanced privacy for users. - -## Overview - -Tor support provides: - -- **Anonymous Access**: Hidden service (.onion) address -- **Enhanced Privacy**: No IP address logging -- **Censorship Resistance**: Accessible from restricted networks -- **Optional Usage**: Regular HTTP/HTTPS access remains available - -## Docker Setup - -### Using Docker Compose - -The included `compose.yml` automatically sets up Tor: - -```yaml -version: '3.8' - -services: - routstr: - build: . - environment: - - TOR_PROXY_URL=socks5://tor:9050 - ports: - - 8000:8000 - - tor: - image: ghcr.io/hundehausen/tor-hidden-service:latest - volumes: - - tor-data:/var/lib/tor - environment: - - HS_ROUTER=routstr:8000:80 - depends_on: - - routstr - -volumes: - tor-data: -``` - -Start with: - -```bash -docker compose up -d -``` - -### Getting Your Onion Address - -After starting, retrieve your hidden service address: - -```bash -# View Tor logs -docker compose logs tor - -# Or directly from the hostname file -docker exec tor cat /var/lib/tor/hidden_service/hostname -``` - -Your onion address will look like: - -``` -roustrjfsdgfiueghsklchg.onion -``` - -## Manual Tor Setup - -### Install Tor - -```bash -# Ubuntu/Debian -sudo apt-get install tor - -# macOS -brew install tor - -# Start Tor -sudo systemctl start tor -``` - -### Configure Hidden Service - -Edit `/etc/tor/torrc`: - -```bash -# Hidden service configuration -HiddenServiceDir /var/lib/tor/routstr/ -HiddenServicePort 80 127.0.0.1:8000 - -# Optional: Restrict to v3 addresses -HiddenServiceVersion 3 -``` - -Restart Tor: - -```bash -sudo systemctl restart tor -``` - -Get onion address: - -```bash -sudo cat /var/lib/tor/routstr/hostname -``` - -## Client Configuration - -### Using Tor with Python - -```python -import httpx -from openai import OpenAI - -# Configure SOCKS proxy -proxies = { - "http://": "socks5://127.0.0.1:9050", - "https://": "socks5://127.0.0.1:9050" -} - -# Create client with Tor -http_client = httpx.Client(proxies=proxies) - -client = OpenAI( - api_key="sk-...", - base_url="http://roustrjfsdgfiueghsklchg.onion/v1", - http_client=http_client -) - -# Use normally -response = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "Hello via Tor!"}] -) -``` - -### Using Tor with cURL - -```bash -# Install torify -sudo apt-get install torsocks - -# Make request through Tor -torify curl http://roustrjfsdgfiueghsklchg.onion/v1/models - -# Or with explicit proxy -curl --socks5 127.0.0.1:9050 http://roustrjfsdgfiueghsklchg.onion/v1/models -``` - -### JavaScript/Node.js - -```javascript -import { SocksProxyAgent } from 'socks-proxy-agent'; -import OpenAI from 'openai'; - -// Create SOCKS agent -const agent = new SocksProxyAgent('socks5://127.0.0.1:9050'); - -// Configure OpenAI client -const openai = new OpenAI({ - apiKey: 'sk-...', - baseURL: 'http://roustrjfsdgfiueghsklchg.onion/v1', - httpAgent: agent -}); - -// Use normally -const response = await openai.chat.completions.create({ - model: 'gpt-3.5-turbo', - messages: [{ role: 'user', content: 'Hello via Tor!' }] -}); -``` - -## Configuration - -### Environment Variables - -Configure Tor proxy for outgoing connections: - -```bash -# .env -TOR_PROXY_URL=socks5://tor:9050 # Docker -# or -TOR_PROXY_URL=socks5://127.0.0.1:9050 # Local -``` - -### Publishing Onion Address - -Make your onion address discoverable: - -```bash -# .env -ONION_URL=http://roustrjfsdgfiueghsklchg.onion -``` - -This will be included in: - -- `/v1/info` endpoint -- Nostr announcements -- Admin dashboard - -## Security Considerations - -### Hidden Service Security - -1. **Keep Private Key Secure** - - ```bash - # Backup hidden service keys - sudo tar -czf tor-keys-backup.tar.gz /var/lib/tor/routstr/ - - # Restore to maintain same address - sudo tar -xzf tor-keys-backup.tar.gz -C / - ``` - -2. **Access Control** - - ```bash - # Restrict to authenticated clients - HiddenServiceAuthorizeClient stealth client1,client2 - ``` - -3. **Rate Limiting** - - ```bash - # In torrc - HiddenServiceMaxStreams 100 - HiddenServiceMaxStreamsCloseCircuit 1 - ``` - -### Operational Security - -1. **Separate Tor Instance** - - ```yaml - # Use dedicated Tor container - tor: - image: ghcr.io/hundehausen/tor-hidden-service:latest - restart: always - networks: - - tor_network - ``` - -2. **Monitor Tor Health** - - ```python - async def check_tor_connection(): - """Verify Tor connectivity.""" - try: - async with httpx.AsyncClient( - proxies={"all://": TOR_PROXY_URL} - ) as client: - response = await client.get( - "https://check.torproject.org/api/ip" - ) - data = response.json() - return data.get("IsTor", False) - except Exception: - return False - ``` - -3. **Logging Considerations** - - ```python - # Don't log .onion addresses with IPs - def sanitize_logs(message: str) -> str: - # Remove IP addresses when .onion is present - if ".onion" in message: - message = re.sub(r'\d+\.\d+\.\d+\.\d+', '[IP]', message) - return message - ``` - -## Performance Optimization - -### Connection Pooling - -```python -# Reuse Tor circuits -class TorConnectionPool: - def __init__(self, proxy_url: str): - self.proxy_url = proxy_url - self._clients = [] - - async def get_client(self) -> httpx.AsyncClient: - if not self._clients: - client = httpx.AsyncClient( - proxies={"all://": self.proxy_url}, - timeout=httpx.Timeout(30.0), - limits=httpx.Limits( - max_keepalive_connections=5, - max_connections=10 - ) - ) - self._clients.append(client) - return self._clients[0] -``` - -### Circuit Management - -```python -# Rotate Tor circuits periodically -async def rotate_tor_circuit(): - """Signal Tor to create new circuit.""" - async with httpx.AsyncClient() as client: - # Tor control port (requires configuration) - response = await client.post( - "http://localhost:9051", - data="AUTHENTICATE\r\nSIGNAL NEWNYM\r\n" - ) -``` - -### Caching Strategies - -```python -# Cache responses for Tor users -@lru_cache(maxsize=1000) -def get_cached_response( - endpoint: str, - params_hash: str -) -> Optional[dict]: - """Cache frequently accessed data.""" - # Longer cache for Tor users due to latency - return cache.get(f"tor:{endpoint}:{params_hash}") -``` - -## Monitoring - -### Tor Metrics - -Track Tor-specific metrics: - -```python -class TorMetrics: - def __init__(self): - self.tor_requests = 0 - self.tor_errors = 0 - self.circuit_builds = 0 - self.average_latency = 0 - - async def record_request( - self, - duration: float, - success: bool - ): - self.tor_requests += 1 - if not success: - self.tor_errors += 1 - - # Update average latency - self.average_latency = ( - (self.average_latency * (self.tor_requests - 1) + duration) - / self.tor_requests - ) -``` - -### Health Checks - -```python -@router.get("/health/tor") -async def tor_health(): - """Check Tor service health.""" - checks = { - "tor_proxy": await check_tor_proxy(), - "hidden_service": await check_hidden_service(), - "circuit_established": await check_circuit() - } - - status = "healthy" if all(checks.values()) else "unhealthy" - - return { - "status": status, - "checks": checks, - "metrics": { - "tor_requests_total": metrics.tor_requests, - "tor_error_rate": metrics.tor_errors / max(metrics.tor_requests, 1), - "average_latency_ms": metrics.average_latency * 1000 - } - } -``` - -## Troubleshooting - -### Common Issues - -**Hidden Service Not Accessible** - -```bash -# Check Tor logs -docker compose logs tor -# or -sudo journalctl -u tor - -# Verify service is running -sudo systemctl status tor - -# Test locally -curl --socks5 127.0.0.1:9050 http://your-onion.onion/v1/info -``` - -**Slow Connection** - -- Tor adds 3+ hops of latency -- Use connection pooling -- Implement aggressive caching -- Consider increasing timeouts - -**Connection Errors** - -```python -# Implement Tor-specific retry logic -async def tor_retry(func, max_retries=5): - for attempt in range(max_retries): - try: - return await func() - except httpx.ProxyError: - if attempt < max_retries - 1: - # Exponential backoff for circuit building - await asyncio.sleep(2 ** attempt) - else: - raise -``` - -### Debugging - -Enable Tor debug logging: - -```bash -# In torrc -Log debug file /var/log/tor/debug.log - -# Monitor in real-time -tail -f /var/log/tor/debug.log -``` - -## Best Practices - -### For Operators - -1. **Backup Hidden Service Keys** - - Store securely offline - - Enables service recovery - - Maintains same .onion address - -2. **Monitor Tor Health** - - Check circuit establishment - - Track request latency - - Alert on failures - -3. **Separate Concerns** - - Run Tor in separate container - - Isolate from main application - - Use internal networks - -### For Users - -1. **Verify Onion Addresses** - - Check against multiple sources - - Bookmark verified addresses - - Watch for phishing - -2. **Handle Higher Latency** - - Increase client timeouts - - Implement retries - - Use connection pooling - -3. **Enhance Privacy** - - Use Tor Browser for web access - - Avoid mixing Tor/clearnet - - Don't include identifying info - -## Advanced Configuration - -### Multi-Hop Onion Services - -For extra security, chain multiple Tor instances: - -```yaml -# compose.yml -services: - tor-entry: - image: tor:latest - command: tor -f /etc/tor/torrc.entry - - tor-middle: - image: tor:latest - command: tor -f /etc/tor/torrc.middle - - tor-exit: - image: tor:latest - command: tor -f /etc/tor/torrc.exit -``` - -### Onion Service Authentication - -Require client authorization: - -```bash -# Generate client auth -openssl rand -base64 32 > client_auth_key - -# In torrc -HiddenServiceDir /var/lib/tor/routstr/ -HiddenServicePort 80 127.0.0.1:8000 -HiddenServiceAuthorizeClient stealth payments -``` - -### Load Balancing - -Distribute load across multiple instances: - -```nginx -# Onion service nginx config -upstream routstr_backends { - server routstr1:8000; - server routstr2:8000; - server routstr3:8000; -} - -server { - listen 80; - location / { - proxy_pass http://routstr_backends; - } -} -``` - -## Next Steps - -- [Nostr Discovery](nostr.md) - Announce your onion service -- [Docker Setup](../getting-started/docker.md) - Container configuration diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 566d1307..c7b91b3b 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -425,4 +425,4 @@ All API key usage is logged: - [Endpoints](endpoints.md) - Complete endpoint reference - [Errors](errors.md) - Error handling guide -- [Using the API](../user-guide/using-api.md) - Integration examples +- [Using the API](../client/integration.md) - Integration examples diff --git a/docs/api/endpoints.md b/docs/api/endpoints.md index abff6d64..2e9c6aba 100644 --- a/docs/api/endpoints.md +++ b/docs/api/endpoints.md @@ -527,4 +527,4 @@ Rate limit information is included in response headers. - [Errors](errors.md) - Error handling reference - [Authentication](authentication.md) - Auth details -- [Examples](../user-guide/using-api.md) - Code examples +- [Integration Guide](../client/integration.md) - Code examples diff --git a/docs/api/errors.md b/docs/api/errors.md index c94d04ab..dc970cb1 100644 --- a/docs/api/errors.md +++ b/docs/api/errors.md @@ -589,4 +589,4 @@ class ErrorMetrics: - [Authentication](authentication.md) - Auth error details - [Endpoints](endpoints.md) - Endpoint-specific errors -- [Examples](../user-guide/using-api.md) - Error handling examples +- [Integration Guide](../client/integration.md) - Error handling examples diff --git a/docs/api/overview.md b/docs/api/overview.md index 4849b5bc..b5636509 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -99,19 +99,19 @@ All errors follow a consistent format: Standard OpenAI-compatible endpoints: -- **Chat Completions**: `/v1/chat/completions` -- **Completions**: `/v1/completions` *(Coming soon)* -- **Embeddings**: `/v1/embeddings` *(Coming soon)* -- **Images**: `/v1/images/generations` *(Coming soon)* -- **Audio**: `/v1/audio/transcriptions` *(Coming soon)* - **Models**: `/v1/models` +- **Responses**: `/v1/responses` +- **Chat Completions**: `/v1/chat/completions` +- **Embeddings**: `/v1/embeddings` +- **Completions**: `/v1/completions` *(planned)* +- **Images**: `/v1/images/generations` *(planned)* +- **Audio**: `/v1/audio/transcriptions` *(planned)* ### Payment Endpoints Routstr-specific payment management: -- **Wallet**: `/v1/wallet/*` -- **Balance**: `/v1/balance` +- **Balance**: `/v1/balance/*` - **Node Info**: `/v1/info` ### Admin Endpoints @@ -137,9 +137,25 @@ Protected administrative functions: | Header | Description | |--------|-------------| -| `X-Routstr-Version` | API version override | | `X-Cashu` | eCash token for per-request payment | -| `X-Max-Cost` | Maximum acceptable cost in sats | + +#### X-Cashu: Stateless Per-Request Payment + +Instead of using `Authorization: Bearer sk-...`, you can send a Cashu token directly in the `X-Cashu` header. The response will include an `X-Cashu-Refund` header with your change. + +```bash +curl https://api.routstr.com/v1/chat/completions \ + -H "X-Cashu: cashuA3s8jKx9..." \ + -H "Content-Type: application/json" \ + -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}' +``` + +The response includes your change in the same header: +``` +X-Cashu: cashuA7k2mNp4... +``` + +This is fully stateless—no session, no `/v1/balance/refund` call needed. However, **streaming does not work with `X-Cashu`** because the refund can only be calculated after the full response is generated. ## Response Headers @@ -149,16 +165,8 @@ Protected administrative functions: |--------|-------------| | `Content-Type` | Response format | | `Content-Length` | Response size | -| `X-Routstr-Request-ID` | Unique request identifier | -| `X-Routstr-Version` | API version used | - -### Cost Headers - -| Header | Description | -|--------|-------------| -| `X-Routstr-Cost` | Request cost in sats | -| `X-Routstr-Balance` | Remaining balance | -| `X-Cashu` | Change token (if applicable) | +| `X-Request-ID` | Unique request identifier | +| `X-Cashu` | Change token (when request used `X-Cashu` header) | ## Streaming Responses @@ -245,8 +253,6 @@ X-Webhook-Signature: sha256=... - Current version: `v1` - Version in URL path: `/v1/endpoint` -- Override with header: `X-Routstr-Version: v2` -- Deprecation notices: 6 months ## Status Codes @@ -284,82 +290,23 @@ Responses are compressed with gzip when: - Response is larger than 1KB - Content type is compressible -## Pagination +## Batch Requests *(planned)* -List endpoints support pagination: +Process multiple operations in one request. Coming soon. + +## Node Info + +Get node metadata: ``` -GET /v1/transactions?limit=50&offset=100 +GET /v1/info ``` -Response includes pagination metadata: - -```json -{ - "data": [...], - "has_more": true, - "total": 500, - "limit": 50, - "offset": 100 -} -``` - -## Field Filtering - -Select specific fields in responses: - -``` -GET /v1/models?fields=id,name,pricing -``` - -## Batch Requests - -Process multiple operations in one request: - -```json -POST /v1/batch -{ - "requests": [ - {"method": "POST", "endpoint": "/chat/completions", "body": {...}}, - {"method": "GET", "endpoint": "/models"}, - {"method": "GET", "endpoint": "/balance"} - ] -} -``` - -## Idempotency - -Prevent duplicate operations: - -``` -Idempotency-Key: unique-request-id -``` - -Keys are stored for 24 hours. - -## Health Check - -Monitor service status: - -``` -GET /health - -Response: -{ - "status": "healthy", - "version": "0.2.0", - "timestamp": "2024-01-01T00:00:00Z", - "checks": { - "database": "ok", - "upstream": "ok", - "mint": "ok" - } -} -``` +Supported models and pricing are available at `/v1/models`. ## Next Steps - [Authentication](authentication.md) - Detailed auth guide - [Endpoints](endpoints.md) - Complete endpoint reference - [Errors](errors.md) - Error handling guide -- [Examples](../user-guide/using-api.md) - Code examples +- [Integration Guide](../client/integration.md) - Code examples diff --git a/docs/client/integration.md b/docs/client/integration.md new file mode 100644 index 00000000..daf576a9 --- /dev/null +++ b/docs/client/integration.md @@ -0,0 +1,143 @@ +# Using the API + +Routstr is **OpenAI-compatible**. Almost any AI application, SDK, or tool that supports custom endpoints will work out of the box. Just change two things: + +``` +BASE_URL → https://api.routstr.com/v1 +API_KEY → sk-... or cashuA... +``` + +**Both work as API keys:** +- `sk-7f8e9d...` — Session key (from Lightning invoice or Cashu import) +- `cashuA3s8j...` — Raw Cashu token (use directly from your wallet) + +If the app lets you set a base URL and API key, you're good to go. + +--- + +## Quick Setup Examples + +### OpenAI SDK (Python/JS) + +```python +client = OpenAI(base_url="https://api.routstr.com/v1", api_key="sk-...") # or any provider's URL +``` + +### Claude Code + +```bash +export ANTHROPIC_BASE_URL=https://api.routstr.com/v1 +export ANTHROPIC_AUTH_TOKEN=sk-... +``` + +### Any OpenAI-compatible app + +Look for "Custom API endpoint", "Base URL", or "OpenAI-compatible" in settings. Paste the URL and key. + +--- + +## Detailed Examples + +### Python (Official SDK) + +```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="") +``` + +### Node.js + +```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(); +``` + +### cURL + +```bash +# Works with session key or raw Cashu token +curl https://api.routstr.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer cashuA3s8jKx9..." \ + -d '{ + "model": "gpt-4o-mini", + "messages": [{"role": "user", "content": "Hello!"}] + }' +``` + +--- + +## Error Handling + +### Insufficient Balance (402 Payment Required) +If your session runs out of funds, the API will return a `402` error. + +```json +{ + "error": { + "message": "Insufficient balance. Current: 1000 msat, Required: 5000 msat", + "type": "insufficient_balance", + "code": 402 + } +} +``` + +**Action**: Top up your key using the `/lightning/invoice` (topup purpose) or `/v1/balance/topup` endpoints. + +### Rate Limiting +Routstr passes through rate limits from the upstream provider. Handle `429 Too Many Requests` with standard exponential backoff. + +--- + +## Advanced: Tor Access + +If the node is running as a hidden service, use a SOCKS5 proxy (like `127.0.0.1:9050`). + +**Python:** +```python +import httpx +from openai import OpenAI + +proxy_mounts = { + "http://": httpx.HTTPTransport(proxy="socks5://127.0.0.1:9050"), + "https://": httpx.HTTPTransport(proxy="socks5://127.0.0.1:9050"), +} + +client = OpenAI( + base_url="http://verylongonionaddress.onion/v1", + api_key="sk-...", + http_client=httpx.Client(mounts=proxy_mounts), +) +``` diff --git a/docs/client/introduction.md b/docs/client/introduction.md new file mode 100644 index 00000000..72479a19 --- /dev/null +++ b/docs/client/introduction.md @@ -0,0 +1,142 @@ +# Introduction to Routstr + +Welcome to the Routstr Core User Guide. This guide will help you understand how to use Routstr to access AI APIs with Bitcoin micropayments. + +## What You'll Learn + +- How the payment system works (Cashu eCash) +- Creating and managing API keys (Ephemeral Sessions) +- Making API calls through Routstr +- Using the admin dashboard + +## Prerequisites + +### 💰 Wallet + +Cashu ([cashu.me](https://cashu.me)) or Lightning ([Strike](https://strike.me), Cash App, etc.) + +### 🌐 Provider + +A Routstr node, e.g. `https://api.routstr.com/v1` + +### 🤖 Client + +OpenAI SDK, Claude Code, Cursor, or any OpenAI-compatible tool + +--- + +## How Routstr Works + +Routstr is a **Payment Proxy**. It sits between your code and the AI provider. + +### Traditional API vs Routstr + +| Traditional | Routstr | +|---|---| +| Credit Card Required | Bitcoin / Lightning / eCash | +| Monthly Billing | Pay-per-request (Real-time) | +| KYC / Account | No Account / Private | +| Single Provider | Aggregated Providers | + +### Key Concepts + +#### 1. Cashu eCash + +Digital bearer tokens backed by Bitcoin. They are instant, private, and have no fees for internal transfers. Routstr uses these tokens as the "credits" for API requests. + +#### 2. Ephemeral Sessions (API Keys) + +Instead of a permanent account, you create a **Session**. + +- You fund a session with eCash or Lightning. +- Routstr gives you an `api_key` (`sk-...`) representing that session. +- You use the `api_key` until funds run out or you finish your task. +- You can **refund** the remaining balance back to your wallet at any time. + +#### 3. Millisats (msats) + +Everything is priced in **millisatoshis**. + +- 1 Satoshi (sat) = 1,000 msats. +- This allows for extremely precise pricing (e.g., 0.05 sats per prompt). + +--- + +## Workflow: Zero to Intelligence + +### 1. Fund a Session + +You need an `api_key` with a balance. + +**Easiest: Use the Web UI** +Visit the node's root page (e.g., [api.routstr.com](https://api.routstr.com)) or [chat.routstr.com](https://chat.routstr.com) → Settings to create a key visually with Lightning. + +**Option A: Lightning Invoice (CLI)** +Generate an invoice and pay it with any Lightning wallet. + +```bash +curl -X POST https://api.routstr.com/lightning/invoice \ + -d '{"amount_sats": 1000, "purpose": "create"}' +``` + +*Returns an invoice (`bolt11`) and an ID. Once paid, the status endpoint returns your `api_key`.* + +**Option B: Cashu Token (Best for privacy & devs)** +If you have a Cashu wallet, you can copy a token string (`cashuA...`) and use it directly. + +- **Direct Usage**: Use the token *as* your API key in the `Authorization` header. +- **Import**: Or exchange it for a standard `sk-...` key: + +```bash +curl "https://api.routstr.com/v1/balance/create?initial_balance_token=cashuA..." +``` + +*Returns your `api_key` immediately.* + +### 2. Configure Your Client + +Use the standard OpenAI SDK, just changing the `base_url` and `api_key`. + +```python +from openai import OpenAI + +client = OpenAI( + base_url="https://api.routstr.com/v1", + api_key="sk-7f8e9d..." # The key from Step 1 +) +``` + +### 3. Make Requests + +```python +response = client.chat.completions.create( + model="gpt-4o", + messages=[{"role": "user", "content": "Explain quantum computing."}] +) +``` + +### 4. Withdraw Change + +When you are done, get your change back as a Cashu token. + +```bash +curl -X POST https://api.routstr.com/v1/balance/refund \ + -H "Authorization: Bearer sk-7f8e9d..." +``` + +*Returns a `token` that you can paste back into Nutstash or Minibits to reclaim your funds.* + +--- + +## Supported Features + +- **Responses**: `/v1/responses` (OpenAI Responses API) +- **Chat Completions**: `/v1/chat/completions` (Streaming supported) +- **Embeddings**: `/v1/embeddings` +- **Models**: `/v1/models` (List available models and prices) + +## Next Steps + +- **[Payment Flow](payments.md)**: Detailed breakdown of the funding lifecycle. +- **[Models & Pricing](../provider/pricing.md)**: How costs are calculated. +- **[Admin Dashboard](../provider/dashboard.md)**: Managing your node if you are the operator. diff --git a/docs/client/payments.md b/docs/client/payments.md new file mode 100644 index 00000000..f8d3ffd2 --- /dev/null +++ b/docs/client/payments.md @@ -0,0 +1,97 @@ +# Payment Flow + +Routstr uses a **Pre-paid, Ephemeral** payment model. Pay first, use the funds, withdraw the rest. No accounts, no credit cards, no trails. + +``` +💰 Deposit → 🤖 Use AI → 💸 Withdraw Change +``` + +## 1. Creating a Balance (Deposit) + +To start making requests, you must create a "Balance" (represented by an API Key). + +### Method A: Lightning Network (Bolt11) + +**Ideal for**: Users connecting from a standard Lightning wallet (Strike, Cash App, WoS). + +1. **Request Invoice**: + `POST /lightning/invoice` with `{"amount_sats": 5000, "purpose": "create"}`. +2. **Pay Invoice**: User scans and pays the QR code/bolt11 string. +3. **Receive Key**: Routstr detects the payment and issues a new API Key (`sk-...`) pre-loaded with 5,000 sats (5,000,000 msats). + +### Method B: Cashu Token Import + +**Ideal for**: Private, instant access or automated agents. + +1. **Generate Token**: User creates a token in their local wallet (e.g., 1000 sats). +2. **Import**: `GET /v1/balance/create?initial_balance_token=cashuA...` +3. **Receive Key**: Routstr claims the token and issues an API Key (`sk-...`) with that balance. + +--- + +## 2. Consuming Funds (Inference) + +Every time you make a request to `/v1/chat/completions` (or others), the cost is deducted from your balance **in real-time**. + +### Cost Calculation + +`Cost = (Input_Tokens * Price_Input) + (Output_Tokens * Price_Output) + Request_Fee` + +- Prices are defined per model (see `/v1/models`). +- If you stream the response, the balance is deducted incrementally or finalized at the end of the stream. +- If your balance hits 0 mid-stream, the connection is closed. + +### Headers + +Routstr checks the `Authorization: Bearer sk-...` header to identify which balance to charge. + +--- + +## 3. Topping Up + +If your balance runs low, you don't need a new key. You can top up the existing one. + +### Via Lightning + +`POST /lightning/invoice` with `{"amount_sats": 1000, "purpose": "topup", "api_key": "sk-..."}`. +*Once paid, the funds are added to your existing key.* + +### Via Cashu + +`POST /v1/balance/topup` with `{"cashu_token": "..."}` and `Authorization: Bearer sk-...`. + +--- + +## 4. Refund (Withdrawal) + +Don't leave large balances sitting on a node—it's a hot wallet. When you're done, get your sats back. + +### Endpoint + +`POST /v1/balance/refund` + +**Headers**: +`Authorization: Bearer sk-...` + +**Response**: + +```json +{ + "token": "cashuAeyJ0b2tlbiI6W3sibWludCI6...", + "msats": "450000" +} +``` + +You can verify the refund was successful by checking that the API Key is now invalid or has 0 balance. Copy the `token` string and paste it into your Cashu wallet to claim the Bitcoin. + +--- + +## Summary + +| Step | Action | Result | +|------|--------|--------| +| 💰 **Deposit** | Pay Lightning invoice or import Cashu | Get `sk-...` key | +| 🤖 **Use** | Make API requests | Balance decreases | +| 💸 **Refund** | Call `/v1/balance/refund` | Get Cashu token back | + +That's it. No monthly bills, no surprise charges, no data harvesting. diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md index 45ae0cb3..8136bcd0 100644 --- a/docs/contributing/architecture.md +++ b/docs/contributing/architecture.md @@ -4,7 +4,7 @@ This document describes the high-level architecture of Routstr Core, helping con ## System Overview -Routstr Core is a FastAPI-based reverse proxy that adds Bitcoin micropayments to OpenAI-compatible APIs. +Routstr Core is a FastAPI-based reverse proxy that adds Bitcoin micropayments to OpenAI-compatible APIs and can optionally announce providers via Nostr. ```mermaid graph TB @@ -20,7 +20,7 @@ graph TB Auth[Auth Module] Payment[Payment Module] Proxy[Proxy Module] - DB[(SQLite DB)] + DB[(SQLModel DB)] API --> Auth Auth --> Payment @@ -40,57 +40,87 @@ graph TB The main application is initialized in `routstr/core/main.py`: -- **Lifespan Management**: Handles startup/shutdown tasks -- **Middleware**: CORS, logging, error handling -- **Routers**: Modular endpoint organization -- **Background Tasks**: Price updates, automatic payouts +- **Lifespan Management**: Runs migrations, initializes DB, refreshes pricing/models, starts background tasks +- **Middleware**: CORS and request logging +- **Routers**: Admin, pricing/models, balance/wallet, providers discovery, proxy +- **Background Tasks**: Price refresh, model map refresh, payouts, node announcements, provider discovery refresh ### Authentication System Located in `routstr/auth.py`, handles: -- **API Key Validation**: Hashed key storage and lookup -- **Balance Checking**: Ensures sufficient funds -- **Rate Limiting**: Optional request throttling -- **Token Redemption**: Converts eCash to balance +- **API Key Validation**: SHA-256 hashed key lookup and persistence +- **Balance Checking**: Ensures sufficient funds before requests +- **Token Redemption**: Converts Cashu tokens to balance ### Payment Processing The `routstr/payment/` module manages: - **Cost Calculation**: Token-based or fixed pricing -- **Model Pricing**: Dynamic pricing from models.json -- **Currency Conversion**: BTC/USD rate management -- **Fee Application**: Exchange and provider fees +- **Model Pricing**: Derived from upstream providers and DB overrides +- **Currency Conversion**: BTC/USD price refresh and conversion +- **Fee Application**: Provider fee applied to upstream model pricing ### Request Proxying `routstr/proxy.py` handles: -- **Request Forwarding**: Preserves headers and body -- **Response Streaming**: Efficient memory usage -- **Usage Tracking**: Counts tokens and costs -- **Error Handling**: Graceful upstream failures +- **Request Forwarding**: Forwards requests to selected upstream providers +- **Response Streaming**: Streaming and non-streaming paths +- **Usage Tracking**: Adjusts costs after upstream responses +- **Error Handling**: Maps upstream errors to consistent responses ### Database Layer Using SQLModel in `routstr/core/db.py`: ```python -# Core models -APIKey: - - id: Primary key - - key_hash: Hashed API key +# Core tables +ApiKey: + - hashed_key: Primary key (SHA-256 of key or Cashu token) - balance: Current balance (msats) - - created_at: Timestamp - - metadata: JSON field + - reserved_balance: Reserved balance (msats) + - refund_address: Optional LNURL for refunds + - key_expiry_time: Optional refund expiry timestamp + - total_spent: Total spent (msats) + - total_requests: Request count + - refund_mint_url: Mint URL for refunds + - refund_currency: Refund currency -Transaction: +UpstreamProviderRow: - id: Primary key - - api_key_id: Foreign key - - amount: Transaction amount - - type: deposit/usage/withdrawal - - timestamp: When occurred + - provider_type: openai/anthropic/azure/openrouter/etc. + - base_url: Provider API base URL + - api_key: Provider API key + - api_version: Optional API version + - enabled: Provider enabled flag + - provider_fee: Provider fee multiplier + +ModelRow: + - id: Model ID + - upstream_provider_id: Provider foreign key + - name: Model name + - architecture: JSON + - pricing: JSON + - sats_pricing: JSON + - per_request_limits: JSON + - top_provider: JSON + - canonical_slug: Canonical model slug + - alias_ids: Model aliases + - enabled: Model enabled flag + +LightningInvoice: + - id: Primary key + - bolt11: Invoice + - amount_sats: Amount in sats + - payment_hash: Payment hash + - status: pending/paid/expired/cancelled + - api_key_hash: Optional associated API key + - purpose: create/topup + - created_at: Unix timestamp + - expires_at: Unix timestamp + - paid_at: Unix timestamp ``` ## Request Flow @@ -107,10 +137,10 @@ sequenceDiagram C->>R: API Request + Key R->>D: Validate Key D-->>R: Key Info + Balance - R->>R: Check Balance + R->>R: Reserve Max Cost R->>P: Forward Request P-->>R: AI Response - R->>D: Deduct Cost + R->>D: Finalize Cost (adjust by usage) R-->>C: Return Response ``` @@ -124,36 +154,20 @@ sequenceDiagram participant M as Cashu Mint participant D as Database - C->>R: Create Key Request + Token - R->>W: Validate Token + C->>R: Request + Cashu Token + R->>W: Redeem Token W->>M: Verify with Mint M-->>W: Token Valid W-->>R: Token Amount - R->>D: Create Key + Balance - R-->>C: Return API Key + R->>D: Create/Update Key + Balance + R-->>C: Continue Request ``` ## Key Design Decisions ### 1. Async Architecture -Everything is async for maximum performance: - -```python -async def handle_request(request: Request) -> Response: - # Non-blocking database queries - api_key = await get_api_key(request.headers["Authorization"]) - - # Concurrent operations - balance_check, rate_limit = await asyncio.gather( - check_balance(api_key), - check_rate_limit(api_key) - ) - - # Stream response without blocking - async for chunk in proxy_request(request): - yield chunk -``` +The system is async end-to-end, with background tasks for pricing refresh, provider discovery, model map refresh, and payouts. ### 2. Modular Design @@ -166,82 +180,46 @@ Components are loosely coupled: ### 3. Error Handling -Graceful degradation and clear error messages: - -```python -class RoustrError(Exception): - """Base exception with structured error response""" - status_code: int = 500 - error_type: str = "internal_error" - -class InsufficientBalanceError(RoustrError): - status_code = 402 - error_type = "insufficient_balance" -``` +Exceptions are handled by FastAPI exception handlers to return consistent JSON responses with a request ID. ### 4. Database Migrations -Using Alembic for schema management: - -- Auto-migrations on startup -- Version control for schema changes -- Rollback capability -- Zero-downtime updates +Alembic migrations are run on startup, and tables are created for any models not tracked by migrations. ## Security Architecture ### API Key Security - **Storage**: SHA-256 hashed keys -- **Generation**: Cryptographically secure random -- **Validation**: Constant-time comparison -- **Rotation**: Support for key expiry +- **Generation**: Cryptographically secure random (when creating new keys) +- **Validation**: Hash lookup in DB +- **Expiry**: Optional refund flow via `key_expiry_time` and `refund_address` ### Payment Security -- **Token Validation**: Cryptographic verification -- **Double-Spend Prevention**: Mint verification -- **Balance Protection**: Atomic transactions -- **Audit Trail**: All transactions logged +- **Token Validation**: Cashu token redemption via mint +- **Balance Protection**: Atomic updates and reserved balance tracking +- **Audit Trail**: Structured logging of payments and adjustments ### Network Security -- **HTTPS**: Enforced in production - **CORS**: Configurable origins -- **Rate Limiting**: Per-key limits - **Input Validation**: Pydantic models ## Performance Considerations ### Caching Strategy -```python -# Model pricing cache -@lru_cache(maxsize=100) -def get_model_price(model_id: str) -> ModelPrice: - return MODELS.get(model_id) - -# Balance cache with TTL -balance_cache = TTLCache(maxsize=1000, ttl=60) -``` +Model and provider selections are cached in process memory and refreshed on a schedule. ### Database Optimization -- **Connection Pooling**: Reuse connections -- **Indexed Queries**: Key lookups are O(1) -- **Batch Operations**: Group updates - **Async I/O**: Non-blocking queries +- **Atomic Updates**: Balance reservation and finalization updates ### Streaming Responses -Efficient memory usage for large responses: - -```python -async def stream_response(upstream_response): - async for chunk in upstream_response.aiter_bytes(): - # Process chunk without loading full response - yield process_chunk(chunk) -``` +Streaming responses are forwarded from upstream providers with usage tracking hooks. ## Extension Points @@ -273,8 +251,6 @@ async def stream_response(upstream_response): - Mock external dependencies - Test business logic in isolation -- Fast execution (< 1 second per test) -- High coverage target (> 80%) ### Integration Tests @@ -285,10 +261,7 @@ async def stream_response(upstream_response): ### Performance Tests -- Load testing with locust -- Memory profiling -- Database query optimization -- Response time benchmarks +- Response time benchmarks (as needed) ## Monitoring and Observability @@ -308,41 +281,17 @@ logger.info("api_request", extra={ ### Metrics Collection -Key metrics tracked: - -- Request rate by endpoint -- Token usage by model -- Balance changes -- Error rates -- Response times +Structured logs are emitted for requests, pricing, and payment events. ### Health Checks -```python -@app.get("/health") -async def health_check(): - return { - "status": "healthy", - "version": __version__, - "database": await check_db(), - "upstream": await check_upstream(), - "mints": await check_mints() - } -``` +Use `/v1/info` for basic service metadata and configuration visibility. ## Deployment Architecture ### Container Structure -```dockerfile -# Multi-stage build -FROM python:3.11-slim AS builder -# Install dependencies - -FROM python:3.11-slim -# Copy only runtime needs -# Run as non-root user -``` +See `core/Dockerfile` for the current container build configuration. ### Environment Configuration @@ -352,10 +301,9 @@ FROM python:3.11-slim ### Scaling Considerations -- **Horizontal**: Multiple instances behind load balancer +- **Horizontal**: Multiple instances behind a load balancer - **Vertical**: Async handles high concurrency -- **Database**: Consider PostgreSQL for scale -- **Caching**: Redis for distributed cache +- **Database**: Configure `DATABASE_URL` for external databases ## Future Architecture diff --git a/docs/contributing/code-structure.md b/docs/contributing/code-structure.md index 3ecd4f50..40432164 100644 --- a/docs/contributing/code-structure.md +++ b/docs/contributing/code-structure.md @@ -7,10 +7,13 @@ This guide provides a detailed overview of Routstr Core's codebase organization ``` routstr-core/ ├── routstr/ # Main application package -│ ├── __init__.py # Package initialization, loads .env -│ ├── auth.py # Authentication and authorization +│ ├── __init__.py # Package initialization, exports FastAPI app +│ ├── algorithm.py # Model selection/mapping logic +│ ├── auth.py # Bearer/Cashu auth and payment handling │ ├── balance.py # Balance management endpoints │ ├── discovery.py # Nostr relay discovery +│ ├── lightning.py # Lightning invoice topups +│ ├── nip91.py # Node announcement logic │ ├── proxy.py # Request proxying logic │ ├── wallet.py # Cashu wallet operations │ │ @@ -18,19 +21,23 @@ routstr-core/ │ │ ├── __init__.py │ │ ├── admin.py # Admin dashboard and API │ │ ├── db.py # Database models and connection -│ │ ├── exceptions.py # Custom exception classes +│ │ ├── exceptions.py # Exception handlers │ │ ├── logging.py # Structured logging setup │ │ ├── main.py # FastAPI app initialization │ │ └── middleware.py # HTTP middleware components │ │ -│ └── payment/ # Payment processing -│ ├── __init__.py -│ ├── cost_calculation.py # Usage cost calculation -│ ├── helpers.py # Payment utilities -│ ├── lnurl.py # Lightning URL support -│ ├── models.py # Model pricing management -│ ├── price.py # BTC/USD price handling -│ └── x_cashu.py # Cashu header protocol +│ ├── payment/ # Payment processing +│ │ ├── __init__.py +│ │ ├── cost_calculation.py # Usage cost calculation +│ │ ├── helpers.py # Payment utilities +│ │ ├── lnurl.py # Lightning URL support +│ │ ├── models.py # Model pricing management +│ │ └── price.py # BTC/USD price handling +│ │ +│ └── upstream/ # Upstream provider integrations +│ ├── base.py # Base provider logic +│ ├── helpers.py # Provider init and model refresh +│ └── ... # Provider implementations │ ├── tests/ # Test suite │ ├── __init__.py @@ -45,7 +52,12 @@ routstr-core/ │ └── versions/ # Migration files │ ├── scripts/ # Utility scripts -│ └── models_meta.py # Fetch model pricing +│ ├── models_meta.py # Fetch model pricing +│ └── ... # Build/update helpers +│ +├── examples/ # Example clients +├── testing-clients/ # HTML test clients +├── ui/ # Next.js admin UI │ ├── docs/ # Documentation ├── logs/ # Application logs (git ignored) @@ -71,30 +83,27 @@ routstr-core/ #### `routstr/__init__.py` ```python -# Loads environment variables -import dotenv -dotenv.load_dotenv() - -# Exports FastAPI app from .core.main import app as fastapi_app + +__all__ = ["fastapi_app"] ``` #### `routstr/core/main.py` ```python # FastAPI application setup -app = FastAPI( - title="Routstr Node", - lifespan=lifespan, # Manages startup/shutdown -) +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) ``` @@ -102,29 +111,24 @@ app.include_router(proxy_router) #### `routstr/auth.py` -Handles API key validation and authorization: +Handles bearer key validation and payment lifecycle (bearer or Cashu token): ```python -class APIKeyAuth: - """FastAPI dependency for API key authentication""" - - async def __call__(self, request: Request) -> APIKey: - # Extract and validate API key - # Check balance - # Return authenticated key object - -# Usage in routes: -@router.get("/protected") -async def protected_route(api_key: APIKey = Depends(APIKeyAuth())): - pass +async def validate_bearer_key( + bearer_key: str, + session: AsyncSession, + refund_address: Optional[str] = None, + key_expiry_time: Optional[int] = None, +) -> ApiKey: + """Validate bearer API key or redeem Cashu token into a balance.""" ``` Key functions: -- `create_api_key()` - Generate new API keys -- `validate_api_key()` - Verify and retrieve key -- `check_balance()` - Ensure sufficient funds -- `update_last_used()` - Track usage +- `validate_bearer_key()` - Validate API key or Cashu token +- `pay_for_request()` - Reserve max cost before upstream call +- `adjust_payment_for_tokens()` - Adjust final cost after response +- `revert_pay_for_request()` - Refund on upstream failure ### Payment Processing @@ -133,56 +137,30 @@ Key functions: Calculates request costs: ```python -def calculate_request_cost( - model: str, - prompt_tokens: int, - completion_tokens: int, - **kwargs -) -> CostData: - """Calculate cost in millisatoshis""" - # Model-based or fixed pricing - # Token counting - # Fee application - # Currency conversion +async def calculate_cost( + response_data: dict, max_cost: int, session: AsyncSession +) -> CostData | MaxCostData | CostDataError: + """Calculate cost in millisatoshis from response usage or model pricing.""" ``` #### `routstr/payment/models.py` -Manages model pricing data: +Manages model pricing, database overrides, and pricing refresh: ```python -class ModelPrice: +class Model(BaseModel): id: str name: str - pricing: dict[str, float] # USD prices - context_length: int - -# Global model registry -MODELS: dict[str, ModelPrice] = load_models() + pricing: Pricing + sats_pricing: Pricing | None = None -# Dynamic price updates async def update_sats_pricing(): - """Background task to update BTC prices""" + """Periodic task to update sats pricing for providers and overrides.""" ``` -#### `routstr/payment/x_cashu.py` +#### `routstr/proxy.py` + `routstr/upstream/*` -Implements Cashu payment protocol: - -```python -class XCashuHandler: - """Handle x-cashu header payments""" - - async def process_request_payment( - self, - token: str, - estimated_cost: int - ) -> PaymentResult: - # Validate token - # Check minimum amount - # Process payment - # Generate change -``` +The `x-cashu` header is handled by the proxy route and delegated to upstream providers. ### Request Proxying @@ -191,17 +169,11 @@ class XCashuHandler: Core proxy functionality: ```python -@router.api_route("/{path:path}", methods=ALL_METHODS) -async def proxy_request( - request: Request, - path: str, - api_key: APIKey = Depends(APIKeyAuth()) -) -> Response: - """Forward requests to upstream provider""" - # Build upstream request - # Stream response - # Track usage - # Deduct costs +@proxy_router.api_route("/{path:path}", methods=["GET", "POST"], response_model=None) +async def proxy( + request: Request, path: str, session: AsyncSession = Depends(get_session) +) -> Response | StreamingResponse: + """Forward requests to upstream provider and charge usage.""" ``` Key features: @@ -215,27 +187,29 @@ Key features: #### `routstr/core/db.py` -SQLModel definitions: +SQLModel definitions (selected): ```python -class APIKey(SQLModel, table=True): - id: int | None = Field(primary_key=True) - key_hash: str = Field(index=True, unique=True) - balance: int # millisatoshis - total_deposited: int = 0 +class ApiKey(SQLModel, table=True): + hashed_key: str = Field(primary_key=True) + balance: int + reserved_balance: int = 0 + refund_address: str | None = None + key_expiry_time: int | None = None total_spent: int = 0 - created_at: datetime - expires_at: datetime | None = None - metadata: dict = Field(default_factory=dict, sa_column=Column(JSON)) + total_requests: int = 0 -class Transaction(SQLModel, table=True): - id: int | None = Field(primary_key=True) - api_key_id: int = Field(foreign_key="apikey.id") - amount: int # can be negative - balance_after: int - type: TransactionType - description: str - timestamp: datetime +class LightningInvoice(SQLModel, table=True): + id: str = Field(primary_key=True) + bolt11: str + amount_sats: int + status: str + +class UpstreamProviderRow(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + provider_type: str + base_url: str + api_key: str ``` ### Admin Interface @@ -245,18 +219,17 @@ class Transaction(SQLModel, table=True): Web dashboard and admin API: ```python -@admin_router.get("/admin/") +@admin_router.get("/admin") async def admin_dashboard(request: Request): """Render admin HTML interface""" # Authentication check # Load statistics # Render template -@admin_router.post("/admin/api/withdraw") +@admin_router.post("/admin/withdraw") async def withdraw_balance( - api_key: str, - amount: int | None = None -) -> WithdrawalResponse: + request: Request, withdraw_request: WithdrawRequest +) -> dict[str, str]: """Generate eCash token for withdrawal""" ``` @@ -271,25 +244,14 @@ Features: #### `routstr/wallet.py` -Cashu wallet operations: +Cashu wallet operations (function-based): ```python -class WalletManager: - """Manage Cashu wallet instances""" - - async def redeem_token( - self, - token: str, - mint_url: str | None = None - ) -> int: - """Redeem eCash token and return value""" - - async def create_token( - self, - amount: int, - mint_url: str - ) -> str: - """Create eCash token for withdrawal""" +async def recieve_token(token: str) -> tuple[int, str, str]: + """Redeem eCash token and return amount/unit/mint.""" + +async def send_token(amount: int, unit: str, mint_url: str | None = None) -> str: + """Create eCash token for withdrawal.""" ``` ### Utility Modules @@ -301,12 +263,9 @@ Structured logging configuration: ```python def setup_logging(): """Configure JSON structured logging""" - # Set log level - # Configure formatters - # Add handlers - -class RequestIdMiddleware: - """Add request ID to all logs""" + +class RequestIdFilter(logging.Filter): + """Attach request ID to log records.""" ``` #### `routstr/core/middleware.py` @@ -316,27 +275,18 @@ HTTP middleware components: ```python class LoggingMiddleware: """Log all HTTP requests/responses""" - -class ErrorHandlingMiddleware: - """Consistent error responses""" ``` #### `routstr/core/exceptions.py` -Custom exception hierarchy: +Exception handlers: ```python -class RoustrError(Exception): - """Base exception with error details""" - status_code: int - error_type: str - detail: str +async def http_exception_handler(request: Request, exc: Exception) -> JSONResponse: + """HTTP exception handler with request ID""" -class PaymentError(RoustrError): - """Payment-related errors""" - -class UpstreamError(RoustrError): - """Upstream API errors""" +async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse: + """Fallback exception handler with request ID""" ``` ## Configuration Files @@ -348,7 +298,7 @@ Project metadata and dependencies: ```toml [project] name = "routstr" -version = "0.2.0" +version = "0.2.2" dependencies = [ "fastapi[standard]>=0.115", "sqlmodel>=0.0.24", @@ -409,13 +359,13 @@ Using FastAPI's DI system: ```python # Define dependency -async def get_db() -> AsyncSession: - async with async_session() as session: +async def get_session() -> AsyncGenerator[AsyncSession, None]: + async with AsyncSession(engine, expire_on_commit=False) as session: yield session # Use in routes @router.get("/items") -async def get_items(db: AsyncSession = Depends(get_db)): +async def get_items(db: AsyncSession = Depends(get_session)): result = await db.execute(select(Item)) return result.scalars().all() ``` diff --git a/docs/contributing/setup.md b/docs/contributing/setup.md index 35f72f12..e1576e0b 100644 --- a/docs/contributing/setup.md +++ b/docs/contributing/setup.md @@ -22,22 +22,7 @@ git clone https://github.com/YOUR_USERNAME/routstr-core.git cd routstr-core ``` -### 2. Install uv - -We use [uv](https://github.com/astral-sh/uv) for fast, reliable Python package management: - -```bash -# Using the installer script -curl -LsSf https://astral.sh/uv/install.sh | sh - -# Or with pip -pip install uv - -# Or with Homebrew (macOS) -brew install uv -``` - -### 3. Set Up Environment +### 2. Set Up Environment Run the setup command: @@ -47,13 +32,13 @@ make setup This will: -- ✅ Install uv if not present +- ✅ Install [uv](https://github.com/astral-sh/uv) if not present - ✅ Create a virtual environment - ✅ Install all dependencies - ✅ Install dev tools (mypy, ruff, pytest) - ✅ Install project in editable mode -### 4. Configure Environment +### 3. Configure Environment Create your environment file: @@ -71,7 +56,7 @@ ADMIN_PASSWORD=development-password DATABASE_URL=sqlite+aiosqlite:///dev.db ``` -### 5. Verify Installation +### 4. Verify Installation Run these commands to verify your setup: @@ -168,42 +153,92 @@ Understanding the codebase: ``` routstr-core/ ├── routstr/ # Main package -│ ├── __init__.py # Package initialization +│ ├── __init__.py +│ ├── algorithm.py # Provider selection algorithms │ ├── auth.py # Authentication logic -│ ├── balance.py # Balance management +│ ├── 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 -│ │ ├── db.py # Database models +│ │ ├── 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 -│ │ └── middleware.py # HTTP middleware +│ │ ├── main.py # FastAPI app entry +│ │ ├── middleware.py # HTTP middleware +│ │ └── settings.py # Configuration │ │ -│ └── payment/ # Payment processing -│ ├── cost_calculation.py # Cost logic -│ ├── helpers.py # Utilities -│ ├── lnurl.py # Lightning URLs -│ ├── models.py # Model pricing -│ ├── price.py # BTC pricing -│ └── x_cashu.py # Cashu headers +│ ├── 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 -│ ├── unit/ # Unit tests -│ └── integration/ # Integration tests +├── migrations/ # Alembic migrations +├── scripts/ # Utility scripts +├── docs/ # Documentation +├── examples/ # Usage examples │ -├── migrations/ # Database migrations -├── scripts/ # Utility scripts -├── docs/ # Documentation -│ -├── Makefile # Dev commands -├── pyproject.toml # Project config -└── compose.yml # Docker setup +├── Makefile # Dev commands +├── pyproject.toml # Project config +└── compose.yml # Docker setup ``` +### Admin Dashboard (UI) + +The admin dashboard is a Next.js app using: + +- **Next.js 14** with App Router +- **shadcn/ui** for components +- **Tailwind CSS** for styling +- **pnpm** for package management + +```bash +# Development +cd ui +pnpm install +pnpm dev # http://localhost:3000 + +# Build for production +pnpm build +``` + +The UI is served by the FastAPI backend at `/admin/` when built. Use `make build-ui` to build and copy to the backend. + ## Common Tasks ### Adding a New Endpoint @@ -383,7 +418,7 @@ rm -rf test_*.db Now that you're set up: 1. Read the [Architecture Overview](architecture.md) -3. Check [open issues](https://github.com/routstr/routstr-core/issues) -4. Start with a small contribution +2. Check [open issues](https://github.com/routstr/routstr-core/issues) +3. Start with a small contribution Happy coding! 🚀 diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index d8f75124..a80cccb6 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -6,576 +6,228 @@ This guide covers testing practices, patterns, and tools used in Routstr Core de We follow these principles: -- **Test Behavior, Not Implementation** - Tests should survive refactoring -- **Fast Feedback** - Unit tests run in milliseconds -- **Reliable Tests** - No flaky tests allowed -- **Clear Failures** - Tests should clearly indicate what broke +- Test behavior, not implementation +- Fast feedback +- Reliable tests +- Clear failures ## Test Structure ``` tests/ -├── __init__.py -├── conftest.py # Shared fixtures and configuration -├── unit/ # Fast, isolated unit tests -│ ├── test_auth.py -│ ├── test_balance.py -│ ├── test_cost_calculation.py -│ ├── test_models.py -│ └── test_wallet.py -└── integration/ # Component integration tests - ├── test_api_endpoints.py - ├── test_payment_flow.py - ├── test_proxy_streaming.py - └── test_real_mint.py +├── integration/ +│ ├── conftest.py +│ ├── utils.py +│ ├── test_wallet_topup.py +│ ├── test_wallet_refund.py +│ ├── test_wallet_information.py +│ ├── test_proxy_get_endpoints.py +│ ├── test_proxy_post_endpoints.py +│ └── ... more integration tests +├── unit/ +│ ├── test_algorithm.py +│ ├── test_fee_consistency.py +│ ├── test_image_tokens.py +│ ├── test_logging_securityfilter.py +│ ├── test_payment_helpers.py +│ ├── test_settings.py +│ ├── test_wallet.py +│ └── ... more unit tests +└── run_integration.py ``` ## Running Tests -### Quick Test Commands +### Make Targets + +```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 +``` + +### Direct pytest Commands ```bash # Run all tests -make test +pytest -# Run unit tests only (fast) -make test-unit +# Run a specific test file +pytest tests/unit/test_wallet.py -v -# Run integration tests -make test-integration +# Run a specific test +pytest tests/unit/test_wallet.py::test_get_balance -v -# Run with coverage -make test-coverage - -# Run specific test file -uv run pytest tests/unit/test_auth.py -v - -# Run specific test -uv run pytest tests/unit/test_auth.py::test_create_api_key -v - -# Run tests matching pattern -uv run pytest -k "balance" -v +# Run tests matching a pattern +pytest -k "wallet" -v ``` -### Test Markers +## Test Modes (Integration) -Use markers to categorize tests: +Integration tests support two execution modes: -```python -@pytest.mark.slow -async def test_heavy_computation(): - pass +- Mock mode (default): uses in-memory mocks, no Docker required +- Docker mode: uses real Docker services (Cashu mint, mock OpenAI, Nostr relay) -@pytest.mark.requires_docker -async def test_real_services(): - pass +Use the runner script for Docker mode: -# Run without slow tests -pytest -m "not slow" +```bash +./tests/run_integration.py +``` + +Or manually: + +```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 +``` + +## Test Markers + +Markers are defined in `pyproject.toml`: + +- `integration` +- `unit` +- `slow` +- `requires_docker` +- `requires_real_mint` +- `performance` +- `asyncio` + +Examples: + +```bash +# Skip slow tests +pytest -m "not slow" -v # Run only integration tests -pytest -m "integration" +pytest -m "integration" -v + +# Run performance tests +pytest -m "performance" -v ``` +## Fixtures and Utilities + +### Core Integration Fixtures + +Defined in `tests/integration/conftest.py`: + +- `integration_client` - Async HTTP client for the FastAPI app +- `authenticated_client` - Client with a pre-created API key +- `testmint_wallet` - Test wallet for generating Cashu tokens +- `db_snapshot` - Database state snapshot/diff helper +- `create_api_key` - Helper to create API keys for tests +- `integration_engine`, `integration_session` - Async DB engine/session +- `background_tasks_controller` - Control background tasks in tests +- `mock_upstream_server` - Mock upstream API responses + +### Integration Utilities + +Defined in `tests/integration/utils.py`: + +- `CashuTokenGenerator` +- `ResponseValidator` +- `PerformanceValidator` +- `ConcurrencyTester` +- `DatabaseStateValidator` +- `MockServiceBuilder` +- `TestDataBuilder` + ## Writing Tests ### Unit Test Example ```python -# tests/unit/test_auth.py -import pytest -from routstr.auth import create_api_key, validate_api_key +from routstr.algorithm import calculate_model_cost_score +from routstr.payment.models import Architecture, Model, Pricing -class TestAPIKeyAuth: - """Test API key authentication functionality""" - - async def test_create_api_key(self, test_db): - """Test creating a new API key""" - # Arrange - initial_balance = 10000 - - # Act - api_key = await create_api_key( - balance=initial_balance, - name="Test Key" - ) - - # Assert - assert api_key.key.startswith("sk-") - assert len(api_key.key) == 32 - assert api_key.balance == initial_balance - - async def test_validate_invalid_key(self, test_db): - """Test validation fails for invalid key""" - # Act & Assert - with pytest.raises(InvalidAPIKeyError): - await validate_api_key("invalid_key") + +def test_calculate_model_cost_score_basic() -> None: + model = Model( + id="test-model", + name="Test test-model", + created=1234567890, + description="Test model", + context_length=8192, + architecture=Architecture( + modality="text", + input_modalities=["text"], + output_modalities=["text"], + tokenizer="gpt", + instruct_type=None, + ), + pricing=Pricing( + prompt=0.001, + completion=0.002, + request=0.0, + image=0.0, + web_search=0.0, + internal_reasoning=0.0, + ), + ) + assert calculate_model_cost_score(model) == 0.002 ``` ### Integration Test Example ```python -# tests/integration/test_payment_flow.py import pytest from httpx import AsyncClient -class TestPaymentFlow: - """Test end-to-end payment flows""" - - @pytest.mark.asyncio - async def test_token_redemption_flow( - self, - app_client: AsyncClient, - mock_cashu_wallet - ): - """Test complete token redemption and API usage""" - # Arrange - token = create_test_token(amount=5000) - mock_cashu_wallet.redeem.return_value = 5000 - - # Act - Create API key - response = await app_client.post( - "/v1/wallet/create", - json={"cashu_token": token} - ) - - # Assert - Key created - assert response.status_code == 200 - api_key = response.json()["api_key"] - assert response.json()["balance"] == 5000 - - # Act - Use API key - response = await app_client.post( - "/v1/chat/completions", - headers={"Authorization": f"Bearer {api_key}"}, - json={ - "model": "gpt-3.5-turbo", - "messages": [{"role": "user", "content": "Hi"}] - } - ) - - # Assert - Request successful - assert response.status_code == 200 - assert "choices" in response.json() -``` -## Test Fixtures - -### Common Fixtures - -Located in `tests/conftest.py`: - -```python -@pytest.fixture -async def test_db(): - """Provide a clean test database""" - # Create in-memory SQLite database - engine = create_async_engine("sqlite+aiosqlite:///:memory:") - - async with engine.begin() as conn: - await conn.run_sync(SQLModel.metadata.create_all) - - async_session = sessionmaker(engine, class_=AsyncSession) - - async with async_session() as session: - yield session - - await engine.dispose() - -@pytest.fixture -async def app_client(test_db): - """Provide test client with test database""" - app.dependency_overrides[get_db] = lambda: test_db - - async with AsyncClient(app=app, base_url="http://test") as client: - yield client - - app.dependency_overrides.clear() - -@pytest.fixture -def mock_cashu_wallet(mocker): - """Mock Cashu wallet for testing""" - mock = mocker.patch("routstr.wallet.Wallet") - mock.return_value.redeem.return_value = 1000 - return mock -``` - -### Using Fixtures - -```python -async def test_with_fixtures( - test_db, # Get test database - app_client, # Get test HTTP client - mock_cashu_wallet # Get mocked wallet -): - # Use fixtures in test - pass -``` - -## Mocking Strategies - -### Mocking External Services - -```python -# Mock upstream API -@pytest.fixture -def mock_openai(mocker): - mock_response = mocker.Mock() - mock_response.json.return_value = { - "choices": [{ - "message": {"content": "Hello!"}, - "finish_reason": "stop" - }], - "usage": { - "prompt_tokens": 10, - "completion_tokens": 20, - "total_tokens": 30 - } - } - - mocker.patch( - "httpx.AsyncClient.post", - return_value=mock_response - ) - -# Mock Cashu mint -@pytest.fixture -def mock_mint(mocker): - mint = mocker.patch("routstr.wallet.Mint") - mint.return_value.check_proof_state.return_value = True - return mint -``` - -### Mocking Time - -```python -from freezegun import freeze_time - -@freeze_time("2024-01-01 12:00:00") -async def test_time_dependent(): - # Time is frozen during test - key = await create_api_key(expires_in_days=30) - assert key.expires_at == datetime(2024, 1, 31, 12, 0, 0) -``` - -## Test Patterns - -### Testing Async Code - -```python -# Always mark async tests +@pytest.mark.integration @pytest.mark.asyncio -async def test_async_function(): - result = await async_operation() - assert result == expected - -# Test async context managers -async def test_async_context(): - async with create_resource() as resource: - assert resource.is_active +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 ``` -### Testing Exceptions - -```python -# Test specific exception -async def test_raises_specific_error(): - with pytest.raises(InsufficientBalanceError) as exc_info: - await deduct_balance(api_key, amount=999999) - - assert "Insufficient balance" in str(exc_info.value) - assert exc_info.value.status_code == 402 - -# Test exception details -async def test_exception_details(): - with pytest.raises(RoustrError) as exc_info: - await risky_operation() - - error = exc_info.value - assert error.error_type == "validation_error" - assert error.detail == "Invalid input" -``` - -### Testing Streaming Responses - -```python -async def test_streaming_response(): - """Test streaming chat completion""" - chunks = [] - - async with app_client.stream( - "POST", - "/v1/chat/completions", - json={"model": "gpt-3.5-turbo", "stream": True} - ) as response: - async for line in response.aiter_lines(): - if line.startswith("data: "): - chunks.append(json.loads(line[6:])) - - # Verify chunks - assert len(chunks) > 0 - assert chunks[-1] == "[DONE]" -``` - -### Database Testing - -```python -async def test_database_transaction(test_db): - """Test atomic transactions""" - async with test_db.begin(): - # Create test data - api_key = APIKey(key_hash="test", balance=1000) - test_db.add(api_key) - await test_db.flush() - - # Test rollback - try: - async with test_db.begin_nested(): - api_key.balance = -100 # Invalid - await test_db.flush() - raise ValueError("Rollback") - except ValueError: - pass - - # Verify rollback worked - await test_db.refresh(api_key) - assert api_key.balance == 1000 -``` - -## Performance Testing - -### Benchmark Tests - -```python -@pytest.mark.benchmark -def test_performance(benchmark): - """Benchmark critical functions""" - result = benchmark(expensive_function, arg1, arg2) - assert result == expected - -# Run benchmarks -pytest --benchmark-only -``` - -### Load Testing - -```python -# tests/integration/test_load.py -async def test_concurrent_requests(app_client): - """Test handling multiple concurrent requests""" - async def make_request(i): - response = await app_client.get(f"/test/{i}") - return response.status_code - - # Make 100 concurrent requests - tasks = [make_request(i) for i in range(100)] - results = await asyncio.gather(*tasks) - - # All should succeed - assert all(status == 200 for status in results) -``` - -## Test Data - -### Factories - -```python -# tests/factories.py -from datetime import datetime, timedelta - -def create_test_api_key(**kwargs): - """Factory for test API keys""" - defaults = { - "key": f"sk-test-{uuid4().hex[:8]}", - "balance": 10000, - "created_at": datetime.utcnow(), - "expires_at": datetime.utcnow() + timedelta(days=30) - } - defaults.update(kwargs) - return APIKey(**defaults) - -def create_test_token(amount: int = 1000, mint: str = None): - """Factory for test Cashu tokens""" - # Create valid test token structure - return base64.encode(...) -``` - -### Test Constants - -```python -# tests/constants.py -TEST_MODELS = { - "gpt-3.5-turbo": { - "prompt": 0.0015, - "completion": 0.002 - }, - "gpt-4": { - "prompt": 0.03, - "completion": 0.06 - } -} - -TEST_API_KEY = "sk-test-1234567890" -TEST_MINT_URL = "https://testmint.example.com" -``` - -## Coverage - -### Running Coverage +## Debugging Tips ```bash -# Generate coverage report -make test-coverage +# Show print output +pytest -s tests/unit/test_wallet.py -# View HTML report -open htmlcov/index.html - -# Coverage with specific tests -pytest --cov=routstr --cov-report=html tests/unit/ -``` - -### Coverage Configuration - -In `pyproject.toml`: - -```toml -[tool.coverage.run] -source = ["routstr"] -omit = ["tests/*", "*/migrations/*"] - -[tool.coverage.report] -exclude_lines = [ - "pragma: no cover", - "def __repr__", - "raise AssertionError", - "raise NotImplementedError", - "if TYPE_CHECKING:" -] -``` - -## Debugging Tests - -### Print Debugging - -```python -# Use -s flag to see print output -pytest -s tests/unit/test_auth.py - -# In test -async def test_debug(): - print(f"Value: {value}") # Will show with -s - assert value == expected -``` - -### Interactive Debugging - -```python # Drop into debugger on failure pytest --pdb - -# Set breakpoint in test -async def test_debug(): - import pdb; pdb.set_trace() - # Execution stops here ``` -### Logging in Tests - -```python -# Enable debug logging in tests -import logging -logging.basicConfig(level=logging.DEBUG) - -# Or use caplog fixture -async def test_logging(caplog): - with caplog.at_level(logging.INFO): - await function_that_logs() - - assert "Expected message" in caplog.text -``` - -## CI/CD Integration - -### GitHub Actions - -Tests run automatically on: - -- Pull requests -- Pushes to main -- Nightly schedules - -See `.github/workflows/test.yml` for configuration. - -### Pre-commit Hooks - -Install pre-commit hooks: - -```bash -pre-commit install - -# Run manually -pre-commit run --all-files -``` - -## Best Practices - -### Do's - -1. ✅ Write tests first (TDD) -2. ✅ Keep tests simple and focused -3. ✅ Use descriptive test names -4. ✅ Test edge cases -5. ✅ Mock external dependencies -6. ✅ Use fixtures for setup -7. ✅ Assert specific values - -### Don'ts - -1. ❌ Don't test implementation details -2. ❌ Don't use production services -3. ❌ Don't rely on test order -4. ❌ Don't ignore flaky tests -5. ❌ Don't skip error cases -6. ❌ Don't use hard-coded waits -7. ❌ Don't commit commented tests - ## Troubleshooting -### Common Issues - -**Async Test Errors** - -```python -# Wrong -def test_async(): # Missing async - await function() - -# Right -async def test_async(): - await function() -``` - -**Database State** - -```python -# Ensure clean state -@pytest.fixture(autouse=True) -async def cleanup(test_db): - yield - # Cleanup after each test - await test_db.execute("DELETE FROM apikey") - await test_db.commit() -``` - -**Mock Not Working** - -```python -# Check import path -mocker.patch("routstr.wallet.Wallet") # Full path -# Not just "Wallet" -``` +- Docker mode failures: check `docker ps` and `docker-compose -f compose.testing.yml logs` +- Connection errors: make sure ports 3338, 3000, 8000, and 8088 are free +- Slow tests: use `pytest -m "not slow"` or `make test-fast` ## Next Steps -- See [Architecture](architecture.md) for system design -- Read [Setup Guide](setup.md) for environment setup +- See [Architecture](architecture.md) +- Read [Setup Guide](setup.md) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md deleted file mode 100644 index 246678c6..00000000 --- a/docs/getting-started/configuration.md +++ /dev/null @@ -1,260 +0,0 @@ -# Configuration - -Routstr Core is configured via a single settings row in the database. Environment variables are only used on first run to seed that row (with a few computed defaults like `ONION_URL`). After that, the database is the source of truth. You can update settings at runtime via the admin API. `DATABASE_URL` is always env-only. - -## Environment Variables - -### Core Settings - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `UPSTREAM_BASE_URL` | Base URL of the OpenAI-compatible API to proxy | - | ✅ | -| `UPSTREAM_API_KEY` | API key for the upstream service | - | ❌ | -| `ADMIN_PASSWORD` | Password for admin dashboard access | - | ⚠️ | - -### Node Information - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `NAME` | Public name of your Routstr node | `ARoutstrNode` | ❌ | -| `DESCRIPTION` | Description of your node | `A Routstr Node` | ❌ | -| `NPUB` | Nostr public key for node identity | - | ❌ | -| `HTTP_URL` | Public HTTP URL of your node | - | ❌ | -| `ONION_URL` | Tor hidden service URL | - | ❌ | - -### Cashu Configuration - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `CASHU_MINTS` | Comma-separated list of trusted Cashu mint URLs | `https://mint.minibits.cash/Bitcoin` | ❌ | -| `RECEIVE_LN_ADDRESS` | Lightning address for automatic payouts | - | ❌ | - -### Pricing Configuration - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `FIXED_PRICING` | Force fixed per-request pricing (ignore model token pricing) | `false` | ❌ | -| `FIXED_COST_PER_REQUEST` | Fixed cost per API request in sats | `1` | ❌ | -| `FIXED_PER_1K_INPUT_TOKENS` | Optional override: sats per 1000 input tokens | `0` | ❌ | -| `FIXED_PER_1K_OUTPUT_TOKENS` | Optional override: sats per 1000 output tokens | `0` | ❌ | -| `EXCHANGE_FEE` | Exchange rate markup (1.005 = 0.5% fee) | `1.005` | ❌ | -| `UPSTREAM_PROVIDER_FEE` | Provider fee markup (1.05 = 5% fee) | `1.05` | ❌ | - -### Network & Discovery - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `CORS_ORIGINS` | Comma-separated list of allowed CORS origins | `*` | ❌ | -| `TOR_PROXY_URL` | SOCKS5 proxy URL for Tor connections | `socks5://127.0.0.1:9050` | ❌ | -| `RELAYS` | Comma-separated nostr relays used for provider discovery | sane defaults | ❌ | -| `PROVIDERS_REFRESH_INTERVAL_SECONDS` | Provider cache refresh interval | `300` | ❌ | - -### Logging Configuration - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `LOG_LEVEL` | Logging level (DEBUG, INFO, WARNING, ERROR) | `INFO` | ❌ | -| `ENABLE_CONSOLE_LOGGING` | Enable console log output | `true` | ❌ | - -### Other - -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `CHAT_COMPLETIONS_API_VERSION` | Append `api-version` to `/chat/completions` (Azure OpenAI) | - | ❌ | -| `DATABASE_URL` | SQLite database connection string | `sqlite+aiosqlite:///keys.db` | ❌ | -| `REFUND_CACHE_TTL_SECONDS` | Cache TTL for refund responses (seconds) | `3600` | ❌ | - -## Configuration Examples - -### Basic OpenAI Proxy - -```bash -# .env -UPSTREAM_BASE_URL=https://api.openai.com/v1 -UPSTREAM_API_KEY=sk-... -ADMIN_PASSWORD=my-secure-password -``` - -### Custom AI Provider - -```bash -# .env -UPSTREAM_BASE_URL=https://api.anthropic.com/v1 -UPSTREAM_API_KEY=your-anthropic-key -MODELS_PATH=/app/config/anthropic-models.json -``` - -### Azure OpenAI (optional) - -```bash -# .env -UPSTREAM_BASE_URL=https://.openai.azure.com/openai/deployments/ -UPSTREAM_API_KEY= -CHAT_COMPLETIONS_API_VERSION=2024-05-01-preview -``` - -### High-Security Setup - -```bash -# .env -UPSTREAM_BASE_URL=https://api.openai.com/v1 -UPSTREAM_API_KEY=sk-... -ADMIN_PASSWORD=very-long-secure-password-here -CORS_ORIGINS=https://myapp.com,https://app.myapp.com -TOR_PROXY_URL=socks5://tor:9050 -LOG_LEVEL=WARNING -``` - -### Public Node Configuration - -```bash -# .env -NAME=Lightning AI Gateway -DESCRIPTION=Fast and reliable AI API access with Bitcoin payments -NPUB=npub1abcd... -HTTP_URL=https://api.lightning-ai.com -ONION_URL=http://lightningai.onion -CASHU_MINTS=https://mint1.com,https://mint2.com -``` - -## Pricing - -- Default: pricing comes from your `models.json`. -- Force fixed per-request pricing: set `FIXED_PRICING=true` and `FIXED_COST_PER_REQUEST`. -- Optional token overrides when using model pricing: set - `FIXED_PER_1K_INPUT_TOKENS` and/or `FIXED_PER_1K_OUTPUT_TOKENS`. -- Legacy envs are still accepted and mapped automatically: - `MODEL_BASED_PRICING` → `!FIXED_PRICING`, `COST_PER_REQUEST` → `FIXED_COST_PER_REQUEST`, - `COST_PER_1K_*` → `FIXED_PER_1K_*`. - -Example fixed pricing: - -```bash -FIXED_PRICING=true -FIXED_COST_PER_REQUEST=10 -``` - -## Custom Models Configuration - -Create a `models.json` file: - -```json -{ - "models": [ - { - "id": "gpt-4", - "name": "GPT-4", - "pricing": { - "prompt": "0.00003", - "completion": "0.00006", - "request": "0" - } - }, - { - "id": "gpt-3.5-turbo", - "name": "GPT-3.5 Turbo", - "pricing": { - "prompt": "0.0000015", - "completion": "0.000002", - "request": "0" - } - } - ] -} -``` - -## Security Best Practices - -### Admin Password - -Generate a strong password: - -```bash -openssl rand -base64 32 -``` - -### API Keys - -- Rotate upstream API keys regularly -- Use read-only keys when possible -- Monitor key usage - -### Network Security - -- Restrict CORS origins in production -- Use HTTPS for public endpoints -- Enable Tor for anonymity - -### Database Security - -- Regular backups -- Encrypted storage volumes -- Restricted file permissions - -## Troubleshooting - -### Check Current Configuration - -```bash -# View all environment variables -docker exec routstr env | sort - -# Test configuration -curl http://localhost:8000/v1/info -``` - -### Common Issues - -**Missing Upstream URL** - -``` -ERROR: UPSTREAM_BASE_URL not set -Solution: Set UPSTREAM_BASE_URL in .env -``` - -**Invalid Cashu Mint** - -``` -ERROR: Failed to connect to mint -Solution: Verify CASHU_MINTS URLs are accessible -``` - -**Database Errors** - -``` -ERROR: Database connection failed -Solution: Check DATABASE_URL and file permissions -``` - -## Advanced Configuration - -### Multiple Mints - -Configure fallback mints: - -```bash -CASHU_MINTS=https://primary.mint,https://backup1.mint,https://backup2.mint -``` - -### Custom Database - -Use PostgreSQL instead of SQLite: - -```bash -DATABASE_URL=postgresql+asyncpg://user:pass@localhost/routstr -``` - -### Proxy Settings - -For corporate environments: - -```bash -HTTP_PROXY=http://proxy.company.com:8080 -HTTPS_PROXY=http://proxy.company.com:8080 -``` - -## Next Steps - -- [User Guide](../user-guide/introduction.md) - Start using Routstr -- [Admin Dashboard](../user-guide/admin-dashboard.md) - Manage your node -- [Custom Pricing](../advanced/custom-pricing.md) - Advanced pricing strategies diff --git a/docs/getting-started/docker.md b/docs/getting-started/docker.md deleted file mode 100644 index 9357a44f..00000000 --- a/docs/getting-started/docker.md +++ /dev/null @@ -1,337 +0,0 @@ -# Docker Setup - -This guide covers deploying Routstr Core using Docker for production environments. - -## Docker Images - -Official images are available on GitHub Container Registry: - -```bash -ghcr.io/routstr/proxy:latest -``` - -## Basic Docker Run - -### Minimal Setup - -```bash -docker run -d \ - --name routstr \ - -p 8000:8000 \ - -e UPSTREAM_BASE_URL=https://api.openai.com/v1 \ - -e UPSTREAM_API_KEY=sk-... \ - -e ADMIN_PASSWORD=secure-password \ - ghcr.io/routstr/proxy:latest -``` - -### With Persistent Storage - -```bash -docker run -d \ - --name routstr \ - -p 8000:8000 \ - -v routstr-data:/app/data \ - -v routstr-logs:/app/logs \ - -e UPSTREAM_BASE_URL=https://api.openai.com/v1 \ - -e UPSTREAM_API_KEY=sk-... \ - -e DATABASE_URL=sqlite+aiosqlite:///data/keys.db \ - ghcr.io/routstr/proxy:latest -``` - -## Docker Compose Setup - -### Basic Configuration - -Create `compose.yml`: - -```yaml -version: '3.8' - -services: - routstr: - image: ghcr.io/routstr/proxy:latest - ports: - - "8000:8000" - volumes: - - ./data:/app/data - - ./logs:/app/logs - env_file: - - .env - restart: unless-stopped -``` - -### With Tor Hidden Service - -The included `compose.yml` provides Tor support: - -```yaml -version: '3.8' - -services: - routstr: - build: . # Or use image: ghcr.io/routstr/proxy:latest - volumes: - - .:/app - - ./logs:/app/logs - env_file: - - .env - environment: - - TOR_PROXY_URL=socks5://tor:9050 - ports: - - 8000:8000 - extra_hosts: - - "host.docker.internal:host-gateway" - - tor: - image: ghcr.io/hundehausen/tor-hidden-service:latest - volumes: - - tor-data:/var/lib/tor - environment: - - HS_ROUTER=routstr:8000:80 - depends_on: - - routstr - -volumes: - tor-data: -``` - -### Environment File - -Create `.env` file: - -```bash -# Required -UPSTREAM_BASE_URL=https://api.openai.com/v1 -UPSTREAM_API_KEY=your-api-key -ADMIN_PASSWORD=secure-admin-password - -# Cashu Configuration -CASHU_MINTS=https://mint.minibits.cash/Bitcoin - -# Optional -NAME=My Routstr Node -DESCRIPTION=Pay-per-use AI API proxy -NPUB=npub1... -HTTP_URL=https://api.mynode.com -ONION_URL=http://mynode.onion - -# Pricing (optional) -FIXED_PRICING=false -EXCHANGE_FEE=1.005 -UPSTREAM_PROVIDER_FEE=1.05 -``` - -## Building Custom Image - -### Dockerfile Overview - -The provided Dockerfile: - -- Uses Alpine Linux for small size -- Installs required dependencies for secp256k1 -- Runs as non-root user -- Exposes port 8000 - -### Build Locally - -```bash -# Clone repository -git clone https://github.com/routstr/routstr-core.git -cd routstr-core - -# Build image -docker build -t my-routstr:latest . - -# Run custom image -docker run -d \ - --name routstr \ - -p 8000:8000 \ - --env-file .env \ - my-routstr:latest -``` - -## Deployment Considerations - -### Resource Requirements - -- **CPU**: 1-2 cores recommended -- **Memory**: 512MB-1GB -- **Storage**: 1GB + database growth -- **Network**: Low latency to upstream provider - -### Health Checks - -Add health check to compose.yml: - -```yaml -services: - routstr: - # ... other config ... - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/v1/info"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s -``` - -### Reverse Proxy Setup - -#### Nginx Example - -```nginx -server { - listen 443 ssl http2; - server_name api.yournode.com; - - ssl_certificate /path/to/cert.pem; - ssl_certificate_key /path/to/key.pem; - - location / { - proxy_pass http://localhost:8000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # For streaming responses - proxy_buffering off; - proxy_cache off; - proxy_set_header Connection ''; - proxy_http_version 1.1; - chunked_transfer_encoding off; - } -} -``` - -#### Caddy Example - -```caddy -api.yournode.com { - reverse_proxy localhost:8000 { - flush_interval -1 - } -} -``` - -## Monitoring - -### Log Management - -View logs: - -```bash -# Docker -docker logs -f routstr - -# Docker Compose -docker compose logs -f routstr - -# Log files -tail -f ./logs/routstr.log -``` - -### Metrics - -Monitor key metrics: - -- Request count and latency -- Token validation success rate -- Upstream API errors -- Database size growth - -## Backup and Recovery - -### Database Backup - -```bash -# Backup SQLite database -docker exec routstr sqlite3 /app/data/keys.db ".backup /app/data/backup.db" - -# Copy backup locally -docker cp routstr:/app/data/backup.db ./backup-$(date +%Y%m%d).db -``` - -### Restore from Backup - -```bash -# Stop service -docker compose down - -# Restore database -docker cp ./backup.db routstr:/app/data/keys.db - -# Restart service -docker compose up -d -``` - -## Security Considerations - -### Environment Variables - -- Never commit `.env` files -- Use Docker secrets for sensitive data -- Rotate API keys regularly -- Use strong admin passwords - -### Network Security - -- Use HTTPS/TLS termination -- Restrict admin interface access -- Enable firewall rules -- Monitor for suspicious activity - -### Container Security - -- Run as non-root user -- Use read-only filesystem where possible -- Limit container capabilities -- Keep base image updated - -## Troubleshooting - -### Container Won't Start - -```bash -# Check logs -docker logs routstr - -# Verify environment -docker exec routstr env | grep -E "(UPSTREAM|CASHU|ADMIN)" - -# Test database connection -docker exec routstr sqlite3 /app/data/keys.db ".tables" -``` - -### Permission Issues - -```bash -# Fix volume permissions -sudo chown -R 1000:1000 ./data ./logs -``` - -### Network Issues - -```bash -# Test upstream connectivity -docker exec routstr curl -I https://api.openai.com - -# Check DNS resolution -docker exec routstr nslookup api.openai.com -``` - -## Production Checklist - -- [ ] Set strong `ADMIN_PASSWORD` -- [ ] Configure proper `UPSTREAM_BASE_URL` and `UPSTREAM_API_KEY` -- [ ] Set up persistent volumes for data and logs -- [ ] Configure reverse proxy with TLS -- [ ] Set up monitoring and alerting -- [ ] Implement backup strategy -- [ ] Test disaster recovery -- [ ] Document deployment process - -## Next Steps - -- [Configuration Guide](configuration.md) - All environment variables -- [Admin Dashboard](../user-guide/admin-dashboard.md) - Manage your node diff --git a/docs/getting-started/overview.md b/docs/getting-started/overview.md deleted file mode 100644 index 1381f2fb..00000000 --- a/docs/getting-started/overview.md +++ /dev/null @@ -1,156 +0,0 @@ -# Overview - -Routstr Core is a powerful payment proxy that brings Bitcoin micropayments to AI APIs. This overview will help you understand the core concepts and architecture. - -## Core Concepts - -### Payment Proxy - -Routstr acts as a transparent proxy between your application and OpenAI-compatible APIs. It: - -- Intercepts API requests -- Validates payment tokens -- Forwards requests to the upstream provider -- Tracks usage and deducts costs -- Returns responses to the client - -### Cashu eCash Protocol - -[Cashu](https://cashu.space) is a Bitcoin eCash protocol that enables: - -- **Privacy**: Payments are unlinkable and untraceable -- **Instant Settlement**: No waiting for blockchain confirmations -- **Micropayments**: Send fractions of a satoshi -- **Offline Capability**: Tokens can be transferred without internet - -### Lightning Network Integration - -Routstr connects to the Lightning Network through Cashu mints, enabling: - -- Fast Bitcoin deposits and withdrawals -- Global payment reach -- Low transaction fees -- No minimum payment amounts - -## Architecture - -### System Components - -```mermaid -graph TB - subgraph "Client Side" - A[AI Application] - B[OpenAI SDK] - C[eCash Wallet] - end - - subgraph "Routstr Core" - D[FastAPI Server] - E[Auth Module] - F[Payment Module] - G[Proxy Module] - H[SQLite Database] - end - - subgraph "External Services" - I[Upstream AI Provider] - J[Cashu Mint] - K[Bitcoin/Lightning] - end - - A --> B - B --> D - C --> D - D --> E - E --> F - F --> J - D --> G - G --> I - D --> H - J --> K -``` - -### Key Modules - -1. **Authentication** (`auth.py`) - - API key validation - - Balance checking - - Request authorization - -2. **Payment Processing** (`payment/`) - - Token validation - - Cost calculation - - Balance updates - - Pricing models - -3. **Proxy Handler** (`proxy.py`) - - Request forwarding - - Response streaming - - Usage tracking - - Error handling - -4. **Wallet Management** (`wallet.py`) - - Cashu wallet integration - - Token redemption - - Balance management - - Automatic payouts - -5. **Admin Interface** (`core/admin.py`) - - Web dashboard - - Balance viewing - - Key management - - Withdrawal interface - -## Payment Flow - -### Standard Flow (API Key) - -1. User deposits eCash tokens to create an API key -2. Client sends requests with the API key -3. Routstr checks balance and forwards request -4. Cost is deducted based on actual usage -5. Response is returned to client - -### Per-Request Flow (Coming Soon) - -1. Client includes eCash token in request header -2. Routstr validates token meets minimum amount -3. Request is processed -4. Change is returned in response header -5. No account or balance needed - -## Supported Features - -### API Compatibility - -- ✅ Chat completions (streaming and non-streaming) -- ✅ Text completions -- ✅ Embeddings -- ✅ Image generation -- ✅ Audio transcription/translation -- ✅ Model listing -- ✅ Custom endpoints - -### Payment Features - -- ✅ Multiple Cashu mint support -- ✅ Automatic balance tracking -- ✅ Model-based pricing -- ✅ USD to BTC conversion -- ✅ Configurable fees -- ✅ Balance withdrawals - -### Operational Features - -- ✅ Docker deployment -- ✅ Tor hidden service support -- ✅ Nostr relay discovery -- ✅ Database migrations -- ✅ Comprehensive logging -- ✅ Admin dashboard - -## Next Steps - -- [Quick Start](quickstart.md) - Get running in minutes -- [Docker Setup](docker.md) - Deploy with containers -- [Configuration](configuration.md) - Customize your instance \ No newline at end of file diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md deleted file mode 100644 index de17dc0c..00000000 --- a/docs/getting-started/quickstart.md +++ /dev/null @@ -1,226 +0,0 @@ -# Quick Start - -Get Routstr Core up and running in minutes with Docker or local development setup. - -## Prerequisites - -- Docker and Docker Compose (for production) -- Python 3.11+ (for development) -- A Cashu-compatible wallet (optional for testing) - -## Option 1: Docker (Recommended) - -### Quick Run - -The fastest way to start Routstr Core: - -```bash -docker run -d \ - --name routstr-proxy \ - -p 8000:8000 \ - -e UPSTREAM_BASE_URL=https://api.openai.com/v1 \ - -e UPSTREAM_API_KEY=your-openai-api-key \ - ghcr.io/routstr/proxy:latest -``` - -### Docker Compose - -For a full setup with Tor support: - -1. Clone the repository: - -```bash -git clone https://github.com/routstr/routstr-core.git -cd routstr-core -``` - -2. Create environment file: - -```bash -cp .env.example .env -# Edit .env with your settings -``` - -3. Start the services: - -```bash -docker compose up -d -``` - -This will start: - -- Routstr proxy on port 8000 -- Tor hidden service (optional) -- Automatic database migrations - -### Verify Installation - -Check that Routstr is running: - -```bash -curl http://localhost:8000/v1/info -``` - -You should see: - -```json -{ - "name": "ARoutstrNode", - "description": "A Routstr Node", - "version": "0.2.0", - "npub": "", - "mints": ["https://mint.minibits.cash/Bitcoin"], - "models": {...} -} -``` - -## Option 2: Local Development - -### Install Dependencies - -1. Install [uv](https://github.com/astral-sh/uv) package manager: - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -``` - -2. Clone and setup: - -```bash -git clone https://github.com/routstr/routstr-core.git -cd routstr-core -uv sync -``` - -3. Configure environment: - -```bash -cp .env.example .env -# Edit .env with your settings -``` - -### Run the Server - -```bash -fastapi run routstr --host 0.0.0.0 --port 8000 -``` - -## First API Call - -### 1. Get an eCash Token - -You'll need a Cashu token to pay for API calls. Options: - -- Use a [Cashu wallet](https://cashu.space) to create tokens -- Get test tokens from a testnet mint -- Use the example token (for testing only) - -### 2. Create an API Key - -Send your eCash token to create an API key: - -```bash -curl -X POST http://localhost:8000/v1/wallet/create \ - -H "Content-Type: application/json" \ - -d '{ - "cashu_token": "cashuAeyJ0b2..." - }' -``` - -Response: - -```json -{ - "api_key": "rUvK7...", - "balance": 10000 -} -``` - -### 3. Make an API Call - -Use your API key like a normal OpenAI key: - -```python -import openai - -client = openai.OpenAI( - api_key="rUvK7...", # Your Routstr API key - base_url="http://localhost:8000/v1" -) - -response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "Hello!"}] -) - -print(response.choices[0].message.content) -``` - -## Example Client - -Run the included example: - -```bash -CASHU_TOKEN="your-token" python example.py -``` - -This demonstrates: - -- Creating an API key from a token -- Making streaming chat requests -- Automatic balance deduction - -## Testing the Setup - -### Check Available Models - -```bash -curl http://localhost:8000/v1/models -``` - -### View Admin Dashboard - -Open in your browser. - -Default password is set in `ADMIN_PASSWORD` environment variable. - -### Monitor Logs - -Docker: - -```bash -docker compose logs -f routstr -``` - -Local: - -```bash -# Logs are in ./logs/ directory -tail -f logs/routstr.log -``` - -## Common Issues - -### Connection Refused - -- Ensure the service is running: `docker ps` -- Check firewall settings -- Verify port 8000 is not in use - -### Invalid API Key - -- Ensure you've created an API key with sufficient balance -- Check the token was valid and had value -- Verify the mint URL is accessible - -### Upstream Errors - -- Check `UPSTREAM_BASE_URL` is correct -- Verify `UPSTREAM_API_KEY` if required -- Test upstream service directly - -## Next Steps - -- [Configuration Guide](configuration.md) - Customize settings -- [Docker Setup](docker.md) - Production deployment -- [User Guide](../user-guide/introduction.md) - Detailed usage diff --git a/docs/getting-started/ui-configuration.md b/docs/getting-started/ui-configuration.md deleted file mode 100644 index b619b160..00000000 --- a/docs/getting-started/ui-configuration.md +++ /dev/null @@ -1,53 +0,0 @@ -# UI Configuration - -This guide explains how to configure the Routstr UI for different environments. - -## Environment Variables - -The UI uses Next.js environment variables to configure API endpoints and authentication. - -### Centralized Configuration - -This project uses a centralized configuration approach with a single `.env` file in the project root. This file contains both backend and frontend configuration variables. - -Create or update your `.env` file in the project root: - -```bash -# .env (in project root) - -# UI Configuration (NEXT_PUBLIC_ variables are exposed to the browser) -NEXT_PUBLIC_API_URL=http://127.0.0.1:8000 -``` - -### Development vs Production - -The same `.env` file is used for both development and production. Simply change the values: - -**Development:** - -```bash -NEXT_PUBLIC_API_URL=http://127.0.0.1:8000 -``` - -**Production:** - -```bash -NEXT_PUBLIC_API_URL=https://api.yourroutstr.com -``` - -## Building the UI - -The build process automatically reads configuration from the root `.env` file: - -```bash -# From the project root -make ui-build -# or -./scripts/build-ui.sh -``` - -The build script will automatically: - -- Load `NEXT_PUBLIC_*` variables from the root `.env` file -- Use them during the Next.js build process -- Display warnings if the `.env` file is missing diff --git a/docs/index.md b/docs/index.md index aeacbcf4..9a72b9b7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,83 +1,44 @@ # Routstr Core Documentation -Welcome to the official documentation for **Routstr Core** - a FastAPI-based reverse proxy that enables Bitcoin micropayments for OpenAI-compatible APIs using the Cashu eCash protocol. +**Routstr** is a decentralized protocol for permissionless AI inference. It enables an open marketplace where anyone can buy and sell compute using **Bitcoin eCash (Cashu)**. -## What is Routstr Core? +--- -Routstr Core is a payment proxy that sits between API clients and OpenAI-compatible services. It enables: +## 🐣 For Clients (Users & Builders) -- **Pay-per-request billing** using Bitcoin eCash tokens -- **Seamless integration** with existing OpenAI clients -- **Privacy-preserving payments** through the Cashu protocol -- **Flexible pricing models** with per-token or per-request billing -- **Multi-provider support** for various AI model providers +If you want to use AI models in your application without accounts or KYC. -### Key Features +- **[Introduction](client/introduction.md)**: How the ecosystem works. +- **[Payment Flow](client/payments.md)**: Funding sessions, topping up, and refunds. +- **[Integration Guide](client/integration.md)**: Code examples for Python, JS, and cURL. -- 🪙 **Cashu Wallet Integration** - Accept Lightning payments and redeem eCash tokens -- 🔑 **API Key Management** - Secure key storage with balance tracking -- 💰 **Dynamic Pricing** - Model-based pricing with live BTC/USD conversion -- 🎛️ **Admin Dashboard** - Web interface for balance and key management -- 🌐 **Nostr Discovery** - Find providers through decentralized relay network -- 🐋 **Docker Support** - Easy deployment with optional Tor hidden service -- ⚡ **Lightning Fast** - Minimal latency overhead for API requests +## 🦁 For Providers (Node Operators) -## How It Works +If you want to run a node, resell API access, or monetize hardware. -```mermaid -sequenceDiagram - participant Client - participant Routstr as Routstr Proxy - participant DB as Database - participant Upstream as AI Provider - participant Wallet as Cashu Wallet +- **[Quick Start](provider/quickstart.md)**: Deploy a node in 5 minutes. +- **[Deployment](provider/deployment.md)**: Production Docker setup. +- **[Configuration](provider/configuration.md)**: Environment variables and settings. +- **[Dashboard](provider/dashboard.md)**: Managing your node visually. +- **[Pricing Strategy](provider/pricing.md)**: Setting margins and fees. +- **[Discovery](provider/discovery.md)**: Announcing your node on Nostr. +- **[Tor Support](provider/tor.md)**: Running an anonymous hidden service. - Client->>Routstr: API Request + eCash Token - Routstr->>Wallet: Validate & Redeem Token - Wallet-->>Routstr: Token Value (sats) - Routstr->>DB: Store/Update Balance - Routstr->>Upstream: Forward API Request - Upstream-->>Routstr: API Response + Usage Data - Routstr->>DB: Deduct Actual Cost - Routstr-->>Client: API Response -``` +--- -## Quick Links +## 🔌 API Reference -
+- **[Overview](api/overview.md)**: Base URL, headers, and standards. +- **[Endpoints](api/endpoints.md)**: Full list of REST endpoints. +- **[Authentication](api/authentication.md)**: Handling API keys and tokens. +- **[Errors](api/errors.md)**: Status codes and debugging. -- :rocket: **[Quick Start](getting-started/quickstart.md)** +## 🛠️ Contributing - Get up and running with Docker in minutes +- **[Architecture](contributing/architecture.md)**: System design. +- **[Setup](contributing/setup.md)**: Development environment. +- **[Testing](contributing/testing.md)**: Running tests. -- :gear: **[Configuration](getting-started/configuration.md)** +--- - Learn about environment variables and settings - -- :book: **[User Guide](user-guide/introduction.md)** - - Comprehensive guide for using Routstr - -- :hammer: **[Contributing](contributing/setup.md)** - - Help improve Routstr Core - -
- -## Use Cases - -- **AI Application Developers** - Add Bitcoin payments to your AI apps without managing infrastructure -- **API Resellers** - Resell API access with custom pricing and profit margins -- **Privacy-Focused Users** - Access AI models without revealing personal information -- **Micropayment Experiments** - Test new business models with instant, small payments - -## Getting Help - -- 📖 Browse the [User Guide](user-guide/introduction.md) for detailed usage instructions -- 🐛 Report issues on [GitHub](https://github.com/routstr/routstr-core/issues) -- 💬 Join the community discussions -- 🔧 Check the [API Reference](api/overview.md) for technical details - -## License - -Routstr Core is open source software licensed under the GPLv3. See the [LICENSE](https://github.com/routstr/routstr-core/blob/main/LICENSE) file for details. +*Powered by [Cashu](https://cashu.space) and [Nostr](https://nostr.com).* diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000..9e0ddbc8 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,76 @@ +# Overview + +Routstr is a decentralized protocol for **permissionless, private, and censorship-resistant AI inference**. It creates an open marketplace where anyone can sell llm-tokens and anyone can buy them using privacy-preserving micropayments. + +By combining **Nostr** (for censorship-resistant discovery and communication) and **Cashu** (for private, instant Bitcoin eCash payments), Routstr effectively removes the "middleman" from the AI ecosystem. + +## How it Works + +The network consists of independent **Providers** (Sellers) and **Clients** (Buyers). There is no central server, no login, and no credit card required. + +1. **Discovery (Nostr)**: Providers announce their availability, models (e.g., `gpt-4o`, `deepseek-r1`), and prices on the Nostr network. +2. **Payment (Cashu)**: Clients pay providers directly using Bitcoin eCash (Cashu tokens). These payments are untraceable and settle instantly. +3. **Inference (Proxy)**: The Provider acts as a gateway (or runs local hardware), executing the AI model and returning the result to the Client. + +## Who is this for? + +The documentation is split into two paths depending on your goal: + +### 🐣 I want to BUILD on Routstr (Client) + +You are a developer building an AI agent, a chat app, or a script, and you want access to AI models without API keys, subscriptions, or KYC. + +* **No Accounts**: Just get a wallet. +* **Privacy**: Your requests are mixed with thousands of others; providers can't profile you. +* **Choice**: Switch between hundreds of providers instantly for the best price/performance. + +👉 **[Go to Client Guide](client/introduction.md)** + +### 🦁 I want to RUN a Node (Provider) + +You have API credits (OpenAI, Anthropic, etc.) or GPU capacity and want to earn Bitcoin by selling AI access to the network. + +* **Monetize API Keys**: Connect your OpenAI/Anthropic/OpenRouter accounts and earn sats on every request. +* **Monetize Hardware**: Run local models (via vLLM, Ollama) and sell access. +* **Permissionless**: No approval needed. Start the container, configure via dashboard, start earning. + +!!! note "Coming Soon" + Future versions will support node-to-node routing—run a gateway without needing your own AI provider credentials. + +👉 **[Go to Provider Guide](provider/quickstart.md)** + +--- + +## Architecture + +Routstr is built on a modular stack defined by the [Routstr Improvement Protocols (RIPs)](https://github.com/routstr/rips). + +```mermaid +flowchart LR + subgraph Client + A[App / Agent] + end + + subgraph Provider + B[Routstr Node
Proxy + Auth + Billing] + end + + subgraph Upstream + C[OpenAI / Anthropic
vLLM / Ollama / ...] + end + + A -- "Request +
Cashu Token" --> B + B -- "Forward
Request" --> C + C -- "Response +
Usage" --> B + B -- "Response +
Refund Token" --> A +``` + +## Why Routstr? + +| Feature | Closed AI | Routstr | +| :--- | :--- | :--- | +| **Access** | Account, KYC, Credit Card | Permissionless, Bitcoin-native | +| **Privacy** | Full Logging & Tracking | Blinded Payments, Ephemeral Sessions | +| **Resilience** | Single Point of Failure | Decentralized Network | +| **Pricing** | Fixed, Monopolistic | Dynamic, Market-driven | +| **Global** | Geofenced | Borderless (Tor/I2P supported) | diff --git a/docs/provider/advanced-pricing.md b/docs/provider/advanced-pricing.md new file mode 100644 index 00000000..7bb3bd21 --- /dev/null +++ b/docs/provider/advanced-pricing.md @@ -0,0 +1,114 @@ +# Advanced Pricing + +Advanced pricing strategies for fine-tuned control over your revenue model. + +--- + +## Default Behavior + +By default, Routstr: + +1. **Fetches costs** from your upstream provider +2. **Applies markup** using your fee settings +3. **Converts to sats** using real-time BTC price + +**Formula**: `Price = Upstream Cost × Exchange Fee × Upstream Fee` + +--- + +## Strategy 1: Fixed Per-Request + +Charge a flat fee regardless of model or tokens used. + +**Configure in Dashboard** → **Settings** → **Pricing**: + +- Enable **Fixed Pricing** +- Set **Fixed Cost Per Request** (in sats) + +**Use cases**: + +- Internal tools with predictable usage +- Simple "pay once, get response" APIs +- Subscription-like tiers + +--- + +## Strategy 2: Fixed Per-Token + +Override dynamic pricing with global per-token rates. + +**Configure in Dashboard** → **Settings** → **Pricing**: + +| Setting | Description | +|---------|-------------| +| **Fixed Per 1K Input** | Sats per 1,000 prompt tokens | +| **Fixed Per 1K Output** | Sats per 1,000 completion tokens | + +When set to non-zero values, these override model-specific pricing for all models. + +--- + +## Strategy 3: Per-Model Custom Pricing + +Set specific prices for individual models, overriding both upstream cost and global fees. + +**Configure in Dashboard** → **Models**: + +1. Click on a model (e.g., `gpt-4`) +2. Enter **Prompt Price** and **Completion Price** (USD per 1M tokens) +3. Save + +**Example**: OpenAI charges $30/1M for GPT-4. Set your price to $35/1M to lock in a margin regardless of fee settings. + +--- + +## Minimum Charge + +Prevent dust transactions and spam: + +| Setting | Description | Default | +|---------|-------------|---------| +| **Min Request Cost** | Minimum charge in msats | 1000 (1 sat) | + +If a request's calculated cost falls below this (e.g., very short prompts), the client pays the minimum. + +--- + +## Combining Strategies + +Strategies apply in order of specificity: + +1. **Per-model override** (highest priority) +2. **Fixed per-token rates** +3. **Dynamic pricing with fees** (default) +4. **Fixed per-request** (overrides all above if enabled) + +**Example setup**: + +- Dynamic pricing as default (10% markup) +- GPT-4 locked at $35/1M (premium model) +- Claude Haiku at 5 sats/1K tokens (budget option) +- Minimum 1 sat per request + +--- + +## Pricing for Profit + +### High-Volume Strategy + +Lower margins, more clients: + +- Exchange Fee: 1.002 (0.2%) +- Upstream Fee: 1.05 (5%) + +### Premium Strategy + +Higher margins, fewer clients: + +- Exchange Fee: 1.01 (1%) +- Upstream Fee: 1.25 (25%) + +### Mixed Strategy + +- Cheap models (GPT-3.5, Haiku): Low margin to attract volume +- Premium models (GPT-4, Opus): High margin for profit diff --git a/docs/provider/configuration.md b/docs/provider/configuration.md new file mode 100644 index 00000000..aaa00e86 --- /dev/null +++ b/docs/provider/configuration.md @@ -0,0 +1,122 @@ +# Configuration + +Routstr is configured primarily through the **Admin Dashboard**. All settings persist in the database and take effect immediately—no restarts required. + +For automated deployments, you can optionally pre-configure settings via environment variables. + +--- + +## Admin Dashboard (Primary) + +Access the dashboard at `/admin/` on your node. + +### Upstream Providers + +Connect to your AI provider(s): + +| Setting | Description | +|---------|-------------| +| **Upstream URL** | API endpoint (e.g., `https://api.openai.com/v1`) | +| **API Key** | Your provider's API key | + +### Node Identity + +How your node appears to clients: + +| Setting | Description | +|---------|-------------| +| **Name** | Display name (e.g., "Fast GPT-4 Node") | +| **Description** | Brief description of your service | + +### Pricing + +Control your profit margins: + +| Setting | Description | Default | +|---------|-------------|---------| +| **Fixed Pricing** | Charge flat rate per request vs. per-token | Off | +| **Exchange Fee** | Buffer for BTC volatility | 1.005 (0.5%) | +| **Upstream Fee** | Your profit markup | 1.10 (10%) | + +See [Pricing](pricing.md) for detailed strategies. + +### Cashu Mints + +Which mints to accept payments from: + +| Setting | Description | +|---------|-------------| +| **Mints** | List of trusted Cashu mint URLs | + +### Lightning Withdrawals + +Automatic profit withdrawal: + +| Setting | Description | +|---------|-------------| +| **Lightning Address** | Your LN address for withdrawals | + +### Security + +| Setting | Description | +|---------|-------------| +| **Admin Password** | Password for dashboard access | + +### Nostr Discovery + +Announce your node on the network: + +| Setting | Description | +|---------|-------------| +| **Npub** | Your Nostr public key | +| **Nsec** | Your Nostr private key (for signing) | +| **Relays** | Relays to publish announcements | + +See [Discovery](discovery.md) for details. + +--- + +## Environment Variables (Optional) + +Use environment variables for: + +- **Automated deployments** (CI/CD, infrastructure-as-code) +- **Secrets management** (external secret stores) +- **Initial bootstrap** (set once, manage via dashboard later) + +### All Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `UPSTREAM_BASE_URL` | Upstream API endpoint | — | +| `UPSTREAM_API_KEY` | Upstream API key | — | +| `ADMIN_PASSWORD` | Dashboard password | (none) | +| `DATABASE_URL` | Database connection string | `sqlite+aiosqlite:///keys.db` | +| `NAME` | Node display name | `ARoutstrNode` | +| `DESCRIPTION` | Node description | `A Routstr Node` | +| `NPUB` | Nostr public key (bech32) | — | +| `NSEC` | Nostr private key | — | +| `CASHU_MINTS` | Comma-separated mint URLs | `https://mint.minibits.cash/Bitcoin` | +| `RECEIVE_LN_ADDRESS` | Lightning address for withdrawals | — | +| `TOR_PROXY_URL` | SOCKS5 proxy for Tor | `socks5://127.0.0.1:9050` | +| `CORS_ORIGINS` | Allowed CORS origins | `*` | +| `RELAYS` | Nostr relays (comma-separated) | (default set) | + +### Priority + +Environment variables are read on startup. Dashboard settings override them and persist in the database. Once you change a setting in the dashboard, the env var is ignored for that setting. + +--- + +## Models + +Manage which AI models you offer: + +1. Go to **Models** in the dashboard +2. Models are auto-discovered from your upstream +3. For each model, you can: + - **Enable/Disable** — hide expensive models you don't want to serve + - **Override pricing** — set custom per-token rates + - **Create aliases** — friendly names for models + +See [Pricing](pricing.md) for per-model pricing strategies. diff --git a/docs/provider/dashboard.md b/docs/provider/dashboard.md new file mode 100644 index 00000000..aaa860bf --- /dev/null +++ b/docs/provider/dashboard.md @@ -0,0 +1,208 @@ +# Admin Dashboard + +The Admin Dashboard is your command center for managing your Routstr provider node. Configure providers, monitor earnings, manage models, and withdraw profits—all from a web interface. + +**URL**: `http://your-node:8000/admin/` + +--- + +## Overview Tab + +The main dashboard view shows your node's financial status at a glance. + +### Wallet Summary + +| Metric | Description | +|--------|-------------| +| **Total Wallet** | All Bitcoin currently held by your node | +| **User Balances** | Funds belonging to active client sessions | +| **Your Balance** | Your profit: `Total - User Balances` | + +### Mint Status + +Shows connected Cashu mints and their balances. Each mint displays: + +- Connection status +- Balance in sats/msats +- Unit type + + + +--- + +## Sessions Tab + +View and manage active client sessions (API keys). + +### Session List + +| Column | Description | +|--------|-------------| +| **Hashed Key** | Privacy-preserving identifier (not the actual key) | +| **Balance** | Remaining funds in the session | +| **Spent** | Total amount spent by this session | +| **Requests** | Number of API calls made | +| **Created** | When the session was created | +| **Expires** | Auto-expiry time (if set) | + +### Actions + +- **View Details** — See full session history +- **Revoke** — Terminate a session (remaining balance returns to your wallet) + + + +--- + +## Models Tab + +Manage which AI models you offer to clients. + +### Model List + +Shows all models available from your upstream provider(s): + +| Column | Description | +|--------|-------------| +| **Model ID** | The model identifier (e.g., `gpt-4o`) | +| **Enabled** | Whether clients can use this model | +| **Input Price** | Cost per 1M input tokens (USD) | +| **Output Price** | Cost per 1M output tokens (USD) | +| **Custom** | Whether pricing is overridden | + +### Actions + +- **Import Models** — Fetch latest model list from upstream +- **Enable/Disable** — Toggle model availability +- **Edit Pricing** — Override default pricing for a model +- **Create Alias** — Map a friendly name to a model + +### Editing a Model + +Click on any model to configure: + +| Field | Description | +|-------|-------------| +| **Enabled** | Show this model to clients | +| **Prompt Price** | Custom price per 1M input tokens (USD) | +| **Completion Price** | Custom price per 1M output tokens (USD) | +| **Alias** | Alternative name for this model | + + + + +--- + +## Settings Tab + +Configure all node settings. Changes take effect immediately. + +### Upstream + +Connect to your AI provider: + +| Field | Description | +|-------|-------------| +| **Base URL** | API endpoint (e.g., `https://api.openai.com/v1`) | +| **API Key** | Your provider's secret key | + + + +### Node Identity + +| Field | Description | +|-------|-------------| +| **Name** | Public display name | +| **Description** | Brief description of your service | +| **Npub** | Nostr public key for discovery | + +### Pricing + +| Field | Description | +|-------|-------------| +| **Fixed Pricing** | Toggle flat-rate vs. per-token pricing | +| **Fixed Cost** | Sats per request (when fixed pricing enabled) | +| **Exchange Fee** | Multiplier for BTC volatility buffer | +| **Upstream Fee** | Your profit margin multiplier | + +**Example**: With Exchange Fee `1.005` and Upstream Fee `1.10`: + +- Upstream cost: $30/1M tokens +- Your price: $30 × 1.005 × 1.10 = $33.17/1M tokens + +### Cashu Mints + +Manage which mints you accept payments from: + +- **Add Mint** — Enter a mint URL +- **Remove Mint** — Stop accepting from a mint +- **Test Connection** — Verify mint is reachable + + + +### Lightning + +| Field | Description | +|-------|-------------| +| **Lightning Address** | Your LN address for automatic withdrawals | + +### Nostr Discovery + +| Field | Description | +|-------|-------------| +| **Nsec** | Private key for signing announcements | +| **Relays** | Where to publish your node advertisement | + +### Security + +| Field | Description | +|-------|-------------| +| **Admin Password** | Password for dashboard access | + +!!! warning "Set a Password" + The dashboard has no password by default. Always set one for production nodes. + + + +--- + +## Withdraw Tab + +Withdraw your profits to a Lightning wallet. + +### Steps + +1. **Select Mint** — Choose which mint to withdraw from +2. **Enter Amount** — How many sats to withdraw +3. **Generate Token** — Creates a Cashu token +4. **Redeem** — Paste the token into your Cashu wallet and melt to Lightning + + + +### Alternative: Lightning Address + +If you've configured a Lightning Address in Settings, profits can be automatically swept to your wallet (coming soon). + +--- + +## Logs Tab + +View node logs for debugging without SSH access. + +### Features + +- **Filter by Level** — Error, Warning, Info, Debug +- **Search** — Find specific entries +- **Time Range** — View logs from specific periods +- **Auto-refresh** — Watch logs in real-time + +### Common Log Entries + +| Entry | Meaning | +|-------|---------| +| `Upstream request failed` | Problem connecting to your AI provider | +| `Invalid token` | Client sent an invalid Cashu token | +| `Session expired` | API key reached its time limit | +| `Insufficient balance` | Client ran out of funds mid-request | + + diff --git a/docs/provider/deployment.md b/docs/provider/deployment.md new file mode 100644 index 00000000..4ad21e78 --- /dev/null +++ b/docs/provider/deployment.md @@ -0,0 +1,196 @@ +# Deployment + +Production deployment guide for Routstr Provider nodes. + +## Docker Compose (Recommended) + +For production, use Docker Compose with persistent storage and optional Tor support. + +### Basic Setup + +Create a `compose.yml`: + +```yaml +services: + routstr: + image: ghcr.io/routstr/proxy:latest + container_name: routstr + restart: unless-stopped + ports: + - "8000:8000" + volumes: + - ./data:/app/data + - ./logs:/app/logs +``` + +Start the node: + +```bash +docker compose up -d +``` + +Then configure everything via the [Admin Dashboard](http://localhost:8000/admin/). + +--- + +## With Tor (Anonymous Access) + +Add Tor to serve your node as a hidden service—no port forwarding needed. + +```yaml +services: + routstr: + image: ghcr.io/routstr/proxy:latest + container_name: routstr + restart: unless-stopped + ports: + - "8000:8000" + volumes: + - ./data:/app/data + - ./logs:/app/logs + environment: + - TOR_PROXY_URL=socks5://tor:9050 + depends_on: + - tor + + tor: + image: ghcr.io/hundehausen/tor-hidden-service:latest + container_name: tor + restart: unless-stopped + volumes: + - ./tor-data:/var/lib/tor + environment: + - HS_ROUTER=routstr:8000:80 +``` + +After starting, find your `.onion` address: + +```bash +docker exec tor cat /var/lib/tor/hidden_service/hostname +``` + +See [Tor Support](tor.md) for details. + +--- + +## Pre-Configuration (Optional) + +While everything can be configured via the dashboard, you can pre-configure settings with environment variables for automated deployments. + +### Using Environment Variables + +```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-... + + # Secure the dashboard (recommended) + - ADMIN_PASSWORD=your-secure-password + + # Node identity + - NAME=My Provider Node + - DESCRIPTION=Fast GPT-4 access via Lightning + + # Lightning withdrawals + - RECEIVE_LN_ADDRESS=me@walletofsatoshi.com + volumes: + - ./data:/app/data +``` + +### Using an .env File + +```yaml +services: + routstr: + image: ghcr.io/routstr/proxy:latest + env_file: + - .env + volumes: + - ./data:/app/data +``` + +Example `.env`: + +```bash +UPSTREAM_BASE_URL=https://api.openai.com/v1 +UPSTREAM_API_KEY=sk-proj-... +ADMIN_PASSWORD=change-me +NAME=My Provider Node +RECEIVE_LN_ADDRESS=me@walletofsatoshi.com +``` + +See [Configuration](configuration.md) for all available options. + +--- + +## Persistence + +Routstr stores all data in `/app/data`: + +| Path | Contents | +|------|----------| +| `keys.db` | SQLite database (settings, API keys, sessions) | +| `.wallet/` | Cashu wallet data (your Bitcoin!) | + +!!! warning "Back Up Your Data" + The `./data` volume contains your wallet. Losing it means losing funds. Back up regularly. + +--- + +## Reverse Proxy (Optional) + +For custom domains and SSL, use a reverse proxy like Caddy or nginx. + +### Caddy Example + +``` +api.yournode.com { + reverse_proxy localhost:8000 +} +``` + +### nginx Example + +```nginx +server { + listen 443 ssl; + server_name api.yournode.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://localhost:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +--- + +## Updates + +Pull the latest image and restart: + +```bash +docker compose pull +docker compose up -d +``` + +--- + +## Building from Source + +```bash +git clone https://github.com/routstr/routstr-core.git +cd routstr-core +docker build -t routstr-local . +``` diff --git a/docs/provider/discovery.md b/docs/provider/discovery.md new file mode 100644 index 00000000..c0d06036 --- /dev/null +++ b/docs/provider/discovery.md @@ -0,0 +1,95 @@ +# Discovery + +Routstr uses **Nostr** as a decentralized directory for service discovery. Your node announces its presence, models, and pricing on Nostr relays, allowing clients to find you without a central server. + +--- + +## How It Works + +1. **Provider Advertisement (Kind 38421)**: Your node periodically publishes an event with its URL, models, and pricing +2. **Client Discovery**: Clients query relays for these events to find suitable providers + +--- + +## Configuration + +Configure discovery in **Dashboard** → **Settings** → **Nostr**. + +### Required Settings + +| Field | Description | +|-------|-------------| +| **Npub** | Your node's public identity (clients use this to verify your node) | +| **Nsec** | Your node's private key (used to sign advertisements) | +| **Relays** | Where to publish your announcements | + +### Default Relays + +If not configured, Routstr publishes to: + +- `wss://relay.damus.io` +- `wss://relay.nostr.band` +- `wss://nos.lol` + +--- + +## Advertisement Format + +Your node publishes events like: + +```json +{ + "kind": 38421, + "content": { + "name": "My Routstr Node", + "description": "Fast GPT-4 access via Lightning", + "endpoints": { + "http": "https://api.mynode.com", + "onion": "http://xyz...onion" + }, + "models": ["gpt-4", "claude-3-opus"], + "pricing": { ... } + }, + "tags": [ + ["d", "routstr-provider"], + ["g", "US"] + ] +} +``` + +--- + +## Tor Integration + +If you're running with Tor (see [Tor Support](tor.md)), your `.onion` address is automatically included in announcements. This allows clients to connect anonymously. + +--- + +## Verify Your Announcements + +Check if your node is broadcasting: + +1. Copy your `Npub` +2. Search on [Nostr.band](https://nostr.band) or [Primal](https://primal.net) +3. Look for Kind 38421 events + +--- + +## Generating Keys + +If you don't have a Nostr identity: + +1. Use any Nostr client (e.g., [Primal](https://primal.net), [Damus](https://damus.io)) +2. Create an account +3. Export your keys (npub and nsec) +4. Enter them in the dashboard + +Or generate keys programmatically: + +```python +from nostr_sdk import Keys + +keys = Keys.generate() +print(f"npub: {keys.public_key().to_bech32()}") +print(f"nsec: {keys.secret_key().to_bech32()}") +``` diff --git a/docs/provider/pricing.md b/docs/provider/pricing.md new file mode 100644 index 00000000..2907c691 --- /dev/null +++ b/docs/provider/pricing.md @@ -0,0 +1,91 @@ +# Pricing + +Routstr's pricing engine lets you act as a retailer of AI compute. You pay upstream providers (OpenAI, Anthropic, etc.) at their rates and sell to clients with your markup. + +--- + +## Pricing Strategies + +Configure these in **Dashboard** → **Settings** → **Pricing**. + +### Dynamic Pricing (Default) + +Passes through upstream costs plus your percentage markup. + +**Formula**: `Client Price = Upstream Cost × Exchange Fee × Upstream Fee` + +| Setting | Description | Default | +|---------|-------------|---------| +| **Exchange Fee** | Buffer for BTC price volatility | 1.005 (0.5%) | +| **Upstream Fee** | Your profit margin | 1.10 (10%) | + +**Example**: GPT-4 costs $30/1M tokens from OpenAI. With default settings: + +- Price: $30 × 1.005 × 1.10 = $33.17/1M tokens +- At $60k BTC: ~55,000 sats/1M tokens + +### Fixed Pricing + +Charge a flat rate per request, regardless of model or token count. + +| Setting | Description | +|---------|-------------| +| **Fixed Pricing** | Enable flat-rate mode | +| **Fixed Cost** | Sats per request | + +**Best for**: Simple proxies, internal tools, or subscription-like access. + +--- + +## Per-Model Pricing + +Override pricing for specific models in **Dashboard** → **Models**. + +1. Click on a model +2. Enter custom **Prompt Price** and **Completion Price** (USD per 1M tokens) +3. Save + +This overrides both the upstream cost and your global markup for that model. + +**Example**: Lock GPT-4 at $35/1M tokens regardless of OpenAI's actual rate or your fee settings. + +--- + +## Token-Based Overrides + +Set global fixed rates per token (overrides dynamic pricing for all models): + +| Setting | Description | +|---------|-------------| +| **Fixed Per 1K Input** | Sats per 1,000 prompt tokens | +| **Fixed Per 1K Output** | Sats per 1,000 completion tokens | + +--- + +## Minimum Charge + +Prevent spam with a minimum cost per request: + +| Setting | Description | Default | +|---------|-------------|---------| +| **Min Request Cost** | Minimum charge in msats | 1000 (1 sat) | + +If a request's calculated cost is lower than this, the client pays the minimum instead. + +--- + +## Cost Tracking + +Routstr tracks balances in **millisats (msats)** for precision with cheap models. + +- 1 sat = 1,000 msats +- API responses include cost in msats +- Lightning withdrawals round down to whole sats + +### Client Verification (RIP-05) + +Clients can verify charges: + +1. Fetch `/v1/models` for your advertised rates +2. Calculate expected cost from token counts +3. Compare to `x-routstr-cost` response header diff --git a/docs/provider/quickstart.md b/docs/provider/quickstart.md new file mode 100644 index 00000000..2771c1f3 --- /dev/null +++ b/docs/provider/quickstart.md @@ -0,0 +1,99 @@ +# Quick Start + +Start earning Bitcoin by selling AI access in under 5 minutes. + +## What You'll Build + +A **Routstr Provider Node** acts as a gateway that: + +1. **Connects** to upstream AI providers (OpenAI, Anthropic, OpenRouter, etc.) +2. **Accepts** Bitcoin payments via Cashu eCash +3. **Serves** AI requests to clients on the network + +You bring the API keys, Routstr handles the billing, payments, and client management. + +!!! tip "Future: Node-to-Node Routing" + In future versions, you'll be able to run a node that connects to other Routstr nodes—eliminating the need to configure upstream providers yourself. For now, you'll need your own API credentials. + +--- + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) installed +- API credentials from at least one AI provider (OpenAI, Anthropic, OpenRouter, etc.) + +--- + +## 1. Start the Node + +```bash +docker run -d \ + --name routstr \ + -p 8000:8000 \ + -v routstr-data:/app/data \ + ghcr.io/routstr/proxy:latest +``` + +Verify it's running: + +```bash +curl http://localhost:8000/v1/info +``` + +--- + +## 2. Configure via Dashboard + +Open the **Admin Dashboard** at [http://localhost:8000/admin/](http://localhost:8000/admin/). + +!!! note "Default Access" + The dashboard has no password by default. Set one immediately in Settings for production use. + +### Connect Your AI Providers + +1. Navigate to **Settings** → **Upstream** +2. Enter your upstream URL (e.g., `https://api.openai.com/v1`) +3. Enter your API key +4. Save + +### Set Your Profit Margin + +1. Go to **Settings** → **Pricing** +2. Configure your markup (default is 10%) +3. Optionally set a fixed price per request instead + +### Secure the Dashboard + +1. Go to **Settings** → **Admin** +2. Set a strong password +3. Save and re-login + +--- + +## 3. Start Earning + +Once configured, your node is live. Clients pay you in Bitcoin (via Cashu tokens) for every AI request. + +### Monitor Your Earnings + +The dashboard shows: + +- **Total Wallet**: All Bitcoin held by your node +- **User Balances**: Funds belonging to active client sessions +- **Your Balance**: Your profit (`Total - User Balances`) + +### Withdraw Profits + +1. Go to **Withdraw** in the dashboard +2. Select amount and mint +3. Generate a Cashu token +4. Redeem to your Lightning wallet + +--- + +## Next Steps + +- **[Deployment](deployment.md)**: Production setup with Docker Compose and Tor +- **[Dashboard Guide](dashboard.md)**: Full reference for all dashboard features +- **[Pricing](pricing.md)**: Configure pricing strategies and per-model overrides +- **[Discovery](discovery.md)**: Announce your node on Nostr for clients to find you diff --git a/docs/provider/tor.md b/docs/provider/tor.md new file mode 100644 index 00000000..187e4c56 --- /dev/null +++ b/docs/provider/tor.md @@ -0,0 +1,50 @@ +# Tor Support + +Running Routstr as a **Tor Hidden Service** allows you to offer API access anonymously and bypass NAT/firewalls without port forwarding. + +## Automatic Setup (Docker) + +The standard `compose.yml` includes a Tor container pre-configured to serve your node. + +1. **Start the stack**: `docker compose up -d` +2. **Wait**: Tor takes about 30 seconds to generate keys and bootstrap. +3. **Find your address**: + ```bash + docker exec tor cat /var/lib/tor/hidden_service/hostname + ``` + Output: `v2xyz...longaddress.onion` + +Routstr will automatically detect this address (via the `discover_onion_url_from_tor` logic) and include it in: +- The `/v1/info` endpoint. +- Nostr announcements (RIP-02). + +## Manual Setup + +If you are running outside Docker or managing Tor yourself: + +1. **Install Tor**: `sudo apt install tor` +2. **Edit `torrc`**: + ``` + HiddenServiceDir /var/lib/tor/routstr/ + HiddenServicePort 80 127.0.0.1:8000 + ``` +3. **Restart Tor**: `sudo systemctl restart tor` +4. **Get Address**: `sudo cat /var/lib/tor/routstr/hostname` +5. **Configure Routstr**: + Set `ONION_URL=http://youraddress.onion` in your `.env` file so the node knows its own address. + +## Client Usage + +Clients connecting to your `.onion` address must route traffic through SOCKS5. + +**Python Example:** +```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") +) +``` diff --git a/docs/user-guide/admin-dashboard.md b/docs/user-guide/admin-dashboard.md deleted file mode 100644 index 2bdf94b3..00000000 --- a/docs/user-guide/admin-dashboard.md +++ /dev/null @@ -1,216 +0,0 @@ -# Admin Dashboard - -The Routstr admin dashboard is a modern web interface for managing your node, monitoring wallet balances, configuring AI models and providers, and handling Bitcoin Lightning payments through Cashu eCash. - -## Accessing the Dashboard - -### Authentication - -The dashboard is protected by password authentication: - -1. Navigate to `/admin/` in your browser -2. Enter the admin password -3. Optional: Configure custom base URL if not pre-configured -4. Click "Login" - -The interface supports both environment-configured URLs and manual URL entry for deployment flexibility. - -## Dashboard Overview - -The main dashboard consists of four primary sections accessible through a collapsible sidebar: - -- **Dashboard** - Wallet balance monitoring and fund management -- **Models** - AI model management and testing -- **Providers** - Upstream provider configuration -- **Settings** - Node configuration and admin preferences - -### Navigation - -## Dashboard Page - -### Wallet Balance Management - -#### Balance Display Options - -Switch between display units using the toggle buttons: - -- **msat** - Millisatoshis (highest precision) -- **sat** - Satoshis (standard Bitcoin unit) -- **usd** - US Dollar equivalent (when exchange rate available) - -#### Balance Overview - -The dashboard displays three key metrics: - -- **Your Balance (Total)** - Available funds for node operator -- **Total Wallet** - Combined balance across all Cashu mints -- **User Balance** - Funds held for API key holders - -#### Detailed Balance Breakdown - -View balances by mint with the following information: - -| Column | Description | -| ----------- | ------------------------------------- | -| Mint / Unit | Cashu mint URL and currency unit | -| Wallet | Total funds in this mint | -| Users | Funds belonging to API key holders | -| Owner | Your available funds (Wallet - Users) | - -### Temporary Balances - -Monitor API key activity with: - -- **Summary Cards** - Total balance, total spent, total requests -- **Search Functionality** - Filter by key hash or refund address -- **Detailed Table** - Individual key balances with expiry times -- **Auto-refresh** - Updates every 60 seconds - -### Fund Management - -#### Withdrawing Funds - -To withdraw your available balance: - -1. Click the **Withdraw** button -2. Select which mint to withdraw from -3. Specify the amount (or withdraw full balance) -4. Click **Generate Token** -5. Copy the generated eCash token -6. Import the token into your Cashu wallet - -#### Real-time Updates - -- Balances refresh automatically every 30 seconds -- Manual refresh option available -- Live Bitcoin/USD exchange rate integration -- Error handling for mint connectivity issues - -## Models Management Page - -### Model Organization - -Models are organized by provider groups with tabs: - -- **All Models** - Combined view of all available models -- **Provider-specific tabs** - Individual providers (OpenRouter, Azure, etc.) -- Badge indicators showing active/total model counts - -### Model Management Features - -#### Individual Model Operations - -For each model you can: - -- **Toggle Enable/Disable** - Control model availability -- **View Details** - Context length, pricing, description -- **Edit Configuration** - Model-specific settings -- **Status Indicators** - Green badges for enabled, gray for disabled - -#### Bulk Operations - -- **Select All/Deselect All** - Quick selection controls -- **Bulk Enable/Disable** - Mass model management -- **Bulk Delete** - Remove model overrides -- **Provider-level Actions** - Apply settings to all models in a provider - -#### Model Information Display - -- **Model Types** - Text, embedding, image, audio, multimodal indicators -- **Pricing Information** - Per-million-token costs for input/output -- **Context Length** - Maximum tokens supported -- **API Key Status** - Whether credentials are configured -- **Free Model Indicators** - No-cost models clearly marked - -## Providers Management Page - -### Upstream Provider Configuration - -Manage AI provider connections and credentials: - -#### Provider Types Supported - -- **OpenRouter** - Multi-model aggregator -- **Azure OpenAI** - Microsoft's OpenAI service -- **OpenAI** - Direct OpenAI integration -- **Custom Providers** - Any OpenAI-compatible API - -#### Adding New Providers - -1. Click **Add Provider** -2. Select **Provider Type** from dropdown -3. Enter **Base URL** (auto-populated for known providers) -4. Add **API Key** for authentication -5. Set **API Version** (required for Azure) -6. Toggle **Enabled** status -7. Click **Create** - -#### Provider Management - -**Provider Cards Display:** - -- Provider type and status (Enabled/Disabled) -- Base URL configuration -- Action buttons (Models, Edit, Delete) - -**Available Actions:** - -- **Edit** - Modify provider configuration -- **Delete** - Remove provider (with confirmation) -- **View Models** - Expand model discovery interface -- **Enable/Disable** - Toggle provider availability - -#### Model Discovery - -Each provider shows two types of models: - -**Provided Models Tab:** - -- Auto-discovered from provider's catalog -- Read-only model information -- Real-time availability updates - -**Custom Models Tab:** - -- Manually configured model overrides -- Extend or override provider catalog -- Individual enable/disable controls - -## Settings Page - -### Node Configuration - -Configure core node settings and preferences: - -#### Basic Information - -- **Node Name** - Identifier for your node -- **Node Description** - Descriptive text for your service -- **HTTP URL** - Public HTTP endpoint -- **Onion URL** - Tor hidden service address - -#### Nostr Integration - -- **Public Key (npub)** - Your Nostr public identity -- **Private Key (nsec)** - Nostr private key with show/hide toggle -- **Nostr Relays** - Configure relays for provider announcements - -#### Cashu Mint Management - -- **Add Mint URLs** - Configure multiple Cashu mint endpoints -- **Remove Mints** - Delete unused mint configurations -- **Mint Validation** - Verify mint endpoint connectivity - -#### Settings Features - -- **Real-time Save** - Changes apply immediately -- **Validation** - Form validation with error feedback -- **Secure Fields** - Password masking with reveal toggles -- **Reload Functionality** - Refresh configuration from server - -## Next Steps - -- [Payment Flow](payment-flow.md) - Understanding Bitcoin payment processing -- [Using the API](using-api.md) - Making API requests to your node -- [Models & Pricing](models-pricing.md) - Configuring model pricing and fees -- [API Reference](../api/overview.md) - Complete API documentation diff --git a/docs/user-guide/introduction.md b/docs/user-guide/introduction.md deleted file mode 100644 index c0fbd192..00000000 --- a/docs/user-guide/introduction.md +++ /dev/null @@ -1,245 +0,0 @@ -# User Guide Introduction - -Welcome to the Routstr Core User Guide. This guide will help you understand how to use Routstr to access AI APIs with Bitcoin micropayments. - -## What You'll Learn - -- How the payment system works -- Creating and managing API keys -- Making API calls through Routstr -- Using the admin dashboard -- Managing your balance - -## Prerequisites - -Before starting, you'll need: - -1. **A Running Routstr Instance** - - Either your own deployment or access to a public node - - The base URL (e.g., `https://api.routstr.com/v1`) - -2. **A Cashu Wallet** (optional but recommended) - - [Nutstash](https://nutstash.app) - Web wallet - - [Minibits](https://www.minibits.cash) - Mobile wallet - - [Cashu.me](https://cashu.me) - Simple web wallet - -3. **An API Client** - - OpenAI Python/JavaScript SDK - - Any HTTP client (curl, Postman, etc.) - - Your application code - -## How Routstr Works - -### Traditional API Access - -```mermaid -graph LR - A[Your App] --> B[OpenAI API] - B --> A -``` - -- Direct connection to provider -- Monthly billing -- Credit card required -- Usage limits - -### With Routstr - -```mermaid -graph LR - A[Your App] --> B[Routstr Proxy] - B --> C[OpenAI API] - C --> B - B --> A - D[Bitcoin/eCash] --> B -``` - -- Pay per request with Bitcoin -- No credit card needed -- Anonymous payments -- Instant settlement - -## Key Concepts - -### eCash Tokens - -- Digital bearer tokens backed by Bitcoin -- Can be sent like cash - whoever has the token owns it -- Redeemable at Cashu mints for Bitcoin -- Perfect for micropayments - -### API Keys - -- Created by depositing eCash tokens -- Track your balance and usage -- Can be topped up anytime -- Optional expiry and refund address - -### Balance Management - -- Measured in millisatoshis (msats) -- 1 Bitcoin = 100,000,000 sats = 100,000,000,000 msats -- Deducted based on actual usage -- Withdrawable as eCash tokens - -## Typical Workflow - -### 1. Get Bitcoin/eCash - -Options: - -- Buy Bitcoin and deposit to a Cashu mint -- Receive eCash tokens from someone else -- Use a testnet mint for testing - -### 2. Use Your eCash - -You have two options for using your eCash tokens with Routstr: - -#### Option A: Create a Persistent Wallet - -Create a wallet with an API key for multiple requests: - -```bash -POST /v1/wallet/create -{ - "cashu_token": "cashuAeyJ0..." -} -``` - -This returns an API key (`sk-...`) and your balance. The wallet persists between requests. - -#### Option B: Direct Token Usage - -Use your Cashu token directly as the API key: - -```python -client = OpenAI( - api_key="cashuAeyJ0...", # Your Cashu token directly - base_url="https://api.routstr.com/v1" -) -``` - -Routstr automatically converts the token to access the associated wallet. Each request consumes from the token's balance. - -### 3. Make API Calls - -With either method: - -```python -# Using persistent wallet API key -client = OpenAI( - api_key="sk-...", - base_url="https://api.routstr.com/v1" -) - -# Or using Cashu token directly -client = OpenAI( - api_key="cashuAeyJ0...", - base_url="https://api.routstr.com/v1" -) -``` - -### 4. Monitor Usage - -- Check balance: `GET /v1/wallet/balance` -- View admin dashboard -- Track costs per request - -### 5. Withdraw Funds - -When done, withdraw remaining balance as eCash through the admin interface. - -## Supported Endpoints - -Routstr supports all standard OpenAI endpoints: - -- ✅ `/v1/chat/completions` - Chat models -- 🚧 `/v1/completions` - Text completion (Coming soon) -- 🚧 `/v1/embeddings` - Text embeddings (Coming soon) -- 🚧 `/v1/images/generations` - Image generation (Coming soon) -- 🚧 `/v1/audio/transcriptions` - Audio to text (Coming soon) -- 🚧 `/v1/audio/translations` - Audio translation (Coming soon) -- ✅ `/v1/models` - List available models -- ✅ Custom provider endpoints - -## Cost Structure - -### Pricing Models - -1. **Fixed Cost Per Request** - - Simple flat fee per API call - - Good for uniform usage - -2. **Token-Based Pricing** - - Pay per input/output token - - More accurate for varied usage - -3. **Model-Based Pricing** - - Different rates per model - - Reflects actual provider costs - -### Cost Calculation - -``` -Total Cost = Base Fee + (Input Tokens * Input Rate) + (Output Tokens * Output Rate) -``` - -Fees may include: - -- Exchange rate markup (BTC/USD conversion) -- Provider margin -- Node operator fee - -## Getting Support - -### Documentation - -- This user guide for general usage -- [API Reference](../api/overview.md) for technical details -- [Contributing Guide](../contributing/setup.md) for developers - -### Community - -- GitHub Issues for bugs and features -- Nostr for decentralized discussion -- Node operator contact info - -### Troubleshooting - -Common issues and solutions: - -- [Payment Flow](payment-flow.md) - Understanding the payment process -- [Using the API](using-api.md) - API integration guide -- [Admin Dashboard](admin-dashboard.md) - Managing your node - -## Security Considerations - -### API Key Security - -- Treat API keys like passwords -- Never share or commit them -- Rotate keys regularly -- Use environment variables - -### Payment Security - -- eCash tokens are bearer instruments -- Verify mint trustworthiness -- Keep backups of tokens -- Use small amounts for testing - -### Network Security - -- Always use HTTPS connections -- Verify SSL certificates -- Consider using Tor for privacy -- Monitor for unusual activity - -## Next Steps - -Ready to start? Continue with: - -1. [Payment Flow](payment-flow.md) - Detailed payment process -2. [Using the API](using-api.md) - Making your first calls -3. [Admin Dashboard](admin-dashboard.md) - Managing your account diff --git a/docs/user-guide/models-pricing.md b/docs/user-guide/models-pricing.md deleted file mode 100644 index c5f08850..00000000 --- a/docs/user-guide/models-pricing.md +++ /dev/null @@ -1,466 +0,0 @@ -# Models & Pricing - -Understanding how Routstr calculates costs is essential for managing your API usage efficiently. This guide explains the pricing models and how to configure them. - -## Pricing Models - -Routstr supports three pricing models: - -### 1. Fixed Pricing - -Simple per-request charging: - -```bash -FIXED_PRICING=true -FIXED_COST_PER_REQUEST=10 # 10 sats per request -``` - -**Best for:** - -- Uniform API usage -- Simple applications -- Predictable costs - -### 2. Token-Based Pricing - -Charge based on actual token usage: - -```bash -FIXED_PRICING=false # use model pricing -FIXED_COST_PER_REQUEST=1 # optional base fee -FIXED_PER_1K_INPUT_TOKENS=5 # optional override -FIXED_PER_1K_OUTPUT_TOKENS=15 # optional override -``` - -**Best for:** - -- Varied request sizes -- Fair usage billing -- Cost optimization - -### 3. Model-Based Pricing - -Dynamic pricing based on model costs: - -```bash -FIXED_PRICING=false -EXCHANGE_FEE=1.005 # 0.5% exchange fee -UPSTREAM_PROVIDER_FEE=1.05 # 5% provider fee -``` - -**Best for:** - -- Multiple models -- Market-based pricing -- Automatic updates - -## Model Configuration - -### Default Models - -Routstr includes pricing for popular models: - -| Model | Input ($/1K) | Output ($/1K) | Context | Notes | -|-------|--------------|---------------|---------|-------| -| gpt-3.5-turbo | $0.0015 | $0.002 | 16K | Fast, economical | -| gpt-4 | $0.03 | $0.06 | 8K | Advanced reasoning | -| gpt-4-turbo | $0.01 | $0.03 | 128K | Large context | -| claude-3-opus | $0.015 | $0.075 | 200K | Best quality | -| claude-3-sonnet | $0.003 | $0.015 | 200K | Balanced | -| llama-2-70b | $0.0007 | $0.0009 | 4K | Open source | - -### Custom Models File - -Create `models.json` to override defaults: - -```json -{ - "models": [ - { - "id": "gpt-4-vision", - "name": "GPT-4 Vision", - "pricing": { - "prompt": "0.00003", - "completion": "0.00006", - "request": "0", - "image": "0.00255" - }, - "context_length": 128000, - "supports_vision": true - }, - { - "id": "custom-model", - "name": "My Custom Model", - "pricing": { - "prompt": "0.001", - "completion": "0.002", - "request": "0.0001" - }, - "context_length": 8192 - } - ] -} -``` - -### Auto-updating Models - -Fetch latest models from OpenRouter: - -```bash -# Update models from API -python scripts/models_meta.py - -# Or manually -curl https://openrouter.ai/api/v1/models > models.json -``` - -## Cost Calculation - -### Understanding the Formula - -``` -Base Cost = (Input Tokens × Input Rate) + (Output Tokens × Output Rate) + Request Fee - -Bitcoin Price = Current BTC/USD rate (e.g., $50,000) -Sats Cost = (Base Cost / Bitcoin Price) × 100,000,000 - -Final Cost = Sats Cost × Exchange Fee × Provider Fee -``` - -### Example Calculations - -**Example 1: Simple Chat (gpt-3.5-turbo)** - -``` -Input: 50 tokens -Output: 150 tokens -Model rates: $0.0015/1K input, $0.002/1K output - -USD Cost = (50/1000 × 0.0015) + (150/1000 × 0.002) - = $0.000075 + $0.0003 - = $0.000375 - -At $50,000/BTC: 0.75 sats -With 5.5% total fees: 0.79 sats -``` - -**Example 2: Large Context (gpt-4)** - -``` -Input: 2,000 tokens -Output: 500 tokens -Model rates: $0.03/1K input, $0.06/1K output - -USD Cost = (2000/1000 × 0.03) + (500/1000 × 0.06) - = $0.06 + $0.03 - = $0.09 - -At $50,000/BTC: 180 sats -With 5.5% total fees: 190 sats -``` - -**Example 3: Image Generation (dall-e-3)** - -``` -Model: dall-e-3 -Size: 1024x1024 -Quality: standard -Cost: $0.04 per image - -At $50,000/BTC: 80 sats -With 5.5% fees: 84 sats -``` - -## Fee Structure - -### Exchange Fee - -Covers Bitcoin/USD conversion costs: - -```bash -EXCHANGE_FEE=1.005 # 0.5% default -``` - -Factors: - -- Exchange rate volatility -- Conversion costs -- Price update frequency - -### Provider Fee - -Node operator's margin: - -```bash -UPSTREAM_PROVIDER_FEE=1.05 # 5% default -``` - -Covers: - -- Infrastructure costs -- Maintenance -- Support -- Profit margin - -### Calculating Total Fees - -``` -Total Multiplier = EXCHANGE_FEE × UPSTREAM_PROVIDER_FEE -Example: 1.005 × 1.05 = 1.05525 (5.525% total) -``` - -## Special Pricing - -### Image Models - -Image generation uses per-image pricing: - -| Model | Size | Quality | Price | -|-------|------|---------|-------| -| dall-e-2 | 256x256 | - | $0.016 | -| dall-e-2 | 512x512 | - | $0.018 | -| dall-e-2 | 1024x1024 | - | $0.02 | -| dall-e-3 | 1024x1024 | standard | $0.04 | -| dall-e-3 | 1024x1024 | hd | $0.08 | -| dall-e-3 | 1024x1792 | standard | $0.08 | -| dall-e-3 | 1024x1792 | hd | $0.12 | - -### Audio Models - -Audio pricing by duration: - -| Model | Type | Price | -|-------|------|-------| -| whisper-1 | Transcription | $0.006/minute | -| whisper-1 | Translation | $0.006/minute | -| tts-1 | Text-to-speech | $0.015/1K chars | -| tts-1-hd | HD speech | $0.03/1K chars | - -### Embedding Models - -Lower costs for embeddings: - -| Model | Price/1K tokens | -|-------|-----------------| -| text-embedding-3-small | $0.00002 | -| text-embedding-3-large | $0.00013 | -| text-embedding-ada-002 | $0.0001 | - -## Monitoring Costs - -### Per-Request Tracking - -Each API response includes usage data: - -```json -{ - "usage": { - "prompt_tokens": 50, - "completion_tokens": 150, - "total_tokens": 200 - }, - "x-routstr-cost": { - "sats": 79, - "usd": 0.000375, - "breakdown": { - "prompt_cost": 15, - "completion_cost": 60, - "fees": 4 - } - } -} -``` - -### Daily Summaries - -View in admin dashboard: - -- Total requests -- Token usage by model -- Cost distribution -- Trending patterns - -### Cost Alerts - -Set up notifications: - -```python -# Example monitoring script -def check_daily_spend(api_key): - balance_start = get_balance(api_key, "00:00") - balance_now = get_balance(api_key) - spent = balance_start - balance_now - - if spent > DAILY_LIMIT: - send_alert(f"Daily spend exceeded: {spent} sats") -``` - -## Optimization Strategies - -### Model Selection - -Choose the right model for each task: - -| Task | Recommended Model | Why | -|------|-------------------|-----| -| Simple Q&A | gpt-3.5-turbo | Fast, cheap, sufficient | -| Code generation | gpt-4 | Better reasoning | -| Summarization | claude-3-haiku | Good balance | -| Creative writing | claude-3-opus | Best quality | -| Embeddings | text-embedding-3-small | Optimized for vectors | - -### Prompt Engineering - -Reduce costs with efficient prompts: - -```python -# Expensive -prompt = """ -You are an AI assistant. Your task is to help users. -Please provide detailed, comprehensive answers. -Now, answer this question: What is 2+2? -""" - -# Economical -prompt = "Calculate: 2+2" -``` - -### Caching Strategies - -Implement smart caching: - -```python -# Cache embedding results -@lru_cache(maxsize=1000) -def get_embedding(text): - return client.embeddings.create( - model="text-embedding-3-small", - input=text - ) - -# Cache common responses -COMMON_RESPONSES = { - "greeting": "Hello! How can I help you?", - "goodbye": "Goodbye! Have a great day!" -} -``` - -### Batch Processing - -Process multiple items efficiently: - -```python -# Instead of multiple calls -for item in items: - response = client.chat.completions.create(...) - -# Use single call with formatted prompt -prompt = "\n".join([f"{i+1}. {item}" for i, item in enumerate(items)]) -response = client.chat.completions.create( - messages=[{"role": "user", "content": f"Process these items:\n{prompt}"}] -) -``` - -## Custom Pricing Rules - -### Time-Based Pricing - -Implement off-peak discounts: - -```python -def calculate_multiplier(): - hour = datetime.now().hour - if 2 <= hour <= 6: # 2 AM - 6 AM - return 0.8 # 20% discount - elif 18 <= hour <= 22: # 6 PM - 10 PM - return 1.2 # 20% premium - return 1.0 -``` - -### Model-Specific Rules - -Custom pricing logic: - -```python -def adjust_model_price(model, base_price): - # Premium for latest models - if "turbo" in model or "latest" in model: - return base_price * 1.1 - - # Discount for older models - if "legacy" in model: - return base_price * 0.8 - - return base_price -``` - -## Pricing Transparency - -### Public Pricing Page - -Display current rates: - -```html - - - - - - - - -
ModelInput (sats/1K)Output (sats/1K)
-``` - -### Cost Estimation API - -Provide cost estimates: - -```bash -POST /v1/estimate -{ - "model": "gpt-4", - "prompt_tokens": 500, - "max_tokens": 200 -} - -Response: -{ - "estimated_cost_sats": 45, - "breakdown": { - "prompt": 30, - "completion": 12, - "fees": 3 - } -} -``` - -## Troubleshooting - -### Pricing Mismatches - -**Issue**: Costs don't match expectations - -- Check current BTC/USD rate -- Verify fee settings -- Review model configuration - -**Issue**: Models not found - -- Update models.json -- Check model ID spelling -- Verify upstream support - -### Fee Calculations - -**Issue**: Fees seem too high - -- Review EXCHANGE_FEE setting -- Check UPSTREAM_PROVIDER_FEE -- Calculate total multiplier - -## Next Steps - -- [API Reference](../api/overview.md) - Technical details -- [Custom Pricing](../advanced/custom-pricing.md) - Advanced configuration -- [Contributing](../contributing/setup.md) - Help improve Routstr diff --git a/docs/user-guide/payment-flow.md b/docs/user-guide/payment-flow.md deleted file mode 100644 index 87784500..00000000 --- a/docs/user-guide/payment-flow.md +++ /dev/null @@ -1,373 +0,0 @@ -# Payment Flow - -Understanding how payments work in Routstr is key to using the system effectively. This guide explains the payment process in detail. - -## Overview - -Routstr uses a pre-funded account model where: - -1. Users deposit eCash tokens to create an API key -2. Each API request deducts from the balance -3. Users can withdraw remaining balance as eCash - -## Creating an API Key - -### Step 1: Obtain eCash Token - -Get a Cashu token from any compatible source: - -**Option A: From a Cashu Wallet** - -```bash -# Example: Creating a 10,000 sat token -cashu send 10000 -``` - -**Option B: Lightning Invoice** - -```bash -# Some mints support direct Lightning deposits -curl -X POST https://mint.example.com/v1/mint/quote/bolt11 \ - -d '{"amount": 10000, "unit": "sat"}' -``` - -**Option C: Test Tokens** - -```bash -# Get test tokens from testnet mints -# Check mint documentation for faucets -``` - -### Step 2: Create API Key - -**Note: The POST /v1/wallet/create endpoint is coming soon. Currently, you can use Cashu tokens directly as API keys in the Authorization header.** - -Send your token to Routstr: - -```bash -curl -X POST https://api.routstr.com/v1/wallet/create \ - -H "Content-Type: application/json" \ - -d '{ - "cashu_token": "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vbWlu..." - }' -``` - -**Request Parameters:** - -- `cashu_token` (required): The eCash token to deposit - -**Response:** - -```json -{ - "api_key": "sk-1234567890abcdef", - "balance": 10000000, - "created_at": "2024-01-01T00:00:00Z" -} -``` - -### Step 3: Verify Balance - -Check your key's balance: - -```bash -curl -X GET https://api.routstr.com/v1/wallet/balance \ - -H "Authorization: Bearer sk-1234567890abcdef" -``` - -Response: - -```json -{ - "balance": 10000000, - "total_deposited": 10000000, - "total_spent": 0, - "last_used": null -} -``` - -## Making API Requests - -### Cost Calculation - -Costs are calculated based on: - -1. **Request Type** - - Chat completions - - Embeddings - - Image generation - - Audio processing - -2. **Token Usage** - - Input tokens (prompt) - - Output tokens (response) - - Model-specific rates - -3. **Additional Costs** - - Base request fee - - Image generation fees - - Audio processing time - -### Example: Chat Completion - -```python -import openai - -client = openai.OpenAI( - api_key="sk-1234567890abcdef", - base_url="https://api.routstr.com/v1" -) - -# Make request -response = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[ - {"role": "user", "content": "Hello, how are you?"} - ] -) - -# Check usage -print(f"Input tokens: {response.usage.prompt_tokens}") -print(f"Output tokens: {response.usage.completion_tokens}") -print(f"Total tokens: {response.usage.total_tokens}") -``` - -### Cost Breakdown - -For the above request: - -``` -Model: gpt-3.5-turbo -Input tokens: 13 -Output tokens: 27 -Model rates: $0.0015/1K input, $0.002/1K output - -USD Cost = (13/1000 * 0.0015) + (27/1000 * 0.002) = $0.0000735 -BTC/USD Rate: $50,000 -BTC Cost = 0.0000735 / 50000 = 0.00000000147 BTC = 147 sats -With fees (5%): 154 sats - -Final cost: 154 sats -``` - -## Balance Management - -### Monitoring Usage - -Track your usage in real-time: - -```bash -# Get current balance -curl -X GET https://api.routstr.com/v1/wallet/balance \ - -H "Authorization: Bearer your-api-key" - -# View recent transactions (through admin dashboard) -# Access at https://api.routstr.com/admin/ -``` - -### Low Balance Handling - -When balance is insufficient: - -```json -{ - "error": { - "type": "insufficient_balance", - "message": "Insufficient balance. Current: 100 sats, Required: 154 sats", - "code": "payment_required" - } -} -``` - -### Topping Up - -Add funds to existing key: - -```bash -curl -X POST https://api.routstr.com/v1/wallet/topup \ - -H "Authorization: Bearer your-api-key" \ - -H "Content-Type: application/json" \ - -d '{ - "cashu_token": "cashuAeyJ0b2..." - }' -``` - -## Withdrawing Balance - -### Via Admin Dashboard - -1. Navigate to `/admin/` -2. Enter admin password -3. Find your API key -4. Click "Withdraw" -5. Receive eCash token - -### Via API (if enabled) - -```bash -curl -X POST https://api.routstr.com/v1/wallet/withdraw \ - -H "Authorization: Bearer your-api-key" \ - -H "Content-Type: application/json" \ - -d '{ - "amount": 5000, - "mint": "https://mint.minibits.cash/Bitcoin" - }' -``` - -## Payment Security - -### Token Validation - -Routstr validates tokens by: - -1. Checking signature validity -2. Verifying with the issuing mint -3. Ensuring no double-spending -4. Confirming sufficient value - -### Failed Payments - -Common failure reasons: - -- Invalid token signature -- Already spent token -- Untrusted mint -- Network issues with mint - -### Refund Policy - -- Unused balance can be withdrawn anytime -- Expired keys with balance can be refunded -- Node operators may have additional policies - -## Advanced Features - -### Multi-Mint Support - -Routstr accepts tokens from multiple mints: - -```bash -CASHU_MINTS=https://mint1.com,https://mint2.com,https://mint3.com -``` - -Benefits: - -- Redundancy if one mint is down -- User choice of mints -- Geographic distribution - -### Automatic Payouts - -Configure automatic Lightning payouts: - -```bash -RECEIVE_LN_ADDRESS=satoshi@getalby.com -``` - -When enabled: - -- Balances above threshold are swept -- Converted to Lightning payments -- Sent to configured address - -### Per-Request Payments (Coming Soon) - -Future support for Nut-24 headers: - -```bash -curl -X POST https://api.routstr.com/v1/chat/completions \ - -H "x-cashu: cashuAeyJ0..." \ - -H "Content-Type: application/json" \ - -d '{...}' -``` - -Response includes change: - -``` -HTTP/1.1 200 OK -x-cashu: cashuAeyJjaGFuZ2Ui... -``` - -## Best Practices - -### API Key Management - -1. **Separate Keys per Application** - - Easier tracking - - Better security - - Independent budgets - -2. **Set Expiration Dates** - - Automatic cleanup - - Security improvement - - Budget control - -3. **Monitor Balances** - - Set up alerts - - Regular checks - - Usage analytics - -### Cost Optimization - -1. **Choose Appropriate Models** - - Smaller models for simple tasks - - Larger models only when needed - -2. **Optimize Prompts** - - Concise, clear instructions - - Avoid unnecessary tokens - -3. **Use Streaming** - - Early termination possible - - Better user experience - -### Security - -1. **Secure Storage** - - Environment variables - - Secrets management - - Never in code - -2. **Network Security** - - Always use HTTPS - - Verify certificates - - Consider Tor for privacy - -3. **Regular Rotation** - - Change keys periodically - - Withdraw unused funds - - Audit usage logs - -## Troubleshooting - -### Payment Rejected - -**Error:** "Invalid token" - -- Check token format -- Verify mint is trusted -- Ensure not already spent - -**Error:** "Insufficient value" - -- Token value too low -- Check current pricing -- Add larger token - -### Balance Discrepancies - -- Allow for price fluctuations -- Check model pricing updates -- Review transaction history - -### Mint Issues - -- Try different mint from list -- Check mint status -- Contact mint operator - -## Next Steps - -- [Using the API](using-api.md) - Integration guide -- [Admin Dashboard](admin-dashboard.md) - Account management -- [Models & Pricing](models-pricing.md) - Cost details diff --git a/docs/user-guide/using-api.md b/docs/user-guide/using-api.md deleted file mode 100644 index 5ad32509..00000000 --- a/docs/user-guide/using-api.md +++ /dev/null @@ -1,545 +0,0 @@ -# Using the API - -This guide shows how to integrate Routstr with your applications using various programming languages and tools. - -## API Compatibility - -Routstr maintains full compatibility with the OpenAI API, meaning: - -- Existing OpenAI client libraries work without modification -- Only the base URL and API key need to change -- All parameters and responses match OpenAI's format - -## Basic Setup - -### Python - -Using the official OpenAI Python library: - -```python -from openai import OpenAI - -# Initialize client with Routstr endpoint -client = OpenAI( - api_key="sk-...", - base_url="https://api.routstr.com/v1" -) - -# Use exactly like OpenAI -response = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Hello!"} - ] -) - -print(response.choices[0].message.content) -``` - -### JavaScript/TypeScript - -Using the official OpenAI Node.js library: - -```javascript -import OpenAI from 'openai'; - -// Initialize client -const openai = new OpenAI({ - apiKey: 'sk-...', - baseURL: 'https://api.routstr.com/v1' -}); - -// Make a request -async function main() { - const completion = await openai.chat.completions.create({ - model: 'gpt-3.5-turbo', - messages: [ - { role: 'system', content: 'You are a helpful assistant.' }, - { role: 'user', content: 'Hello!' } - ] - }); - - console.log(completion.choices[0].message.content); -} - -main(); -``` - -### cURL - -Direct HTTP requests: - -```bash -curl https://api.routstr.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer sk-..." \ - -d '{ - "model": "gpt-3.5-turbo", - "messages": [ - {"role": "user", "content": "Hello!"} - ] - }' -``` - -## Common Use Cases - -### Chat Completions - -Standard chat with conversation history: - -```python -messages = [] - -def chat(user_input): - # Add user message - messages.append({"role": "user", "content": user_input}) - - # Get AI response - response = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=messages, - temperature=0.7, - max_tokens=150 - ) - - # Add AI response to history - ai_message = response.choices[0].message - messages.append({"role": "assistant", "content": ai_message.content}) - - return ai_message.content - -# Usage -print(chat("What's the weather like?")) -print(chat("How should I dress?")) # Maintains context -``` - -### Streaming Responses - -For real-time output: - -```python -stream = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "Write a short story"}], - stream=True -) - -for chunk in stream: - if chunk.choices[0].delta.content is not None: - print(chunk.choices[0].delta.content, end="", flush=True) -``` - -### Function Calling - -Using OpenAI's function calling feature: - -```python -tools = [{ - "type": "function", - "function": { - "name": "get_weather", - "description": "Get current weather", - "parameters": { - "type": "object", - "properties": { - "location": {"type": "string"}, - "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} - }, - "required": ["location"] - } - } -}] - -response = client.chat.completions.create( - model="gpt-4", - messages=[{"role": "user", "content": "What's the weather in Tokyo?"}], - tools=tools, - tool_choice="auto" -) - -# Check if function was called -if response.choices[0].message.tool_calls: - tool_call = response.choices[0].message.tool_calls[0] - print(f"Function: {tool_call.function.name}") - print(f"Arguments: {tool_call.function.arguments}") -``` - -### Embeddings - -Generate text embeddings: - -```python -response = client.embeddings.create( - model="text-embedding-3-small", - input="The quick brown fox jumps over the lazy dog" -) - -embedding = response.data[0].embedding -print(f"Embedding dimension: {len(embedding)}") -``` - -### Image Generation - -Create images with DALL-E: - -```python -response = client.images.generate( - model="dall-e-3", - prompt="A futuristic city with flying cars", - size="1024x1024", - quality="standard", - n=1 -) - -image_url = response.data[0].url -print(f"Image URL: {image_url}") -``` - -### Audio Transcription - -Convert speech to text: - -```python -with open("audio.mp3", "rb") as audio_file: - response = client.audio.transcriptions.create( - model="whisper-1", - file=audio_file, - response_format="text" - ) - -print(response.text) -``` - -## Error Handling - -### Balance Errors - -Handle insufficient balance gracefully: - -```python -try: - response = client.chat.completions.create( - model="gpt-4", - messages=[{"role": "user", "content": "Hello"}] - ) -except Exception as e: - if "insufficient_balance" in str(e): - print("Low balance! Please top up your API key.") - # Implement top-up logic - else: - raise -``` - -### Rate Limiting - -Implement exponential backoff: - -```python -import time -from typing import Optional - -def make_request_with_retry( - func, - max_retries: int = 3, - initial_delay: float = 1.0 -) -> Optional[any]: - for attempt in range(max_retries): - try: - return func() - except Exception as e: - if "rate_limit" in str(e) and attempt < max_retries - 1: - delay = initial_delay * (2 ** attempt) - print(f"Rate limited. Waiting {delay}s...") - time.sleep(delay) - else: - raise - return None -``` - -### Connection Errors - -Handle network issues: - -```python -import httpx - -# Configure timeout and retries -client = OpenAI( - api_key="sk-...", - base_url="https://your-node.com/v1", - timeout=httpx.Timeout(60.0, connect=5.0), - max_retries=2 -) -``` - -## Advanced Features - -### Using Tor - -Route requests through Tor for privacy: - -```python -import httpx - -# Configure Tor proxy -proxies = { - "http://": "socks5://127.0.0.1:9050", - "https://": "socks5://127.0.0.1:9050" -} - -http_client = httpx.Client(proxies=proxies) - -client = OpenAI( - api_key="sk-...", - base_url="http://your-onion-address.onion/v1", - http_client=http_client -) -``` - -### Custom Headers - -Add custom headers if needed: - -```python -import httpx - -class CustomClient(httpx.Client): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.headers["X-Custom-Header"] = "value" - -client = OpenAI( - api_key="sk-...", - base_url="https://your-node.com/v1", - http_client=CustomClient() -) -``` - -### Azure OpenAI compatibility - -To use Azure OpenAI through Routstr with minimal changes: - -- Set `UPSTREAM_BASE_URL` to your Azure deployments URL, for example: `https://.openai.azure.com/openai/deployments/` -- Set `CHAT_COMPLETIONS_API_VERSION=2024-05-01-preview` - -When this env var is set, Routstr automatically appends `api-version=2024-05-01-preview` to all upstream `/chat/completions` requests. - -### Async Operations - -For high-performance applications: - -```python -import asyncio -from openai import AsyncOpenAI - -async_client = AsyncOpenAI( - api_key="sk-...", - base_url="https://your-node.com/v1" -) - -async def process_messages(messages): - tasks = [] - for msg in messages: - task = async_client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": msg}] - ) - tasks.append(task) - - responses = await asyncio.gather(*tasks) - return [r.choices[0].message.content for r in responses] - -# Run async -messages = ["Hello", "How are you?", "What's 2+2?"] -results = asyncio.run(process_messages(messages)) -``` - -## Best Practices - -### 1. Environment Variables - -Never hardcode API keys: - -```python -import os -from openai import OpenAI - -client = OpenAI( - api_key=os.getenv("ROUTSTR_API_KEY"), - base_url=os.getenv("ROUTSTR_BASE_URL", "https://api.routstr.com/v1") -) -``` - -### 2. Error Logging - -Implement comprehensive logging: - -```python -import logging - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -try: - response = client.chat.completions.create(...) - logger.info(f"Request successful. Tokens used: {response.usage.total_tokens}") -except Exception as e: - logger.error(f"API request failed: {e}") - raise -``` - -### 3. Cost Tracking - -Monitor your usage: - -```python -class UsageTracker: - def __init__(self): - self.total_tokens = 0 - self.total_requests = 0 - - def track(self, response): - self.total_tokens += response.usage.total_tokens - self.total_requests += 1 - - # Estimate cost (example rates) - cost_per_1k = 0.002 # $0.002 per 1K tokens - estimated_cost = (self.total_tokens / 1000) * cost_per_1k - - logger.info(f"Total usage: {self.total_tokens} tokens, " - f"${estimated_cost:.4f} (~{estimated_cost * 50000:.0f} sats)") - -tracker = UsageTracker() -response = client.chat.completions.create(...) -tracker.track(response) -``` - -### 4. Caching Responses - -Reduce costs with intelligent caching: - -```python -import hashlib -import json -from functools import lru_cache - -@lru_cache(maxsize=100) -def cached_completion(prompt: str, model: str = "gpt-3.5-turbo"): - response = client.chat.completions.create( - model=model, - messages=[{"role": "user", "content": prompt}], - temperature=0 # Deterministic for caching - ) - return response.choices[0].message.content - -# Repeated calls with same prompt use cache -result1 = cached_completion("What is 2+2?") -result2 = cached_completion("What is 2+2?") # From cache, no API call -``` - -## Testing - -### Mock Responses - -For development without spending sats: - -```python -class MockOpenAI: - class Completions: - def create(self, **kwargs): - return type('Response', (), { - 'choices': [type('Choice', (), { - 'message': type('Message', (), { - 'content': 'Mock response' - })() - })], - 'usage': type('Usage', (), { - 'total_tokens': 10 - })() - })() - - def __init__(self): - self.chat = type('Chat', (), { - 'completions': self.Completions() - })() - -# Use mock in tests -if os.getenv('TESTING'): - client = MockOpenAI() -else: - client = OpenAI(...) -``` - -### Integration Tests - -Test your Routstr integration: - -```python -def test_routstr_connection(): - try: - # Test models endpoint - models = client.models.list() - assert len(models.data) > 0 - - # Test simple completion - response = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "test"}], - max_tokens=5 - ) - assert response.choices[0].message.content - - print("✅ Routstr integration working!") - return True - except Exception as e: - print(f"❌ Integration test failed: {e}") - return False -``` - -## Troubleshooting - -### Common Issues - -**SSL Certificate Errors** - -```python -# For development only - not for production! -import ssl -import httpx - -client = OpenAI( - api_key="sk-...", - base_url="https://localhost:8000/v1", - http_client=httpx.Client(verify=False) -) -``` - -**Timeout Issues** - -```python -# Increase timeout for slow connections -client = OpenAI( - api_key="sk-...", - base_url="https://your-node.com/v1", - timeout=httpx.Timeout(120.0) # 2 minutes -) -``` - -**Debugging Requests** - -```python -import logging -import httpx - -# Enable debug logging -logging.basicConfig(level=logging.DEBUG) -httpx_logger = logging.getLogger("httpx") -httpx_logger.setLevel(logging.DEBUG) -``` - -## Next Steps - -- [Admin Dashboard](admin-dashboard.md) - Manage your account -- [Models & Pricing](models-pricing.md) - Understanding costs -- [API Reference](../api/overview.md) - Technical details diff --git a/mkdocs.yml b/mkdocs.yml index b8cbfaac..9221c714 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -77,29 +77,27 @@ extra: nav: - Home: index.md - - Getting Started: - - Overview: getting-started/overview.md - - Quick Start: getting-started/quickstart.md - - Docker Setup: getting-started/docker.md - - Configuration: getting-started/configuration.md - - User Guide: - - Introduction: user-guide/introduction.md - - Payment Flow: user-guide/payment-flow.md - - Using the API: user-guide/using-api.md - - Admin Dashboard: user-guide/admin-dashboard.md - - Models & Pricing: user-guide/models-pricing.md - - Contributing: - - Setup Development: contributing/setup.md - - Architecture: contributing/architecture.md - - Code Structure: contributing/code-structure.md - - Testing: contributing/testing.md + - Overview: overview.md + - Client Guide: + - Introduction: client/introduction.md + - Payment Flow: client/payments.md + - Integration: client/integration.md + - Provider Guide: + - Quick Start: provider/quickstart.md + - Dashboard: provider/dashboard.md + - Deployment: provider/deployment.md + - Configuration: provider/configuration.md + - Pricing: provider/pricing.md + - Advanced Pricing: provider/advanced-pricing.md + - Discovery: provider/discovery.md + - Tor Support: provider/tor.md - API Reference: - Overview: api/overview.md - Authentication: api/authentication.md - Endpoints: api/endpoints.md - Errors: api/errors.md - - Advanced: - - Tor Support: advanced/tor.md - - Nostr Discovery: advanced/nostr.md - - Custom Pricing: advanced/custom-pricing.md - - Migrations: advanced/migrations.md + - Contributing: + - Setup Development: contributing/setup.md + - Architecture: contributing/architecture.md + - Code Structure: contributing/code-structure.md + - Testing: contributing/testing.md