WordPress

Content → Gutenberg blocks

Send Markdown, HTML or JSON and get native WordPress block markup back — the kind the block editor opens without complaining.

Reading this with an AI agent?

The entire documentation lives in one Markdown file, built to be fetched and read by an LLM. Copy the prompt below and paste it into ChatGPT, Claude, Cursor or any agent with web access — it asks the agent to read the file before helping you.

Open the Markdown

https://api.searchops.io/doc/docxapi.md

The problem this solves

Pushing AI-generated content into WordPress usually goes one of two ways, and both are bad.

Send raw HTML to the content field and WordPress wraps the whole thing in a single Classic block. It renders on the front end, but in the editor it is one opaque lump: no headings to click, no list to reorder, no way for an editor to fix a paragraph without touching HTML.

Send Markdown and it is worse — WordPress does not parse Markdown. You get literal ## and ** characters in the published post.

And hand-writing block markup by hand tends to produce the message every WordPress user recognises:

“This block contains unexpected or invalid content.” The editor compares the saved HTML against what the block says it should look like. One missing class, one attribute out of place, and the block is flagged as broken.

This endpoint produces the markup the editor expects, and verifies it before returning.

One request

curl
curl -X POST https://api.searchops.io/v1/gutenberg/convert \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_live_YOUR_KEY" \
  -d '{
    "input_format": "markdown",
    "content": "## How it works\n\nText with **bold** and a [link](https://example.com).\n\n- one\n- two"
  }'

The response carries the markup plus a report on what happened to your content:

response
{
  "success": true,
  "gutenberg": "<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">How it works</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Text with <strong>bold</strong> and a <a href=\"https://example.com/\">link</a>.</p>\n<!-- /wp:paragraph -->",
  "blocks": ["core/heading", "core/list", "core/list-item", "core/paragraph"],
  "validation": { "syntax": "valid", "round_trip": "valid", "target": "not_checked" },
  "warnings": [],
  "losses": [],
  "media": [],
  "serializer": { "profile": "portable-article", "catalog": "2026-08-04", "api": "1.0.0" },
  "generation_time_ms": 34
}

Put gutenberg straight into the content field of the WordPress REST API. Nothing else to transform.

What the markup looks like

Gutenberg stores blocks as HTML wrapped in special comments. That is what makes each element editable in the editor rather than one frozen lump:

block markup
<!-- wp:heading -->
<h2 class="wp-block-heading">How it works</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Text with <strong>bold</strong> and a <a href="https://example.com/">link</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul class="wp-block-list">
<!-- wp:list-item -->
<li>one</li>
<!-- /wp:list-item -->
</ul>
<!-- /wp:list -->

The comment delimiters carry the block name and its attributes; the HTML between them is what the block saves. Both have to match what the block expects — classes like wp-block-heading are part of the validation, not decoration.

Blocks produced

The default profile is portable-article: only blocks that ship with WordPress and are available on virtually every install, so nothing depends on the theme or on plugins.

Your contentBlock
Paragraphs, bold, italic, inline code, linkscore/paragraph
Headingscore/heading
Lists, including nested and orderedcore/list + core/list-item
Block quotescore/quote
Fenced codecore/code
Preformatted textcore/preformatted
Tablescore/table
Images by URLcore/image
Horizontal rulescore/separator
Collapsible sectionscore/details
HTML with no mappingcore/html

Images are referenced by their original URL. Uploading them to the site’s media library, which is what gives you a wp-image-123 class and a real attachment, is not part of this release — see Publishing a post.

Three ways in

The default, and the best fit for LLM output.

request
{
  "input_format": "markdown",
  "content": "## Title\n\nText with **bold**."
}

Nothing is dropped silently

When something in your content cannot survive the conversion, the response says so. Every entry carries a stable code you can branch on, and the path that points at the offending node.

warnings and losses
{
  "warnings": [
    {
      "code": "HEADING_DEMOTED",
      "severity": "warning",
      "path": "/children/0",
      "message": "H1 demoted to H2: the post title already provides the page H1.",
      "action": "degraded"
    }
  ],
  "losses": [
    {
      "code": "SANITIZE_UNSAFE_URL",
      "severity": "loss",
      "path": "/children/3",
      "message": "Link target uses a protocol that is not allowed and was removed.",
      "action": "dropped"
    }
  ]
}

warnings means the content came through in a different shape. losses means something did not come through at all. A conversion can succeed with both — check them if fidelity matters to you.

The H1 rule

By default an h1 in your content is demoted to h2, with a warning. In WordPress the post title already renders as the page H1, so a second one hurts both SEO and screen-reader navigation. Change it with options.h1_policy: demote (default), keep or remove.

Validation before you get the markup

Every conversion is parsed back and re-serialised before the response is sent. If the result does not survive that round trip, you get a 422 instead of markup — and the request is not charged.

The reasoning is simple: broken block markup does not fail loudly. It publishes fine and only surfaces when a human opens the editor, which may be days later. An error at request time is cheaper than that.

Inspecting existing markup

POST /v1/gutenberg/parse takes block markup and returns its structure. Useful when a post is already broken and you need to find where. It does not consume credits.

curl
curl -X POST https://api.searchops.io/v1/gutenberg/parse \
  -H "Content-Type: application/json" -H "x-api-key: sk_live_YOUR_KEY" \
  -d '{"content":"<!-- wp:paragraph -->\n<p>x</p>\n<!-- /wp:paragraph -->"}'

Safety

  • Link protocols are filtered. Only http, https, mailto, tel and relative URLs survive. A javascript: target is removed and the link text is kept, with a SANITIZE_UNSAFE_URL loss.
  • Your text cannot become markup. Content that happens to contain block delimiters is escaped, so it appears as text in the post instead of restructuring it.
  • Scripts, iframes and inline handlers are removed from HTML input, along with their content.