Files
RenewCMS/docs/api/articles.md
T

6.8 KiB

Reading articles

Every route that returns an article accepts the optional format query parameter and returns the article body in that format. Articles are stored as HTML (Tiptap output). Storage and the request bodies of POST /v1/article/ and PATCH /v1/article/{id} stay HTML.

PATCH /v1/article/{id} takes body and site_id, and nothing else. The request must contain at least one of them, and null for either answers 400. Behaviour change: {} and {"body": null} used to set an empty body and now answer 400; send {"body": ""} to empty a body.

Routes that honour format

  • GET /v1/article/
  • GET /v1/article/{id}
  • POST /v1/article/
  • PATCH /v1/article/{id}
  • PATCH /v1/article/{id}/publish
  • PATCH /v1/article/{id}/unpublish

Visibility is unchanged: anonymous and unverified callers only see online articles, and an offline article answers 404 Article not found.

Pagination, search and counts

GET /v1/article/ always answers one page inside an envelope, with or without query parameters:

{
  "items": [],
  "total": 30,
  "page": 1,
  "size": 25,
  "counts": { "all": 30, "online": 30, "draft": 0 }
}
Parameter Values Default
page integer >= 1 1
size 5, 25, 50 or 100 25
q up to 200 characters, matched anywhere in the title, ignoring case and accents none
status online or draft none
site the key of a site; an unknown key gives an empty page, an empty value is 400 none
sort title, status, created_at or updated_at, then :asc or :desc (lowercase, for example title:asc) updated_at:desc
  • items is [] when empty, never null. Each item is the article object below.
  • Items follow sort (default updated_at:desc) across the whole list before paging. title ignores case, status orders on online state (status:asc puts drafts first), and every ordering ends with id descending so pages never overlap.
  • total is the number of visible articles matching q and status, before paging. page and size echo the values applied.
  • counts ignores q and status, so tab counts do not move while searching. For anonymous and unverified callers all equals online and draft is 0, and status=draft answers items: [] and total: 0.
  • A page beyond the last answers 200 with items: [] and the real total.
  • An invalid page, size, status, sort or an over-long q answers 400 with the plain-text message The request cannot be processed due to a mismatch in the format of the body. An invalid format is reported first.

Breaking change: this route used to answer a bare JSON array. Clients must read the items property.

The format parameter

Value body body_format
empty or absent markdown string markdown
markdown markdown string markdown
html the stored HTML string html
json JSON array of blocks json

Values are case-insensitive. When format is repeated, the first value is used. Any other value answers 400 with the plain-text message The format must be one of: markdown, html, json. before any article is read or written. An invalid id is reported first, ahead of the format.

Breaking change: the default used to be HTML. Clients that need HTML must send ?format=html. The web interface does so.

Article object

{
  "id": 1,
  "title": "Backyard Renovation Update",
  "body": "## Plan\n\nReplace the **old** deck.",
  "body_format": "markdown",
  "is_online": true,
  "site_id": 1,
  "created_at": "2026-03-12T09:14:00Z",
  "updated_at": "2026-09-10T07:00:00Z"
}

Markdown output

Headings, bold, italic, links, bullet and ordered lists, blockquotes, fenced code blocks, rules, images and inline code are converted. Anything else is reduced to its text. script and style content is removed, and the source HTML is never passed through; the only HTML in the output is the alignment wrapper described below. List indentation attributes are dropped. Links with a scheme other than http, https, mailto or tel are reduced to their text. Characters that markdown would read as formatting (*, _, `, [, ], <, \) and line starts that would read as a heading, list, quote or rule (#, -, +, >, 1.) are backslash-escaped, so plain text stays plain text. A line break becomes a backslash followed by a newline.

Text alignment

Markdown has no syntax for alignment, so centered, right-aligned and justified paragraphs and headings are wrapped in a div with an inline style. The blank lines around the content make CommonMark parse the inside as markdown:

<div style="text-align: center">

A **centered** paragraph

</div>

Left alignment is the default and produces no wrapper. Your markdown renderer must allow raw HTML for the alignment to show (html: true in markdown-it); otherwise the wrapper tags appear as text, and a client that does not want them can request ?format=json instead, where the alignment is a field.

JSON block schema

body is a native JSON array. Every block has a type.

Block Fields
heading level (1 to 6), align (only when center, right or justify), children (text nodes)
paragraph align (only when center, right or justify), children (text nodes, empty array for an empty paragraph)
list ordered (boolean), items (array of items, each an array of blocks)
blockquote children (blocks)
code language (empty string when unknown), text
hr none
image src, alt (empty string when missing)

Text node: {"type":"text","text":"...","marks":[...]}. marks is always an array, listed outermost first. A mark is {"type":"bold"}, {"type":"italic"} or {"type":"link","href":"..."}. A line break becomes a "\n" inside the text. Adjacent text with identical marks is merged. Inline code, underline, strike-through and other formatting degrade to plain text. An image inside a paragraph becomes its own block and splits the paragraph. An empty body is [].

Example for <h2>Plan</h2><p>Replace the <strong>old</strong> deck.</p>:

[
  {"type":"heading","level":2,"children":[{"type":"text","text":"Plan","marks":[]}]},
  {"type":"paragraph","children":[
    {"type":"text","text":"Replace the ","marks":[]},
    {"type":"text","text":"old","marks":[{"type":"bold"}]},
    {"type":"text","text":" deck.","marks":[]}
  ]}
]

Errors

Status Body
400 The server expects the ID to be in the format of an unsigned 32-bit integer (uint32). for an invalid id
400 The format must be one of: markdown, html, json. for an unsupported format
404 Article not found
500 Internal server error when the body cannot be converted