Relay Information Document Specification

Nostr Relay Information Document Specification

1. Overview

A Nostr relay may expose a machine-readable JSON document over HTTP that describes its identity, operator, capabilities, and operational constraints. Clients can retrieve this document to discover what a relay supports before or after connecting to it.

The document is served from the same base URL used for WebSocket connections, distinguished from other request types by an Accept header. All fields in the document are optional — the document is a best-effort, self-reported description, and no field is guaranteed to be present.

2. Retrieving the Document

Rule 2.1: HTTP Request

The client retrieves the document by sending an HTTP GET request to the relay’s base URL with the following header:

Accept: application/nostr+json

Example:

GET / HTTP/1.1
Host: relay.example.com
Accept: application/nostr+json

Rule 2.2: Accept Header Matching

The relay identifies an information document request by checking the Accept header against the exact string application/nostr+json. This is a strict equality check — not a substring match, not content negotiation, and not wildcard matching.

request.header("Accept") == "application/nostr+json"

Requests that send Accept: application/nostr+json, */* or any other value must not be treated as information document requests. Clients must send the header with precisely this value.

Rule 2.3: URL Protocol Conversion

Relay URLs use the WebSocket protocol (wss:// or ws://). Before making the HTTP request, the client converts the URL to its HTTP equivalent:

wss://  →  https://
ws://   →  http://

The host, path, port, and query string are left unchanged.

Example:

wss://relay.example.com      →  https://relay.example.com
ws://localhost:8080/nostr    →  http://localhost:8080/nostr

Rule 2.4: Multiplexing with WebSocket

The relay serves both its WebSocket endpoint and the information document from the same base URL. Dispatch is determined by request headers:

on GET /:
  if Upgrade == "websocket":
    → handle WebSocket connection
  else if Accept == "application/nostr+json":
    → serve information document
  else:
    → serve fallback response (landing page, redirect, etc.)

3. Response Format

Rule 3.1: Status and Headers

A successful response must include:

HTTP/1.1 200 OK
Content-Type: application/nostr+json
Access-Control-Allow-Origin: *

The Access-Control-Allow-Origin: * header is required so that browser-based clients can fetch the document from any origin without CORS restrictions.

Rule 3.2: Body

The response body is a single JSON object. All fields are optional. Clients must not treat a missing or null field as an error.

Example response:

{
  "name": "My Relay",
  "description": "A public Nostr relay.",
  "pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
  "contact": "mailto:admin@example.com",
  "supported_nips": [1, 2, 9, 11, 40],
  "software": "https://github.com/example/myrelay",
  "version": "1.4.2",
  "limitation": {
    "max_message_length": 131072,
    "max_subscriptions": 20,
    "auth_required": false,
    "payment_required": false
  }
}

Rule 3.3: Unknown Fields

Clients must silently ignore any fields in the response that they do not recognize. The document format evolves over time, and relay implementations may include experimental or future fields.

4. Document Fields

Rule 4.1: Identity Fields

These fields describe the relay and its operator.

Field Type Description
name string Human-readable name for the relay
description string Description of the relay’s purpose or audience
pubkey string Relay operator’s public key, lowercase hex-encoded
contact string Contact address for the operator (e.g. mailto: or https:)
icon string URL to a square image representing the relay
banner string URL to a banner image for the relay

The pubkey field, when present, must be a raw 32-byte public key encoded as 64 lowercase hexadecimal characters. Bech32-encoded keys must not appear in this field.

Rule 4.2: Software Fields

These fields identify the relay implementation.

Field Type Description
software string URL or identifier for the relay software
version string Version string of the relay software

Rule 4.3: Capability Fields

These fields declare what protocol features the relay supports.

Field Type Description
supported_nips array of numbers Protocol feature numbers this relay implements

Clients should consult supported_nips before using protocol features that require explicit relay support.

Because some relay implementations serialize a single-element list as a bare scalar, clients should accept both "supported_nips": [1] and "supported_nips": 1 as equivalent.

Rule 4.4: Limitation Fields

The optional limitation object describes constraints the relay enforces on clients. All subfields are optional.

Access control:

Field Type Description
auth_required boolean Whether authentication is required before any action
payment_required boolean Whether payment is required before any action
restricted_writes boolean Whether some write condition must be fulfilled

Size and throughput limits:

Field Type Description
max_message_length integer Maximum byte length of any incoming WebSocket message
max_subscriptions integer Maximum concurrent subscriptions per connection
max_filters integer Maximum filters per subscription request
max_limit integer Maximum value of the limit filter field
max_subid_length integer Maximum character length of a subscription ID
min_prefix integer Minimum hex prefix length for ID and pubkey filters
max_event_tags integer Maximum number of tags on a single event
max_content_length integer Maximum character length of an event’s content field

Proof-of-work:

Field Type Description
min_pow_difficulty integer Minimum proof-of-work difficulty required on incoming events

Event validity bounds:

Field Type Description
created_at_lower_limit integer Earliest Unix timestamp accepted in created_at
created_at_upper_limit integer Latest Unix timestamp accepted in created_at

Example:

{
  "limitation": {
    "max_message_length": 65536,
    "max_subscriptions": 10,
    "max_filters": 5,
    "max_limit": 1000,
    "max_subid_length": 128,
    "min_prefix": 4,
    "max_event_tags": 2500,
    "max_content_length": 8192,
    "min_pow_difficulty": 0,
    "auth_required": false,
    "payment_required": true,
    "restricted_writes": false,
    "created_at_lower_limit": 1577836800,
    "created_at_upper_limit": 9999999999
  }
}

Rule 4.5: Policy and Community Fields

Field Type Description
relay_countries array of strings ISO 3166-1 alpha-2 country codes indicating the relay’s jurisdiction or audience
language_tags array of strings BCP-47 language tags for the relay’s primary languages, in preference order
tags array of strings Arbitrary descriptive labels (e.g. "bitcoin-only", "sfw-only")
posting_policy string URL to a human-readable document describing the relay’s posting rules
privacy_policy string URL to the relay’s privacy policy
terms_of_service string URL to the relay’s terms of service

Rule 4.6: Payment Fields

Field Type Description
payments_url string URL to a page with payment or subscription details
fees object Structured fee schedule (see Rule 4.7)

Rule 4.7: Fee Schedule

The fees object groups fees into three categories:

{
  "fees": {
    "admission": [
      { "amount": 1000000, "unit": "msats" }
    ],
    "subscription": [
      { "amount": 5000, "unit": "msats", "period": 2592000 }
    ],
    "publication": [
      { "amount": 100, "unit": "msats", "kinds": [1, 6, 7] }
    ]
  }
}

Each fee entry contains:

Field Type Description
amount integer The cost
unit string The currency denomination (e.g. "msats")
period integer For subscription fees, the billing period in seconds
kinds array of integers For publication fees, the event kinds this fee applies to; absent means all kinds

Rule 4.8: Retention Fields

The retention array describes how long or how many events the relay stores, optionally scoped to specific event kinds:

{
  "retention": [
    { "kinds": [0, 1, 3], "time": 86400 },
    { "kinds": [[10000, 19999]], "count": 1000 },
    { "time": 3600, "count": 10000 }
  ]
}

Each entry contains:

Field Type Description
kinds array Event kinds this rule applies to; entries may be integers or two-element [start, end] range arrays; absent means the rule applies globally
time integer or null Seconds to retain events; null means retain forever; 0 means events of this kind are not stored
count integer or null Maximum number of events retained

5. Failure Handling

Rule 5.1: Failed Fetch

If the HTTP request fails for any reason — network error, connection timeout, non-JSON response body, relay not implementing this endpoint — the client must treat the result as “no document available.” This must not be treated as a fatal error or cause the relay connection to be closed.

Rule 5.2: Missing Fields

Clients must handle any field being absent. The correct behavior when a field is missing is to proceed as if that information is unknown, not to assume a default value or abort the operation.

Rule 5.3: Partial Documents

A relay may return a document containing only a subset of known fields. Clients must process whatever fields are present and ignore the rest.


Optional Features

The following behaviors are implemented by some clients and relays but are not required.

Optional: Fetch Triggering Strategies

Optional Rule 6.1: Automatic Fetch on Connection

A client implementation may automatically initiate an information document fetch whenever a new relay connection is opened, without requiring any explicit caller action. This ensures metadata is available before it is first needed.

on_relay_connected(relay_url):
  fetch_information_document(relay_url)
  // continues in background; does not block connection use

Optional Rule 6.2: Lazy Fetch on Demand

A client implementation may defer the fetch until something explicitly requests the document. Establishing a connection does not trigger a request.

get_relay_information(relay_url):
  if not already_fetching(relay_url):
    start_fetch(relay_url)
  return await fetch_result(relay_url)

Optional: Caching

Optional Rule 7.1: Per-Relay Lifetime Cache

A client may cache the fetched document for the lifetime of the relay object or session. When multiple parts of the application request the document for the same relay URL simultaneously, only one HTTP request is issued; all requesters receive the same result. Late requesters receive the cached result without triggering a new request.

fetch_information_document(url):
  if cache.has(url):
    return cache.get(url)  // immediate, no network request

  result = await http_get(url)
  cache.set(url, result)
  return result

Optional Rule 7.2: Application-Wide Registry

A client may store fetched documents in a single shared registry keyed by relay URL. Any part of the application can retrieve a document by URL without knowing whether it has already been fetched.

registry = Map<url, RelayDocument>

load_document(url):
  if registry.has(url):
    return  // already loaded

  doc = await fetch_information_document(url)
  registry.set(url, doc)

Optional: Timeouts

Optional Rule 8.1: Automatic Timeout

A client may apply an automatic timeout to the fetch request. If the relay does not respond within the timeout period, the fetch is treated as a failure per Rule 5.1.

Recommended timeout range: 7–10 seconds.

Optional Rule 8.2: Caller-Supplied Deadline

A client may accept a caller-provided deadline or timeout and honor it in preference to any automatic timeout. The automatic timeout applies only when the caller provides none.

fetch_information_document(url, deadline=null):
  effective_deadline = deadline ?? now() + DEFAULT_TIMEOUT
  return http_get_with_deadline(url, effective_deadline)

Optional: URL Canonicalization

Optional Rule 9.1: Full Canonicalization

Beyond the protocol conversion required by Rule 2.3, a client may apply additional normalization to relay URLs before use:

  • Lowercase the hostname
  • Strip trailing slashes from the path
  • Infer the protocol when absent: loopback addresses (localhost, 127.0.0.1) use http://; all others use https://
  • Accept bare hostnames without a protocol prefix as valid input

Normalization table:

Input Normalized
WSS://Relay.Example.COM/ wss://relay.example.com
https://relay.example.com wss://relay.example.com
relay.example.com wss://relay.example.com
localhost:8080 ws://localhost:8080

Normalization must be idempotent — applying it multiple times must produce the same result.

normalize(normalize(normalize("WSS://Relay.Example.COM/")))
// => "wss://relay.example.com"

Optional: Request Optimization

Optional Rule 10.1: Batched Fetching

Rather than issuing one HTTP request per relay, a client may accumulate multiple relay URLs over a short time window and dispatch them together in a single batch request to a backend service. This reduces request count when many relay connections are opened in quick succession, at the cost of a small additional latency for each individual relay.

pending_urls = []

load_document(url):
  pending_urls.push(url)
  schedule_batch_flush()  // debounced, fires after window closes

flush_batch():
  urls = pending_urls.splice(0)
  results = await batch_fetch_service.post("/relay/info", { urls })
  for { url, document } in results:
    registry.set(url, document)

Optional Rule 10.2: Intermediary Fetching

A client may route requests through an intermediary service rather than fetching relay documents directly. The client sends a list of relay URLs to the intermediary, which retrieves the documents on the client’s behalf and returns them in a single response.

This shifts the fetch work server-side and can improve reliability in client environments where direct outbound HTTP connections are constrained.

Optional: Relay-Side Live Field Values

Optional Rule 11.1: Runtime Authentication State

A relay may determine the value of limitation.auth_required at request time by reading from its live configuration rather than from a value fixed at startup. This ensures the field accurately reflects the relay’s current state if authentication requirements are changed at runtime without a restart.

Optional Rule 11.2: Build-Time Version Injection

A relay may populate the version field from build-time metadata (such as a version string injected at compile time) and apply this value in preference to any version string stored in a configuration file. This guarantees the advertised version always matches the running binary.

Optional Rule 11.3: Capability-Driven supported_nips

A relay may derive the contents of supported_nips by introspecting which protocol handlers are actually registered at runtime, rather than maintaining a manually curated static list. This prevents the advertised capabilities from drifting out of sync with the implementation.

build_supported_nips(relay):
  nips = [1, 11]  // always supported
  if relay.delete_handler is registered:
    nips.add(9)
  if relay.count_handler is registered:
    nips.add(45)
  if relay.negentropy_enabled:
    nips.add(77)
  return nips

Optional: Multiple Relay Identities

Optional Rule 12.1: Per-Path Relay Documents

A single server process may host multiple distinct relay identities, each mounted at a different URL path and each serving its own independent information document. Each identity has its own name, pubkey, description, and other fields, and is independently addressable as a relay.

Example:

GET /          Accept: application/nostr+json  →  outbox relay document
GET /inbox     Accept: application/nostr+json  →  inbox relay document
GET /private   Accept: application/nostr+json  →  private relay document

Looking for comments…

Searching Nostr relays. This may take a moment the first time this article is opened.