22 KiB
C-Relay API Documentation
Complete API reference for the C-Relay event-based administration system and advanced features.
Table of Contents
- Overview
- Authentication
- Admin API
- Configuration Reference
- Real-time Monitoring
- Direct Message Admin
- Response Formats
- Error Handling
- 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
- Admin Keypair: Generated on first startup, used to sign all admin commands
- Relay Keypair: The relay's identity on the Nostr network
- Admin Events: Kind 23456 events with NIP-44 encrypted commands
- Response Events: Kind 23457 events with NIP-44 encrypted responses
- 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:
- Stop the relay
- Delete the database file (
*.db) - Restart the relay (generates new keys)
- 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 be23456for admin commandspubkey: Admin public key (hex format)content: NIP-44 encrypted JSON array containing the commandtags: Must include["p", "relay_pubkey"]tagsig: 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: Always23457for admin responsespubkey: Relay public key (hex format)content: NIP-44 encrypted JSON response objecttags: 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 eventsconfig- Configuration parametersauth_rules- Authentication rulessubscription_events- Subscription lifecycle logevent_broadcasts- Event broadcast log
Views:
recent_events- Last 1000 eventsevent_stats- Event statistics by typesubscription_analytics- Subscription metricsactive_subscriptions_log- Currently active subscriptionsevent_kinds_view- Event distribution by kindtop_pubkeys_view- Top 10 pubkeys by event counttime_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_connectionsrelay_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
- The relay has its own keypair (shown on startup)
- The relay knows the admin public key
- 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:
- Find the relay's public key (shown on startup)
- Send a DM: "stats"
- 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
- Configuration Guide - Detailed configuration options
- Deployment Guide - Production deployment
- NIP-42 Authentication - Authentication setup
- User Guide - 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