WordPress

Publishing a post

Conversion and publishing are deliberately separate. This page shows how to take the markup and create the post yourself.

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

Why this API never touches your site

The conversion endpoint does not ask for your WordPress URL, username or password, and does not connect to your site. You convert here, then publish with your own credentials.

That is a deliberate boundary. Handing write access to your CMS to a third-party service is a large trust decision, and it is not one you should have to make in order to format some text. It also means nothing here can publish, edit or delete anything on your site — there is no capability to abuse.

Getting credentials

WordPress ships with Application Passwords, which are the right tool here: revocable, scoped to one integration, and separate from the account password.

  1. In WordPress, open Users → Profile.
  2. Scroll to Application Passwords, name it after the automation, and create.
  3. Copy the generated password — it is shown once.

Authenticate with HTTP Basic: the username is your WordPress login, the password is the generated one.

Application Passwords require HTTPS on the WordPress site. Over plain HTTP, WordPress disables the feature.

Creating the post

curl
curl -X POST https://your-site.com/wp-json/wp/v2/posts \
  -H "Content-Type: application/json" \
  -u "user:application-password" \
  -d '{
    "title": "My post",
    "content": "<!-- wp:paragraph -->\n<p>Text</p>\n<!-- /wp:paragraph -->",
    "status": "draft"
  }'

The content field takes the gutenberg string from the conversion response, unchanged. Your HTTP client escapes it into the JSON body; nothing else is needed.

FieldNotes
titleRenders as the page H1. This is why the converter demotes an h1 in your content.
contentThe block markup.
statusdraft, publish, pending or private. Start with draft.
featured_mediaAttachment ID. The featured image is not a block inside the content.
categories, tagsArrays of term IDs, not names.
slug, excerpt, authorOptional, standard REST fields.

The full n8n flow

Two HTTP Request nodes: convert, then publish. Import this and replace the API key and the site URL.

n8n workflow
{
  "nodes": [
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.searchops.io/v1/gutenberg/convert",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [{ "name": "x-api-key", "value": "sk_live_YOUR_KEY" }]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ input_format: 'markdown', content: $json.article }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [1900, 400],
      "id": "b7c1f0a2-1d3e-4f5a-9c8b-0e1d2c3b4a59",
      "name": "Markdown to blocks"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://your-site.com/wp-json/wp/v2/posts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ title: $('Markdown to blocks').item.json.title || 'Draft', content: $json.gutenberg, status: 'draft' }) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [2120, 400],
      "id": "c8d2e1b3-2e4f-5a6b-8d9c-1f2e3d4c5b60",
      "name": "Create WordPress draft"
    }
  ],
  "connections": {
    "Markdown to blocks": {
      "main": [[{ "node": "Create WordPress draft", "type": "main", "index": 0 }]]
    }
  },
  "pinData": {}
}

Note that the second node reads $json.gutenberg — the field from the conversion response. Configure the WordPress node with Basic Auth credentials holding your username and application password.

Publish as draft while you are building the flow. Opening one draft in the editor tells you more than any amount of markup inspection — if the blocks are clickable and nothing is flagged, the conversion is right.

Images

Converted content references images by their original URL. WordPress will render them, but they stay hosted wherever they were.

To bring an image into the media library, upload it first and use the returned attachment:

upload media
curl -X POST https://your-site.com/wp-json/wp/v2/media \
  -u "user:application-password" \
  -H "Content-Disposition: attachment; filename=cover.jpg" \
  -H "Content-Type: image/jpeg" \
  --data-binary @cover.jpg

The response includes an id, which you can pass as featured_media when creating the post. Rewriting in-content images to library attachments is not part of this release.

When something looks wrong

SymptomCause
Post is one Classic blockRaw HTML was sent instead of the converted markup. Check that you used the gutenberg field.
Literal ## and ** in the postMarkdown was sent straight to WordPress. It does not parse Markdown — convert first.
“Unexpected or invalid content”The markup was modified after conversion. Even whitespace inside a block’s HTML can break validation. Send it through unchanged.
401 from WordPressApplication Password wrong, or the site is not on HTTPS.
rest_cannot_createThe user lacks permission to publish. Check the role.
Blocks render but styling differsExpected. Appearance comes from the theme, not from the markup.