WordPress Skill
Publish and manage blog content on a WordPress site via the built-in WP REST API
(/wp-json/wp/v2/...), authenticated with an Application Password — no OAuth app needed.
Draft-first rule
Every post is created with "status": "draft" first. Show the draft (title + body + any
scheduled time) to the human and get explicit approval before flipping status to publish or
future. Never create a post directly as publish.
Setup
- In WordPress admin: Users → Profile (for the account you'll post as) → scroll to "Application Passwords" → enter a name (e.g. "MarketingClaw") → Add New Application Password.
- Copy the generated password (shown once, spaces included).
- Set environment variables:
export WORDPRESS_URL="https://yourblog.example.com" export WORDPRESS_USER="your-wp-username" export WORDPRESS_APP_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx" # keep the spaces - Application Passwords require HTTPS on the site (or
WP_ENVIRONMENT_TYPE=localfor local dev).
All requests use HTTP Basic auth: curl -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD".
Create a draft post
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" \
-X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/posts" \
-H "Content-Type: application/json" \
-d '{
"title": "5 Things We Learned Shipping v2",
"content": "<p>Full post body as HTML...</p>",
"status": "draft",
"categories": [3],
"tags": [12, 18]
}' | jq '{id, link, status}'
Update a post / publish / schedule
# Publish now (only after approval)
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" \
-X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/posts/POST_ID" \
-H "Content-Type: application/json" \
-d '{"status": "publish"}' | jq '{id, link, status}'
# Schedule for later (only after approval) — status=future + a future "date" (site-local time)
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" \
-X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/posts/POST_ID" \
-H "Content-Type: application/json" \
-d '{"status": "future", "date": "2026-07-15T09:00:00"}' | jq '{id, link, status, date}'
date is site-local time without a timezone suffix; use date_gmt (with a Z suffix) instead
if you want to specify UTC explicitly.
Media upload
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" \
-X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/media" \
-H "Content-Disposition: attachment; filename=\"hero.jpg\"" \
-H "Content-Type: image/jpeg" \
--data-binary "@/path/to/hero.jpg" | jq '{id, source_url}'
# Set as the post's featured image
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" \
-X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/posts/POST_ID" \
-H "Content-Type: application/json" \
-d '{"featured_media": MEDIA_ID}' | jq '{id, featured_media}'
Categories and tags
# List / find
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" "${WORDPRESS_URL%/}/wp-json/wp/v2/categories?per_page=100" | jq '.[] | {id, name}'
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" "${WORDPRESS_URL%/}/wp-json/wp/v2/tags?per_page=100" | jq '.[] | {id, name}'
# Create
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" -X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/categories" \
-H "Content-Type: application/json" -d '{"name": "Product"}' | jq '{id, name}'
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" -X POST "${WORDPRESS_URL%/}/wp-json/wp/v2/tags" \
-H "Content-Type: application/json" -d '{"name": "launch"}' | jq '{id, name}'
List / check posts
curl -s -u "$WORDPRESS_USER:$WORDPRESS_APP_PASSWORD" \
"${WORDPRESS_URL%/}/wp-json/wp/v2/posts?status=draft,future,publish&per_page=20" | \
jq '.[] | {id, title: .title.rendered, status, date, link}'
Note: filtering by non-publish statuses requires an authenticated request (Basic auth above) —
anonymous requests only ever see published posts.
Notes
contentaccepts raw HTML (Gutenberg block markup or plain HTML both render fine); avoid hand-writing block comments unless you need specific block behavior.- Rate limits are host-dependent (WordPress core has none by default; managed hosts like WordPress.com or WP Engine may throttle) — back off on 429s.
- Application Passwords can be revoked per-app from the same Profile screen without touching the user's main login password.
- Never print
$WORDPRESS_APP_PASSWORDin logs or chat.