Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sandwichfarm/nostr-watch/llms.txt

Use this file to discover all available pages before exploring further.

The rstate REST API exposes relay intelligence aggregated from multiple independent NIP-66 monitors. All endpoints return JSON. The base URL for the hosted API is https://api.nostr.watch/v2. When running rstate locally with REST_ENABLED=true, the default base URL is http://localhost:3000. Interactive OpenAPI documentation is available at /docs when the server is running.
Most endpoints are public and require no authentication. The only authenticated endpoint is PUT /policy, which requires NIP-98 HTTP Auth.

GET /health/ping

Returns server health, version, uptime, and cache statistics. Use this to verify the server is reachable and to inspect cache efficiency. Response:
{
  "status": "ok",
  "version": "0.1.0",
  "uptime": 3600,
  "relayCount": { "transport": 2, "ingestion": 3 },
  "observationCount": 15420,
  "cache": {
    "size": 1523,
    "maxSize": 10000,
    "hitRate": 0.85,
    "hitRatePercent": 85
  },
  "timestamp": 1709740800000
}
status
string
Overall server status. One of ok (all connections healthy), degraded (partial connectivity), or error (no connections).
version
string
Server software version.
uptime
number
Server uptime in seconds.
relayCount
object
observationCount
number
Total number of relay observations in the current dataset.
cache
object
curl -s https://api.nostr.watch/v2/health/ping | jq '.'

GET /relays

List all relays with pagination and sorting. Use the format parameter to control response verbosity. Query parameters:
limit
number
Results per page. Default 50, maximum 200.
offset
number
Pagination offset. Default 0.
sortBy
string
Sort field. One of url, updated, observationCount, or lastSeen. Default url.
sortOrder
string
Sort direction. asc or desc. Default asc.
format
string
Response verbosity. full includes per-monitor attribution, detailed returns aggregated values (default), simple returns URLs only.
Response: { relays: [], total, limit, offset }
# First 10 relays sorted by most recently updated
curl 'https://api.nostr.watch/v2/relays?limit=10&sortBy=updated&sortOrder=desc'

GET /relays/state

Get the aggregated state for a specific relay URL. Returns a single relay object or a 404 error if the relay is not in the dataset. Query parameters:
relayUrl
string
required
Relay WebSocket URL, URL-encoded. Example: wss%3A%2F%2Frelay.damus.io
format
string
Response verbosity. full, detailed (default), or simple.
Response: { relay: {} } or 404 with { error: { code: "RELAY_NOT_FOUND", message } }
curl 'https://api.nostr.watch/v2/relays/state?relayUrl=wss%3A%2F%2Frelay.damus.io'

POST /relays/search

Filter relays using one or more criteria. All filter fields are optional — only relays matching all provided filters are returned. Request body:
network
string
Network type filter. One of clearnet, tor, i2p, or hybrid.
nips
number[]
Return only relays that support all listed NIP numbers. Example: [1, 42].
software
object
labels
object[]
Array of { namespace, value } label filters. Example: [{ "namespace": "country", "value": "US" }].
maxLatency
object
minSupport
number
Minimum monitor support ratio (0–1). Only include relays confirmed by at least this fraction of monitors. Example: 0.7.
limit
number
Results per page. Default 100.
offset
number
Pagination offset. Default 0.
format
string
Response verbosity. full, detailed (default), or simple.
Response: { relays: [], total, limit, offset }
# Find clearnet relays supporting NIP-42 with low latency
curl -X POST https://api.nostr.watch/v2/relays/search \
  -H "Content-Type: application/json" \
  -d '{
    "network": "clearnet",
    "nips": [42],
    "maxLatency": { "open": 200 },
    "minSupport": 0.7
  }'

GET /relays/nearby

Find relays near a geographic coordinate using Haversine distance. Relay location is derived from the geohash (g tag) in NIP-66 events. Query parameters:
lat
number
required
Latitude, -90 to 90.
lon
number
required
Longitude, -180 to 180.
radius
number
Search radius in km. Default 100.
format
string
Response verbosity. full, detailed (default), or simple.
Response: { relays: [], center: { lat, lon }, radius } — each relay object includes a distance field in km.
# Relays within 100km of San Francisco
curl 'https://api.nostr.watch/v2/relays/nearby?lat=37.7749&lon=-122.4194&radius=100'

GET /relays/by/software

Group all relays by their software family (from the NIP-11 info document). Returns counts and relay lists per software family. Pass the family query parameter to filter down to a single family. Query parameters:
family
string
Filter to a specific software family name, e.g. strfry. Omit to get all groups.
Response (no filter): { groups: { family: [relayUrls] } } Response (with filter): { relays: [relayUrls], total }
# All software groups
curl https://api.nostr.watch/v2/relays/by/software

# Only strfry relays
curl 'https://api.nostr.watch/v2/relays/by/software?family=strfry'

GET /relays/by/network

Group relays by network type (clearnet, tor, i2p). Response: { groups: [{ network, count, relays }] }
curl https://api.nostr.watch/v2/relays/by/network

GET /relays/by/nip

Group relays by which NIPs they support. Use the nip parameter to filter to a single NIP, and minSupport to exclude NIP support with low monitor agreement. Query parameters:
nip
number
Filter to a specific NIP number (e.g. 42). Omit to get all NIP groups.
minSupport
number
Minimum monitor support ratio (0–1). Default 0.5.
Response: { groups: [{ nip, count, avgSupport, relays }] }
curl 'https://api.nostr.watch/v2/relays/by/nip?nip=42&minSupport=0.8'

GET /relays/by/country

Group relays by country. Relay country is derived from geo label data. Pass the countryCode parameter to filter to a specific country. Query parameters:
countryCode
string
ISO 3166-1 alpha-2 country code (e.g. US, DE). Omit to get all countries.
Response: { groups: [{ countryCode, countryName, count, relays }] }
curl 'https://api.nostr.watch/v2/relays/by/country?countryCode=US'

POST /relays/compare

Compare 1–10 relay URLs side by side. Returns full state objects for each relay plus a diff highlighting shared and differing properties. Request body:
relayUrls
string[]
required
Array of 1–10 relay WebSocket URLs to compare.
Response:
{
  "relays": [],
  "comparison": {
    "common": {
      "nips": [1, 11, 42],
      "requirements": ["!auth"]
    },
    "differences": {
      "network": false,
      "software": true,
      "latency": true
    }
  }
}
relays
object[]
Full relay state objects for each requested URL.
comparison.common.nips
number[]
NIP numbers supported by all compared relays.
comparison.common.requirements
string[]
R tag values shared by all relays (e.g. !auth, !payment).
comparison.differences
object
Booleans indicating where the relays differ: network, software, latency.
curl -X POST https://api.nostr.watch/v2/relays/compare \
  -H "Content-Type: application/json" \
  -d '{"relayUrls": ["wss://relay.damus.io", "wss://relay.nostr.band"]}'

Error responses

All error responses follow a consistent format:
{
  "error": {
    "code": "RELAY_NOT_FOUND",
    "message": "Relay wss://example.com not found"
  }
}
HTTP statusCodeMeaning
400VALIDATION_ERRORInvalid request parameters
404RELAY_NOT_FOUNDRelay URL not in the dataset
429RATE_LIMITEDToo many requests — check the Retry-After header
500INTERNAL_ERRORServer error
NIP-66 data updates roughly every hour. Cache responses locally rather than polling frequently. The GET /health/ping response includes cache hit rate so you can verify your caching is effective.

Build docs developers (and LLMs) love