Skip to content

Finder Service

The Finder service is responsible for discovering new Nostr relays from external APIs. It runs continuously, periodically fetching relay lists and adding newly discovered URLs to the database.

PropertyValue
Service Namefinder
LifecycleContinuous (run_forever)
Default Interval3600 seconds (1 hour)
DependenciesPostgreSQL, Internet access
Configurationyaml/services/finder.yaml

Each cycle, the Finder:

  1. Fetch API Sources: Queries configured API endpoints for relay lists
  2. Validate URLs: Ensures URLs are valid WebSocket addresses
  3. Detect Network: Identifies clearnet vs Tor (.onion) relays
  4. Batch Insert: Adds new relays to the database (idempotent)
yaml/services/finder.yaml
# Cycle interval (seconds between discovery runs)
interval: 3600.0 # 1 hour (minimum: 60.0)
# External API discovery
api:
enabled: true
sources:
- url: https://api.nostr.watch/v1/online
enabled: true
timeout: 30.0 # Request timeout (1.0-120.0)
- url: https://api.nostr.watch/v1/offline
enabled: true
timeout: 30.0
delay_between_requests: 1.0 # Delay between API calls (0.0-10.0)
# Event-based discovery (planned feature)
events:
enabled: false
FieldTypeDefaultRangeDescription
intervalfloat3600.0≥60.0Seconds between cycles
api.enabledbooltrue-Enable API discovery
api.sources[].urlstring--API endpoint URL
api.sources[].enabledbooltrue-Enable this source
api.sources[].timeoutfloat30.01.0-120.0Request timeout
api.delay_between_requestsfloat1.00.0-10.0Inter-request delay

The default configuration uses nostr.watch APIs:

EndpointDescription
/v1/onlineCurrently reachable relays
/v1/offlinePreviously known but currently offline relays

Response format:

[
"wss://relay.damus.io",
"wss://relay.nostr.band",
"wss://nos.lol"
]

You can add additional API sources:

api:
sources:
# Default sources
- url: https://api.nostr.watch/v1/online
enabled: true
- url: https://api.nostr.watch/v1/offline
enabled: true
# Custom source (must return JSON array of URLs)
- url: https://my-relay-list.example.com/api/relays
enabled: true
timeout: 60.0

Requirements for custom sources:

  • Must return HTTP 200 with JSON body
  • Body must be an array of WebSocket URLs
  • URLs must be valid wss:// or ws:// addresses

Finder runs automatically in the stack:

finder:
command: ["python", "-m", "services", "finder"]
restart: unless-stopped
depends_on:
initializer:
condition: service_completed_successfully
Terminal window
cd implementations/bigbrotr
python -m services finder
Terminal window
python -m services finder --log-level DEBUG
Terminal window
python -m services finder --config yaml/services/finder.yaml

Typical Finder output:

INFO finder: cycle_started
INFO finder: fetching_source url=https://api.nostr.watch/v1/online
INFO finder: source_fetched url=https://api.nostr.watch/v1/online relays=2341
INFO finder: fetching_source url=https://api.nostr.watch/v1/offline
INFO finder: source_fetched url=https://api.nostr.watch/v1/offline relays=6524
INFO finder: relays_discovered total=8865 new=127 clearnet=7892 tor=973
INFO finder: cycle_completed duration=4.56
INFO finder: waiting seconds=3600

Finder automatically detects network type from URLs:

PatternNetwork
*.oniontor
Everything elseclearnet
def detect_network(url: str) -> str:
if ".onion" in url:
return "tor"
return "clearnet"

Finder uses the insert_relay stored procedure:

-- Idempotent insertion (ignores duplicates)
INSERT INTO relays (url, network, inserted_at)
VALUES ($1, $2, $3)
ON CONFLICT (url) DO NOTHING;

This means:

  • Running Finder multiple times is safe
  • Existing relays are not modified
  • Only truly new relays are added
WARNING finder: source_timeout url=https://api.nostr.watch/v1/online timeout=30.0

The Finder continues with other sources and retries on the next cycle.

WARNING finder: source_invalid_response url=https://example.com status=500

Non-200 responses are logged and skipped.

WARNING finder: source_network_error url=https://example.com error=Connection refused

Network errors are logged; the Finder continues with other sources.

-- Relays discovered over time
SELECT
DATE(TO_TIMESTAMP(inserted_at)) as date,
COUNT(*) as relays_added
FROM relays
GROUP BY DATE(TO_TIMESTAMP(inserted_at))
ORDER BY date DESC
LIMIT 30;
SELECT network, COUNT(*) as count
FROM relays
GROUP BY network;
  • Rate Limiting: Use delay_between_requests to avoid overwhelming APIs
  • Timeout Tuning: Increase timeouts for slow APIs
  • Interval: Hourly discovery is sufficient; more frequent adds little value