API

Content API

One authenticated GET endpoint that answers in four modes: a single article, a paginated category feed, the full sitemap, or nothing. Everything a blog front end needs to render published content.


This is the endpoint your website calls. It is read-only, it returns published content only, and it does exactly four things depending on which query parameters you send.

GET/api/external/articles

Read published articles from one project. The project is the one the API key belongs to -- there is no project parameter.

Auth Project API key, read (level 2) or read + write (level 3)

Parameters

handlestringURL handle of one article, for example guides/getting-started. Returns the full document.
article_idstringMongoDB ObjectId of one article. Alternative to handle.
categorystringCategory slug for a paginated feed, or all for every category. Requires lang, page and limit.
langstringLanguage code, matched exactly as the project stores it -- usually uppercase, for example EN. Required with category. Max 3 characters.
pageintegerZero-indexed page number. Required with category.
limitintegerItems per page, 1 to 30. Required with category.
sitemapbooleantrue returns every published handle with its dates, and ignores every other parameter.
private_keystringDeprecated way to pass the API key. Use the Authorization header.

Responses

  • 200The article, the feed, or the sitemap. Shape depends on the mode.
  • 400A category feed was requested without page, limit or lang.
  • 401Missing or unknown API key.
  • 403The key exists but its permission level does not allow reading.
  • 404No published article matches the handle or article_id.

One mode per request

The parameters are evaluated in order: sitemap, then category, then article_id, then handle. Mixing them wastes a query and returns only the last one that matched. Sending none of them returns an empty array.

Mode 1: one article#

The workhorse. Give it the handle from your route and it returns the whole document, markdown body included, plus any structured metadata attached to the article and currently in effect.

curl -H "Authorization: Bearer $REBLOG_API_KEY" \
  "https://reblog.so/api/external/articles?handle=guides/getting-started"

The response is the article document. Every field is described on the article object page.

Mode 2: a category feed#

Paginated, newest first, one language at a time. Pass category=all to list everything in that language instead of one section.

First page of ten guides in English
curl -H "Authorization: Bearer $REBLOG_API_KEY" \
  "https://reblog.so/api/external/articles?category=guides&lang=EN&page=0&limit=10"
Response
{
  "articles": [
    {
      "_id": "66f3c1a2b4d5e6f708192a3b",
      "title": "Getting started with Reblog",
      "handle": "guides/getting-started",
      "lang": "EN",
      "status": "published",
      "date": "2026-02-11T09:00:00.000Z",
      "cover": "https://cdn.example.com/covers/getting-started.png",
      "meta_description": "How to publish your first article.",
      "categories": ["guides"]
    }
  ],
  "has_more": true,
  "page": 0,
  "limit": 10
}

`has_more`, not a total

The feed fetches one extra row to decide whether another page exists, rather than counting the whole collection on every request. There is no total field: paginate until has_more is false.

`lang` is matched exactly

Project languages are stored with a fixed casing, almost always uppercase. lang=EN works, lang=en returns an empty list rather than an error. Use the casing shown in the project's language settings.

Mode 3: the sitemap#

Every published article across every language, projected down to the three fields a sitemap needs. Cheap enough to call on a schedule.

bash
curl -H "Authorization: Bearer $REBLOG_API_KEY" \
  "https://reblog.so/api/external/articles?sitemap=true"
Response
{
  "articles": [
    {
      "handle": "guides/getting-started",
      "date": "2026-02-11T09:00:00.000Z",
      "edit_date": "2026-03-02T14:22:10.000Z"
    }
  ]
}

Use edit_date for <lastmod> when it is present, and fall back to date.

Drafts and scheduled posts never appear#

Every mode applies the same two conditions: the article's status must be published, and its publication date must already have passed. An article scheduled for next Tuesday is invisible to this endpoint until next Tuesday, so you can safely serve the response from a cache without leaking an embargo.

Caching#

The endpoint sends no cache headers -- caching is yours to decide, because only you know how fast your blog moves. Two patterns work well:

  • Incremental static regeneration -- fetch with next: { revalidate: 3600 } in a Next.js server component, and let the framework rebuild pages in the background.
  • Build-time fetch -- pull the sitemap, then each article, and ship static HTML. Re-run the build when you publish.

Never call this from the browser

The request carries a key with project-wide read access. Fetch it on the server, in a route handler, or at build time, and send your visitors the rendered result.