# C-Relay API Documentation Complete API reference for the C-Relay event-based administration system and advanced features. ## Table of Contents - [Overview](#overview) - [Authentication](#authentication) - [Admin API](#admin-api) - [Event Structure](#event-structure) - [Configuration Management](#configuration-management) - [Auth Rules Management](#auth-rules-management) - [System Commands](#system-commands) - [Database Queries](#database-queries) - [Configuration Reference](#configuration-reference) - [Real-time Monitoring](#real-time-monitoring) - [Direct Message Admin](#direct-message-admin) - [Response Formats](#response-formats) - [Error Handling](#error-handling) - [Examples](#examples) ## Overview C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as cryptographically signed Nostr events. This provides: - **Cryptographic security**: All commands must be signed with the admin private key - **Audit trail**: Complete history of all administrative actions - **Real-time updates**: Configuration changes applied instantly - **Standard protocol**: Uses Nostr events, no custom protocols - **NIP-44 encryption**: All admin commands and responses are encrypted ### Key Concepts 1. **Admin Keypair**: Generated on first startup, used to sign all admin commands 2. **Relay Keypair**: The relay's identity on the Nostr network 3. **Admin Events**: Kind 23456 events with NIP-44 encrypted commands 4. **Response Events**: Kind 23457 events with NIP-44 encrypted responses 5. **Event-Based Config**: All settings stored as events in the database ## Authentication ### Admin Private Key The admin private key is displayed **only once** during first startup: ``` ======================================== IMPORTANT: SAVE THIS ADMIN PRIVATE KEY ======================================== Admin Private Key: nsec1abc123... Admin Public Key: npub1def456... ======================================== ``` **Critical**: Save this key immediately. It cannot be recovered and is required for all administrative operations. ### Secure Storage Store the admin private key securely: ```bash # Environment variable export C_RELAY_ADMIN_KEY="nsec1abc123..." # Secure file echo "nsec1abc123..." > ~/.c-relay-admin chmod 600 ~/.c-relay-admin # Password manager (recommended) # Store in 1Password, Bitwarden, etc. ``` ### Key Loss Recovery If you lose the admin private key: 1. Stop the relay 2. Delete the database file (`*.db`) 3. Restart the relay (generates new keys) 4. **Note**: This deletes all events and configuration ## Admin API ### Event Structure All admin commands use the same event structure with NIP-44 encrypted content. #### Admin Command Event (Kind 23456) ```json { "id": "computed_event_id", "pubkey": "admin_public_key_hex", "created_at": 1234567890, "kind": 23456, "content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...", "tags": [ ["p", "relay_public_key_hex"] ], "sig": "event_signature" } ``` **Fields**: - `kind`: Must be `23456` for admin commands - `pubkey`: Admin public key (hex format) - `content`: NIP-44 encrypted JSON array containing the command - `tags`: Must include `["p", "relay_pubkey"]` tag - `sig`: Valid signature from admin private key #### Encrypted Content Format The `content` field contains a NIP-44 encrypted JSON array: ```json ["command_name", "param1", "param2", ...] ``` Examples: ```json ["config_query", "all"] ["config_update", [{"key": "relay_name", "value": "My Relay", ...}]] ["blacklist", "pubkey", "abc123..."] ["auth_query", "all"] ["sql_query", "SELECT * FROM events LIMIT 10"] ``` #### Admin Response Event (Kind 23457) ```json { "id": "response_event_id", "pubkey": "relay_public_key_hex", "created_at": 1234567890, "kind": 23457, "content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...", "tags": [ ["p", "admin_public_key_hex"], ["e", "request_event_id"] ], "sig": "response_signature" } ``` **Fields**: - `kind`: Always `23457` for admin responses - `pubkey`: Relay public key (hex format) - `content`: NIP-44 encrypted JSON response object - `tags`: Includes `["p", "admin_pubkey"]` and optionally `["e", "request_id"]` - `sig`: Valid signature from relay private key ### Configuration Management #### Query All Configuration **Command**: ```json ["config_query", "all"] ``` **Response** (decrypted): ```json { "query_type": "config_all", "total_results": 27, "timestamp": 1234567890, "data": [ { "key": "relay_name", "value": "C-Relay", "data_type": "string", "category": "relay", "description": "Relay name displayed in NIP-11" }, { "key": "auth_enabled", "value": "false", "data_type": "boolean", "category": "auth", "description": "Enable whitelist/blacklist authentication" } ] } ``` #### Update Configuration **Command**: ```json ["config_update", [ { "key": "relay_name", "value": "My Awesome Relay", "data_type": "string", "category": "relay" }, { "key": "max_subscriptions_per_client", "value": "50", "data_type": "integer", "category": "limits" } ]] ``` **Response** (decrypted): ```json { "query_type": "config_update", "status": "success", "total_results": 2, "timestamp": 1234567890, "data": [ { "key": "relay_name", "value": "My Awesome Relay", "status": "updated" }, { "key": "max_subscriptions_per_client", "value": "50", "status": "updated" } ] } ``` ### Auth Rules Management #### Add Blacklist Rule **Command**: ```json ["blacklist", "pubkey", "abc123def456..."] ``` **Response** (decrypted): ```json { "query_type": "auth_add", "status": "success", "message": "Blacklist rule added successfully", "timestamp": 1234567890 } ``` #### Add Whitelist Rule **Command**: ```json ["whitelist", "pubkey", "def456abc123..."] ``` #### Delete Auth Rule **Command**: ```json ["delete_auth_rule", "blacklist", "pubkey", "abc123def456..."] ``` **Response** (decrypted): ```json { "query_type": "auth_delete", "status": "success", "message": "Auth rule deleted successfully", "timestamp": 1234567890 } ``` #### Query All Auth Rules **Command**: ```json ["auth_query", "all"] ``` **Response** (decrypted): ```json { "query_type": "auth_rules_all", "total_results": 5, "timestamp": 1234567890, "data": [ { "rule_type": "blacklist", "pattern_type": "pubkey", "pattern_value": "abc123...", "action": "deny" }, { "rule_type": "whitelist", "pattern_type": "pubkey", "pattern_value": "def456...", "action": "allow" } ] } ``` #### Query Specific Rule Type **Command**: ```json ["auth_query", "whitelist"] ``` #### Query Specific Pattern **Command**: ```json ["auth_query", "pattern", "abc123..."] ``` ### System Commands #### System Status **Command**: ```json ["system_command", "system_status"] ``` **Response** (decrypted): ```json { "query_type": "system_status", "timestamp": 1234567890, "status": "running", "uptime_seconds": 86400, "version": "0.6.0", "relay_pubkey": "relay_public_key_hex", "database_size_bytes": 10485760, "total_events": 15432, "active_connections": 42, "active_subscriptions": 156 } ``` #### Clear All Auth Rules **Command**: ```json ["system_command", "clear_all_auth_rules"] ``` **Response** (decrypted): ```json { "query_type": "system_command", "status": "success", "message": "All auth rules cleared", "timestamp": 1234567890 } ``` #### Database Statistics **Command**: ```json ["stats_query"] ``` **Response** (decrypted): ```json { "query_type": "stats_query", "timestamp": 1234567890, "database_size_bytes": 10485760, "total_events": 15432, "database_created_at": 1234567800, "latest_event_at": 1234567890, "event_kinds": [ { "kind": 1, "count": 12000, "percentage": 77.8 }, { "kind": 0, "count": 2500, "percentage": 16.2 } ], "time_stats": { "total": 15432, "last_24h": 234, "last_7d": 1456, "last_30d": 5432 }, "top_pubkeys": [ { "pubkey": "abc123...", "event_count": 1234, "percentage": 8.0 } ] } ``` ### Database Queries #### SQL Query Command Execute read-only SQL queries against the relay database. **Command**: ```json ["sql_query", "SELECT * FROM events ORDER BY created_at DESC LIMIT 10"] ``` **Response** (decrypted): ```json { "query_type": "sql_query", "request_id": "request_event_id", "timestamp": 1234567890, "query": "SELECT * FROM events ORDER BY created_at DESC LIMIT 10", "execution_time_ms": 45, "row_count": 10, "columns": ["id", "pubkey", "created_at", "kind", "content", "tags", "sig"], "rows": [ ["abc123...", "def456...", 1234567890, 1, "Hello world", "[]", "sig123..."], ["ghi789...", "jkl012...", 1234567880, 0, "{\"name\":\"Alice\"}", "[]", "sig456..."] ] } ``` #### Security Features - **Read-only**: Only SELECT statements allowed - **Query timeout**: 5 seconds maximum - **Result limit**: 1000 rows maximum - **Logging**: All queries logged with execution time - **Validation**: SQL injection protection #### Available Tables and Views **Core Tables**: - `events` - All Nostr events - `config` - Configuration parameters - `auth_rules` - Authentication rules - `subscription_events` - Subscription lifecycle log - `event_broadcasts` - Event broadcast log **Views**: - `recent_events` - Last 1000 events - `event_stats` - Event statistics by type - `subscription_analytics` - Subscription metrics - `active_subscriptions_log` - Currently active subscriptions - `event_kinds_view` - Event distribution by kind - `top_pubkeys_view` - Top 10 pubkeys by event count - `time_stats_view` - Time-based statistics #### Example Queries **Recent events**: ```sql SELECT id, pubkey, created_at, kind FROM events ORDER BY created_at DESC LIMIT 20 ``` **Event distribution**: ```sql SELECT * FROM event_kinds_view ORDER BY count DESC ``` **Active subscriptions**: ```sql SELECT * FROM active_subscriptions_log ORDER BY created_at DESC ``` **Database statistics**: ```sql SELECT (SELECT COUNT(*) FROM events) as total_events, (SELECT COUNT(*) FROM subscription_events) as total_subscriptions, (SELECT COUNT(DISTINCT pubkey) FROM events) as unique_pubkeys ``` **Events by specific pubkey**: ```sql SELECT id, created_at, kind, content FROM events WHERE pubkey = 'abc123...' ORDER BY created_at DESC LIMIT 50 ``` **Events in time range**: ```sql SELECT COUNT(*) as count, kind FROM events WHERE created_at BETWEEN 1234567000 AND 1234567890 GROUP BY kind ORDER BY count DESC ``` ## Configuration Reference ### Basic Relay Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `relay_name` | string | "C-Relay" | Relay name (NIP-11) | | `relay_description` | string | "C Nostr Relay" | Relay description | | `relay_contact` | string | "" | Admin contact info | | `relay_software` | string | "c-relay" | Software identifier | | `relay_version` | string | auto | Software version | | `supported_nips` | string | "1,9,11,13,15,20,33,40,42,45,50,70" | Supported NIPs | | `language_tags` | string | "*" | Supported languages | | `relay_countries` | string | "*" | Supported countries | | `posting_policy` | string | "" | Posting policy URL | | `payments_url` | string | "" | Payment URL | ### Connection & Limits | Key | Type | Default | Range | Restart Required | |-----|------|---------|-------|------------------| | `max_connections` | integer | 1000 | 1-10000 | Yes | | `max_subscriptions_per_client` | integer | 25 | 1-100 | No | | `max_total_subscriptions` | integer | 5000 | 100-50000 | No | | `max_message_length` | integer | 65536 | 1024-1048576 | No | | `max_event_tags` | integer | 2000 | 10-10000 | No | | `max_content_length` | integer | 65536 | 1-1048576 | No | ### Authentication & Access Control | Key | Type | Default | Description | |-----|------|---------|-------------| | `auth_enabled` | boolean | false | Enable whitelist/blacklist | | `nip42_auth_required` | boolean | false | Require NIP-42 auth | | `nip42_auth_required_kinds` | string | "" | Kinds requiring NIP-42 | | `nip42_challenge_timeout` | integer | 300 | Challenge timeout (seconds) | ### Proof of Work (NIP-13) | Key | Type | Default | Values | Description | |-----|------|---------|--------|-------------| | `pow_min_difficulty` | integer | 0 | 0-40 | Minimum PoW difficulty | | `pow_mode` | string | "optional" | disabled/optional/required | PoW enforcement mode | ### Event Expiration (NIP-40) | Key | Type | Default | Description | |-----|------|---------|-------------| | `nip40_expiration_enabled` | boolean | true | Enable expiration support | | `nip40_expiration_strict` | boolean | false | Reject expired events | | `nip40_expiration_filter` | boolean | true | Filter expired from results | | `nip40_expiration_grace_period` | integer | 300 | Grace period (seconds) | ### Monitoring | Key | Type | Default | Description | |-----|------|---------|-------------| | `kind_24567_reporting_throttle_sec` | integer | 5 | Monitoring event throttle | ### Dynamic vs Restart-Required **Dynamic (No Restart)**: - All NIP-11 relay information - Authentication settings - Subscription limits - Event validation limits - Proof of Work settings - Expiration settings **Restart Required**: - `max_connections` - `relay_port` - Database settings ## Real-time Monitoring C-Relay provides subscription-based real-time monitoring using ephemeral events (kind 24567). ### Activation Subscribe to kind 24567 events to activate monitoring: ```json ["REQ", "monitoring-sub", {"kinds": [24567]}] ``` ### Monitoring Event Types Subscribe to specific monitoring types using d-tag filters: ```json ["REQ", "event-kinds", {"kinds": [24567], "#d": ["event_kinds"]}] ["REQ", "time-stats", {"kinds": [24567], "#d": ["time_stats"]}] ["REQ", "top-pubkeys", {"kinds": [24567], "#d": ["top_pubkeys"]}] ["REQ", "cpu-metrics", {"kinds": [24567], "#d": ["cpu_metrics"]}] ``` ### Event Structure ```json { "kind": 24567, "pubkey": "relay_pubkey", "created_at": 1234567890, "content": "{\"data_type\":\"event_kinds\",\"timestamp\":1234567890,...}", "tags": [ ["d", "event_kinds"] ] } ``` ### Monitoring Types #### Event Distribution (`event_kinds`) ```json { "data_type": "event_kinds", "timestamp": 1234567890, "total_events": 15432, "kinds": [ {"kind": 1, "count": 12000, "percentage": 77.8}, {"kind": 0, "count": 2500, "percentage": 16.2} ] } ``` #### Time Statistics (`time_stats`) ```json { "data_type": "time_stats", "timestamp": 1234567890, "total_events": 15432, "last_24h": 234, "last_7d": 1456, "last_30d": 5432 } ``` #### Top Publishers (`top_pubkeys`) ```json { "data_type": "top_pubkeys", "timestamp": 1234567890, "top_pubkeys": [ {"pubkey": "abc123...", "count": 1234, "percentage": 8.0}, {"pubkey": "def456...", "count": 987, "percentage": 6.4} ] } ``` #### CPU Metrics (`cpu_metrics`) ```json { "data_type": "cpu_metrics", "timestamp": 1234567890, "cpu_percent": 12.5, "memory_mb": 45.2, "uptime_seconds": 86400 } ``` #### Active Subscriptions (`active_subscriptions`) - Admin Only ```json { "data_type": "active_subscriptions", "timestamp": 1234567890, "total_subscriptions": 156, "subscriptions_by_client": [ {"client_id": "client1", "count": 12}, {"client_id": "client2", "count": 8} ] } ``` ### Configuration Control monitoring frequency: ```json ["config_update", [{ "key": "kind_24567_reporting_throttle_sec", "value": "10", "data_type": "integer", "category": "monitoring" }]] ``` ### Performance - Events are ephemeral (not stored) - Automatic activation/deactivation based on subscriptions - Throttling prevents excessive event generation - Minimal overhead when no clients monitoring ## Direct Message Admin Control your relay by sending direct messages from any Nostr client. ### Setup 1. The relay has its own keypair (shown on startup) 2. The relay knows the admin public key 3. Send NIP-17 direct messages to the relay ### Available Commands Send a DM containing any of these keywords: | Command | Aliases | Response | |---------|---------|----------| | Statistics | stats, statistics | Database statistics | | Configuration | config, configuration | Current configuration | ### Example Using any Nostr client that supports NIP-17: 1. Find the relay's public key (shown on startup) 2. Send a DM: "stats" 3. Receive a DM with current relay statistics ### Response Format The relay responds with a NIP-17 DM containing: **Stats Response**: ``` Relay Statistics ================ Total Events: 15,432 Database Size: 10.5 MB Active Connections: 42 Active Subscriptions: 156 Uptime: 1 day, 2 hours ``` **Config Response**: ``` Relay Configuration =================== Name: My Awesome Relay Description: Community relay Max Subscriptions: 25 Auth Enabled: false PoW Difficulty: 0 ``` ## Response Formats ### Success Response ```json { "query_type": "command_name", "status": "success", "message": "Operation completed successfully", "timestamp": 1234567890, "data": [...] } ``` ### Error Response ```json { "query_type": "command_name", "status": "error", "error": "Error description", "timestamp": 1234567890 } ``` ### Query Response ```json { "query_type": "query_name", "total_results": 10, "timestamp": 1234567890, "data": [...] } ``` ## Error Handling ### Common Errors | Error | Cause | Solution | |-------|-------|----------| | `invalid_signature` | Event signature invalid | Check admin private key | | `unauthorized` | Wrong admin pubkey | Use correct admin key | | `invalid_command` | Unknown command | Check command format | | `validation_failed` | Invalid parameter value | Check parameter ranges | | `database_error` | Database operation failed | Check database integrity | | `timeout` | Query took too long | Simplify query or increase timeout | ### Error Response Example ```json { "query_type": "config_update", "status": "error", "error": "field validation failed: invalid port number '99999' (must be 1-65535)", "timestamp": 1234567890 } ``` ## Examples ### JavaScript/TypeScript Example ```javascript import { SimplePool, nip44, getPublicKey, finalizeEvent } from 'nostr-tools'; const adminPrivkey = 'your_admin_privkey_hex'; const adminPubkey = getPublicKey(adminPrivkey); const relayPubkey = 'relay_pubkey_hex'; const relayUrl = 'ws://localhost:8888'; // Create admin command async function sendAdminCommand(command) { const pool = new SimplePool(); // Encrypt command with NIP-44 const encryptedContent = await nip44.encrypt( adminPrivkey, relayPubkey, JSON.stringify(command) ); // Create event const event = finalizeEvent({ kind: 23456, created_at: Math.floor(Date.now() / 1000), tags: [['p', relayPubkey]], content: encryptedContent }, adminPrivkey); // Publish event await pool.publish([relayUrl], event); // Subscribe to response const sub = pool.sub([relayUrl], [{ kinds: [23457], '#p': [adminPubkey], since: Math.floor(Date.now() / 1000) }]); return new Promise((resolve) => { sub.on('event', async (event) => { // Decrypt response const decrypted = await nip44.decrypt( adminPrivkey, relayPubkey, event.content ); resolve(JSON.parse(decrypted)); sub.unsub(); }); }); } // Query configuration const config = await sendAdminCommand(['config_query', 'all']); console.log(config); // Update configuration const result = await sendAdminCommand(['config_update', [ { key: 'relay_name', value: 'My Relay', data_type: 'string', category: 'relay' } ]]); console.log(result); // Add blacklist rule const blacklist = await sendAdminCommand([ 'blacklist', 'pubkey', 'abc123...' ]); console.log(blacklist); // Execute SQL query const query = await sendAdminCommand([ 'sql_query', 'SELECT * FROM events ORDER BY created_at DESC LIMIT 10' ]); console.log(query); ``` ### Python Example ```python from nostr_sdk import Keys, Client, EventBuilder, Filter, nip44 admin_privkey = "your_admin_privkey_hex" relay_pubkey = "relay_pubkey_hex" relay_url = "ws://localhost:8888" # Initialize keys = Keys.parse(admin_privkey) client = Client(keys) client.add_relay(relay_url) client.connect() # Send admin command async def send_admin_command(command): # Encrypt command encrypted = nip44.encrypt( keys.secret_key(), relay_pubkey, json.dumps(command) ) # Create event event = EventBuilder.new( kind=23456, content=encrypted, tags=[["p", relay_pubkey]] ).to_event(keys) # Publish await client.send_event(event) # Wait for response filter = Filter().kind(23457).pubkey(relay_pubkey).since(int(time.time())) events = await client.get_events_of([filter], timeout=5) if events: # Decrypt response decrypted = nip44.decrypt( keys.secret_key(), relay_pubkey, events[0].content() ) return json.loads(decrypted) # Query configuration config = await send_admin_command(["config_query", "all"]) print(config) ``` ### Bash/curl Example ```bash #!/bin/bash # Note: This is a simplified example. Real implementation requires: # - NIP-44 encryption # - Event signing # - WebSocket connection RELAY_URL="ws://localhost:8888" ADMIN_PRIVKEY="your_admin_privkey" RELAY_PUBKEY="relay_pubkey" # Use nostrtool or similar for proper event creation nostrtool event \ --kind 23456 \ --content "$(echo '["config_query","all"]' | nip44-encrypt)" \ --tag p "$RELAY_PUBKEY" \ --private-key "$ADMIN_PRIVKEY" \ | nostrtool send "$RELAY_URL" ``` --- ## Additional Resources - **[Configuration Guide](docs/configuration_guide.md)** - Detailed configuration options - **[Deployment Guide](docs/deployment_guide.md)** - Production deployment - **[NIP-42 Authentication](docs/NIP-42_Authentication.md)** - Authentication setup - **[User Guide](docs/user_guide.md)** - End-user documentation ## Support For API questions or issues: - Open an issue on GitHub - Check existing documentation - Join the Nostr community --- **API Version**: 0.6.0 **Last Updated**: 2026-01-23