Skip to content

Content blocks

Post bodies are a block document, not a single HTML string. This keeps content portable across themes and safe to render. You send it as the blocks_json field of POST /v1/posts.

{
"v": 1,
"blocks": [
{ "id": "p1", "type": "paragraph", "text": "…" }
]
}
  • v — schema version. Always 1.
  • blocks — an ordered array. Render order = array order.

Every block has:

Field Type Required Notes
id string yes Unique, non-empty within the document (e.g. p1, h1, b_ab12).
type string yes One of the block types below.
text string depends Text/HTML payload (see each type).
attrs object depends Type-specific attributes.
type Carries Renders as
paragraph text (inline HTML) <p>
heading attrs.level (2–6), text <h2>…<h6>
list attrs.style "unordered"/"ordered" + text (one item per line) <ul> / <ol>
code attrs.language, text (verbatim) <pre><code>
quote text <blockquote>
image attrs.src (or attrs.media_id), attrs.alt <figure><img>
divider — <hr>

text may contain inline HTML: <a href>, <strong>, <em>, <code>, <br>. It is sanitized server-side (see below).

{ "id": "p1", "type": "paragraph",
"text": "Read the <a href=\"https://docs.canverly.com\">docs</a> for <strong>more</strong>." }
{ "id": "h1", "type": "heading", "attrs": { "level": 2 }, "text": "Section title" }

Put one item per line in text, separated by \n. Items may contain inline HTML.

{ "id": "l1", "type": "list", "attrs": { "style": "unordered" },
"text": "First item\nSecond item\nThird with a <a href=\"https://x.com\">link</a>" }

text is preserved verbatim (no HTML interpretation). attrs.language drives syntax highlighting.

{ "id": "c1", "type": "code", "attrs": { "language": "python" },
"text": "print('hello')" }

Reference an already-hosted image by URL, or a Canverly media id if you have one.

{ "id": "i1", "type": "image",
"attrs": { "src": "https://cdn.example.com/cover.jpg", "alt": "Cover" } }

Inline HTML inside block text (and list items) is rendered as HTML, then run through an allowlist sanitizer when published through the public API. This means:

  • Kept: formatting tags (a, strong, em, code, b, i, u, s, p, br, ul/ol/li, h1–h6, blockquote, img, pre, span, tables) and http/https/mailto links.
  • Stripped: <script>, event handlers (onerror, onclick, …), javascript: URLs, <iframe>, and anything else not on the allowlist.

So you can safely send rich text, but you cannot inject scripts into a site through an API key. If you need plain text, just send plain text — it is passed through unchanged.

{
"v": 1,
"blocks": [
{ "id": "p1", "type": "paragraph",
"text": "An intro paragraph with a <a href=\"https://canverly.com\">link</a>." },
{ "id": "h1", "type": "heading", "attrs": { "level": 2 }, "text": "Steps" },
{ "id": "l1", "type": "list", "attrs": { "style": "ordered" },
"text": "Get a key\nPOST /v1/posts\nVerify it's live" },
{ "id": "c1", "type": "code", "attrs": { "language": "bash" },
"text": "curl https://api.canverly.com/v1/sites/me -H 'Authorization: Bearer ck_…'" },
{ "id": "q1", "type": "quote", "text": "Publishing should be one API call." }
]
}