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.