NIPs by PolleramaCommunity NIPs, surfaced by trustConnect
npub10mtatsat7ph...

Relay Information Document Specification

Published May 2, 2026

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:[email protected]",
  "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.

FieldTypeDescription
namestringHuman-readable name for the relay
descriptionstringDescription of the relay's purpose or audience
pubkeystringRelay operator's public key, lowercase hex-encoded
contactstringContact address for the operator (e.g. mailto: or https:)
iconstringURL to a square image representing the relay
bannerstringURL 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.

FieldTypeDescription
softwarestringURL or identifier for the relay software
versionstringVersion string of the relay software

Rule 4.3: Capability Fields

These fields declare what protocol features the relay supports.

FieldTypeDescription
supported_nipsarray of numbersProtocol 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:

FieldTypeDescription
auth_requiredbooleanWhether authentication is required before any action
payment_requiredbooleanWhether payment is required before any action
restricted_writesbooleanWhether some write condition must be fulfilled

Size and throughput limits:

FieldTypeDescription
max_message_lengthintegerMaximum byte length of any incoming WebSocket message
max_subscriptionsintegerMaximum concurrent subscriptions per connection
max_filtersintegerMaximum filters per subscription request
max_limitintegerMaximum value of the limit filter field
max_subid_lengthintegerMaximum character length of a subscription ID
min_prefixintegerMinimum hex prefix length for ID and pubkey filters
max_event_tagsintegerMaximum number of tags on a single event
max_content_lengthintegerMaximum character length of an event's content field

Proof-of-work:

FieldTypeDescription
min_pow_difficultyintegerMinimum proof-of-work difficulty required on incoming events

Event validity bounds:

FieldTypeDescription
created_at_lower_limitintegerEarliest Unix timestamp accepted in created_at
created_at_upper_limitintegerLatest 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

FieldTypeDescription
relay_countriesarray of stringsISO 3166-1 alpha-2 country codes indicating the relay's jurisdiction or audience
language_tagsarray of stringsBCP-47 language tags for the relay's primary languages, in preference order
tagsarray of stringsArbitrary descriptive labels (e.g. "bitcoin-only", "sfw-only")
posting_policystringURL to a human-readable document describing the relay's posting rules
privacy_policystringURL to the relay's privacy policy
terms_of_servicestringURL to the relay's terms of service

Rule 4.6: Payment Fields

FieldTypeDescription
payments_urlstringURL to a page with payment or subscription details
feesobjectStructured 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:

FieldTypeDescription
amountintegerThe cost
unitstringThe currency denomination (e.g. "msats")
periodintegerFor subscription fees, the billing period in seconds
kindsarray of integersFor 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:

FieldTypeDescription
kindsarrayEvent kinds this rule applies to; entries may be integers or two-element [start, end] range arrays; absent means the rule applies globally
timeinteger or nullSeconds to retain events; null means retain forever; 0 means events of this kind are not stored
countinteger or nullMaximum 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:

InputNormalized
WSS://Relay.Example.COM/wss://relay.example.com
https://relay.example.comwss://relay.example.com
relay.example.comwss://relay.example.com
localhost:8080ws://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