You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Blog: read posts in-app and manage the email subscription #87
A public Blog (/blog) of product updates and long-form writing, with an email subscription
using double opt-in and one-click unsubscribe. /help/blog documents post states, writing,
scheduling, publishing and unpublishing — but the authoring endpoints are all under /api/admin/blog/* and are admin-only, so authoring is out of scope for this client.
Unused API surface
POST /api/blog/subscribe ({ email })
GET /api/blog/subscribe/confirm — double opt-in confirmation from the email
GET|POST /api/blog/unsubscribe — human-facing link and the RFC-8058 one-click target
The constraint
There is no public blog-listing endpoint in the OpenAPI spec — the blog is server-rendered.
Reading it in-app therefore means a Custom Tab or a lightweight scrape, not a JSON feed. Decide
which before building.
Filed from the parity review of feature/parity-wip @ 14d7795 against interlinedlist.com (OpenAPI /api/openapi.json, 233 paths) and the public help centre.
What a public blog endpoint would need, so in-app rendering becomes possible
#88 shipped the honest ceiling for today — a themed Custom Tab — because there is no public JSON
endpoint and scraping is off the table. Here is the concrete ask that would lift that ceiling.
The admin surface already models a post: POST /api/admin/blog accepts title, slug, excerpt, content, published, publishedAt, heroImageUrl, heroImageAlt. A public read-only projection of
that same model is all the client needs.
GET /api/blog — published posts, newest first
Query:limit (default 20, max 50) and cursor. Please reuse the opaque base64 keyset nextCursor convention GET /api/messages already uses, so the existing Paging 3 pattern
applies unchanged. If keyset is not available, offset plus pagination { total, offset, limit, hasMore } (the /api/lists shape) is an acceptable fallback.
excerpt must be plain text, not HTML — it goes straight into a Compose Text in a feed row.
Unauthenticated, since the blog is public — but a bearer token must be tolerated, because the
app's OkHttp stack attaches one to every call.
GET /api/blog/{slug} — one post
Slug, not id. That is what the shared web URL carries, so a deep link resolves without an
extra lookup.
The body field is the decisive one.content as raw HTML is the worst case for Android — it
forces a WebView, which defeats the entire point of rendering in-app. Please expose the post's Markdown source (contentMarkdown), or a small structured block array. If only HTML can be
offered, a sanitised whitelist (p, h2-h4, ul/ol/li, a, code, pre, blockquote, img, em, strong)
is workable; arbitrary publisher HTML is not.
404 with the standard { "error", "code" } envelope for an unpublished or unknown slug —
today that case would be indistinguishable from a network error.
Cheaper alternative
GET /api/blog/feed.json (JSON Feed 1.1) would satisfy this client and any third party from a
single endpoint, and costs less to maintain than a bespoke pair.
Until one of those exists, the Custom Tab in #88 is the correct ceiling. #89 (the email
subscription) is unaffected and is being worked separately — those endpoints already exist.
Activity
Adron commentedon Sep 16, 2026
What a public blog endpoint would need, so in-app rendering becomes possible
#88 shipped the honest ceiling for today — a themed Custom Tab — because there is no public JSON
endpoint and scraping is off the table. Here is the concrete ask that would lift that ceiling.
The admin surface already models a post:
POST /api/admin/blogacceptstitle, slug, excerpt, content, published, publishedAt, heroImageUrl, heroImageAlt. A public read-only projection ofthat same model is all the client needs.
GET /api/blog— published posts, newest firstlimit(default 20, max 50) andcursor. Please reuse the opaque base64 keysetnextCursorconventionGET /api/messagesalready uses, so the existing Paging 3 patternapplies unchanged. If keyset is not available,
offsetpluspagination { total, offset, limit, hasMore }(the/api/listsshape) is an acceptable fallback.{ "posts": [ … ], "pagination": { … } }— a named collection key, matchinglists/messages/organizations.id, slug, title, excerpt, publishedAt, updatedAt, heroImageUrl, heroImageAlt, readingMinutes?, tags?[].excerptmust be plain text, not HTML — it goes straight into a ComposeTextin a feed row.app's OkHttp stack attaches one to every call.
GET /api/blog/{slug}— one postextra lookup.
contentas raw HTML is the worst case for Android — itforces a WebView, which defeats the entire point of rendering in-app. Please expose the post's
Markdown source (
contentMarkdown), or a small structured block array. If only HTML can beoffered, a sanitised whitelist (
p, h2-h4, ul/ol/li, a, code, pre, blockquote, img, em, strong)is workable; arbitrary publisher HTML is not.
404with the standard{ "error", "code" }envelope for an unpublished or unknown slug —today that case would be indistinguishable from a network error.
Cheaper alternative
GET /api/blog/feed.json(JSON Feed 1.1) would satisfy this client and any third party from asingle endpoint, and costs less to maintain than a bespoke pair.
Until one of those exists, the Custom Tab in #88 is the correct ceiling. #89 (the email
subscription) is unaffected and is being worked separately — those endpoints already exist.