I wanted a simple thing: write a post in Markdown, run one command, and have it appear — fully formatted — on my self-hosted WordPress site. No logging into the editor, no copy-paste, no reformatting. An AI agent drafts; a script publishes.
The pipeline itself took an afternoon. Getting past my host’s firewall and WordPress’s authentication took four specific fixes — each one a dead end that looked like a different problem than it was. If you’re wiring up any kind of automated publishing to WordPress, these are the walls you’ll hit, and here’s how to get through them.
The shape of the pipeline
It’s deliberately boring, which is the point. A Markdown file carries the content plus a little front matter (title, slug, tags, draft-or-publish). A script converts the Markdown to HTML and POSTs it to the WordPress REST API. WordPress does the rest.
The front matter is just a few lines at the top of each file:
---
title: My Post Title
status: draft # draft | publish
slug: my-post-slug
tags: [automation, wordpress]
---
# Your content, in plain Markdown...
Authentication uses a WordPress Application Password — a revocable, API-only credential you generate inside your profile. The config never contains your real login:
{
"site": "https://your-site.com",
"user": "your-wp-username",
"password": "xxxx xxxx xxxx xxxx xxxx xxxx"
}
That’s the entire design. Now the four walls.
Wall 1 — A “406 Not Acceptable” that’s really a firewall
Symptom406 Not Acceptable before authentication is even checked.
CauseMany managed hosts run a security firewall (mod_security) that blocks requests which don’t look like a real browser. A bare scripting client gets bounced.
FixSend a normal browser User-Agent (and an Accept header) with every request.
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36
Accept: application/json
One header turned a hard 406 into a clean connection. If your API calls die before they reach WordPress at all, this is almost always why.
Wall 2 — “rest_not_logged_in” even with correct credentials
Symptom401 — rest_not_logged_in (“You are not currently logged in”), even though the credentials are right.
CauseTwo things hide behind this one message. WordPress’s REST API silently ignores your normal login password — it only accepts an Application Password. And on some hosts, Apache strips the Authorization header before it reaches PHP, so WordPress sees no credentials at all.
FixGenerate an Application Password (Users → Profile → Application Passwords), use your real login username as the user, and if the header is being stripped, add a pass-through rule to .htaccess.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</IfModule>
The tell: a wrong password returns incorrect_password, but no credentials arriving returns rest_not_logged_in. If you see the latter, stop fiddling with the password — check the header and confirm you’re using an Application Password, not your login one.
Wall 3 — Never hand over your real password
This one is a discipline, not a bug. It’s tempting to drop your actual WordPress password into a config file to “just make it work.” Don’t — and you can’t anyway, because the REST API won’t accept it.
It’s scoped to the API, you can revoke it from the same profile screen the moment you stop using it, and it never exposes your real login. Keep the config file private, and treat the app password like any other secret.
Wall 4 — Make the styling survive WordPress
SymptomCustom layout — charts, callouts, styled blocks — gets stripped or flattened on publish.
CauseWordPress sanitizes post content unless the author has the unfiltered_html capability (administrators on a single site do).
FixBuild the post body as self-contained HTML with a scoped <style> block and inline SVG for any charts, so it renders identically on any theme. Then verify on the live page, not just in preview.
The verification step matters more than it sounds. A raw text fetch of a published page can’t tell you whether the CSS actually applied — so I loaded each live post in a real browser and scrolled the whole thing. That’s how you catch a stripped <style> block or a chart that didn’t render before your readers do.
The marketing-ops payoff
Once the four walls are behind you, what you have isn’t a one-off script — it’s a repeatable content operation. The loop I settled on:
- Draft in Markdown — by hand or with an AI agent — with front matter controlling title, tags, and status.
- Publish as drafts first for a whole batch, review them in the dashboard, then promote the keepers.
- Scrub before you ship. A quick pass to genericize anything internal — product names, vendor tools, internal endpoints — before a post goes public and gets indexed forever.
- Verify rendering on the live page every time. Cheap insurance against a silently broken layout.
- Schedule a cadence so posts drip out on set days instead of all at once.
The pipeline is the easy part. The value is in the four fixes that turn “the API keeps rejecting me” into a publishing system you actually trust — and in the review and verification steps that keep an automated pipeline from quietly shipping something wrong.
Marketing operations is full of these afternoons: a workflow that should be trivial, gated by three or four undocumented quirks. Write the fixes down once and the next person — or the next you — gets the afternoon back.
What’s a publishing or ops workflow you’ve automated that turned out to be one stubborn auth wall away from working?