SEO Control Layer self-updates
WordPress REST API plugin for SEO Remediation Agent — RankMath meta, snippet injection, cache purge, auto-update
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/penzenmaster/rankmath-rest-bridge/archive/refs/heads/main.zipShips its own WordPress updater (Plugin Update Checker), so new versions show up under Dashboard → Updates.
RankRocket SEO Control Layer
WordPress plugin that is the native SEO control layer for the RankRocket
remediation pipeline. Manages title/meta, schema injection, image ALT text,
llms.txt, XML sitemap, robots.txt, cache purge, and self-updates. RankMath is
not required; rank_math_* post-meta is read as a migration fallback.
- REST namespace:
rankrocket-seo/v1 - Requires: PHP 7.4+, WordPress 5.9+
- Auth: All write endpoints require
manage_optionscapability via WordPress Application Passwords.
Caching Compatibility
If your host runs a persistent page or object cache (SiteGround SuperCacher, Redis Object Cache, W3 Total Cache, WP Rocket, LiteSpeed Cache), you must exclude the REST API path from full-page caching to avoid stale GET responses after writes:
SiteGround SuperCacher: Exclude /wp-json/ from Dynamic Cache.
WP Rocket: Add /wp-json/(.*) to the "Never Cache URL(s)" list.
W3 Total Cache / LiteSpeed / Breeze: Exclude /wp-json/ from page cache rules.
POST /cache/purge flushes the WordPress object cache and all detected
plugin-level caches. Call it after any bulk operation if you need read-after-write
consistency without a page reload.
REST API — Key Endpoints
All examples use:
BASE="https://example.com/wp-json/rankrocket-seo/v1"
CRED="admin:APP_PASSWORD"
SEO Meta
POST /update — write or clear SEO meta for a post or term
Accepts a post_id that resolves to either a post/page or a taxonomy term.
The plugin detects the type automatically and writes to post meta or term meta
accordingly.
# Write title + description to a post
curl -X POST "$BASE/update" -u "$CRED" \
-H "Content-Type: application/json" \
-d '{"post_id": 123, "title": "New Title", "description": "New description."}'
# Write term meta to a taxonomy term
curl -X POST "$BASE/update" -u "$CRED" \
-H "Content-Type: application/json" \
-d '{"post_id": 21, "title": "Category Title", "description": "Category description."}'
Response includes object_type: "post" or object_type: "term" so callers
know which path was taken.
Clearing fields with unset_fields
To delete a previously-stored meta value, pass its name in the unset_fields
array. Sending an empty string ("title": "") is intentionally a no-op —
empty strings are skipped so that pipeline templates that render a blank value
for a missing field don't accidentally wipe live SEO data. Use unset_fields
for explicit, deliberate deletion.
curl -X POST "$BASE/update" -u "$CRED" \
-H "Content-Type: application/json" \
-d '{"post_id": 123, "unset_fields": ["title", "og_image"]}'
Valid field names: title, description, focus_keyword, robots,
og_title, og_description, og_image, canonical, twitter_card,
twitter_title, twitter_description, twitter_image.
Returns 422 invalid_unset_field for unrecognised names (includes
valid_fields array). Returns 422 unset_write_conflict if the same field
appears in both write params and unset_fields.
GET /get/{id} — read SEO meta
curl "$BASE/get/123" -u "$CRED"
POST /preview-update — dry-run diff (posts only)
Returns what would change without writing. Returns 422 term_not_supported
for term IDs — use POST /update directly for term meta writes.
Schema (JSON-LD)
GET /schema/{post_id} — read the stored JSON-LD schema
curl "$BASE/schema/123" -u "$CRED"
Returns whatever was stored: a single node object, or a @graph envelope
for multi-node writes. schema: null if nothing has been written yet.
POST /schema/{post_id} — validate and store JSON-LD schema
Accepts any of three shapes in the schema field. All three validate each
node's @type against the allowed list and normalize to a canonical
@graph envelope on write, except a single node, which is stored exactly
as received.
1. Single node (unchanged since v3.0.0):
curl -X POST "$BASE/schema/123" -u "$CRED" \
-H "Content-Type: application/json" \
-d '{"schema": {"@context": "https://schema.org", "@type": "Service", "name": "Bookkeeping"}}'
2. Bare array of nodes — wrapped into a @graph envelope with
@context defaulted to https://schema.org:
curl -X POST "$BASE/schema/123" -u "$CRED" \
-H "Content-Type: application/json" \
-d '{"schema": [
{"@type": "Service", "name": "Bookkeeping"},
{"@type": "BreadcrumbList", "itemListElement": []}
]}'
3. @graph envelope (preferred canonical form for multi-node writes):
curl -X POST "$BASE/schema/123" -u "$CRED" \
-H "Content-Type: application/json" \
-d '{"schema": {
"@context": "https://schema.org",
"@graph": [
{"@type": "Service", "name": "Bookkeeping"},
{"@type": "FAQPage", "mainEntity": []}
]
}}'
Provider references between nodes (e.g. Service.provider -> a
LocalBusiness.@id on another page) are stored verbatim — the caller is
responsible for keeping @id values consistent across nodes and pages.
Optional dry_run: true validates without writing.
Errors:
422 validation_failed— a node's@typeisn't in the allowed list, a required field is missing, or a@grapharray is empty. Theerrorsarray is per-node for multi-node payloads (e.g.schema: @graph[1] @type 'Widget' not allowed. Allowed: ...).413 validation_failed— more than 20 nodes in one graph (raise via therrseo_schema_graph_max_nodesfilter).
The wp_head emitter (priority 5) outputs whatever is stored verbatim in
one `