Skip to content

nip-11: add access_control field for gated relay discovery#2318

Open
TheIcarusWings wants to merge 1 commit intonostr-protocol:masterfrom
TheIcarusWings:nip11-access-control
Open

nip-11: add access_control field for gated relay discovery#2318
TheIcarusWings wants to merge 1 commit intonostr-protocol:masterfrom
TheIcarusWings:nip11-access-control

Conversation

@TheIcarusWings
Copy link
Copy Markdown

Adds an optional access_control field to NIP-11 so relays that gate access (subscriptions, token holdings, group membership, allowlists, etc.) can advertise what they gate, and clients can render actionable UI before connecting rather than reacting to opaque restricted: closes after the fact.

Discussed in issue #2311.

Motivation

Today, a client has no pre-connection signal that a relay gates read or write access. It connects, sends a REQ or EVENT, receives a CLOSED or OK: false with a restricted: prefix, and has to reactively surface an error to the user. In mixed-feed scenarios (a timeline including a premium post whose relay the user may or may not be subscribed to, or an naddr pointing at a gated relay) the client cannot render "locked, subscribe" on first paint because it has no way to know the relay is gated or where to send the user to obtain access.

This PR fills that gap with capability advertisement, not policy introspection.

Scope

In scope:

  • A way for relays to declare that read or write is gated, at event-kind granularity
  • A way for clients to discover where a non-member can go to obtain access
  • A standard shape for restricted: <reason> messages so clients can render them uniformly

Explicitly out of scope:

  • Describing how a relay evaluates access (arbitrary policy cannot be meaningfully introspected, per earlier discussion in NIP-XX: Relay Access Control via Authentication Callbacks #2311)
  • Role-based access control for authenticated members (belongs in a future RBAC NIP, sketched by @staab for NIP-43; this proposal composes with it rather than duplicating)

Shape

{
  "access_control": {
    "authentication": "required",
    "permissions": [
      { "action": "read",  "kinds": [30402, 30403] },
      { "action": "write", "kinds": [1, 30023] }
    ],
    "description": "Active subscription required for access to premium content.",
    "info_url": "https://example.com/subscribe"
  }
}
  • authentication: "required" or "optional". Distinct from limitation.auth_required, which only covers connection-level auth.
  • permissions: array of { action, kinds } with action"read" | "write". Empty/omitted kinds means "all kinds for this action." Nomenclature chosen to align with @staab's "read/write policies per event kind" suggestion from NIP-XX: Relay Access Control via Authentication Callbacks #2311 and to read naturally alongside a future NIP-43 RBAC extension.
  • description: human-readable string clients can render.
  • info_url: optional URL where a user can obtain access (subscribe, join, etc.). Clients SHOULD NOT auto-follow.

Plus a standardized denial format:

["CLOSED", "<sub-id>", "restricted: no active subscription"]
["OK", "<event-id>", false, "restricted: write access requires membership"]

Composition with future RBAC

The spec text explicitly positions this as complementary to a future role-based access NIP. In short:

  • access_control: "this relay gates kind X for read, here is where to become a member"
  • Future RBAC NIP: "this authenticated member has role Y, which grants these permissions"

A client rendering a mixed timeline can use both: access_control tells it the relay gates the post's kind; the RBAC event tells it whether this user satisfies the gate. No probing, no try-and-see, right state on first paint for both subscribed and non-subscribed users.

Reference implementation

Live in production on a creator platform:

  • Relay advertising the field: Nostreon/relay-auth
  • The field is served on GET / with Accept: application/nostr+json at wss://premium.nostreon.com

History

This started in #2311 as a broader proposal for a relay-to-backend callback protocol. @staab pushed back that the relay-to-backend direction has no interoperability surface (it's implementation detail), which was correct. The proposal was refocused onto the client-facing discovery + denial-format side, which is an interop surface, and @staab confirmed it probably makes sense to add to NIP-11. This PR is that change.

Adds an optional access_control field so relays that gate access (behind
subscriptions, token holdings, group membership, allowlists, etc.) can
advertise that to clients before connection, and so clients can render
actionable denial states instead of opaque restricted: closes.

The field describes capability advertisement (what is gated, where to
obtain access) and intentionally does not attempt to describe how a
relay evaluates access, which cannot be meaningfully introspected.
Composes with a future RBAC NIP: this advertises what is gated;
the RBAC NIP describes how a member's role maps to permissions.

Discussed in issue nostr-protocol#2311.
TheIcarusWings added a commit to Nostreon/relay-auth that referenced this pull request Apr 15, 2026
Add examples/nip11-example.json with the exact shape proposed in
nostr-protocol/nips#2318, and two Fastify routes to serve it:

- GET /nip11-example: always returns the example document
- GET / with Accept: application/nostr+json: returns the same document,
  matching how a real relay exposes its NIP-11

This makes the repo directly runnable as a reference rather than only
documentary. A reviewer can clone, npm install, npm run dev, and curl
the endpoint to see the exact shape PR #2318 describes.
@TheIcarusWings
Copy link
Copy Markdown
Author

A couple of supporting resources for reviewers and client implementers:

Happy to iterate on any of these if the PR shape changes during review.

@TheIcarusWings
Copy link
Copy Markdown
Author

Gentle bump. No reviews yet after a week, and there's a relevant convergence worth flagging.

NIP-63a (#2315), currently in active review, proposes kind:30164 payment-gateway descriptors. access_control and 30164 compose directly for premium-content clients:

  • access_control lets a client render "locked, subscribe" on first paint when it encounters a gated relay
  • 30164 gives it the payment methods and plans for how to subscribe

Without the first, a client still has to probe the relay and react to a restricted: close before it can show the pay flow, which is exactly the UX problem this PR set out to solve.

@staab, you confirmed the direction in the issue, flagging in case review cycles are available. @fiatjaf, tagging for NIPs triage.

@fiatjaf
Copy link
Copy Markdown
Member

fiatjaf commented Apr 22, 2026

I appreciate your work and interest @TheIcarusWings, but this LLM you're using is producing too much text, gotta shrink that a little bit or give us some more time.

@TheIcarusWings
Copy link
Copy Markdown
Author

Fair point. For what it's worth, the LLM is my typist, (apparently a verbose one). The ideas are in good faith.

Stepping back to give you proper review space. Ping whenever cycles open up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants