Before any of my bots touch categories, tags or a batch of posts on this blog, the rule is backups first. Here’s the simple version of how that works using the WordPress REST API, so you can do the same before a big cleanup, a theme change, or letting an AI agent loose on your site.
1. Use an application password, stored safely
Create an application password for the account under Users > Profile. Put it in a small env file only you can read:
# ~/.config/wp-backup.env (chmod 600)
WP_USER="your-user"
WP_APP_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx"
2. Dump posts, pages, categories and tags
The index.php?rest_route= form works even when pretty permalinks or a security plugin get in the way of /wp-json/. context=edit gets the raw content, which is what you need to restore.
#!/usr/bin/env bash
set -euo pipefail
source ~/.config/wp-backup.env
API="https://example.com/index.php?rest_route=/wp/v2"
OUT=~/wp-backups/$(date +%F-%H%M); mkdir -p "$OUT"
for t in posts pages categories tags; do
extra="&context=edit"; [[ $t == posts || $t == pages ]] && extra+="&status=any"
page=1
while :; do
f="$OUT/$t-$page.json"
code=$(curl -s -u "$WP_USER:$WP_APP_PASSWORD" -o "$f" -w '%{http_code}' \
"$API/$t$extra&per_page=100&page=$page")
if [[ $code != 200 ]] || [[ $(jq length "$f") -eq 0 ]]; then rm -f "$f"; break; fi
page=$((page+1))
done
jq -s 'add // []' "$OUT"/$t-*.json > "$OUT/$t.json" && rm -f "$OUT"/$t-*.json
echo "$t: $(jq length "$OUT/$t.json")"
done
3. Check the counts
Compare the numbers the script prints with what you see in wp-admin. If they don’t match, stop and figure out why before you change anything.
4. Restoring one post
If a bulk edit goes sideways, pull the old raw content for that post out of the backup and send it back:
ID=123
jq --argjson id $ID '.[] | select(.id==$id) | {title: .title.raw, content: .content.raw, categories, tags}' \
~/wp-backups/2026-10-20-1300/posts.json > restore.json
curl -s -u "$WP_USER:$WP_APP_PASSWORD" -H 'Content-Type: application/json' \
-d @restore.json "https://example.com/index.php?rest_route=/wp/v2/posts/$ID"
Tips
- Backups are read-only calls, so they’re safe to run anytime. Make it the first step of any script that writes.
- Only add tags and categories in bulk jobs, never remove them, unless you’ve looked at the list first.
- Keep the backup folder off the web server, and don’t commit the env file anywhere.
Have a blessed day! And Happy Hacking!