# 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. ## 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`. ## 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 ```json { "id": 1, "title": "Backyard Renovation Update", "body": "## Plan\n\nReplace the **old** deck.", "body_format": "markdown", "is_online": true, "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: ```markdown
A **centered** paragraph
``` 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 `

Plan

Replace the old deck.

`: ```json [ {"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 |