<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>mastodon — nikclayton</title>
    <link>https://nikclayton.writeas.com/tag:mastodon</link>
    <description>Mastodon: &lt;a href=&#34;https://mastodon.social/@nikclayton&#34; rel=&#34;me&#34;&gt;https://mastodon.social/@nikclayton&lt;/a&gt;</description>
    <pubDate>Wed, 11 Nov 2026 13:24:41 +0000</pubDate>
    <item>
      <title>Quick notes on Mastodon pagination</title>
      <link>https://nikclayton.writeas.com/quick-notes-on-mastodon-pagination?pk_campaign=rss-feed</link>
      <description>Prompted by the questions in https://hhmx.de/@nick/16376, the answer&#39;s too long to fit in a post. Doing some monitoring how a original #M...</description>
      <content:encoded><![CDATA[<p>Prompted by the questions in <a href="https://hhmx.de/@nick/16376" rel="nofollow ugc">https://hhmx.de/@nick/16376</a>, the answer&#39;s too long to fit in a post.</p>

<blockquote><p>Doing some monitoring how a original <a href="https://nikclayton.writeas.com/tag:Mastodon" class="hashtag" rel="nofollow ugc"><span>#</span><span class="p-category">Mastodon</span></a> server responds, i never (!) saw the described link header! The response may include no link header or a header like this: Link: ; rel=“next”, ; rel=“prev”</p></blockquote>

<p>You should get a <code>Link</code> header from any API response that returns a page of data, like <a href="https://docs.joinmastodon.org/methods/notifications/#get" rel="nofollow ugc">https://docs.joinmastodon.org/methods/notifications/#get</a>.</p>

<p>[Almost; in testing this I just discovered an edge case, which I&#39;ve reported as a bug at <a href="https://github.com/mastodon/mastodon/issues/25495" rel="nofollow ugc">https://github.com/mastodon/mastodon/issues/25495</a>]</p>

<p>An API response for a single item, like <a href="https://docs.joinmastodon.org/methods/notifications/#get-one" rel="nofollow ugc">https://docs.joinmastodon.org/methods/notifications/#get-one</a> won&#39;t include the <code>Link</code> header.</p>

<p>I just tested this with <a href="https://restfox.dev" rel="nofollow ugc">https://restfox.dev</a> against my mastodon.social account (<a href="https://github.com/tuskyapp/Tusky/pull/3610/files?short_path=00c65c2#diff-00c65c2349f395f9f9b78b0fe84d3638b78f3ef4abf98294364b5c4d0ef32473" rel="nofollow ugc">https://github.com/tuskyapp/Tusky/pull/3610/files?short_path=00c65c2#diff-00c65c2349f395f9f9b78b0fe84d3638b78f3ef4abf98294364b5c4d0ef32473</a> has details on how to do this).</p>

<blockquote><p>On my research i saw something in the <a href="https://nikclayton.writeas.com/tag:Tusky" class="hashtag" rel="nofollow ugc"><span>#</span><span class="p-category">Tusky</span></a> source, that looks like Tusky as clients seems to generate a link header? If so, why?</p>

<p><a href="https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSource.kt#L107-L178" rel="nofollow ugc">https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSource.kt#L107-L178</a></p></blockquote>

<p>That function needs to return a page of results as an HTTP <code>Response</code>. The code that calls that function unpacks the <code>Response</code> and parses the contents of the <code>Link</code> header, so the header must be present.</p>

<p>To figure out what page of results to return one of the parameters to <code>getInitialPage</code> is the ID of the item (in this case, a notification) that should be at the start of the page. This is the <code>params.key</code> value.</p>

<p>The Mastodon API lets you make requests like “Get me the page immediately after ID X” or “Get me the page immediately before ID X”.</p>

<p>But it does not let you make a request “Get me the page that includes key X”.</p>

<p>So the code has to do that itself, starting around line 134. It does this by making two API calls. One to retrieve the notification with ID X, and one to retrieve the notifications immediately after that.</p>

<p>If those calls succeed the code has two values to operate on; the notification, and the page of notifications immediately after it.</p>

<p>Remember that this function needs to return a single page of notifications. So it creates a fake page which contains both values. This fake page needs a <code>Link</code> header (because the code that calls this code expects a <code>Link</code> header).</p>

<p>So that&#39;s why it constructs one in this case.</p>

<p>This code is only called to get the first (or initial) page to show the user. After that the pages above and below are loaded with single calls to the Mastodon API, and return the <code>Link</code> header without any alterations.</p>

<blockquote><p>Besides of details, if the header should be named “Link:” or “link:“,</p></blockquote>

<p>HTTP headers are <strong>case-insensitive</strong> (<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" rel="nofollow ugc">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers</a>) so you can use either.</p>

<blockquote><p>Tusky 22 now sometimes reports an array index out of range (-1) or similar (taking this from my memory, for exact message, i have to check it again).</p></blockquote>

<p>That would be a bug, a detailed report with reproduction steps would be appreciated.</p>

<blockquote><p>During development i also saw Tusky sending requests for notifications in an endless loop... wondered why.</p></blockquote>

<p>I suspect the <code>min_id</code> / <code>max_id</code> handling in your server is not correct.</p>

<blockquote><p>And... could someone clarify the difference between since<em>id and min</em>id? If Mastodon will handle this differently?</p>

<p>since<em>id = String. Return results newer than this ID
min</em>id = String. Return results immediately newer than this ID</p>

<p>What should be the difference between “newer” and “immediately newer”?</p></blockquote>

<p>Suppose the user has 10 notifications, numbered 1 through 10.</p>

<pre><code>  1 2 3 4 5 6 7 8 9 10
  ^                 ^
  |                 |
oldest            newest
</code></pre>

<p>They make the request:</p>

<pre><code>/api/v1/notifications?since_id=5&amp;limit=2
</code></pre>

<p>This means they want two (<code>limit=2</code>) notifications that are newer than notification 5 (<code>since_id=5</code>).</p>

<p>The server returns notifications 9 and 10, as these are the two newest notifications.</p>

<p>Think of <code>since_id</code> as “Start at the newest notification, count backwards <code>limit</code> notifications, and return from there”.</p>

<p>Same setup, but the request is now:</p>

<pre><code>/api/v1/notifications?min_id=5&amp;limit=2
</code></pre>

<p>This means they want two (<code>limit=2</code>) notifications that are immediately newer than notification 5 (<code>min_id=5</code>).</p>

<p>The server returns notifications 6 and 7, as these are the two notifications immediately newer than 5.</p>

<p>Think of <code>min_id</code> as “Start at <code>min_id</code>, then return the next <code>limit</code> notifications”.</p>

<p>Given that <code>min_id</code> was added to the API after <code>since_id</code> was my working theory is that this was a design flaw in the API, which <code>min_id</code> fixed after the API was first released.</p>
]]></content:encoded>
      <guid>https://nikclayton.writeas.com/quick-notes-on-mastodon-pagination</guid>
      <pubDate>Sun, 18 Jun 2023 11:30:07 +0000</pubDate>
    </item>
  </channel>
</rss>