Feeds and APIs¶
The built-in reading pages are deliberately plain. The durable part is underneath, so you can replace the front end entirely and keep everything else.
Feeds¶
| URL | What |
|---|---|
/blog/<slug>/feed.rss |
One blog, RSS 2.0 |
/blog/<slug>/feed.atom |
One blog, Atom |
/blog/<slug>/feed.json |
One blog, JSON Feed 1.1 |
/feed/blog.rss |
Newest posts across all public blogs |
/blog/feed.json |
The same, as JSON Feed |
Feeds follow the blog's visibility: a public blog's feed is open, a
members-only blog's feed asks for credentials (readers can't sign in, so it
returns a 401 that a reader can retry with an API token), and a private
blog's feed is not there at all. Drafts never appear in any of them.
Blog pages advertise their feeds in the <head>, so a reader app finds them
from the page URL alone.
Reading data: the public JSON API¶
Anonymous, no token, published content only:
GET /blog/api/blogs/
GET /blog/api/blogs/<slug>/posts/?page=2&tag=ops
GET /blog/api/blogs/<slug>/posts/<post-slug>/ # includes the body
GET /blog/api/blogs/<slug>/tags/
This is what to build a custom reader against — a React front end, a static-site generator, another service. Responses are cacheable for a minute.
For response shapes, pagination, visibility rules, CORS and worked client code — and for rendering posts inside this project's own templates — see Showing posts in your own pages.
Writing data: the authenticated REST API¶
GET|POST /smallstack/api/blog/posts/
GET|PUT|PATCH /smallstack/api/blog/posts/<id>/
…and the same for blogs, tags, issues, subscriptions
These need an API token (mint one under API Tokens) and return only what that user can see. They are the write surface — don't point a public reader UI at them; it will get a 401.
Publishing is not a field you can PATCH. Use the MCP tool below, or the console.
AI clients (MCP)¶
Connect Claude to this site and it can list, read, draft and publish:
list_blog_posts,get_blog_post,search_blog_postsblog_draft_post— write a draft from markdown, with tagsblog_publish_post/blog_unpublish_postblog_list_publishable_documents,blog_publish_runbook_document,blog_resync_post
Each tool sees exactly what the token's owner can see.
Sending a newsletter is not available as a tool, and subscriber addresses are not exposed to AI clients at all. Mailing thousands of real people stays a deliberate human action, and a subscriber list is not something to page into a chat client.
Notifying other systems¶
Turn on a webhook endpoint under Webhooks and subscribe to:
| Event | When |
|---|---|
smallstack_blog.post.created / .updated / .deleted |
a post changes |
smallstack_blog.blog.created / .updated |
a blog changes |
smallstack_blog.issue.updated |
an issue's status changes, including sent |
smallstack_blog.subscription.created / .updated |
someone subscribes or leaves |
There is deliberately no per-delivery event: a 5,000-recipient send would emit 10,000 webhooks and drown whatever is listening. Watch the issue instead.