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 provides standard HTTP endpoints for querying aggregated NIP-66 relay monitoring data. rstate is the aggregation engine at the core of nostr-watch: it ingests raw NIP-66 events from multiple independent monitors, computes consensus values using configurable quorum and MAD-based outlier detection, and serves the results as a REST API. No Nostr knowledge or special libraries are required.

Base URL

The public nostr-watch REST API is available at:
https://api.nostr.watch/v2
When self-hosting, the default is http://localhost:3000. Enable the REST API by setting REST_ENABLED=true in your environment, or rest.enabled: true in config.yaml. Interactive OpenAPI documentation and a live sandbox are available at api.nostr.watch/v2/, and at /docs when running rstate locally.

Quick start

# Health check
curl -s https://api.nostr.watch/v2/health/ping | jq '.'

# List first 10 relays sorted by most recently updated
curl 'https://api.nostr.watch/v2/relays?limit=10&sortBy=updated&sortOrder=desc'

# Get state for a specific relay
curl 'https://api.nostr.watch/v2/relays/state?relayUrl=wss%3A%2F%2Frelay.damus.io'

# Search for relays supporting NIP-42
curl -X POST https://api.nostr.watch/v2/relays/search \
  -H "Content-Type: application/json" \
  -d '{"nips": [42]}'

# Find relays near San Francisco
curl 'https://api.nostr.watch/v2/relays/nearby?lat=37.7749&lon=-122.4194&radius=100'

# Get online relays
curl -X POST https://api.nostr.watch/v2/relays/online \
  -H "Content-Type: application/json" \
  -d '{}'

Endpoint reference

All paths are relative to the base URL. All endpoints return JSON.

Health

MethodPathDescription
GET/health/pingServer health check with cache and connectivity stats
The /health/ping response includes status (ok, degraded, or error), version, uptime, relay connection counts, observation count, and cache statistics.

Relay endpoints

MethodPathDescription
GET/relaysList all relays with pagination and sorting
GET/relays/stateGet state for a specific relay URL
POST/relays/searchSearch relays by network, NIPs, software, labels, and latency
GET/relays/nearbyFind relays near a geographic point (lat, lon, radius in km)
GET/relays/bboxFind relays within a geographic bounding box
Common query parameters for GET /relays:
ParameterTypeDefaultDescription
limitnumber50Results per page (max 200)
offsetnumber0Pagination offset
sortBystringurlSort field: url, updated, observationCount, lastSeen
sortOrderstringascasc or desc
formatstringdetailedfull, detailed, or simple
Search request body (POST /relays/search):
{
  "network": "clearnet",
  "nips": [1, 42],
  "software": { "family": "strfry", "version": "1.0.0" },
  "labels": [{ "namespace": "country", "value": "US" }],
  "maxLatency": { "open": 200, "read": 150, "write": 150 },
  "minSupport": 0.7,
  "limit": 100,
  "offset": 0,
  "format": "detailed"
}
All search filter fields are optional. Only relays matching all provided filters are returned.

Label endpoints

MethodPathDescription
GET/relays/labelsGet labels for a specific relay (pass relayUrl query param)
GET/relays/labels/listList all available labels, optionally filtered by namespace
GET/relays/by/labelFind relays with a specific label (namespace + value required)

Aggregation endpoints

MethodPathDescription
GET/relays/by/softwareGroup relays by software family
GET/relays/by/networkGroup relays by network type (clearnet, Tor, I2P)
GET/relays/by/nipGroup relays by NIP support
GET/relays/by/countryGroup relays by country (ISO 3166-1 alpha-2)

Availability endpoints

MethodPathDescription
POST/relays/onlineGet currently online relays
POST/relays/offlineGet relays that are offline but not yet dead
POST/relays/deadGet probably dead relays (default threshold: 7 days)
POST/relays/compareCompare 1–10 relays side-by-side
Compare request body:
{
  "relayUrls": [
    "wss://relay.damus.io",
    "wss://relay.nostr.band"
  ]
}
Compare response:
{
  "relays": [],
  "comparison": {
    "common": {
      "nips": [1, 11, 42],
      "requirements": ["!auth"]
    },
    "differences": {
      "network": false,
      "software": true,
      "latency": true
    }
  }
}

Monitor endpoints

MethodPathDescription
GET/monitorsList all known monitors with reliability scores
GET/monitors/:pubkeyGet a specific monitor
GET/monitors/:pubkey/analyticsGet analytics for a specific monitor
GET/monitors/analyticsGet analytics for all monitors

Policy endpoints

MethodPathDescription
GET/policyGet current aggregation policy
PUT/policyUpdate aggregation policy (requires NIP-98 auth)

Subscriptions (SSE)

MethodPathDescription
POST/subscriptionsCreate a subscription
GET/subscriptions/eventsServer-Sent Events stream for real-time updates

Authentication and rate limiting

Most REST endpoints are public and unauthenticated. The only endpoint requiring authentication is PUT /policy, which uses NIP-98 HTTP Auth — create a signed Nostr event (kind 27235) and pass it as a Bearer token. The API uses rate limiting to prevent abuse. Default: 10 requests per second, max burst 100. When rate limited, you will receive a 429 Too Many Requests response with a Retry-After header.
async function fetchWithRetry(url: string, options?: RequestInit): Promise<any> {
  const response = await fetch(url, options)

  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After') || '5')
    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
    return fetchWithRetry(url, options)
  }

  return response.json()
}
NIP-66 data updates hourly. Cache responses locally — there is rarely a reason to poll more frequently than that.

Error responses

All errors 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
500INTERNAL_ERRORServer error

Trust model

The REST API serves aggregated data. When using a third-party REST API, you trust that the operator is running an honest rstate instance. To eliminate this trust requirement, self-host rstate (set REST_ENABLED=true) or use raw NIP-66 events to verify data independently.
The aggregation logic is open source. Self-hosting rstate means you ingest raw NIP-66 events directly from Nostr relays and compute results locally — no trust in nostr.watch required.

Next steps

Monitors

Query monitor reliability scores and coverage analytics.

Subscriptions

Subscribe to real-time relay state changes via SSE.

CVM (MCP over Nostr)

Use structured MCP tools over Nostr for better privacy.

Raw NIP-66

Query NIP-66 events directly for trustless verification.

Build docs developers (and LLMs) love