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.
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.
- In WordPress, open Users → Profile.
- Scroll to Application Passwords, name it after the automation, and create.
- 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 -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.
| Field | Notes |
|---|---|
title | Renders as the page H1. This is why the converter demotes an h1 in your content. |
content | The block markup. |
status | draft, publish, pending or private. Start with draft. |
featured_media | Attachment ID. The featured image is not a block inside the content. |
categories, tags | Arrays of term IDs, not names. |
slug, excerpt, author | Optional, 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.
{
"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:
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
| Symptom | Cause |
|---|---|
| Post is one Classic block | Raw HTML was sent instead of the converted markup. Check that you used the gutenberg field. |
Literal ## and ** in the post | Markdown 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 WordPress | Application Password wrong, or the site is not on HTTPS. |
rest_cannot_create | The user lacks permission to publish. Check the role. |
| Blocks render but styling differs | Expected. Appearance comes from the theme, not from the markup. |