<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>mastodon &amp;mdash; 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>Mon, 25 May 2026 03:10:10 +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>&lt;![CDATA[Prompted by the questions in https://hhmx.de/@nick/16376, the answer&#39;s too long to fit in a post.&#xA;&#xA;  Doing some monitoring how a original #Mastodon server responds, i never (!) saw the described link header! The response may include no link header or a header like this: Link: ; rel=&#34;next&#34;, ; rel=&#34;prev&#34;&#xA;&#xA;You should get a Link header from any API response that returns a page of data, like https://docs.joinmastodon.org/methods/notifications/#get.&#xA;&#xA;[Almost; in testing this I just discovered an edge case, which I&#39;ve reported as a bug at https://github.com/mastodon/mastodon/issues/25495]&#xA;&#xA;An API response for a single item, like https://docs.joinmastodon.org/methods/notifications/#get-one won&#39;t include the Link header.&#xA;&#xA;I just tested this with https://restfox.dev against my mastodon.social account (https://github.com/tuskyapp/Tusky/pull/3610/files?shortpath=00c65c2#diff-00c65c2349f395f9f9b78b0fe84d3638b78f3ef4abf98294364b5c4d0ef32473 has details on how to do this).&#xA;&#xA;  On my research i saw something in the #Tusky source, that looks like Tusky as clients seems to generate a link header? If so, why?&#xA;    https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsPagingSource.kt#L107-L178&#xA;&#xA;That function needs to return a page of results as an HTTP Response. The code that calls that function unpacks the Response and parses the contents of the Link header, so the header must be present.&#xA;&#xA;To figure out what page of results to return one of the parameters to getInitialPage is the ID of the item (in this case, a notification) that should be at the start of the page. This is the params.key value.&#xA;&#xA;The Mastodon API lets you make requests like &#34;Get me the page immediately after ID X&#34; or &#34;Get me the page immediately before ID X&#34;.&#xA;&#xA;But it does not let you make a request &#34;Get me the page that includes key X&#34;.&#xA;&#xA;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.&#xA;&#xA;If those calls succeed the code has two values to operate on; the notification, and the page of notifications immediately after it.&#xA;&#xA;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 Link header (because the code that calls this code expects a Link header).&#xA;&#xA;So that&#39;s why it constructs one in this case.&#xA;&#xA;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 Link header without any alterations.&#xA;&#xA;  Besides of details, if the header should be named &#34;Link:&#34; or &#34;link:&#34;,&#xA;&#xA;HTTP headers are case-insensitive (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) so you can use either.&#xA;&#xA;  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).&#xA;&#xA;That would be a bug, a detailed report with reproduction steps would be appreciated.&#xA;&#xA;  During development i also saw Tusky sending requests for notifications in an endless loop... wondered why.&#xA;&#xA;I suspect the minid / maxid handling in your server is not correct.&#xA;&#xA;  And... could someone clarify the difference between sinceid and minid? If Mastodon will handle this differently?&#xA;    sinceid = String. Return results newer than this ID&#xA;  minid = String. Return results immediately newer than this ID&#xA;    What should be the difference between &#34;newer&#34; and &#34;immediately newer&#34;?&#xA;&#xA;Suppose the user has 10 notifications, numbered 1 through 10.&#xA;&#xA;  1 2 3 4 5 6 7 8 9 10&#xA;  ^                 ^&#xA;  |                 |&#xA;oldest            newest&#xA;&#xA;They make the request:&#xA;&#xA;/api/v1/notifications?sinceid=5&amp;limit=2&#xA;&#xA;This means they want two (limit=2) notifications that are newer than notification 5 (sinceid=5).&#xA;&#xA;The server returns notifications 9 and 10, as these are the two newest notifications.&#xA;&#xA;Think of sinceid as &#34;Start at the newest notification, count backwards limit notifications, and return from there&#34;.&#xA;&#xA;Same setup, but the request is now:&#xA;&#xA;/api/v1/notifications?minid=5&amp;limit=2&#xA;&#xA;This means they want two (limit=2) notifications that are immediately newer than notification 5 (minid=5).&#xA;&#xA;The server returns notifications 6 and 7, as these are the two notifications immediately newer than 5.&#xA;&#xA;Think of minid as &#34;Start at minid, then return the next limit notifications&#34;.&#xA;&#xA;Given that minid was added to the API after sinceid was my working theory is that this was a design flaw in the API, which min_id fixed after the API was first released.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Prompted by the questions in <a href="https://hhmx.de/@nick/16376" rel="nofollow">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"><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">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">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">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">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">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"><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">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">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>