Scholar HeistScholar Heist
← All skills
SEOv1.0.0 · 2026-08-07

Wordpress Publisher

The REST API layer: draft + metadata + images in, live URL out. Chosen over browser automation because it does not depend on a session staying alive.

Download skill

What it does

  • Run the six calls in order
  • Respect the two traps inside the order
  • Update, never republish
  • Verify before reporting
SKILL.md
---
name: wordpress-publisher
description: Publish a finished post to WordPress via the REST API — the six calls in their fixed order, duplicate check, media with alt text, explicit author, and the Rank Math metadata endpoint. Use when asked to publish a post to WordPress, automate publishing, push a draft live via API, fix posts landing under the wrong author, fix SEO fields that stay empty, update an existing post by ID, or someone says 'the post published but half of it is broken'. For the connection and auth, see wordpress-api-connector. For the fields being sent, see seo-metadata-generator.
metadata:
  version: 1.0.0
---

# WordPress publisher

The REST API layer: draft + metadata + images in, live URL out. Chosen over
browser automation **because it does not depend on a session staying alive.**

Everything here hangs on one fact: **six calls, in this exact order** — the
featured image must exist before the post references it, and the post must
exist before Rank Math can attach metadata to it. Order violations are the
whole failure catalogue.

## Before you start

| Input | Why |
| --- | --- |
| A verified connection — pre-flight passed | wordpress-api-connector's users/me call, this run |
| The complete draft HTML with images resolved | Images before publish, always — placeholders must be gone |
| The metadata JSON | seo-metadata-generator's output, field-complete |
| `DEFAULT_PUBLISH_STATUS` | **draft for your first ten runs — trust is earned** |
| The explicit author ID | Never the default admin |

## Step 1 — Run the six calls in order

```
1. Duplicate check
   GET  /wp-json/wp/v2/posts?slug={slug}&status=any
2. Upload the featured image, then set its alt text
   POST /wp-json/wp/v2/media                (multipart)
   POST /wp-json/wp/v2/media/{id}           {"alt_text": "..."}
3. Tags: search first, create only if missing
   GET  /wp-json/wp/v2/tags?search={name}
   POST /wp-json/wp/v2/tags                 {"name": "..."}
4. Create the post
   POST /wp-json/wp/v2/posts
   { title: h1, slug, status, content: html, excerpt,
     author: <EXPLICIT ID>, featured_media: media_id,
     categories: [ids], tags: tag_ids }
5. SEO fields — Rank Math's OWN endpoint
   POST /wp-json/rankmath/v1/updateMeta
   { objectID: post_id, objectType: "post",
     meta: { rank_math_title, rank_math_description,
             rank_math_focus_keyword, rank_math_robots: ["index","follow"] } }
6. Verify the live URL renders
   GET  {post_link}
```

Every call carries the browser User-Agent header (the connector's rule).

## Step 2 — Respect the two traps inside the order

- **Rank Math does not store its fields in standard post meta.** Writing them
  to `/wp/v2/posts` with a `meta` object *silently does nothing* — the post
  publishes, the SEO tab looks empty, and nobody notices for a week. Only the
  plugin's endpoint counts. (Yoast users: its fields go through registered post
  meta instead — a different path, same trap class.)
- **Duplicate slugs get checked with `status=any`** — a draft with the same
  slug collides exactly like a published post.

## Step 3 — Update, never republish

"Update this existing post" = **fetch by ID, apply changes, re-save. Same URL,
same rankings, new freshness signal.** This is the refresh path and it is worth
more than most people think. Creating a new post for updated content starts
from zero and cannibalises the old one.

## Step 4 — Verify before reporting

Call 6 fetches the live (or preview) URL and confirms every element rendered:
images present, author correct, no placeholder text. **The publisher reports
the URL it verified**, not the URL it hopes exists.

## Output

```
# Published: <title>

Status: <draft|publish> (per DEFAULT_PUBLISH_STATUS)
URL: <link — fetched and verified>
Post ID: <n> · Author: <id, explicit> · Featured media: <id> (alt ✓)
Tags: <n reused, n created> · Categories: <ids>
Rank Math: updateMeta → 200 ✓
Six calls: 1✓ 2✓ 3✓ 4✓ 5✓ 6✓ (order held)
```

## When it breaks

| What you see | What it means | The fix |
| --- | --- | --- |
| Post live, images broken | Image step ran after publish | The order is fixed: media exists before the post references it |
| SEO tab empty despite "metadata sent" | Rank Math fields written to standard post meta | Call 5 uses /wp-json/rankmath/v1/updateMeta, nothing else |
| Post under "admin" | author omitted, defaulted | Explicit ID on every create — the byline is a credibility signal |
| 403 mid-run | WAF blocking the client User-Agent | Browser UA on every call; see wordpress-api-connector |
| Duplicate URL with -2 suffix | Slug collision unchecked | Call 1 with status=any before anything uploads |
| Tag list breeding duplicates | Created without searching | Get-or-create: search first, create only on miss |
| Updated content, rankings reset | New post instead of update-by-ID | Fetch by ID, re-save — same URL keeps the equity |
| First run published straight to live | Trust given before earned | DEFAULT_PUBLISH_STATUS=draft for the first ten runs |

Never retry a failed call blindly, and never continue past a failed call — call
4 without call 2's media ID publishes a broken post. Check the four (session,
User-Agent, quota, image key), fix, then resume from the failed step.

## Rules

- **The six-call order is fixed**, because each call consumes the previous
  call's output — order violations are every classic failure.
- **Rank Math goes through its own endpoint**, because the silent-nothing
  failure costs a week of unindexed posts before anyone notices.
- **Explicit author, every create**, because defaulting to admin discards the
  authority signal the content was written to build.
- **Draft-first for the first ten runs**, because trust is earned and a live
  mistake is public.
- **Update by ID, never republish**, because the URL carries the rankings and
  a new URL starts from zero.

## Related skills

- **wordpress-api-connector** — the auth and pre-flight this skill assumes.
- **seo-metadata-generator** — produces the JSON call 5 sends.
- **publish-workflow** — the orchestrator that runs this as its fifth step.
- **content-refresher** — the update-by-ID path's strategic owner.

Reviews

Sign in to leave a review.