Skip to content

Blog: read posts in-app and manage the email subscription #87

Open
2 of 2 issues completed
Open

Description

@Adron

What the web app has

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.

Activity

  1. added
    epicA feature area tracked as a parent issue
    parityClosing a gap against interlinedlist.com
    P3Niche or minor
    on Sep 15, 2026
  2. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    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.
    • Envelope: { "posts": [ … ], "pagination": { … } } — a named collection key, matching
      lists / messages / organizations.
    • Row: id, slug, title, excerpt, publishedAt, updatedAt, heroImageUrl, heroImageAlt, readingMinutes?, tags?[].
    • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Niche or minorarea:blogBlogepicA feature area tracked as a parent issueparityClosing a gap against interlinedlist.com

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions