clone

every nostr nip as a chapter of an illustrated, early-2000s printed game manual

main
nip.guide / site / nips / 29.html
29.html 48.0K
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-29 - Relay-based Groups — Nostr Instruction Manual</title>7<meta name="description" content="NIP-29: relay-based groups. Group IDs, the h tag, moderation events 9000-9020, join and leave requests, relay metadata kinds 39000-39005, LiveKit AV spaces, and subgroups.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 29 - Groups</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="29">29</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 29</div>50 <h1>Relay-based Groups</h1>51 <p class="sub">Closed groups that live on a relay: the relay enforces membership, admins moderate with events, and the whole group state is rebuilt from the moderation log.</p>52 </div>53 </div>5455 <main class="spread">56 <div class="main">5758 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>59 <p>This NIP defines groups that only a closed set of users can write to. A group can be public for reading by external users, or private. The rules are not enforced by cryptography. They are enforced by a <strong>relay</strong>: the relay accepts or rejects each event based on the group's membership and settings.</p>60 <p>A group is identified by a random string of any length. This string is the group <strong>id</strong>. There is no protocol-level act of group creation. A relay, most likely when asked by users, creates rules around a specific id. From then on, events sent to that id are subject to those rules, and the id serves as an actual group.</p>61 <p>Members publish ordinary events into the group. A tag named <code>h</code> carries the group id. Admins publish <strong>moderation events</strong> (kinds 9000 to 9020) to add users, remove users, and change settings. The relay itself publishes <strong>metadata events</strong> (kinds 39000 to 39005) that describe the current group state. Users ask to join with kind 9021 and to leave with kind 9022.</p>62 <p>Normally a group belongs to one specific relay. The community may move the group to another relay. It may even fork the group, so the same id exists in different forms across different relays.</p>6364 <div class="box hint">65 <span class="tag">Hint!</span>66 <p>Coming from <a href="28.html">NIP-28</a> public chat channels? Think of NIP-29 as the managed upgrade. Channels have no gatekeeper; here the relay is the gatekeeper, and the admins hold the keys.</p>67 </div>6869 <div class="box tech">70 <span class="tag">Tech Note</span>71 <p>The source spec states "there is no way to create a group", yet the moderation table defines kind 9007 as <code>create-group</code>. Read the first statement as: no <em>protocol object</em> creates a group; a relay decides to honor an id, and kind 9007 is one way to ask it to.</p>72 </div>7374 <h2 class="bar" id="spec"><span class="num">2</span> Group Identity, Migration, and Forks</h2>75 <p>Two things hold a group together: the group id, and the relay that currently enforces access rules over that id on behalf of one or more owners or admins. Enforcement is tied to one relay. The group is only as durable as that relay's willingness and ability to keep hosting it.</p>7677 <h3>Moving a group to another relay</h3>78 <p>The hosting relay can become unresponsive, go offline permanently, or become untrustworthy. Then the group can migrate. Copy the existing events to a different relay. The new relay must agree to receive the old group, to continue enforcing the same rules, and to acknowledge the same admins.</p>7980 <h3>Forking a group</h3>81 <p>Members of a group can disagree. A dissident faction can take the group's history to a different relay that agrees to <em>change</em> the rules and assign admin powers to a different set of users. This is a group <strong>fork</strong>. The new relay keeps the same id but enforces different moderation policies and a different admin roster.</p>82 <p>Forks are not bugs. It is a feature of this NIP that one id can identify different communities at the same time. Each fork lives on its own relay, with its own <code>kind:39000</code> metadata event, its own <code>kind:39001</code> admin list, and its own message history.</p>8384 <figure class="diagram">85 <div class="frame">86 <svg viewBox="0 0 560 210" role="img" aria-label="One group id hosted on two relays: the original relay with admin set A, and a fork relay with admin set B, each with its own metadata and history">87 <rect class="d-actor" x="225" y="10" width="110" height="40" rx="8"/>88 <text class="d-actor-label" x="280" y="35" text-anchor="middle">id: "pizza"</text>89 <rect class="d-actor" x="30" y="100" width="220" height="90" rx="8"/>90 <text class="d-actor-label" x="140" y="125" text-anchor="middle">Relay A (original)</text>91 <text class="d-note" x="140" y="145" text-anchor="middle">39000 metadata - 39001 admins: X, Y</text>92 <text class="d-note" x="140" y="162" text-anchor="middle">history: events 1..n</text>93 <text class="d-note" x="140" y="179" text-anchor="middle">rules: closed, private</text>94 <rect class="d-actor" x="310" y="100" width="220" height="90" rx="8"/>95 <text class="d-actor-label" x="420" y="125" text-anchor="middle">Relay B (fork)</text>96 <text class="d-note" x="420" y="145" text-anchor="middle">39000 metadata' - 39001 admins: Z</text>97 <text class="d-note" x="420" y="162" text-anchor="middle">history: events 1..k + new</text>98 <text class="d-note" x="420" y="179" text-anchor="middle">rules: open reads</text>99 <path class="d-msg" d="M255,52 C220,70 180,80 150,97"/>100 <path class="d-msg d-flow" d="M305,52 C340,70 380,80 410,97"/>101 <text class="d-label" x="480" y="75" text-anchor="middle">fork / migrate</text>102 <circle r="4" fill="#f26a00">103 <animateMotion dur="2.6s" repeatCount="indefinite" path="M305,52 C340,70 380,80 410,97"/>104 </circle>105 </svg>106 </div>107 <figcaption>Fig. 29-1 — <b>Same id, two communities.</b> A fork copies the history to a new relay, then diverges in rules, admins, and events.</figcaption>108 </figure>109110 <div class="box tech">111 <span class="tag">Tech Note</span>112 <p>The fork section of the source opens with "a situation in which it might be desirable to move a relay to a different group". The wording is reversed; the intent, per the rest of the section, is moving a <em>group</em> to a different <em>relay</em>.</p>113 </div>114115 <h3>Detecting migrations and forks</h3>116 <p>Clients SHOULD periodically check for migrations and forks. They MUST check when their primary relay for a group is offline or unreachable. To check, read the <code>kind:10009</code> group-list events of the group's admins and of trusted friends. Cache the admin pubkeys of your groups locally, so the check works while the original relay is down.</p>117 <p>A <code>kind:10009</code> entry can point to a different relay hint than the one the client was using. Then the client SHOULD notify the user that the group may have moved or forked. It SHOULD offer to fetch the group's events from the new relay. If the user agrees, it SHOULD migrate the user's participation and update the user's own <code>kind:10009</code>.</p>118119 <h3>Replicating a group for resilience</h3>120 <p>A group that must not lose history SHOULD keep a live backup replica of all its events on a second relay. The replica relay does not need to enforce any moderation rules. It exists only to preserve history. If the primary relay disappears, the community picks a new authoritative relay and copies the historical events over from the replica.</p>121122 <h2 class="bar"><span class="num">3</span> Referencing a Group</h2>123 <p>Relays generate the events that describe group metadata and group admins. These are <em>addressable</em> events (see <a href="01.html">NIP-01</a>) signed directly by the relay keypair, with the group id as the <code>d</code> tag.</p>124 <p>Identify a group with a <a href="19.html">NIP-19</a> <code>naddr1...</code> code to its <code>kind:39000</code> metadata event. Set the public key to the relay's <code>self</code> public key, the <code>d</code> identifier to the group id, the kind to <code>39000</code>, and add a relay hint to the hosting relay.</p>125 <p>An invite code may be appended to the group identifier:</p>126 <div class="crt">127 <div class="titlebar">Group identifier with invite code</div>128<pre>naddr1...?invite=<span class="c">&lt;code&gt;</span></pre>129 </div>130 <p>Clients should place this code in the <code>code</code> tag of the <code>kind:9021</code> join request. The bech32 charset does not include <code>?</code>. So everything before the <code>?</code> is still a valid identifier on its own. Clients that do not understand the suffix can ignore it.</p>131132 <h2 class="bar"><span class="num">4</span> The h Tag and Timeline References</h2>133 <p>Every event a user sends to a group — chat messages, text notes, moderation events, and so on — MUST carry an <code>h</code> tag with the value set to the group id.</p>134 <div class="tscroll"><table class="ref">135 <caption>The two group tags</caption>136 <tr><th>Tag</th><th>Used in</th><th>Meaning</th></tr>137 <tr><td class="btn">h</td><td>user-created events, kinds 9000-9022</td><td>Group id. Marks the event as sent <em>to</em> the group.</td></tr>138 <tr><td class="btn">d</td><td>relay-generated kinds 39000-39005</td><td>Group id as the addressable-event identifier. Marks the event as <em>about</em> the group.</td></tr>139 </table></div>140141 <h3>The previous tag</h3>142 <p>Group events may reference previous events seen on the same relay, in a <code>previous</code> tag. This stops events from being reused out of context. The client picks which previous events to cite. Each reference is the first 8 characters (4 bytes) of the id of any event among the last 50 events the user saw on the relay, excluding the user's own events. Zero or more references are allowed. Clients are recommended to include at least 3, and relays are recommended to enforce that.</p>143 <div class="crt">144 <div class="titlebar">Example - previous tag</div>145<pre>[<span class="s">"previous"</span>, <span class="s">"eb96c864"</span>, <span class="s">"2db75638"</span>, <span class="s">"b5d1065f"</span>]</pre>146 </div>147 <p>This is a hack. It prevents messages from being broadcast to external relays that host out-of-context forks of the group. Relays are expected to reject any event whose timeline references are not found in their own database. Clients should also check the references, to keep relays honest.</p>148149 <h3>Late publication</h3>150 <p>Relays should prevent late publication: messages published now with a timestamp from hours or days ago. The one exception is a relay that is open to receive a group forked or moved from another relay.</p>151152 <h2 class="bar"><span class="num">5</span> Roles and Group Management</h2>153 <p>A group can have any number of users with elevated access. Role labels identify these users. Relays define the labels arbitrarily (see <code>kind:39003</code> in §8). This NIP does not define what each role can do. That is relay policy, and it can vary.</p>154 <p>A user with the right capability assigns roles by publishing a <code>kind:9000</code> event. Put the target user's pubkey in a <code>p</code> tag, and the roles after it. This works even when the target is already a member: issue the <code>kind:9000</code> again and the relay must simply update the user's roles.</p>155 <p>The roles that carry a real privilege should be listed in the relay's <code>kind:39003</code> event. The relay may also accept other role names, arbitrarily defined by clients, and just do nothing with them. Any user whose roles carry any privilege counts as an <strong>admin</strong> in the broad sense, and may be listed in the group's <code>kind:39001</code> event.</p>156157 <h2 class="bar"><span class="num">6</span> User Events: Join and Leave</h2>158 <p>A group accepts normal user-created events of any kind: chats, threads, long-form articles, calendar events, livestreams, market announcements, and so on. Each keeps the format from its own NIP, plus the <code>h</code> tag. Two special kinds let a user manage their own membership. Both require the <code>h</code> tag.</p>159160 <h3>Join request (kind 9021)</h3>161 <p>Any user can send a <code>kind:9021</code> event to the relay to request admission. The relay MUST reject the request while the user has not been added to the group. The error message SHOULD say whether the rejection is final, whether the request is pending review, or whether special handling applies (for example, payment is required). If the user is already a member, the relay MUST reject the event with <code>duplicate: </code> as the error message prefix.</p>162 <div class="crt">163 <div class="titlebar">Wire format - kind 9021 join request</div>164<pre>{165 <span class="k">"kind"</span>: 9021,166 <span class="k">"content"</span>: <span class="s">"optional reason"</span>,167 <span class="k">"tags"</span>: [168 [<span class="s">"h"</span>, <span class="c">&lt;group-id&gt;</span>],169 [<span class="s">"code"</span>, <span class="c">&lt;optional-invite-code&gt;</span>]170 ]171}</pre>172 </div>173 <p>The optional <code>code</code> tag lets the relay preauthorize acceptance. It pairs with the <code>kind:9009</code> <code>create-invite</code> moderation event.</p>174175 <h3>Leave request (kind 9022)</h3>176 <p>Any user can send a <code>kind:9022</code> event to be removed from the group automatically. The relay answers by issuing a <code>kind:9001</code> event that removes the user.</p>177 <div class="crt">178 <div class="titlebar">Wire format - kind 9022 leave request</div>179<pre>{180 <span class="k">"kind"</span>: 9022,181 <span class="k">"content"</span>: <span class="s">"optional reason"</span>,182 <span class="k">"tags"</span>: [183 [<span class="s">"h"</span>, <span class="c">&lt;group-id&gt;</span>]184 ]185}</pre>186 </div>187188 <figure class="diagram">189 <div class="frame">190 <svg viewBox="0 0 560 300" role="img" aria-label="Sequence diagram: user sends a kind 9021 join request, the relay accepts, emits a kind 9000 put-user event and regenerates the kind 39002 member list, then the user posts into the group">191 <line class="d-wire" x1="110" y1="40" x2="110" y2="285" stroke-dasharray="3 4"/>192 <line class="d-wire" x1="450" y1="40" x2="450" y2="285" stroke-dasharray="3 4"/>193 <rect class="d-actor" x="55" y="8" width="110" height="30" rx="6"/>194 <text class="d-actor-label" x="110" y="28" text-anchor="middle">User</text>195 <rect class="d-actor" x="395" y="8" width="110" height="30" rx="6"/>196 <text class="d-actor-label" x="450" y="28" text-anchor="middle">Group relay</text>197 <path class="d-msg" d="M110,62 L445,62"/>198 <text class="d-label" x="280" y="55" text-anchor="middle">kind 9021 ["h","pizza"] ["code","XYZ"]</text>199 <path class="d-msg blue" d="M450,95 L115,95"/>200 <text class="d-label" x="280" y="88" text-anchor="middle">["OK", ..., true, ""]</text>201 <path class="d-msg blue" d="M450,130 L115,130"/>202 <text class="d-label" x="280" y="123" text-anchor="middle">kind 9000 put-user (relay key)</text>203 <path class="d-msg blue" d="M450,165 L115,165"/>204 <text class="d-label" x="280" y="158" text-anchor="middle">kind 39002 member list regenerated</text>205 <path class="d-msg d-flow" d="M110,210 L445,210"/>206 <text class="d-label" x="280" y="203" text-anchor="middle">kind 9 chat ["h","pizza"] ["previous", ...]</text>207 <text class="d-note" x="280" y="250" text-anchor="middle">Without the invite code, the relay may defer to an admin instead.</text>208 <circle r="4" fill="#f26a00">209 <animateMotion dur="2.6s" repeatCount="indefinite" path="M110,62 L445,62"/>210 </circle>211 </svg>212 </div>213 <figcaption>Fig. 29-2 — <b>Join lifecycle.</b> The user asks; the relay admits with <code>kind:9000</code> and mirrors the new state in <code>kind:39002</code>.</figcaption>214 </figure>215216 <h2 class="bar"><span class="num">7</span> Moderation Events (Kinds 9000-9020)</h2>217 <p>Moderation events change group state. They are optional. The relay master key or a group admin sends them, and relays should reject them when the author is not an authorized admin. They require the <code>h</code> tag. The relay must check that the sending pubkey can perform the given action, based on its role and the relay's internal policy (see <code>kind:39003</code>).</p>218 <div class="crt">219 <div class="titlebar">Wire format - moderation event shape</div>220<pre>{221 <span class="k">"kind"</span>: 90xx,222 <span class="k">"content"</span>: <span class="s">"optional reason"</span>,223 <span class="k">"tags"</span>: [224 [<span class="s">"h"</span>, <span class="c">&lt;group-id&gt;</span>],225 [<span class="s">"previous"</span>, <span class="c">...</span>]226 ]227}</pre>228 </div>229 <p>Each action uses a different kind. Each takes its arguments as tags:</p>230 <div class="tscroll"><table class="ref">231 <caption>Moderation actions</caption>232 <tr><th>Kind</th><th>Name</th><th>Argument tags</th></tr>233 <tr><td class="btn">9000</td><td><code>put-user</code></td><td><code>p</code> with pubkey hex, then optional roles. Adds a member or updates roles.</td></tr>234 <tr><td class="btn">9001</td><td><code>remove-user</code></td><td><code>p</code> with pubkey hex.</td></tr>235 <tr><td class="btn">9002</td><td><code>edit-metadata</code></td><td>All the fields of the group-metadata event (<code>kind:39000</code>).</td></tr>236 <tr><td class="btn">9005</td><td><code>delete-event</code></td><td><code>e</code> with event id hex.</td></tr>237 <tr><td class="btn">9007</td><td><code>create-group</code></td><td>none.</td></tr>238 <tr><td class="btn">9008</td><td><code>delete-group</code></td><td>none.</td></tr>239 <tr><td class="btn">9009</td><td><code>create-invite</code></td><td>arbitrary <code>code</code>.</td></tr>240 <tr><td class="btn">9010</td><td><code>update-pin-list</code></td><td>Zero or more <code>e</code> with event id hex or <code>a</code> with event address.</td></tr>241 </table></div>242 <p>The group state must be fully reconstructable from the canonical sequence of these events: who is a member, who is an admin and with which permission, the group name and picture, and so on.</p>243 <div class="box tech">244 <span class="tag">Tech Note</span>245 <p>The range is stated as kinds 9000-9020, but only eight kinds are defined. Kinds 9003, 9004, 9006, and 9011-9020 are reserved gaps with no meaning; 9006 (<code>edit-group-status</code>) existed historically and was removed in 2024. Do not assign these numbers new meanings inside groups.</p>246 </div>247248 <h2 class="bar"><span class="num">8</span> Relay-Generated Metadata (Kinds 39000-39005)</h2>249 <p>These events carry the group id in a <code>d</code> tag, not an <code>h</code> tag. Only the relay master key MUST create them — the key that the <a href="11.html">NIP-11</a> document states as the <code>"self"</code> pubkey. For each group, a single instance of each kind (or none) should exist at all times. The events are informative only, but they should reflect the latest group state as changed by the moderation events over time.</p>250251 <h3>Group metadata (kind 39000, optional)</h3>252 <p>Defines how clients should display the group. The relay where the event is found must generate and sign it. Relays should not accept a <code>kind:39000</code> signed by anyone else. If the group is forked across relays, each relay carries its own version of this event.</p>253 <div class="crt">254 <div class="titlebar">Wire format - kind 39000 group metadata</div>255<pre>{256 <span class="k">"kind"</span>: 39000,257 <span class="k">"content"</span>: <span class="s">""</span>,258 <span class="k">"tags"</span>: [259 [<span class="s">"d"</span>, <span class="c">&lt;group-id&gt;</span>],260 [<span class="s">"name"</span>, <span class="s">"Pizza Lovers"</span>],261 [<span class="s">"picture"</span>, <span class="s">"https://pizza.com/pizza.png"</span>],262 [<span class="s">"banner"</span>, <span class="s">"https://pizza.com/banner.png"</span>],263 [<span class="s">"about"</span>, <span class="s">"a group for people who love pizza"</span>],264 [<span class="s">"private"</span>],265 [<span class="s">"closed"</span>],266 [<span class="s">"supported_kinds"</span>, <span class="s">"9"</span>, <span class="s">"11"</span>]267 ]268 <span class="c">// other fields...</span>269}</pre>270 </div>271 <div class="tscroll"><table class="ref">272 <caption>Group metadata tags</caption>273 <tr><th>Tag</th><th>Meaning when present</th><th>Meaning when omitted</th></tr>274 <tr><td class="btn">name</td><td colspan="2">Display name of the group.</td></tr>275 <tr><td class="btn">picture</td><td colspan="2">Group picture URL.</td></tr>276 <tr><td class="btn">banner</td><td colspan="2">Group banner image URL.</td></tr>277 <tr><td class="btn">about</td><td colspan="2">Short group description.</td></tr>278 <tr><td class="btn">private</td><td>Only members can <em>read</em> group messages.</td><td>Anyone can read group messages.</td></tr>279 <tr><td class="btn">restricted</td><td>Only members can <em>write</em> messages to the group.</td><td>Anyone can send messages to the group.</td></tr>280 <tr><td class="btn">hidden</td><td>Relays should hide group metadata from non-members.</td><td>Anyone can request the group metadata events.</td></tr>281 <tr><td class="btn">closed</td><td>Join requests are ignored.</td><td>Users can expect join requests to be honored.</td></tr>282 <tr><td class="btn">livekit</td><td>The group supports LiveKit-powered media rooms (live audio/video). See §9.</td><td>No AV support advertised.</td></tr>283 <tr><td class="btn">supported_kinds</td><td>List of stringified kind numbers the group supports. An empty list means no kinds are supported (an AV-only group, for example).</td><td>All kinds are assumed supported.</td></tr>284 <tr><td class="btn">parent</td><td>The group is a subgroup of the referenced group. See §10.</td><td>The group is a root group.</td></tr>285 <tr><td class="btn">child</td><td>Ordered list entries naming the group's subgroups. See §10.</td><td>The group has no subgroups.</td></tr>286 </table></div>287288 <h3>Group admins (kind 39001, optional)</h3>289 <p>Lists each admin along with one or more roles. The roles SHOULD correspond to the roles the relay supports, as advertised by the <code>kind:39003</code> event.</p>290 <div class="crt">291 <div class="titlebar">Wire format - kind 39001 group admins</div>292<pre>{293 <span class="k">"kind"</span>: 39001,294 <span class="k">"content"</span>: <span class="s">"list of admins for the pizza lovers group"</span>,295 <span class="k">"tags"</span>: [296 [<span class="s">"d"</span>, <span class="c">&lt;group-id&gt;</span>],297 [<span class="s">"p"</span>, <span class="c">&lt;pubkey1-as-hex&gt;</span>, <span class="s">"ceo"</span>],298 [<span class="s">"p"</span>, <span class="c">&lt;pubkey2-as-hex&gt;</span>, <span class="s">"secretary"</span>, <span class="s">"gardener"</span>]299 <span class="c">// other pubkeys...</span>300 ]301}</pre>302 </div>303304 <h3>Group members (kind 39002, optional)</h3>305 <p>A list of member pubkeys, each in a <code>p</code> tag. Relays may choose not to publish this information, to restrict which pubkeys can fetch it, or to display only a subset of the members. Clients should not assume this event is always present, or that it contains the full member list.</p>306 <div class="crt">307 <div class="titlebar">Wire format - kind 39002 group members</div>308<pre>{309 <span class="k">"kind"</span>: 39002,310 <span class="k">"content"</span>: <span class="s">"list of members for the pizza lovers group"</span>,311 <span class="k">"tags"</span>: [312 [<span class="s">"d"</span>, <span class="c">&lt;group-id&gt;</span>],313 [<span class="s">"p"</span>, <span class="c">&lt;admin1&gt;</span>],314 [<span class="s">"p"</span>, <span class="c">&lt;member-pubkey1&gt;</span>],315 [<span class="s">"p"</span>, <span class="c">&lt;member-pubkey2&gt;</span>]316 <span class="c">// other pubkeys...</span>317 ]318}</pre>319 </div>320321 <h3>Group roles (kind 39003, optional)</h3>322 <p>The relay MAY publish this event to tell users and clients which roles it supports in its internal logic. Each role is a <code>role</code> tag with a role name and an optional description.</p>323 <p>Example: a relay supports the roles <code>"admin"</code> and <code>"moderator"</code>. The <code>"admin"</code> can edit group metadata, delete messages, and remove users. The <code>"moderator"</code> can only delete messages. The relay could just as well call these roles <code>"ceo"</code> and <code>"secretary"</code>; the exact name is not relevant. How the relay decides its roles and how it handles moderation events based on them is specific to each relay and not specified here.</p>324 <div class="crt">325 <div class="titlebar">Wire format - kind 39003 group roles</div>326<pre>{327 <span class="k">"kind"</span>: 39003,328 <span class="k">"content"</span>: <span class="s">"list of roles supported by this group"</span>,329 <span class="k">"tags"</span>: [330 [<span class="s">"d"</span>, <span class="c">&lt;group-id&gt;</span>],331 [<span class="s">"role"</span>, <span class="c">&lt;role-name&gt;</span>, <span class="c">&lt;optional-description&gt;</span>],332 [<span class="s">"role"</span>, <span class="c">&lt;role-name&gt;</span>, <span class="c">&lt;optional-description&gt;</span>]333 <span class="c">// other roles...</span>334 ]335}</pre>336 </div>337338 <h3>LiveKit participants (kind 39004, optional)</h3>339 <p>The relay MAY publish this event to say who is live in an AV room. The relay should update it whenever a user joins or leaves. Clients are expected to keep an active subscription to it. The event MUST contain zero or more <code>participant</code> tags, each with a participant's public key as lowercase hex.</p>340 <div class="crt">341 <div class="titlebar">Wire format - kind 39004 livekit participants</div>342<pre>{343 <span class="k">"kind"</span>: 39004,344 <span class="k">"content"</span>: <span class="s">""</span>,345 <span class="k">"tags"</span>: [346 [<span class="s">"d"</span>, <span class="c">&lt;group-id&gt;</span>],347 [<span class="s">"participant"</span>, <span class="c">&lt;pubkey&gt;</span>]348 <span class="c">// other participants...</span>349 ]350}</pre>351 </div>352353 <h3>Group pinned events (kind 39005, optional)</h3>354 <p>A list of events pinned in the group, in display order. The <code>kind:9010</code> moderation event carries the full list: <code>e</code> tags for regular events and <code>a</code> tags for addressable events. Pinning, unpinning, reordering, and clearing pins all work the same way — submit a new full list. When the relay accepts a <code>kind:9010</code>, it regenerates this event to mirror it. The relay may limit the number of pins. Clients may display the pinned events at the top of the group view.</p>355 <div class="crt">356 <div class="titlebar">Wire format - kind 39005 pinned events</div>357<pre>{358 <span class="k">"kind"</span>: 39005,359 <span class="k">"content"</span>: <span class="s">""</span>,360 <span class="k">"tags"</span>: [361 [<span class="s">"d"</span>, <span class="c">&lt;group-id&gt;</span>],362 [<span class="s">"e"</span>, <span class="c">&lt;event-id1-as-hex&gt;</span>],363 [<span class="s">"a"</span>, <span class="c">&lt;kind&gt;:&lt;pubkey&gt;:&lt;d-identifier&gt;</span>],364 [<span class="s">"e"</span>, <span class="c">&lt;event-id2-as-hex&gt;</span>]365 <span class="c">// other event references...</span>366 ]367}</pre>368 </div>369370 <h2 class="bar"><span class="num">9</span> Live Audio/Video Spaces</h2>371 <p>A group may support live audio and/or video chat through a LiveKit server. It signals this with the <code>livekit</code> tag in the group's <code>kind:39000</code> announcement event. A group that supports only live AV chat, and no text messages, should use an empty <code>supported_kinds</code> tag. This allows both Discord-style setups (independent AV rooms and text rooms) and Telegram-style setups (one room with both text and AV).</p>372 <p>To join the AV space after seeing a <code>livekit</code> tag, the client calls the token endpoint at the relay path <code>/.well-known/nip29/livekit/&lt;group-id&gt;</code>. The call carries a <a href="98.html">NIP-98</a>-style <code>Authorization</code> header. The relay returns the LiveKit JWT and the URL of the LiveKit server. The client then proceeds with the standard LiveKit flow.</p>373 <p>The <code>Authorization</code> event is a <code>kind:27235</code> event. It must contain the tag <code>["u", "https://relay.tld/.well-known/nip29/livekit/&lt;group-id&gt;"]</code>.</p>374 <ul>375 <li>Relays should enforce access control at JWT creation time, according to the group settings.</li>376 <li>Relays MUST set the JWT <code>sub</code> property (called "identity" in standard LiveKit libraries) so that its first 64 characters are the user's lowercase hex public key. One user can hold several JWTs and join the same room several times, so relays are expected to append a random identifier after those 64 characters.</li>377 <li>To advertise AV support (so clients can offer the option when users create or edit groups), a relay should serve HTTP status <code>204</code> at <code>https://relay.tld/.well-known/nip29/livekit</code>.</li>378 </ul>379380 <h2 class="bar"><span class="num">10</span> Subgroups</h2>381 <p>Groups MAY form a hierarchy. A subgroup is an ordinary NIP-29 group: same <code>kind:39000</code>, same moderation events, same <code>h</code> tag mechanics. The only difference: its metadata carries a <code>parent</code> tag that points at the parent group's <code>d</code> identifier. A group without a <code>parent</code> tag is a root group.</p>382 <div class="crt">383 <div class="titlebar">Example - a three-level tree in kind 39000 events</div>384<pre><span class="c">// root group</span>385{ <span class="k">"kind"</span>: 39000, <span class="k">"tags"</span>: [[<span class="s">"d"</span>, <span class="s">"tech"</span>], [<span class="s">"name"</span>, <span class="s">"Tech"</span>], [<span class="s">"child"</span>, <span class="s">"nostr"</span>]] }386387<span class="c">// subgroup</span>388{ <span class="k">"kind"</span>: 39000, <span class="k">"tags"</span>: [[<span class="s">"d"</span>, <span class="s">"nostr"</span>], [<span class="s">"name"</span>, <span class="s">"Nostr"</span>], [<span class="s">"parent"</span>, <span class="s">"tech"</span>], [<span class="s">"child"</span>, <span class="s">"nip29"</span>]] }389390<span class="c">// sub-subgroup</span>391{ <span class="k">"kind"</span>: 39000, <span class="k">"tags"</span>: [[<span class="s">"d"</span>, <span class="s">"nip29"</span>], [<span class="s">"name"</span>, <span class="s">"NIP-29"</span>], [<span class="s">"parent"</span>, <span class="s">"nostr"</span>]] }</pre>392 </div>393 <p>To build the tree, fetch <code>{"kinds":[39000]}</code> and assemble locally. Events without a <code>parent</code> tag are roots. Group the rest under the <code>d</code> value their <code>parent</code> tag references. The tree is scoped to a single relay. When aggregating across relays, a client MAY see conflicting <code>parent</code> values for the same <code>d</code>. Then the <code>kind:39000</code> from the relay currently serving the group's events is authoritative.</p>394395 <figure class="diagram">396 <div class="frame">397 <svg viewBox="0 0 560 200" role="img" aria-label="Subgroup tree: root group tech has child nostr, which has child nip29; each node is a full NIP-29 group with its own membership">398 <rect class="d-actor" x="30" y="20" width="150" height="44" rx="8"/>399 <text class="d-actor-label" x="105" y="41" text-anchor="middle">d: "tech"</text>400 <text class="d-note" x="105" y="57" text-anchor="middle">root (no parent)</text>401 <rect class="d-actor" x="205" y="78" width="150" height="44" rx="8"/>402 <text class="d-actor-label" x="280" y="99" text-anchor="middle">d: "nostr"</text>403 <text class="d-note" x="280" y="115" text-anchor="middle">parent: tech</text>404 <rect class="d-actor" x="380" y="136" width="150" height="44" rx="8"/>405 <text class="d-actor-label" x="455" y="157" text-anchor="middle">d: "nip29"</text>406 <text class="d-note" x="455" y="173" text-anchor="middle">parent: nostr</text>407 <path class="d-msg" d="M182,52 C210,60 220,72 235,75"/>408 <path class="d-msg" d="M357,110 C385,118 395,130 410,133"/>409 <text class="d-label" x="240" y="45" text-anchor="middle">child #1</text>410 <text class="d-label" x="415" y="103" text-anchor="middle">child #1</text>411 <text class="d-note" x="105" y="150" text-anchor="middle">membership and admin</text>412 <text class="d-note" x="105" y="166" text-anchor="middle">roles do NOT cascade</text>413 </svg>414 </div>415 <figcaption>Fig. 29-3 — <b>Subgroup tree.</b> Each node is a normal group. Only the <code>parent</code> and <code>child</code> tags link them.</figcaption>416 </figure>417418 <h3>Relay support detection</h3>419 <p>A relay that supports subgroups SHOULD advertise it in its <a href="11.html">NIP-11</a> relay information document, under a <code>nip29</code> object:</p>420 <div class="crt">421 <div class="titlebar">NIP-11 fragment</div>422<pre>{423 <span class="k">"supported_nips"</span>: [29],424 <span class="k">"nip29"</span>: { <span class="k">"subgroups"</span>: true }425}</pre>426 </div>427428 <h3>Subgroup rules</h3>429 <ul>430 <li>Parenting, re-parenting, and promotion to root all go through a <code>kind:9002</code> (<code>edit-metadata</code>) event from an authorized admin. Include the desired <code>parent</code> value, or omit the <code>parent</code> tag to detach. The relay then updates the group's <code>kind:39000</code>.</li>431 <li>The relay also updates the parent's <code>kind:39000</code> to include the new child as a <code>child</code> tag, and vice-versa. When a group switches parents, the relay updates the previous parent as well.</li>432 <li>Historical events under the group's <code>h</code> tag stay valid across these transitions.</li>433 <li>A <code>kind:9002</code> MAY carry at most one <code>parent</code> tag. The resulting <code>kind:39000</code> therefore also carries at most one <code>parent</code> tag.</li>434 <li>Absence of the <code>parent</code> tag makes the group a root.</li>435 <li>Relays MUST reject a <code>kind:9002</code> whose <code>parent</code> value would create a cycle, including self-reference.</li>436 <li>If the declared parent does not exist on the relay, the relay MUST reject the <code>kind:9002</code>.</li>437 <li>When a parent is deleted with <code>kind:9008</code>, its remaining children automatically become roots.</li>438 <li>Relays MUST reject any <code>kind:9002</code> carrying <code>["parent", "X"]</code> unless the event author is also an admin of <code>X</code>, per <code>X</code>'s <code>kind:39001</code>.</li>439 <li>A parent's <code>kind:39000</code> MUST include <code>["child", "&lt;id&gt;"]</code> tags as an ordered list of its children. The position of each tag in the array is the order. By default the relay appends new children as they are created. The parent's admin can rearrange the list with a <code>kind:9002</code> carrying the desired <code>child</code> tags in order; each edit replaces the previous list. Relays MUST reject a <code>kind:9002</code> metadata edit that does not include all the children as <code>child</code> tags.</li>440 </ul>441442 <div class="crt">443 <div class="titlebar">Lifecycle example - re-parenting and promotion</div>444<pre><span class="c">// "social" adopts "nostr" (was under "tech")</span>445<span class="send">→ { "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"], ["parent", "social"]] }</span>446<span class="c">// Relay emits:</span>447<span class="recv">← { "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "social"]] }</span>448<span class="recv">← { "kind": 39000, "tags": [["d", "social"], ["child", "nostr"]] }</span>449450<span class="c">// Admin promotes "nostr" to root: edit-metadata without a parent tag</span>451<span class="send">→ { "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"]] }</span>452<span class="c">// Relay emits:</span>453<span class="recv">← { "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"]] }</span>454<span class="recv">← { "kind": 39000, "tags": [["d", "social"]] }</span></pre>455 </div>456457 <h3>Subgroup membership</h3>458 <p>Each subgroup has its own independent membership. Users join through <code>kind:9021</code> and <code>kind:9000</code>, like any other group. Nothing is inherited across the parent-child link. Membership does not cascade: parent membership grants no membership in a child. Each group's member list is defined only by its own <code>kind:9000</code> and <code>kind:9001</code> events. Admin roles do not inherit either. Each group's <code>kind:39001</code> is authoritative for its own scope. A parent admin has no authority over a child unless the child's own <code>kind:39001</code> lists the same pubkey.</p>459 <p>This spec defines no automatic propagation of membership between parent and child. A relay that wants such behavior MAY emit the corresponding <code>kind:9000</code>/<code>kind:9001</code> moderation events itself. Clients MUST NOT assume it: no <code>kind:9000</code> in a child means the user is not a member of that child, regardless of parent membership.</p>460461 <h2 class="bar"><span class="num">11</span> Implementation Notes</h2>462 <h3>Checking your own membership</h3>463 <p>Look at the latest of either <code>kind:9000</code> or <code>kind:9001</code> events for your pubkey in a group. The latest one tells you whether you are currently a member or were removed. If neither exists, assume you are not a member.</p>464 <h3>Adding yourself to a group</h3>465 <p>Anyone can send a <code>kind:9021</code> join request. The relay may generate a <code>kind:9000</code> immediately, or defer the decision to an administrator. If a group is <code>closed</code>, join requests are not honored unless they include an invite code.</p>466 <h3>Storing your list of groups</h3>467 <p><a href="51.html">NIP-51</a> defines <code>kind:10009</code>: a list where clients store the groups a user wants to remember being in. It is also the anchor for fork and migration detection (§2).</p>468469 <h2 class="bar" id="examples"><span class="num">12</span> Worked Example</h2>470 <p>Alice joins the group <code>pizza</code> on <code>wss://groups.example.com</code> with an invite code, posts a chat message, and later leaves. Orange lines go to the relay. Blue lines come from the relay.</p>471 <div class="crt">472 <div class="titlebar">Session transcript - wss://groups.example.com</div>473<pre><span class="c">-- Alice requests to join with the invite code from naddr1...?invite=BEACH --</span>474<span class="send">→ ["EVENT", {"kind":9021,"pubkey":"a1ec3f77…","content":"pineapple tolerated?",475 "tags":[["h","pizza"],["code","BEACH"]], …}]</span>476<span class="recv">← ["OK", "8c41d0aa…", true, ""]</span>477478<span class="c">-- the relay admits her and mirrors the new state --</span>479<span class="recv">← ["EVENT", "sub1", {"kind":9000,"pubkey":"&lt;relay-self-key&gt;",480 "tags":[["h","pizza"],["p","a1ec3f77…","member"],["previous","eb96c864","2db75638","b5d1065f"]], …}]</span>481<span class="recv">← ["EVENT", "sub1", {"kind":39002,"pubkey":"&lt;relay-self-key&gt;",482 "tags":[["d","pizza"],["p","f7a2bd4c…"],["p","a1ec3f77…"]], …}]</span>483484<span class="c">-- Alice posts a chat message with timeline references --</span>485<span class="send">→ ["EVENT", {"kind":9,"content":"margherita night!",486 "tags":[["h","pizza"],["previous","2db75638","b5d1065f","8c41d0aa"]], …}]</span>487<span class="recv">← ["OK", "77b0c2e1…", true, ""]</span>488489<span class="c">-- a second join attempt is rejected --</span>490<span class="send">→ ["EVENT", {"kind":9021,"tags":[["h","pizza"]], …}]</span>491<span class="recv">← ["OK", "91aa07f3…", false, "duplicate: already a member"]</span>492493<span class="c">-- Alice leaves; the relay auto-issues the removal --</span>494<span class="send">→ ["EVENT", {"kind":9022,"content":"diet","tags":[["h","pizza"]], …}]</span>495<span class="recv">← ["OK", "cd80413b…", true, ""]</span>496<span class="recv">← ["EVENT", "sub1", {"kind":9001,"pubkey":"&lt;relay-self-key&gt;",497 "tags":[["h","pizza"],["p","a1ec3f77…"]], …}]</span></pre>498 </div>499500 <h2 class="bar" id="history"><span class="num">13</span> Version History</h2>501 <p>NIP-29 has been reworked heavily since it landed. Notable changes, newest first:</p>502 <ul class="patchnotes">503 <li><span class="ver">2026</span><span class="when">2026-08</span>Added the <code>previous</code> tag example and clarified group migration, fork detection via <code>kind:10009</code>, and replica relays.</li>504 <li><span class="ver">2026</span><span class="when">2026-07</span>Big feature wave: subgroups with <code>parent</code>/<code>child</code> tags, message pinning (<code>kind:9010</code> + <code>kind:39005</code>, with <code>a</code> tags), the <code>?invite=</code> identifier suffix, and the <code>banner</code> metadata tag.</li>505 <li><span class="ver">2026</span><span class="when">2026-05</span>Added LiveKit audio/video spaces: the <code>livekit</code> tag, the token endpoint, JWT rules, and <code>kind:39004</code> participants.</li>506 <li><span class="ver">2026</span><span class="when">2026-01</span>Deleted the "unmanaged groups" concept, clarified the relay <code>self</code> key, and dropped a poorly-thought group identifier recommendation.</li>507 <li><span class="ver">2025</span><span class="when">2025-12</span>Clarified <code>closed</code> vs <code>private</code>, added the <code>hidden</code> metadata tag.</li>508 <li><span class="ver">2024</span><span class="when">2024-11</span>Chat and thread kinds were broken out into their own NIPs; <code>kind:9000</code> was renamed <code>put-user</code>; duplicate joins now get a <code>duplicate: </code> rejection.</li>509 <li><span class="ver">2024</span><span class="when">2024-09</span>Fine-grained permissions were reworked into relay-defined roles; invites simplified into the <code>code</code> tag with <code>kind:9009</code>.</li>510 <li><span class="ver">2024</span><span class="when">2024-07</span>Added <code>kind:9007</code> create-group, <code>kind:9008</code> delete-group, and <code>kind:9022</code> leave requests.</li>511 <li><span class="ver">2024</span><span class="when">2024-02</span>fiatjaf landed "Simple Groups" as NIP-29 (PR #566), replacing an earlier draft in this slot.</li>512 <li><span class="ver">Origin</span><span class="when">2023-11</span>Jon Staab created the file at this number for an image-metadata draft; that content later moved to <a href="92.html">NIP-92</a>.</li>513 </ul>514515 <div class="credits">516 <h3 class="cardhead">Original Proposal - Credits</h3>517 <table>518 <tr><td>Author</td><td>fiatjaf — <a href="https://github.com/nostr-protocol/nips/pull/566">PR #566</a>, "NIP-29: Simple Groups" (2024-02-28)</td></tr>519 <tr><td>First commit</td><td>2023-11-27 — "Add imeta tag" (Jon Staab; the slot then held an unrelated draft that moved to <a href="92.html">NIP-92</a>)</td></tr>520 <tr><td>Status</td><td>draft - optional - relay feature — the relay does the enforcement</td></tr>521 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/29.md">29.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/29.md">full change history</a></td></tr>522 </table>523 </div>524525 <h2 class="bar"><span class="num">14</span> See Also</h2>526 <ul>527 <li><a href="01.html">NIP-01</a> — the event format, addressable events, and the <code>OK</code> rejection prefixes used here.</li>528 <li><a href="11.html">NIP-11</a> — the relay information document; source of the relay <code>self</code> pubkey and the <code>nip29</code> subgroups flag.</li>529 <li><a href="19.html">NIP-19</a> — the <code>naddr</code> encoding used to reference a group.</li>530 <li><a href="51.html">NIP-51</a> — defines <code>kind:10009</code>, the user's list of groups.</li>531 <li><a href="98.html">NIP-98</a> — HTTP auth with <code>kind:27235</code>, used for the LiveKit token endpoint.</li>532 <li><a href="28.html">NIP-28</a> — the older, unmanaged public chat channels this NIP supersedes in practice.</li>533 </ul>534 </div>535536 <aside>537 <div class="cartridge">538 <div class="inner">539 <h3 class="cardhead">Spec Card</h3>540 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>541 <dl>542 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip">optional</span><span class="chip">relay</span></dd>543 <dt>Defines</dt><dd>h/previous/code tags - moderation kinds - relay metadata kinds - subgroups - AV spaces</dd>544 <dt>Kinds</dt><dd>9000-9020 - 9021 - 9022 - 39000-39005</dd>545 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> - <a href="11.html">NIP-11</a> - <a href="51.html">NIP-51</a> - <a href="98.html">NIP-98</a> (AV)</dd>546 <dt>Transport</dt><dd>WebSocket + HTTP well-known (AV)</dd>547 <dt>Enforcement</dt><dd>relay-side, via the relay master key</dd>548 <dt>First commit</dt><dd>2023-11-27</dd>549 <dt>Last updated</dt><dd>2026-08-08</dd>550 </dl>551 </div>552 </div>553 </aside>554 </main>555556 <nav class="navbtns">557 <a href="28.html">◀ NIP-28: Public Chat</a>558 <a href="30.html">NIP-30: Custom Emoji ▶</a>559 </nav>560561 <footer class="pagefoot">562 <a href="../index.html">Nostr Protocol Manual</a>563 <span class="pageno">NIP 29 / F4</span>564 </footer>565</div>566567<script src="../assets/webmcp.js" defer></script>568</body>569</html>