12<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-67 - EOSE Completeness Hint — Nostr Instruction Manual</title>7<meta name="description" content="NIP-67: an optional third element on the EOSE message. Relays hint whether the stored result set is complete, partial, or gated behind AUTH.">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 67 - Relay Ops</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="67">67</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 67</div>50 <h1>EOSE Completeness Hint</h1>51 <p class="sub">One extra element on the <code>EOSE</code> message tells the client whether the relay sent everything it has — no more pagination guesswork.</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 extends the <code>EOSE</code> message from <a href="01.html">NIP-01</a>. A relay may append a third element: an array of hint strings. The hints tell the client whether the relay sent every stored event that matches the subscription's filters.</p>60 <p>Plain <code>EOSE</code> marks the boundary between stored events and real-time events. It does not say whether more stored events exist. Relays commonly enforce an internal cap, for example 500 events per filter. This cap is independent of the client's <code>limit</code>. So clients today guess completeness with a heuristic. If fewer than <code>limit</code> events arrive, the client assumes the result is complete. Otherwise the client sends another <code>REQ</code> with <code>until</code> set to the oldest received <code>created_at</code>, and repeats.</p>61 <p>This heuristic fails in two known ways. First, when the relay's internal cap is lower than the client's <code>limit</code> — cap 300, client asks for 500 — the client receives 300 events and wrongly concludes the result is complete. It silently misses data. Second, when the count of matching events equals the relay's cap exactly, the client cannot tell. It must send one more <code>REQ</code> just to observe zero results. A relay that caps at 300 is always queried at least twice for any subscription that exhausts the cap.</p>62 <p>A single extra field on <code>EOSE</code> removes this ambiguity.</p>6364 <div class="box hint">65 <span class="tag">Hint!</span>66 <p>Read the subscription lifecycle in <a href="01.html">NIP-01</a> first. This chapter changes exactly one message in that flow: the <code>EOSE</code> that ends the stored-event phase.</p>67 </div>6869 <h2 class="bar" id="spec"><span class="num">2</span> The Hinted EOSE Message</h2>70 <p>A relay that implements this NIP MAY append a third element to <code>EOSE</code>. The element is an array of hint strings:</p>7172 <div class="crt">73 <div class="titlebar">Wire format - extended EOSE</div>74<pre>[<span class="s">"EOSE"</span>, <span class="c"><subscription_id></span>, [<span class="c"><hint></span>, ...]]</pre>75 </div>7677 <p>The spec currently defines three hint values:</p>78 <div class="tscroll"><table class="ref">79 <caption>Hint values</caption>80 <tr><th>Hint</th><th>Meaning</th><th>Client action</th></tr>81 <tr><td class="btn">finish</td><td>The relay sent every stored event that matches the subscription's filters.</td><td>The client SHOULD NOT paginate this subscription further.</td></tr>82 <tr><td class="btn">more</td><td>The relay holds more matching stored events than it sent. A relay MAY send this hint but is not required to. Detecting whether more events exist is not cheap on every storage backend.</td><td>The client SHOULD paginate to retrieve the rest.</td></tr>83 <tr><td class="btn">auth</td><td>The relay may hold more matching stored events that unlock after <a href="42.html">NIP-42</a> <code>AUTH</code>. The relay MUST send an <code>AUTH</code> message with a challenge before the <code>EOSE</code> that carries this hint.</td><td>Authenticate, then query again.</td></tr>84 </table></div>8586 <p>The array MAY carry multiple hints at the same time. The array MAY be empty. Clients MUST ignore unknown hint values without error. Future revisions of this NIP may define more hint values. Implementations SHOULD accept unknown hints and treat them as if absent.</p>87 <p>The hints describe stored events only. They have no effect on real-time delivery. New events keep streaming under the regular <a href="01.html">NIP-01</a> rules until the client sends <code>CLOSE</code> or the relay sends <code>CLOSED</code>.</p>8889 <div class="box warn">90 <span class="tag">Warning!</span>91 <p>Presence is definitive; absence is not. When the relay omits the third element, or sends neither <code>finish</code> nor <code>more</code>, do not assume anything. Fall back to the legacy heuristic: paginate with <code>until</code> = oldest received <code>created_at</code>.</p>92 </div>9394 <figure class="diagram">95 <div class="frame">96 <svg viewBox="0 0 560 300" role="img" aria-label="Sequence diagram: client sends REQ, relay returns capped stored events and EOSE with a more hint, client paginates with until, relay answers with EOSE finish">97 <line class="d-wire" x1="110" y1="40" x2="110" y2="285" stroke-dasharray="3 4"/>98 <line class="d-wire" x1="450" y1="40" x2="450" y2="285" stroke-dasharray="3 4"/>99 <rect class="d-actor" x="55" y="8" width="110" height="30" rx="6"/>100 <text class="d-actor-label" x="110" y="28" text-anchor="middle">Client</text>101 <rect class="d-actor" x="395" y="8" width="110" height="30" rx="6"/>102 <text class="d-actor-label" x="450" y="28" text-anchor="middle">Relay</text>103 <path class="d-msg" d="M110,62 L445,62"/>104 <text class="d-label" x="280" y="55" text-anchor="middle">["REQ", "s1", {"kinds":[1],"limit":500}]</text>105 <path class="d-msg blue" d="M450,95 L115,95"/>106 <text class="d-label" x="280" y="88" text-anchor="middle">["EVENT", "s1", {…}] × 300 (internal cap)</text>107 <path class="d-msg blue" d="M450,128 L115,128"/>108 <text class="d-label" x="280" y="121" text-anchor="middle">["EOSE", "s1", ["more"]]</text>109 <path class="d-msg" d="M110,170 L445,170"/>110 <text class="d-label" x="280" y="163" text-anchor="middle">["REQ", "s1", {"kinds":[1],"until":<oldest>,"limit":500}]</text>111 <path class="d-msg blue" d="M450,203 L115,203"/>112 <text class="d-label" x="280" y="196" text-anchor="middle">["EVENT", "s1", {…}] × remaining</text>113 <path class="d-msg blue" d="M450,236 L115,236"/>114 <text class="d-label" x="280" y="229" text-anchor="middle">["EOSE", "s1", ["finish"]]</text>115 <text class="d-note" x="280" y="270" text-anchor="middle">The client stops paginating. Real-time delivery continues as normal.</text>116 <circle r="4" fill="#2e5f9e">117 <animateMotion dur="2.6s" repeatCount="indefinite" path="M450,95 L115,95"/>118 </circle>119 </svg>120 </div>121 <figcaption>Fig. 67-1 — <b>Hinted pagination.</b> The relay's cap is below the client's <code>limit</code>. The <code>more</code> hint keeps the client from stopping early; <code>finish</code> ends the loop.</figcaption>122 </figure>123124 <h2 class="bar" id="ties"><span class="num">3</span> Ties on created_at</h2>125 <p>When a relay does not send <code>finish</code>, several events may share the oldest <code>created_at</code> in the response. A client that paginates with <code>until</code> = that timestamp risks missing events with the same timestamp. So relays SHOULD, when possible, advance the internal cursor past the boundary. Include all events with the boundary <code>created_at</code> in one response. Emit <code>finish</code> only when no older events remain.</p>126127 <figure class="diagram">128 <div class="frame">129 <svg viewBox="0 0 560 150" role="img" aria-label="Timeline of stored events; three events share the boundary timestamp and the relay extends the response window to cover all three">130 <line class="d-wire" x1="20" y1="90" x2="540" y2="90"/>131 <text class="d-note" x="20" y="112">older</text>132 <text class="d-note" x="500" y="112">newer</text>133 <rect x="80" y="78" width="14" height="24" rx="3" fill="#7d8794"/>134 <rect x="130" y="78" width="14" height="24" rx="3" fill="#7d8794"/>135 <rect x="190" y="78" width="14" height="24" rx="3" fill="#f26a00"/>136 <rect x="210" y="78" width="14" height="24" rx="3" fill="#f26a00"/>137 <rect x="230" y="78" width="14" height="24" rx="3" fill="#f26a00"/>138 <rect x="300" y="78" width="14" height="24" rx="3" fill="#2e5f9e"/>139 <rect x="360" y="78" width="14" height="24" rx="3" fill="#2e5f9e"/>140 <rect x="430" y="78" width="14" height="24" rx="3" fill="#2e5f9e"/>141 <rect x="490" y="78" width="14" height="24" rx="3" fill="#2e5f9e"/>142 <path class="d-msg" d="M217,64 L217,74"/>143 <text class="d-label" x="217" y="40" text-anchor="middle">3 events, same created_at</text>144 <text class="d-label" x="217" y="56" text-anchor="middle">(boundary)</text>145 <path class="d-msg blue" d="M186,128 L540,128"/>146 <text class="d-note" x="360" y="144" text-anchor="middle">one response covers the whole boundary group</text>147 </svg>148 </div>149 <figcaption>Fig. 67-2 — <b>Boundary group.</b> The relay stretches the response so no event with the boundary timestamp is left behind.</figcaption>150 </figure>151152 <h2 class="bar" id="compat"><span class="num">4</span> Compatibility and Advertisement</h2>153 <p>The extension is backward compatible in both directions. Clients that do not implement this NIP ignore the extra element. Conforming JSON parsers accept arrays with trailing elements, and existing implementations index <code>EOSE</code> by position. Relays that do not implement this NIP keep sending two-element <code>EOSE</code> messages. Clients then fall back to the legacy heuristic from section 1.</p>154 <p>Relays that implement this NIP SHOULD include <code>67</code> in the <code>supported_nips</code> field of their <a href="11.html">NIP-11</a> relay information document.</p>155156 <div class="box tech">157 <span class="tag">Tech Note</span>158 <p>The third example in the source spec has mismatched subscription IDs: the <code>REQ</code> uses <code>"sub2b"</code> while the <code>EVENT</code>, <code>AUTH</code> response, and <code>EOSE</code> use <code>"sub4"</code>. It also reuses <code>"sub2b"</code> from the second example. The transcripts below use one consistent ID per session.</p>159 </div>160161 <h2 class="bar" id="examples"><span class="num">5</span> Worked Examples</h2>162 <p>Three sessions against one relay. Orange lines go to the relay. Blue lines come from the relay.</p>163 <div class="crt">164 <div class="titlebar">Session transcript - wss://relay.example.com</div>165<pre><span class="c">-- 1. The relay sent every matching stored event --</span>166<span class="send">→ ["REQ", "sub2", {"ids": ["abcd…"]}]</span>167<span class="recv">← ["EVENT", "sub2", {"id":"abcd…", "kind":1, "content":"hello", …}]</span>168<span class="recv">← ["EOSE", "sub2", ["finish"]]</span>169<span class="c">-- the client does not paginate further --</span>170171<span class="c">-- 2. The relay holds more matching events --</span>172<span class="send">→ ["REQ", "sub2b", {"authors": ["abcd…"], "kinds": [1]}]</span>173<span class="recv">← ["EVENT", "sub2b", {…}]</span>174<span class="c"> … 300 events total (internal cap) …</span>175<span class="recv">← ["EOSE", "sub2b", ["more"]]</span>176<span class="send">→ ["REQ", "sub2b", {"authors": ["abcd…"], "kinds": [1], "until": 1751234567}]</span>177178<span class="c">-- 3. More events exist behind AUTH; the public part is complete --</span>179<span class="send">→ ["REQ", "sub4", {"authors": ["abcd…"], "kinds": [1]}]</span>180<span class="recv">← ["EVENT", "sub4", {…}]</span>181<span class="recv">← ["AUTH", "challenge…"]</span>182<span class="recv">← ["EOSE", "sub4", ["auth", "finish"]]</span>183<span class="c">-- AUTH arrived before the EOSE, as required --</span></pre>184 </div>185 <p>In session 3 the array carries two hints. <code>finish</code> says the unauthenticated result is complete. <code>auth</code> says authentication may unlock more. The client authenticates per <a href="42.html">NIP-42</a> and queries again.</p>186187 <h2 class="bar" id="history"><span class="num">6</span> Version History</h2>188 <p>NIP-67 is young and small. Notable changes, newest first:</p>189 <ul class="patchnotes">190 <li><span class="ver">2026</span><span class="when">2026-09</span>fiatjaf added the <code>auth</code> hint and the rule that <code>AUTH</code> must precede the hinted <code>EOSE</code> (<a href="https://github.com/nostr-protocol/nips/pull/2371">#2371</a>).</li>191 <li><span class="ver">Origin</span><span class="when">2026-06</span>mattn published the initial spec with the <code>finish</code> and <code>more</code> hints (<a href="https://github.com/nostr-protocol/nips/pull/2317">#2317</a>).</li>192 </ul>193194 <div class="credits">195 <h3 class="cardhead">Original Proposal - Credits</h3>196 <table>197 <tr><td>Author</td><td>mattn</td></tr>198 <tr><td>First commit</td><td>2026-06-06 — “NIP-67: EOSE Completeness Hint” (<a href="https://github.com/nostr-protocol/nips/pull/2317">PR #2317</a>)</td></tr>199 <tr><td>Status</td><td>draft - optional — relays and clients work fine without it</td></tr>200 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/67.md">67.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/67.md">full change history</a></td></tr>201 </table>202 </div>203204 <h2 class="bar"><span class="num">7</span> See Also</h2>205 <ul>206 <li><a href="01.html">NIP-01</a> — defines <code>REQ</code>, filters, and the base <code>EOSE</code> message this NIP extends.</li>207 <li><a href="42.html">NIP-42</a> — the <code>AUTH</code> flow referenced by the <code>auth</code> hint.</li>208 <li><a href="11.html">NIP-11</a> — the relay information document that advertises support via <code>supported_nips</code>.</li>209 </ul>210 </div>211212 <aside>213 <div class="cartridge">214 <div class="inner">215 <h3 class="cardhead">Spec Card</h3>216 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>217 <dl>218 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip">optional</span></dd>219 <dt>Defines</dt><dd>third <code>EOSE</code> element - hints <code>finish</code> / <code>more</code> / <code>auth</code></dd>220 <dt>Kinds</dt><dd>none — message extension only</dd>221 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> - <a href="42.html">NIP-42</a> for <code>auth</code> - <a href="11.html">NIP-11</a> to advertise</dd>222 <dt>Transport</dt><dd>WebSocket - JSON arrays</dd>223 <dt>First commit</dt><dd>2026-06-06</dd>224 <dt>Last updated</dt><dd>2026-09-01</dd>225 </dl>226 </div>227 </div>228 </aside>229 </main>230231 <nav class="navbtns">232 <a href="66.html">◀ NIP-66: Relay Liveness Monitoring</a>233 <a href="68.html">NIP-68: Picture-first feeds ▶</a>234 </nav>235236 <footer class="pagefoot">237 <a href="../index.html">Nostr Protocol Manual</a>238 <span class="pageno">NIP 67 / F4</span>239 </footer>240</div>241242<script src="../assets/webmcp.js" defer></script>243</body>244</html>