Files
c-relay/API.md

22 KiB

C-Relay API Documentation

Complete API reference for the C-Relay event-based administration system and advanced features.

Table of Contents

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:

# 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)

{
  "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:

["command_name", "param1", "param2", ...]

Examples:

["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)

{
  "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:

["config_query", "all"]

Response (decrypted):

{
  "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:

["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):

{
  "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:

["blacklist", "pubkey", "abc123def456..."]

Response (decrypted):

{
  "query_type": "auth_add",
  "status": "success",
  "message": "Blacklist rule added successfully",
  "timestamp": 1234567890
}

Add Whitelist Rule

Command:

["whitelist", "pubkey", "def456abc123..."]

Delete Auth Rule

Command:

["delete_auth_rule", "blacklist", "pubkey", "abc123def456..."]

Response (decrypted):

{
  "query_type": "auth_delete",
  "status": "success",
  "message": "Auth rule deleted successfully",
  "timestamp": 1234567890
}

Query All Auth Rules

Command:

["auth_query", "all"]

Response (decrypted):

{
  "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:

["auth_query", "whitelist"]

Query Specific Pattern

Command:

["auth_query", "pattern", "abc123..."]

System Commands

System Status

Command:

["system_command", "system_status"]

Response (decrypted):

{
  "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:

["system_command", "clear_all_auth_rules"]

Response (decrypted):

{
  "query_type": "system_command",
  "status": "success",
  "message": "All auth rules cleared",
  "timestamp": 1234567890
}

Database Statistics

Command:

["stats_query"]

Response (decrypted):

{
  "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:

["sql_query", "SELECT * FROM events ORDER BY created_at DESC LIMIT 10"]

Response (decrypted):

{
  "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:

SELECT id, pubkey, created_at, kind 
FROM events 
ORDER BY created_at DESC 
LIMIT 20

Event distribution:

SELECT * FROM event_kinds_view 
ORDER BY count DESC

Active subscriptions:

SELECT * FROM active_subscriptions_log 
ORDER BY created_at DESC

Database statistics:

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:

SELECT id, created_at, kind, content 
FROM events 
WHERE pubkey = 'abc123...' 
ORDER BY created_at DESC 
LIMIT 50

Events in time range:

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:

["REQ", "monitoring-sub", {"kinds": [24567]}]

Monitoring Event Types

Subscribe to specific monitoring types using d-tag filters:

["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

{
  "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)

{
  "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)

{
  "data_type": "time_stats",
  "timestamp": 1234567890,
  "total_events": 15432,
  "last_24h": 234,
  "last_7d": 1456,
  "last_30d": 5432
}

Top Publishers (top_pubkeys)

{
  "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)

{
  "data_type": "cpu_metrics",
  "timestamp": 1234567890,
  "cpu_percent": 12.5,
  "memory_mb": 45.2,
  "uptime_seconds": 86400
}

Active Subscriptions (active_subscriptions) - Admin Only

{
  "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:

["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

{
  "query_type": "command_name",
  "status": "success",
  "message": "Operation completed successfully",
  "timestamp": 1234567890,
  "data": [...]
}

Error Response

{
  "query_type": "command_name",
  "status": "error",
  "error": "Error description",
  "timestamp": 1234567890
}

Query Response

{
  "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

{
  "query_type": "config_update",
  "status": "error",
  "error": "field validation failed: invalid port number '99999' (must be 1-65535)",
  "timestamp": 1234567890
}

Examples

JavaScript/TypeScript Example

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

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

#!/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

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