Documentation
API reference
Read published posts, taxonomy, feeds, and sitemap data without an API key.
Every active hosted Folder Blog exposes a read-only public API on its blog hostname.
No API key or authorization header is required. Responses are cached at the edge
for five minutes and API routes allow cross-origin GET, HEAD, and OPTIONS
requests.
Use https://yourname.folder.blog below as your base URL. Only published posts
appear in public responses.
Blog information
GET /api
{
"name": "Field Notes",
"description": "Notes from outside",
"subdomain": "field-notes",
"features": { "darkMode": true },
"api": {
"version": "1.0",
"endpoints": {
"posts": { "list": "GET /api/posts", "get": "GET /api/posts/:slug" },
"tags": "GET /api/tags",
"categories": "GET /api/categories",
"feed": { "rss": "GET /api/feed.xml", "json": "GET /api/feed.json" },
"sitemap": "GET /api/sitemap.xml"
}
}
}
List posts
GET /api/posts?page=1&limit=20
page defaults to 1; limit defaults to 20 and is capped at 100. Add a
tag, category, or featured=true|false query parameter to filter results.
{
"posts": [
{
"id": "post_123",
"slug": "hello-world",
"title": "Hello World",
"description": "My first post.",
"coverImage": "/media/abc123.png",
"authorName": "Ada Lovelace",
"tags": ["intro"],
"publishedAt": "2026-08-08T13:00:00.000Z",
"readingTime": { "minutes": 2, "words": 340 },
"featured": false
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1,
"hasMore": false
}
}
Filtered list responses have the same posts envelope and add categories to
each post, but their pagination object currently omits total and totalPages.
Get one post
GET /api/posts/hello-world
{
"id": "post_123",
"slug": "hello-world",
"title": "Hello World",
"description": "My first post.",
"content": "<p>Full rendered HTML.</p>",
"coverImage": null,
"authorName": "Ada Lovelace",
"authorImage": null,
"tags": ["intro"],
"categories": ["journal"],
"publishedAt": "2026-08-08T13:00:00.000Z",
"readingTime": { "minutes": 2, "words": 340 },
"featured": false
}
Unknown slugs return 404 with { "error": "Post not found" }.
Tags and categories
GET /api/tags
GET /api/categories
{ "tags": ["fieldwork", "intro"] }
{ "categories": ["journal"] }
Feeds and sitemap
GET /api/feed.xml
GET /api/feed.json
GET /api/sitemap.xml
The RSS and JSON feeds contain up to 50 recent published posts. The sitemap
contains the homepage and up to 1,000 published posts; it returns 404 when the
blog's sitemap setting is disabled.
JavaScript and TypeScript SDK
Install the official package:
npm install folderblog
import { folderBlog } from "folderblog/client";
const blog = folderBlog("yourname.folder.blog");
const posts = await blog.posts.list();
const post = await blog.posts.get("hello-world");
const site = await blog.site.get();
const tags = await blog.tags.list();
Use folderblog/client in browser and edge bundles so the hosted client does
not pull in the Node-oriented Markdown processor. Existing Node applications
can continue importing folderBlog from the root folderblog entry.
See the folderblog SDK README
for client, processor, middleware, and CLI usage. The hosted API is read-only;
publishing still happens through GitHub.
Self-hosted exports use a separate contract
The Node export server returns full ProcessedPost records and also supports
pagination, search, taxonomy counts, related articles, and health checks. Use
the matching client:
import { createRemoteClient } from "folderblog/remote";
const blog = createRemoteClient("https://content.example.com");
const { posts, pagination } = await blog.posts.list({ limit: 20 });
const related = await blog.posts.related(posts[0].slug);
Do not use folderBlog() for this endpoint: that client describes the hosted
folder.blog response model. See Self-hosting FolderBlog for
the complete server/consumer architecture and endpoint table.