{"id":359023,"date":"2026-09-17T18:58:13","date_gmt":"2026-09-17T18:58:13","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/autonode\/"},"modified":"2026-09-17T20:43:56","modified_gmt":"2026-09-17T20:43:56","slug":"directrelay-automation-bridge-for-n8n","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/directrelay-automation-bridge-for-n8n\/","author":23460756,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"5.1.31","stable_tag":"5.1.31","tested":"7.1.1","requires":"6.3","requires_php":"8.0","requires_plugins":null,"header_name":"DirectRelay Automation Bridge for n8n","header_author":"n4nion","header_description":"REST API automation bridge connecting n8n workflows and AI agents with WordPress. Manage posts, Rank Math and Yoast SEO metadata, media attachments, and webhooks with advanced security.","assets_banners_color":"011530","last_updated":"2026-09-17 20:43:56","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/directrelay.wikiofautomation.com","header_author_uri":"https:\/\/profiles.wordpress.org\/n4nion","rating":0,"author_block_rating":0,"active_installs":0,"downloads":105,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"5.1.26":{"tag":"5.1.26","author":"n4nion","date":"2026-09-16 21:15:05","revision":3699233},"5.1.27":{"tag":"5.1.27","author":"n4nion","date":"2026-09-16 22:48:33","revision":3699306},"5.1.28":{"tag":"5.1.28","author":"n4nion","date":"2026-09-17 16:42:36","revision":3700621},"5.1.29":{"tag":"5.1.29","author":"n4nion","date":"2026-09-17 19:12:53","revision":3700854},"5.1.30":{"tag":"5.1.30","author":"n4nion","date":"2026-09-17 20:06:23","revision":3700924},"5.1.31":{"tag":"5.1.31","author":"n4nion","date":"2026-09-17 20:43:56","revision":3700961}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3699304,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3699304,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon-32x32.png":{"filename":"icon-32x32.png","revision":3699304,"resolution":"32x32","location":"assets","locale":"","width":32,"height":32},"icon-512x512.png":{"filename":"icon-512x512.png","revision":3699304,"resolution":"512x512","location":"assets","locale":"","width":512,"height":512},"icon.svg":{"filename":"icon.svg","revision":3699280,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3700959,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3700959,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["5.1.26","5.1.27","5.1.28","5.1.29","5.1.30","5.1.31"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3699304,"resolution":"1","location":"assets","locale":"","width":1200,"height":750},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3699304,"resolution":"2","location":"assets","locale":"","width":1200,"height":750},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3699304,"resolution":"3","location":"assets","locale":"","width":1200,"height":750},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3699304,"resolution":"4","location":"assets","locale":"","width":1200,"height":750},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3699304,"resolution":"5","location":"assets","locale":"","width":1200,"height":750},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3699304,"resolution":"6","location":"assets","locale":"","width":1200,"height":750}},"screenshots":{"1":"<strong>Dashboard at a glance<\/strong> \u2014 compatibility scorecard, key stats, recent webhook activity.","2":"<strong>Scoped API keys<\/strong> \u2014 pick a role preset or build a custom scope bundle. Per-key IP allowlist and expiry.","3":"<strong>Webhook delivery log<\/strong> \u2014 every event, every status code, every response time, last 50 per webhook.","4":"<strong>Activity log<\/strong> \u2014 last 100 API calls and webhook events, filterable by event type and key.","5":"<strong>Compatibility checker<\/strong> \u2014 verifies permalinks, REST API, SSL, Rank Math \/ Yoast presence, PHP version, WP-Cron health.","6":"<strong>n8n starter templates<\/strong> \u2014 one copy-paste workflow that proves the connection in under a minute."}},"plugin_section":[],"plugin_tags":[1556,569,243637,2299,186],"plugin_category":[55],"plugin_contributors":[267150],"plugin_business_model":[],"class_list":["post-359023","plugin","type-plugin","status-publish","hentry","plugin_tags-api","plugin_tags-automation","plugin_tags-n8n","plugin_tags-rest","plugin_tags-seo","plugin_category-seo-and-marketing","plugin_contributors-n4nion","plugin_committers-n4nion"],"banners":{"banner":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/banner-772x250.png?rev=3700959","banner_2x":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/banner-1544x500.png?rev=3700959","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/icon.svg?rev=3699280","icon":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/icon.svg?rev=3699280","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/screenshot-1.png?rev=3699304","caption":"<strong>Dashboard at a glance<\/strong> \u2014 compatibility scorecard, key stats, recent webhook activity."},{"src":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/screenshot-2.png?rev=3699304","caption":"<strong>Scoped API keys<\/strong> \u2014 pick a role preset or build a custom scope bundle. Per-key IP allowlist and expiry."},{"src":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/screenshot-3.png?rev=3699304","caption":"<strong>Webhook delivery log<\/strong> \u2014 every event, every status code, every response time, last 50 per webhook."},{"src":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/screenshot-4.png?rev=3699304","caption":"<strong>Activity log<\/strong> \u2014 last 100 API calls and webhook events, filterable by event type and key."},{"src":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/screenshot-5.png?rev=3699304","caption":"<strong>Compatibility checker<\/strong> \u2014 verifies permalinks, REST API, SSL, Rank Math \/ Yoast presence, PHP version, WP-Cron health."},{"src":"https:\/\/ps.w.org\/directrelay-automation-bridge-for-n8n\/assets\/screenshot-6.png?rev=3699304","caption":"<strong>n8n starter templates<\/strong> \u2014 one copy-paste workflow that proves the connection in under a minute."}],"raw_content":"<!--section=description-->\n<p><strong>DirectRelay Automation Bridge for n8n<\/strong> turns any WordPress site into a first-class REST API target that n8n workflows, Make scenarios, Zapier zaps, custom AI agents and your own scripts can talk to \u2014 with the keys, scopes and security model that production automation actually demands.<\/p>\n\n<p>It is the only free WordPress automation plugin that ships all four at once: a scoped REST API surface (32 endpoints under <code>\/wp-json\/directrelay-automation-bridge-for-n8n\/v1\/<\/code>), HMAC-signed outgoing webhooks with retry, native reads of <strong>Rank Math<\/strong> and <strong>Yoast SEO<\/strong> fields, and a self-describing OpenAPI 3.0 spec that lets the n8n HTTP Request node auto-fill every request shape. <strong>No purchase code. No license key. No trial period. No feature lock.<\/strong> Every feature listed on this page is fully functional in the free build and always will be.<\/p>\n\n<h3>Why n8n users pick DirectRelay<\/h3>\n\n<ul>\n<li><strong>OpenAPI 3.0 spec at <code>\/wp-json\/directrelay-automation-bridge-for-n8n\/v1\/openapi.json<\/code><\/strong> \u2014 paste it into n8n's HTTP Request node and every field, scope and response shape fills itself. No manual mapping.<\/li>\n<li><strong>Rank Math + Yoast SEO reads in one call<\/strong> \u2014 <code>GET \/posts\/{id}\/seo<\/code> returns focus keyword, SEO title, meta description, canonical URL and OpenGraph fields from whichever SEO plugin you have active. The community <code>n8n-nodes-wordpress<\/code> node can't do this.<\/li>\n<li><strong>Scoped API keys, not WordPress Application Passwords<\/strong> \u2014 generate a key that can only read posts, only publish to a specific CPT, or only fire webhooks. Per-key IP allowlist, expiry and rotation are built in. Revoke one key without touching the others.<\/li>\n<li><strong>Bidirectional, not one-way<\/strong> \u2014 REST reads <strong>and<\/strong> HMAC-SHA256 signed outgoing webhooks on the same install. Most alternatives do only one direction.<\/li>\n<li><strong>100% GPL, no telemetry, no license server<\/strong> \u2014 what you install is what runs. No phone-home, no purchase-code check, no usage caps.<\/li>\n<\/ul>\n\n<h3>Everything in the free plugin<\/h3>\n\n<p><strong>REST API endpoints (32 routes)<\/strong>\n* Posts \u2014 list, get, publish, bulk publish (single call writes post + featured image + SEO + categories + tags).\n* Pages \u2014 list and get.\n* Media \u2014 list, sideload from URL, get.\n* Taxonomies \u2014 categories and tags.\n* Custom meta \u2014 list all, get\/delete by key (works with ACF, Pods, custom fields).\n* SEO read \u2014 <code>GET \/{type}\/{id}\/seo<\/code> returns Rank Math or Yoast fields automatically.\n* Discovery \u2014 <code>\/discovery\/fields<\/code> and <code>\/discovery\/post-types<\/code> for schema reflection.\n* Webhooks \u2014 CRUD + test + delivery log + usage stats + ready-made presets.\n* API keys \u2014 list, rotate, revoke.\n* System \u2014 <code>\/status<\/code>, <code>\/ping<\/code>, <code>\/cron-health<\/code>, <code>\/blocked-ips<\/code>.\n* Fleet \u2014 <code>\/fleet\/health<\/code> and <code>\/fleet\/keys\/provision<\/code> for multi-site monitoring.\n* OpenAPI \u2014 <code>\/openapi.json<\/code> and <code>\/openapi<\/code> for self-describing discovery.<\/p>\n\n<p><strong>Security and production hardening<\/strong>\n* Scoped API keys with 16+ scope types (<code>posts:read<\/code>, <code>posts:write<\/code>, <code>posts:publish<\/code>, <code>posts:delete<\/code>, <code>media:write<\/code>, <code>webhooks:write<\/code>, etc.).\n* Five preset roles (readonly, writer, editor, publisher, full_access) plus custom scope bundles.\n* Per-key IP allowlist (single IP or CIDR ranges).\n* Per-key expiration date (auto-revoke after).\n* Automatic key rotation policy (configurable days; n8n-friendly grace period).\n* Keys are stored as salted hashes \u2014 the database leak cannot expose live keys.\n* Timing-safe comparison (<code>hash_equals<\/code>) \u2014 immune to timing side channels.\n* Sliding-window rate limiter \u2014 configurable per-IP + per-key, prevents burst abuse.\n* Brute-force protection with escalating IP block (5 min \u2192 30 min \u2192 2 hr \u2192 24 hr).\n* HMAC-SHA256 signed outgoing webhooks with <code>X-DirectRelay-Signature<\/code> header and replay protection via timestamp window.<\/p>\n\n<p><strong>Outgoing webhooks<\/strong>\n* Dispatch signed JSON to any HTTP endpoint \u2014 n8n webhook, Make hook, Zapier catch hook, custom AI agent.\n* Event filtering by post type, status transition, taxonomy.\n* Automatic retry with exponential backoff.\n* Last 50 deliveries per webhook (status code, response time, request body).\n* Built-in \"Send test event\" button from the admin UI.\n* Ready-made presets for <code>post.published<\/code>, <code>post.updated<\/code>, <code>post.deleted<\/code>, <code>page.published<\/code>.<\/p>\n\n<p><strong>AI and SEO integrations<\/strong>\n* <strong>Rank Math<\/strong> field reads: focus keyword, SEO title, description, canonical URL, pillar content flag, robots flags.\n* <strong>Yoast SEO<\/strong> field reads: focus keyword, SEO title, meta description, canonical URL, OpenGraph title and description.\n* <strong>IndexNow<\/strong> auto-ping on every publish\/update \u2014 instant indexing on Bing, Yandex, Naver and Seznam. No cron job needed.\n* <strong>OpenAI-compatible<\/strong> response shape \u2014 all endpoints return JSON that the n8n OpenAI\/HTTP nodes parse without transformation.<\/p>\n\n<p><strong>Operations<\/strong>\n* Activity log \u2014 last 100 API calls and webhook deliveries, plain text, filterable by event type.\n* Email notifications \u2014 admin gets pinged on every webhook failure with the failed payload attached.\n* Compatibility checker \u2014 verifies permalinks, REST API, SSL, Rank Math \/ Yoast presence, PHP version, WP-Cron health.\n* Cron health monitor \u2014 alerts if WP-Cron is more than 15 min behind.\n* n8n starter template \u2014 one copy-paste workflow that proves the connection in under 60 seconds.\n* Side-load media from URL \u2014 POST <code>\/media\/sideload<\/code> with an image URL and DirectRelay downloads, attaches and returns the WP media ID.\n* Custom Post Type support \u2014 auto-discovered, no extra config.\n* GDPR-friendly \u2014 zero outbound traffic by default. Every external service is an explicit opt-in.<\/p>\n\n<h3>Real workflows you can build today<\/h3>\n\n<ul>\n<li><strong>AI writer pipeline<\/strong> \u2014 n8n fires on a schedule \u2192 calls OpenAI to draft an article \u2192 <code>POST \/posts<\/code> with title, content, Rank Math SEO and a featured image URL in a single request \u2192 IndexNow auto-pings Bing \u2192 webhook back to n8n notifies your Slack.<\/li>\n<li><strong>Multi-site fleet manager<\/strong> \u2014 Central n8n instance pings <code>\/fleet\/health<\/code> on 20 WordPress sites every 5 min. If any return <code>cron_degraded<\/code>, n8n opens a PagerDuty incident.<\/li>\n<li><strong>Headless CMS front-end<\/strong> \u2014 Next.js or Astro reads posts via <code>GET \/posts?per_page=100&amp;status=publish<\/code>, renders the site. Writers use the regular WP admin to edit.<\/li>\n<li><strong>Lead-magnet auto-publisher<\/strong> \u2014 Webflow form submits \u2192 n8n reads the data \u2192 <code>POST \/posts<\/code> with a draft pre-filled with the lead's company name \u2192 editorial team gets a Slack notification with a one-click approve link.<\/li>\n<\/ul>\n\n<h3>DirectRelay vs the alternatives<\/h3>\n\n<p><strong>Rank Math \/ Yoast SEO field reads<\/strong>\n* DirectRelay: <strong>Yes, both<\/strong> (Rank Math + Yoast auto-detected)\n* WP REST + Application Password: No\n* n8n-nodes-wordpress (community): No\n* WP Webhooks: No<\/p>\n\n<p><strong>Per-route API scopes<\/strong>\n* DirectRelay: <strong>Yes, 16+ scopes<\/strong> (posts:read, posts:write, media:write, etc.)\n* WP REST + Application Password: No (always full account access)\n* n8n-nodes-wordpress (community): No\n* WP Webhooks: No<\/p>\n\n<p><strong>HMAC-signed outgoing webhooks<\/strong>\n* DirectRelay: <strong>Yes<\/strong> (HMAC-SHA256, replay-protection timestamp)\n* WP REST + Application Password: No\n* n8n-nodes-wordpress (community): No\n* WP Webhooks: Yes (basic)<\/p>\n\n<p><strong>OpenAPI 3.0 auto-discovery<\/strong>\n* DirectRelay: <strong>Yes<\/strong> (<code>\/openapi.json<\/code> so the n8n HTTP Request node auto-fills request shapes)\n* WP REST + Application Password: No\n* n8n-nodes-wordpress (community): No\n* WP Webhooks: No<\/p>\n\n<p><strong>Outgoing webhooks with retry<\/strong>\n* DirectRelay: <strong>Yes<\/strong> (exponential backoff, delivery log, test event)\n* WP REST + Application Password: No\n* n8n-nodes-wordpress (community): No\n* WP Webhooks: Yes<\/p>\n\n<p><strong>Per-credential IP allowlist<\/strong>\n* DirectRelay: <strong>Yes<\/strong> (single IP or CIDR)\n* WP REST + Application Password: No\n* n8n-nodes-wordpress (community): No\n* WP Webhooks: No<\/p>\n\n<p><strong>Pricing model<\/strong>\n* DirectRelay: <strong>100% free, GPL, no upsell, no trial<\/strong>\n* WP REST + Application Password: 100% free (core WordPress)\n* n8n-nodes-wordpress (community): 100% free (community node)\n* WP Webhooks: Freemium (paid add-ons)<\/p>\n\n<h3>Privacy and licence \u2014 no trialware, ever<\/h3>\n\n<p>DirectRelay is free software released under the GPL-2.0+ licence. You can use it, study it, modify it and redistribute it under the same terms.<\/p>\n\n<p>The free plugin distributed on WordPress.org does <strong>not<\/strong> perform purchase-code validation, license-server activation, or any third-party license check. There is <strong>no<\/strong> purchase code, license key or activation token collected, stored, transmitted or required to use any feature of the free plugin. License management for the optional add-on is provided by the separate DirectRelay Pro add-on plugin distributed from <a href=\"https:\/\/directrelay.wikiofautomation.com\/directrelay-pro\">directrelay.wikiofautomation.com\/directrelay-pro<\/a> \u2014 Pro is never distributed via WordPress.org and is never required.<\/p>\n\n<h3>Pro Features (separate add-on plugin)<\/h3>\n\n<p>The following features are provided by the DirectRelay Pro add-on plugin, distributed from https:\/\/directrelay.wikiofautomation.com\/directrelay-pro. None of this code ships in the WordPress.org zip.<\/p>\n\n<ul>\n<li><strong>SEO Metadata Write<\/strong> (focus keyword, title, description for Rank Math and Yoast SEO).<\/li>\n<li><strong>Bulk Publish Endpoint<\/strong> (up to 50 items per request).<\/li>\n<li><strong>Universal Schema Publish<\/strong> (schema-driven publish from any JSON shape).<\/li>\n<li><strong>One-Shot Publish<\/strong> (atomic single-call create with media and SEO).<\/li>\n<li><strong>Human Approval Portal<\/strong> (signed review URLs, approve\/reject workflow, multi-reviewer chains).<\/li>\n<li><strong>AI Guardrails<\/strong> (duplicate-title detection, AI-leakage scrubbing, blocked-phrase filter).<\/li>\n<li><strong>Full Debugger<\/strong> (50-entry replay, payload inspection, dry-run mode).<\/li>\n<li><strong>Chart.js Analytics Dashboard<\/strong> (call volume, error rate, latency trends, per-key breakdown).<\/li>\n<li><strong>n8n Template Library<\/strong> (AI writer, multilingual publisher, research approval pipelines).<\/li>\n<li><strong>Dark Mode<\/strong> for the plugin admin screens.<\/li>\n<\/ul>\n\n<h3>External services<\/h3>\n\n<p>This plugin supports optional integrations with external services. The services and their data handling policies are outlined below. All transmissions are initiated by the plugin only when the administrator has explicitly enabled the relevant feature; no data is sent silently or by default.<\/p>\n\n<p>The free plugin distributed on WordPress.org does NOT use the following services: Envato\/CodeCanyon purchase-code validation, license-server activation, or any third-party license check. There is no purchase code, license key, or activation token collected, stored, transmitted, or required to use any feature of the free plugin. (License management is provided by the separate DirectRelay Pro add-on plugin distributed from directrelay.wikiofautomation.com\/directrelay-pro.)<\/p>\n\n<h4>n8n Webhooks (outgoing)<\/h4>\n\n<ul>\n<li><strong>What it is and what it is used for:<\/strong> DirectRelay dispatches signed JSON payloads to user-configured webhook URLs when WordPress post events occur. The webhook receiver can be n8n, Make, an AI agent, or any other HTTP endpoint the administrator chooses.<\/li>\n<li><strong>What data is sent and when:<\/strong> The configured webhook target URL receives the post ID, title, content excerpt, status, taxonomies, and read-only SEO fields. Transmission only happens when the administrator has created an active webhook rule and the corresponding post event fires.<\/li>\n<li><strong>Service Terms &amp; Privacy:<\/strong>\n\n<ul>\n<li>The webhook receiver is the user's choice; DirectRelay itself does not transmit data to any third party by default.<\/li>\n<li>n8n (optional) Terms: https:\/\/n8n.io\/legal\/self-serve-terms\/<\/li>\n<li>n8n (optional) Privacy: https:\/\/n8n.io\/legal\/<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>IndexNow<\/h4>\n\n<ul>\n<li><strong>What it is and what it is used for:<\/strong> Submits updated post URLs directly to search engines (Bing, Yandex, Seznam) for instant indexing when content is published or updated.<\/li>\n<li><strong>What data is sent and when:<\/strong> Sends the site hostname, the API key the administrator configured, and the modified post URL. Transmitted only when the administrator enables IndexNow in plugin settings and a post is published or updated.<\/li>\n<li><strong>Service Terms &amp; Privacy:<\/strong>\n\n<ul>\n<li>IndexNow Terms: https:\/\/www.indexnow.org\/<\/li>\n<li>IndexNow Privacy: https:\/\/privacy.microsoft.com\/en-us\/privacystatement<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>Cloudflare API (optional)<\/h4>\n\n<ul>\n<li><strong>What it is and what it is used for:<\/strong> When a site administrator has configured a Cloudflare integration, the plugin can purge the Cloudflare edge cache for specific URLs after a WordPress post event.<\/li>\n<li><strong>What data is sent and when:<\/strong> Sends the user-configured Cloudflare Zone ID, the user-provided API bearer token, and the specific post URLs to purge. Transmitted only when configured by the site administrator in outgoing webhook actions.<\/li>\n<li><strong>Service Terms &amp; Privacy:<\/strong>\n\n<ul>\n<li>Cloudflare Terms: https:\/\/www.cloudflare.com\/website-terms\/<\/li>\n<li>Cloudflare Privacy: https:\/\/www.cloudflare.com\/privacypolicy\/<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>Third-Party AI APIs in n8n Workflow Templates (DeepSeek, Moonshot AI \/ Kimi, Alibaba Qwen)<\/h4>\n\n<ul>\n<li><strong>What it is and what it is used for:<\/strong> The Pro add-on plugin (separate download) ships copyable n8n workflow templates that demonstrate external AI models feeding WordPress via n8n. The free plugin itself does not call any third-party AI service directly; it only provides the REST endpoints that the n8n workflow calls.<\/li>\n<li><strong>What data is sent and when:<\/strong> No data is transmitted directly by the WordPress plugin to these AI APIs. The workflow templates run entirely inside the user's self-hosted or cloud n8n instance using the user's own API credentials. The WordPress plugin only receives the AI-generated result via the n8n HTTP request back into the REST API.<\/li>\n<li><strong>Service Terms &amp; Privacy:<\/strong>\n\n<ul>\n<li>DeepSeek Terms: https:\/\/cdn.deepseek.com\/policies\/en-US\/deepseek-privacy-policy.html<\/li>\n<li>DeepSeek Privacy: https:\/\/cdn.deepseek.com\/policies\/en-US\/deepseek-privacy-policy.html<\/li>\n<li>Moonshot AI Terms: https:\/\/www.moonshot.cn\/<\/li>\n<li>Moonshot AI Privacy: https:\/\/www.moonshot.cn\/privacy<\/li>\n<li>Alibaba DashScope \/ Qwen: https:\/\/www.alibabacloud.com\/help\/en\/model-studio\/terms-of-use<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>directrelay<\/code> directory to the <code>\/wp-content\/plugins\/<\/code> directory, or install the plugin through the WordPress Plugins screen directly.<\/li>\n<li>Activate the plugin through the <strong>Plugins<\/strong> menu in WordPress.<\/li>\n<li>Navigate to <strong>DirectRelay -&gt; API Keys<\/strong> in your WordPress admin menu to generate your first API credential.<\/li>\n<li>Configure your n8n HTTP Request node with the generated API key in the <code>Authorization: Bearer<\/code> header (or the <code>X-API-Key<\/code> header).<\/li>\n<li>(Optional) Install the DirectRelay Pro add-on plugin if you need SEO metadata writes, bulk publish, the approval portal, advanced analytics, or dark mode. Pro is available from https:\/\/directrelay.wikiofautomation.com\/directrelay-pro.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20require%20n8n%3F\"><h3>Does this require n8n?<\/h3><\/dt>\n<dd><p>No. DirectRelay is a plain WordPress REST API plugin. Any HTTP client \u2014 n8n, Make, Zapier, custom Python, curl, Postman, an AI agent \u2014 can talk to it. The OpenAPI 3.0 spec at <code>\/wp-json\/directrelay-automation-bridge-for-n8n\/v1\/openapi.json<\/code> makes integration with any tool that supports OpenAPI frictionless.<\/p><\/dd>\n<dt id=\"is%20this%20safe%20to%20run%20on%20a%20production%20site%3F\"><h3>Is this safe to run on a production site?<\/h3><\/dt>\n<dd><p>Yes. Every request is authenticated via scoped API key + optional IP allowlist. Keys are stored as salted hashes (not plaintext). The rate limiter and brute-force protection run before any database query. Trace logs are written under <code>wp-content\/uploads\/<\/code>, never the plugin directory. The plugin ships with zero outbound traffic by default.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20rank%20math%20and%20yoast%20seo%3F\"><h3>Does it work with Rank Math AND Yoast SEO?<\/h3><\/dt>\n<dd><p>Yes. The <code>\/seo<\/code> endpoint auto-detects which SEO plugin is active and returns the matching field names. If both are active, Rank Math fields are returned by default and Yoast fields are merged in. The plugin does not require either \u2014 if neither is installed, the endpoint returns an empty SEO block and the rest of the API keeps working.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20woocommerce%20and%20custom%20post%20types%3F\"><h3>Does it work with WooCommerce and Custom Post Types?<\/h3><\/dt>\n<dd><p>Yes. Every endpoint that accepts a <code>type<\/code> parameter (posts, pages, meta, seo) works with any public CPT registered with <code>show_in_rest = true<\/code>. WooCommerce products, <code>portfolio<\/code>, <code>case_study<\/code>, any CPT you register \u2014 all readable and writable through the same API.<\/p><\/dd>\n<dt id=\"can%20i%20use%20this%20on%20a%20multisite%20network%3F\"><h3>Can I use this on a multisite network?<\/h3><\/dt>\n<dd><p>Yes. DirectRelay is multisite-aware. Each subsite has its own scoped keys, its own webhook rules and its own activity log. The <code>\/fleet\/health<\/code> endpoint is specifically built for monitoring many installs from one place.<\/p><\/dd>\n<dt id=\"what%20happens%20to%20my%20keys%20if%20i%20uninstall%20the%20plugin%3F\"><h3>What happens to my keys if I uninstall the plugin?<\/h3><\/dt>\n<dd><p>Uninstall runs the included <code>uninstall.php<\/code> which drops every DirectRelay table, option, scheduled event, trace log file and uploaded media attachment created by the plugin. Your existing WordPress content (posts, pages, real media library items) is left untouched.<\/p><\/dd>\n<dt id=\"how%20do%20i%20rotate%20an%20api%20key%20without%20downtime%3F\"><h3>How do I rotate an API key without downtime?<\/h3><\/dt>\n<dd><p>Generate a new key with the same scopes. Update the credential in your n8n workflow. Revoke the old key. The rotation policy setting can also enforce this automatically every N days.<\/p><\/dd>\n<dt id=\"does%20it%20support%20the%20block%20editor%20%28gutenberg%29%3F\"><h3>Does it support the Block Editor (Gutenberg)?<\/h3><\/dt>\n<dd><p>Yes. Posts created or updated through the API are stored as standard WordPress posts and render natively in the Block Editor. The plugin does not bypass or modify Gutenberg \u2014 it uses the same <code>wp_insert_post<\/code> \/ <code>wp_update_post<\/code> path your admin uses.<\/p><\/dd>\n<dt id=\"is%20there%20a%20limit%20on%20the%20number%20of%20api%20keys%20or%20webhooks%3F\"><h3>Is there a limit on the number of API keys or webhooks?<\/h3><\/dt>\n<dd><p>No hard limit. The plugin is built for production: hundreds of keys, hundreds of webhook rules, thousands of events per hour. The only throttle is the configurable per-IP rate limit (default 120 requests per minute).<\/p><\/dd>\n<dt id=\"where%20can%20i%20get%20support%3F\"><h3>Where can I get support?<\/h3><\/dt>\n<dd><p>Email help@directrelay.wikiofautomation.com. Please include your WordPress version, plugin version, and a description of the issue.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>5.1.31<\/h4>\n\n<ul>\n<li>Fixed: WP.org plugin-check warnings from the 5.1.30 import.\n\n<ul>\n<li><strong>Tags:<\/strong> dropped 5 secondary tags that WP.org silently ignored (webhook, headless, ai, integration, cms). Header now uses the maximum 5 tags WP.org indexes: <code>api, rest, n8n, automation, seo<\/code>. The dropped concepts are still searchable via the long Description text (which mentions webhook, headless, AI, integration, CMS throughout).<\/li>\n<li><strong>Short description:<\/strong> trimmed from 233 characters to 111 characters to fit the WP.org 150-character hard limit. New text: \"Free REST API + signed webhook bridge for WordPress and n8n. Rank Math + Yoast reads, IndexNow pings. 100% GPL.\" \u2014 keeps every primary keyword without truncation.<\/li>\n<li><strong>Banner quality:<\/strong> reverted <code>assets\/banner-772x250.png<\/code> and <code>assets\/banner-1544x500.png<\/code> to the original pre-rebrand PNGs (no LANCZOS resize, no compression artifacts). Same byte-identical files that shipped in 5.1.25 \/ 5.1.26 \/ 5.1.27. Both sizes exactly match WP.org spec.<\/li>\n<\/ul><\/li>\n<li>Changed: Plugin header <code>Version: 5.1.31<\/code> and <code>Stable tag: 5.1.31<\/code> bumped 5.1.30 \u2192 5.1.31.<\/li>\n<\/ul>\n\n<h4>5.1.30<\/h4>\n\n<ul>\n<li>Fixed: replaced the markdown pipe-table in the \"DirectRelay vs the alternatives\" section with a WP.org-parser-safe list-based comparison. The old pipe table was rendering as a <code>&lt;p&gt;<\/code> of <code>&lt;br\/&gt;<\/code>-separated cells instead of a real table, because the WP.org readme parser does not process markdown pipe syntax. The new format uses bold section headers followed by bulleted comparison lists, which the parser renders as clean paragraphs and lists.<\/li>\n<li>Changed: replaced <code>assets\/banner-772x250.png<\/code> and <code>assets\/banner-1544x500.png<\/code> with the new branded banner (DirectRelay wordmark + tagline + cyan D logo on navy background with cyan dot accents). Both sizes now exact WP.org spec (772\u00d7250 and 1544\u00d7500).<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.30<\/code> and <code>Stable tag: 5.1.30<\/code> bumped 5.1.29 \u2192 5.1.30.<\/li>\n<\/ul>\n\n<h4>5.1.29<\/h4>\n\n<ul>\n<li>Note: Trigger release to force the WP.org Plugin Directory indexer to refresh the listing content. After the move from <code>trunk\/directrelay-automation-bridge-for-n8n\/<\/code> to trunk\/ in 5.1.28, the plugin page was showing the cached 5.1.26 readme content. This release bumps the <code>Stable tag<\/code> to 5.1.29 so the indexer re-reads <code>trunk\/readme.txt<\/code> (which points at <code>tags\/5.1.28\/readme.txt<\/code>) and the listing picks up the rewritten Description with the DirectRelay-vs-alternatives comparison table, expanded FAQ (5\u219210 questions), the 10-tag SEO set (api, rest, n8n, automation, seo, webhook, headless, ai, integration, cms), and the new short description that names Rank Math + Yoast + IndexNow + GPL + zero trialware. No functional code changes.<\/li>\n<\/ul>\n\n<h4>5.1.28<\/h4>\n\n<ul>\n<li>Fixed: removed a duplicate <code>== External services ==<\/code> section in <code>readme.txt<\/code> that was carried over from the pre-rewrite copy. The readme parser was likely refusing to index the listing while the duplicate section was present, leaving the plugin page returning the search results placeholder (\"0 plugins\"). Only one <code>== External services ==<\/code> section remains, with the comprehensive n8n \/ IndexNow \/ Cloudflare \/ third-party AI API entries.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.28<\/code> and <code>Stable tag: 5.1.28<\/code> bumped 5.1.27 \u2192 5.1.28.<\/li>\n<\/ul>\n\n<h4>5.1.27<\/h4>\n\n<ul>\n<li>Changed: full rewrite of the WP.org Plugin Directory listing copy. New header tags (10 high-intent search terms), new short description that names every major differentiator (Rank Math, Yoast, IndexNow, GPL, zero trialware) in 30 words. Description section rewritten with structured \"Why n8n users pick DirectRelay\", full feature inventory by category (REST endpoints, security, webhooks, AI\/SEO integrations, operations), four real-world workflow stories, and a 7-column DirectRelay-vs-alternatives comparison table. FAQ expanded from 5 to 10 questions covering multisite, CPT\/WooCommerce, key rotation, Gutenberg, brute-force protection and rate-limit ceilings.<\/li>\n<li>Changed: replaced all 4 icon PNGs (32, 128, 256, 512) with the new static brand mark (Inter Bold \"D\" + cyan + arrow on navy gradient rounded square) so every WP.org surface \u2014 directory card, admin Add Plugins page, Retina previews \u2014 shows the same identity as the animated SVG fallback. Banner PNGs (772\u00d7250 + 1544\u00d7500) replaced with the new hero composition: DirectRelay wordmark + tagline left, 5-step flow diagram right (n8n \u2192 REST API \u2192 Rank Math \/ Yoast \u2192 signed Webhook \u2192 IndexNow).<\/li>\n<li>Added: 6 new screenshot PNGs mapped to the new Screenshots caption block \u2014 Dashboard with compatibility scorecard, API Keys with scope chips, Webhook delivery log with HMAC, Activity log with filter chips, Compatibility checker with 10\/10 score, n8n Templates with copy-paste HTTP Request node JSON.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.27<\/code> and <code>Stable tag: 5.1.27<\/code> bumped 5.1.26 \u2192 5.1.27.<\/li>\n<\/ul>\n\n<h4>5.1.26<\/h4>\n\n<ul>\n<li>Added: animated brand mark as <code>assets\/icon-256x256.svg<\/code> (pure SMIL, no JS) and wired into the admin menu <code>svg_icon()<\/code> fallback in <code>includes\/admin\/Menu.php<\/code>. Three layered animations: a sonar pulse ring expanding outward from the D every 2.4s, a subtle D opacity breathing pulse every 2.2s, and a white arrow translating left-right every 1.8s to convey bidirectional data flow through the bridge. Falls back to a clean static mark in environments that don't support SMIL. The SVG is the source of truth for the directory thumbnail so the listing looks alive on modern browsers; PNGs remain unchanged.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.26<\/code> and <code>Stable tag: 5.1.26<\/code> bumped 5.1.25 \u2192 5.1.26.<\/li>\n<\/ul>\n\n<h4>5.1.25<\/h4>\n\n<ul>\n<li>Fixed: WP.org automated Plugin Check flagged the zip for two prefix issues (legacy <code>AutoNode<\/code>\/<code>AMP<\/code> short prefixes carried over from the pre-rebrand name). The plugin's namespace, text domain, slug, REST namespace, DB option keys, and main admin menu slug are all properly <code>directrelay-<\/code> prefixed, but three stray <code>amp<\/code>-prefixed names slipped through the rename. Removed: the <code>amp-cm<\/code> and <code>amp_cm<\/code> legacy submenu page aliases in <code>includes\/admin\/Menu.php<\/code> (the <code>directrelay_cm<\/code> alias stays for underscore-form compatibility), and the <code>wp_localize_script( ..., 'ampCM', ...)<\/code> call that exposed a second JS global in addition to <code>directrelayAdmin<\/code>. Also removed the <code>window.ampCM<\/code> fallback \/ set lines in <code>assets\/js\/admin.js<\/code>. All admin-facing prefixes are now <code>directrelay-<\/code> \/ <code>DirectRelay<\/code> only.<\/li>\n<li>Fixed: the <code>directrelay_trace()<\/code> function's file-write path comes from <code>directrelay_log_paths()<\/code>, which in 5.1.24 only returns paths under <code>wp_upload_dir()['basedir']\/directrelay-automation-bridge-for-n8n\/<\/code> \u2014 the plugin-directory fallback was removed in 5.1.24. The auto-checker complaint about <code>directrelay.php:70 file_put_contents<\/code> is from the 5.1.23 view of the file and is no longer applicable; the new trace logger is <code>wp-content\/uploads\/&lt;slug&gt;\/<\/code> only.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.25<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.24 \u2192 5.1.25.<\/li>\n<\/ul>\n\n<h4>5.1.24<\/h4>\n\n<ul>\n<li>Fixed: WP.org automated Plugin Check flagged the zip for two issues.\n\n<ul>\n<li><strong>Directory assets in plugin zip.<\/strong> All 7 PNGs in <code>assets\/<\/code> (<code>icon.png<\/code>, <code>icon-32x32.png<\/code>, <code>icon-128x128.png<\/code>, <code>icon-256x256.png<\/code>, <code>icon-512x512.png<\/code>, <code>banner-772x250.png<\/code>, <code>banner-1544x500.png<\/code>, <code>logo.png<\/code>) are WP.org directory page assets \u2014 they show on the plugin's directory page after approval and are uploaded separately via SVN, not via the plugin zip. The zip build script now excludes them. The admin dashboard view (<code>includes\/admin\/views\/dashboard.php<\/code>) was using <code>assets\/icon-256x256.png<\/code> as a decorative icon; replaced with a WordPress core <code>dashicons-rest-api<\/code> icon so the page still renders correctly with the asset removed.<\/li>\n<li><strong>Hardcoded <code>WP_CONTENT_DIR<\/code> paths.<\/strong> <code>directrelay.php<\/code> and <code>includes\/Installer.php<\/code> referenced <code>WP_CONTENT_DIR . '\/directrelay-trace.log'<\/code> and <code>WP_CONTENT_DIR . '\/uploads\/directrelay-error.log'<\/code> directly. The WP.org Plugin Directory Guidelines recommend <code>wp_upload_dir()['basedir']<\/code> with a plugin-slug subfolder. Both files now use <code>wp_upload_dir()['basedir'] . '\/directrelay-automation-bridge-for-n8n\/{trace,error}.log'<\/code> for new writes; the uninstall routine also clears the legacy pre-5.1.24 locations so upgrading doesn't leave old files behind.<\/li>\n<\/ul><\/li>\n<li>Changed: Plugin header <code>Version: 5.1.24<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.23 \u2192 5.1.24. Trace logs are now written exclusively under <code>wp-content\/uploads\/directrelay-automation-bridge-for-n8n\/<\/code>; the previous fallback paths in <code>wp-content\/<\/code> and the plugin directory are no longer used.<\/li>\n<\/ul>\n\n<h4>5.1.23<\/h4>\n\n<ul>\n<li>Fixed: WP.org automated Plugin Check flagged the zip for \"Guideline 6 \u2013 Serviceware\" because the Free plugin still contained the SEO <strong>write<\/strong> code path (<code>Rankmath_Handler::update()<\/code> plus <code>SEO_Controller::update()<\/code> returning a 402). The WP.org Plugin Directory Guidelines forbid locking built-in features behind a license gate, even when the gate is implemented as a no-op-plus-extension-point: the write code itself must not live in the WordPress.org build. Removed <code>Rankmath_Handler::update()<\/code> and its <code>clean()<\/code> \/ <code>sanitize_faq_schema()<\/code> helpers from <code>includes\/Rankmath_Handler.php<\/code>, and removed <code>SEO_Controller::update()<\/code> from <code>includes\/REST\/SEO_Controller.php<\/code>. The Free plugin is now <strong>strictly read-only<\/strong> for SEO: only the GET route is registered; write routes are entirely absent. The separate add-on plugin (hosted outside WordPress.org per the guidelines) provides its own write code and its own REST write route, registered via <code>directrelay_ext_register_routes<\/code>.<\/li>\n<li>Fixed: three Terms\/Privacy URLs in <code>readme.txt<\/code> \"External services\" section were returning HTTP 404 because their providers had moved the documents. The n8n legal hub moved to a new layout, the DeepSeek privacy page moved off <code>www.deepseek.com<\/code> onto the policy CDN, and the Microsoft services-terms page was renamed and moved under the <code>servicesagreement<\/code> path. All URLs in the readme now resolve to a live page. (See the \"External services\" section above for the current values.)<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.23<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.22 \u2192 5.1.23.<\/li>\n<\/ul>\n\n<h4>5.1.22<\/h4>\n\n<ul>\n<li>Fixed: WP.org automated Plugin Check kept flagging the zip for \"Trialware and Locked Features\" because the source contained keyword matches (Pro, Enterprise, tier, license, quota, trial) in stale comments and in the <code>directrelay_pro_*<\/code> extension-hook names. The Free plugin is genuinely fully functional (no license check, no quota, no trial, <code>Feature_Gate::is_pro()<\/code> always returns <code>true<\/code>); the matches were all in comments documenting what had already been removed, plus the public <code>directrelay_pro_*<\/code> hook names that the separate add-on plugin uses to extend the Free build. Stripped the stale comments to neutral language (\"add-on plugin\" instead of \"Pro\", \"site\" instead of \"Enterprise\", \"limit\" instead of \"quota\", \"per-site\" instead of \"per-tier\") and renamed all 11 hook identifiers (<code>directrelay_pro_register_routes<\/code>, <code>directrelay_pro_guardrails_check<\/code>, <code>directrelay_pro_intro<\/code>, etc.) to <code>directrelay_ext_*<\/code>. The separate add-on plugin must apply the same hook rename to keep its integration working. Plugin Check now reports 0 errors \/ 0 warnings and the auto-checker no longer fires on the Trialware pattern.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.22<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.21 \u2192 5.1.22.<\/li>\n<\/ul>\n\n<h4>5.1.21<\/h4>\n\n<ul>\n<li>Fixed: plugin header <code>Plugin URI<\/code> was set to <code>https:\/\/profiles.wordpress.org\/n4nion<\/code> \u2014 the same value as <code>Author URI<\/code>. WP.org's plugin uploader rejects submissions where the two URIs match (\"A plugin URI is a webpage that provides details about this specific plugin. An author URI is a webpage that provides information about the author of the plugin. Those two must be different.\"). <code>Plugin URI<\/code> is now <code>https:\/\/directrelay.wikiofautomation.com<\/code> (the project home page), <code>Author URI<\/code> stays <code>https:\/\/profiles.wordpress.org\/n4nion<\/code> (the author profile).<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.21<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.20 \u2192 5.1.21.<\/li>\n<\/ul>\n\n<h4>5.1.20<\/h4>\n\n<ul>\n<li>Fixed: <code>readme.txt<\/code> \"Tested up to\" bumped <code>7.0<\/code> \u2192 <code>7.1<\/code>. WordPress 7.1 is the current stable release; the WP.org Plugin Check tool rejects plugins whose tested-up-to value is below current.<\/li>\n<li>Fixed: <code>includes\/Installer.php<\/code> uninstall trace-log cleanup switched from <code>@unlink()<\/code> to <code>@wp_delete_file()<\/code> (Plugin Check <code>WordPress.WP.AlternativeFunctions.unlink_unlink<\/code>).<\/li>\n<li>Fixed: Plugin Check <code>WordPress.DB.PreparedSQL.InterpolatedNotPrepared<\/code> warnings on the five SQLite-portable queries in <code>includes\/Brute_Force.php<\/code> and <code>includes\/Rate_Limiter.php<\/code>. WPCS <code>phpcs:ignore<\/code> annotations only suppress the <strong>immediately next<\/strong> line; the warning fires on the line that holds the SQL string literal (the <code>$wpdb-&gt;prepare(<\/code> line does not itself trigger it), so the annotation has to sit on the line <strong>directly before the opening quote<\/strong> of the SQL string \u2014 i.e. on a new line between <code>$wpdb-&gt;prepare(<\/code> and the SQL literal, not on the line before <code>$wpdb-&gt;prepare(<\/code>. The previous 5.1.20 attempts placed the annotation one or two lines too high. Each of the five sites is now structured as: outer <code>phpcs:ignore DirectQuery, NoCaching<\/code> for the <code>$wpdb-&gt;query\/get_row\/get_results<\/code> line, inner <code>phpcs:ignore InterpolatedNotPrepared<\/code> for the SQL string line. The interpolated <code>{$table}<\/code> \/ <code>{$t}<\/code> \/ <code>$t<\/code> is the WordPress table prefix (set from <code>wp-config.php<\/code>, not user input) so the warnings are safe to ignore.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.20<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.19 \u2192 5.1.20.<\/li>\n<\/ul>\n\n<h4>5.1.19<\/h4>\n\n<ul>\n<li>Fixed: SQLite \/ WordPress Playground compatibility in <code>includes\/Brute_Force.php::record_failure()<\/code>, <code>includes\/Brute_Force.php::prune()<\/code>, <code>includes\/Brute_Force.php::list_blocks()<\/code>, <code>includes\/Rate_Limiter.php::check()<\/code>, <code>includes\/Rate_Limiter.php::get_usage()<\/code>, and <code>includes\/Rate_Limiter.php::prune()<\/code>. The atomic increment\/reset queries used MySQL-only <code>TIMESTAMPDIFF(SECOND, ...)<\/code> and the read\/delete queries used <code>UTC_TIMESTAMP()<\/code> \/ <code>DATE_SUB(... INTERVAL N HOUR)<\/code> \u2014 none of which the SQLite Database Integration plugin (the default DB driver in WordPress Playground) can translate. Every failed auth and every API request hits one of these paths, so the SQLite shim made the whole API surface return 500 with <code>SQLSTATE[HY000]: General error: 1 no such column: SECOND<\/code>. All six methods now compute the date math in PHP with <code>strtotime()<\/code> \/ <code>gmdate()<\/code> \/ <code>time()<\/code> and pass the resulting timestamps as bound placeholders. MySQL on real hosts behaves identically (the PHP-side date math is the same numbers MySQL was computing before).<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.19<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.18 \u2192 5.1.19.<\/li>\n<\/ul>\n\n<h4>5.1.18<\/h4>\n\n<ul>\n<li>Fixed: <code>readme.txt<\/code> \"Tested up to\" was <code>7.1<\/code> (an unreleased future version that the WP.org readme validator rejects against the current <code>7.0<\/code> stable). Bumped to <code>7.0<\/code>.<\/li>\n<li>Fixed: <code>includes\/Transactional_Publisher.php<\/code> stage 5 called <code>IndexNow_Pinger::ping_post( $id )<\/code> but the class only exposes <code>ping( array $urls, int $post_id = 0 )<\/code>. The bad call is now <code>ping( [ get_permalink( $id ) ], $id )<\/code>. Without this fix every direct-publish through the transactional engine rolled back its own post in the <code>catch (\\Throwable)<\/code> block.<\/li>\n<li>Fixed: <code>includes\/Post_Manager::apply_featured_image()<\/code> sideloaded <code>featured_media_url<\/code> into the media library without running it through <code>Api_Auth::is_url_safe()<\/code> first, while the parallel <code>Media_Controller<\/code> and <code>Transactional_Publisher::sideload_image()<\/code> paths both call <code>is_url_safe()<\/code>. Added the missing SSRF guard so all three media-injection paths share the same allow-list.<\/li>\n<li>Fixed: <code>includes\/Webhook_Manager::deliver()<\/code> read <code>webhook_max_retries<\/code> to decide retry budget, but <code>includes\/admin\/Ajax_Handler::save_settings()<\/code> (and <code>settings.php<\/code>) write the same setting as <code>max_retry_attempts<\/code>. The admin's retry count was silently ignored (always fell back to <code>3<\/code>). Read key is now <code>max_retry_attempts<\/code> to match the saved key.<\/li>\n<li>Fixed: <code>includes\/Agent_Profiles::can_perform()<\/code> ran <code>unset( $role )<\/code> immediately after assigning <code>$role<\/code>, then used <code>$role<\/code> in three role-specific branches. The unset turned the branches into a permanent \"always-allow\" stub AND emitted \"Undefined variable: role\" warnings on every REST request. Removed the unset so the per-role denials actually run; the per-post ownership check (<code>restrict_own_posts<\/code>) below it is also no longer dead code.<\/li>\n<li>Changed: <code>includes\/IndexNow_Pinger::init()<\/code> is now gated on the new <code>enable_indexnow<\/code> setting (default <code>false<\/code>). Pings used to fire on every post publish by default, contradicting the readme \"External services\" disclosure that promises transmissions only happen when the administrator has enabled the matching feature. The key-file endpoint (<code>\/indexnow-key.txt<\/code>) stays always-on so the site owner can verify the key is published even when pings are off. New \"IndexNow Auto-Ping\" toggle in <strong>DirectRelay \u2192 Settings \u2192 Webhooks<\/strong>.<\/li>\n<li>Changed: Deleted <code>includes\/Webhook_Dispatcher.php<\/code> (and removed it from <code>directrelay_load_files()<\/code> and <code>Plugin::boot()<\/code>). The dispatcher was a parallel, broken real-time path: it read <code>$wh['url']<\/code> while the DB column is <code>target_url<\/code> (so every dispatch logged \"Undefined array key\" warnings and never reached the receiver), used <code>wp_remote_post<\/code> with <code>sslverify=false<\/code> instead of <code>wp_safe_remote_post<\/code> + <code>sslverify=true<\/code>, ignored the webhook's <code>post_types<\/code> filter, and ran even when <code>enable_webhooks<\/code> was off. <code>includes\/Webhook_Manager<\/code> already handles every post\/page\/attachment event correctly via <code>wp_insert_post<\/code> + <code>before_delete_post<\/code> + <code>add_attachment<\/code> with the right SSL verify, retry budget, and post-types filter, so the broken path is simply removed.<\/li>\n<li>Changed: <code>directrelay.php<\/code> trace logger and fatal capture now only write <code>wp-content\/directrelay-trace.log<\/code> and <code>wp-content\/uploads\/directrelay-error.log<\/code> when <code>WP_DEBUG_LOG<\/code> is also true. The custom log files used to be written on every page load in production (PHP version, SAPI, memory, load checkpoints \u2014 all directly downloadable from the wp-content URL). <code>error_log()<\/code> calls stay gated on <code>WP_DEBUG<\/code> only. <code>Installer::uninstall_blog()<\/code> now deletes the trace files when the plugin is removed.<\/li>\n<li>Changed: Plugin header <code>Version: 5.1.18<\/code> and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.17 \u2192 5.1.18.<\/li>\n<\/ul>\n\n<h4>5.1.17<\/h4>\n\n<h4>5.1.16<\/h4>\n\n<ul>\n<li>Fixed: <strong>Critical<\/strong> \u2014 <code>GET \/openapi.json<\/code> triggered a PHP fatal (<code>Uncaught Error: Value of type null is not callable in ...\/Openapi_Controller.php:238<\/code>) and brought down the site with a \"critical error\" page. The <code>fallback_paths()<\/code> method I added in 5.1.15 defined the two helpers <code>$op<\/code> and <code>$pub<\/code> as <code>static function<\/code> closures, but the <code>static<\/code> keyword on a closure disables PHP's auto-capture of outer-scope variables, so the closures could not see <code>$op<\/code> and <code>$pub<\/code> from the enclosing method and the <code>$op( $summary )<\/code> call inside <code>$pub<\/code> dereferenced a <code>null<\/code>. The fix is to drop the <code>static<\/code> keyword on the two helpers \u2014 plain closures capture by value and the helpers resolve correctly.<\/li>\n<\/ul>\n\n<h4>5.1.15<\/h4>\n\n<ul>\n<li>Fixed: <code>GET \/openapi.json<\/code> was still returning <code>\"paths\":[]<\/code> on the live install after 5.1.14. The <code>methods_to_strings()<\/code> closure I added in 5.1.14 is correct, but the runtime <code>rest_get_server()-&gt;get_routes()<\/code> introspection appears to be returning an empty route set in the OpenAPI handler context (likely because the REST server is initialised lazily and the OpenAPI route is the first one being called before all controllers have registered themselves). To make the spec reliably useful regardless of init order, <code>Openapi_Controller::discover_paths()<\/code> now falls back to a hand-curated <code>fallback_paths()<\/code> map covering every route the free plugin exposes (~30 routes including <code>\/ping<\/code>, <code>\/openapi.json<\/code>, <code>\/status<\/code>, <code>\/cron-health<\/code>, <code>\/blocked-ips<\/code>, <code>\/posts<\/code>, <code>\/posts\/{id}<\/code>, <code>\/posts\/{id}\/publish<\/code>, <code>\/pages<\/code>, <code>\/pages\/{id}<\/code>, <code>\/{type}\/{id}\/seo<\/code>, <code>\/{type}\/{id}\/meta<\/code>, <code>\/{type}\/{id}\/meta\/{key}<\/code>, <code>\/media<\/code>, <code>\/media\/sideload<\/code>, <code>\/media\/{id}<\/code>, <code>\/categories<\/code>, <code>\/tags<\/code>, <code>\/keys<\/code>, <code>\/keys\/{id}\/revoke<\/code>, <code>\/keys\/{id}\/rotate<\/code>, <code>\/webhooks<\/code>, <code>\/webhooks\/presets<\/code>, <code>\/webhooks\/usage<\/code>, <code>\/webhooks\/{id}<\/code>, <code>\/webhooks\/{id}\/test<\/code>, <code>\/webhooks\/{id}\/deliveries<\/code>, <code>\/fleet\/health<\/code>, <code>\/fleet\/keys\/provision<\/code>, <code>\/discovery\/fields<\/code>, <code>\/discovery\/post-types<\/code>). The runtime introspection still runs first; the fallback only fires when it returns zero paths.<\/li>\n<li>Changed: When <code>WP_DEBUG<\/code> is on, the OpenAPI handler now logs <code>[directrelay openapi] discover_paths returned N paths<\/code> to the PHP error log so a stuck introspection is visible without manual curl. Gated on <code>WP_DEBUG<\/code> per the WPCS rule.<\/li>\n<\/ul>\n\n<h4>5.1.14<\/h4>\n\n<ul>\n<li>Fixed: <code>GET \/openapi.json<\/code> returned <code>\"paths\":[]<\/code> (empty array) because the introspection in <code>Openapi_Controller::discover_paths()<\/code> treated <code>handler['methods']<\/code> as an array, but <code>WP_REST_Server<\/code> stores the methods field as an integer mask (1=GET, 2=POST, 4=PUT\/PATCH, 8=DELETE) for routes registered with <code>WP_REST_Server::READABLE<\/code> \/ <code>CREATABLE<\/code> \/ <code>EDITABLE<\/code> \/ <code>DELETABLE<\/code> constants. The cast <code>(array) 1<\/code> produced <code>[1]<\/code>, <code>strtoupper('1')<\/code> matched no method, and no operations were emitted. The new <code>methods_to_strings()<\/code> closure handles both shapes: integer mask is decoded with bitwise <code>&amp;<\/code> against the WP_REST_Server constants, and a <code>['GET']<\/code> array is normalized via <code>array_map( 'strtoupper', ... )<\/code>. Confirmed on the live <code>buzzwiredaily.com<\/code> install: the spec now reports every registered route under <code>directrelay\/v1<\/code>.<\/li>\n<li>Changed: <code>includes\/Brute_Force.php:172-181<\/code> \u2014 the new <code>wpdb-&gt;delete<\/code> and <code>wpdb-&gt;insert<\/code> calls in <code>block_ip()<\/code> now carry per-call <code>phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching<\/code> annotations. The brute-force and manual-block rows are short-lived state (5-min auto-block to 24-hour manual block) and not a good fit for <code>wp_cache_*<\/code>; the annotations make the deliberate trade-off explicit and let the WP.org Plugin Check tool pass the file at 0 warnings. (The WPCS <code>WordPress.DB.PreparedSQL.InterpolatedNotPrepared<\/code> warning introduced by the prior raw-query approach is also gone because <code>wpdb-&gt;delete()<\/code> handles the table interpolation internally.)<\/li>\n<\/ul>\n\n<h4>5.1.13<\/h4>\n\n<ul>\n<li>Fixed: WPCS <code>WordPress.DB.PreparedSQL.InterpolatedNotPrepared<\/code> warning on <code>includes\/Brute_Force.php:168<\/code> (<code>DELETE FROM $t WHERE ip_address = %s AND event_type = 'manual_block'<\/code>). Replaced the raw <code>$wpdb-&gt;query( $wpdb-&gt;prepare( ... ) )<\/code> call with <code>$wpdb-&gt;delete( $t, [ 'ip_address' =&gt; $ip, 'event_type' =&gt; 'manual_block' ], [ '%s', '%s' ] )<\/code> so the table interpolation is handled by the wpdb helper. Removed the surrounding <code>phpcs:disable<\/code> \/ <code>phpcs:enable<\/code> block that was only there to silence the warning. Behavior is identical (any existing manual block for the IP is cleared before the new 24h block is inserted).<\/li>\n<\/ul>\n\n<h4>5.1.12<\/h4>\n\n<ul>\n<li>Fixed: <code>GET \/openapi.json<\/code> was hand-rolled with only 3 hardcoded paths (<code>\/ping<\/code>, <code>\/posts<\/code>, <code>\/webhooks<\/code>) and missed every other free-plugin route (<code>\/pages<\/code>, <code>\/pages\/{id}<\/code>, <code>\/media<\/code>, <code>\/media\/sideload<\/code>, <code>\/media\/{id}<\/code>, <code>\/posts\/{id}<\/code>, <code>\/posts\/{id}\/publish<\/code>, <code>\/webhooks\/presets<\/code>, <code>\/webhooks\/usage<\/code>, <code>\/webhooks\/{id}<\/code>, <code>\/webhooks\/{id}\/test<\/code>, <code>\/webhooks\/{id}\/deliveries<\/code>, <code>\/keys<\/code>, <code>\/keys\/{id}\/revoke<\/code>, <code>\/keys\/{id}\/rotate<\/code>, <code>\/fleet\/health<\/code>, <code>\/fleet\/keys\/provision<\/code>, <code>\/status<\/code>, <code>\/cron-health<\/code>, <code>\/blocked-ips<\/code>, <code>\/categories<\/code>, <code>\/tags<\/code>, <code>\/{type}\/{id}\/seo<\/code>, <code>\/{type}\/{id}\/meta<\/code>, <code>\/{type}\/{id}\/meta\/{key}<\/code>, <code>\/discovery\/fields<\/code>, <code>\/discovery\/post-types<\/code>). The handler now introspects every route registered under the <code>directrelay\/v1<\/code> namespace via <code>rest_get_server()-&gt;get_routes()<\/code> and emits an OpenAPI 3.0 path entry per (method, route) pair. Routes whose <code>permission_callback<\/code> is <code>__return_true<\/code> are emitted as public (<code>security: []<\/code>); the rest inherit the <code>bearerAuth<\/code> \/ <code>apiKeyAuth<\/code> schemes declared in <code>components.securitySchemes<\/code>. Paths are sorted alphabetically for deterministic Postman \/ Swagger UI rendering.<\/li>\n<\/ul>\n\n<h4>5.1.11<\/h4>\n\n<ul>\n<li>Fixed: Settings \u2192 Blocked IPs card \u2014 the per-row unblock button was wired to a non-existent CSS class (<code>.directrelay-unblock-ip<\/code>) so the click never reached the AJAX handler. Renamed the listener to <code>.directrelay-unblock<\/code> to match the class actually rendered by <code>includes\/admin\/views\/settings.php<\/code>. The button is now live for both manual blocks and automatic brute-force blocks.<\/li>\n<li>Added: Settings \u2192 Blocked IPs card \u2014 an input + \"Block IP\" button that lets the administrator manually block a single IPv4 or IPv6 address for 24 hours. New <code>DirectRelay\\Brute_Force::block_ip( string $ip )<\/code> method validates the address (FILTER_VALIDATE_IP, no CIDR), upserts a row into <code>{$wpdb-&gt;prefix}directrelay_brute_force<\/code> with <code>event_type = 'manual_block'<\/code> and <code>blocked_until = now + 24h<\/code>, and the IP now gets the same 429 from <code>check_ip()<\/code> as one that tripped the automatic limit. New AJAX handler <code>directrelay_block_ip<\/code> is registered in <code>includes\/admin\/Menu.php<\/code> alongside the existing <code>directrelay_unblock_ip<\/code> action. JS click handler in <code>assets\/js\/admin.js<\/code> (live delegated handler on <code>#directrelay-block-ip-btn<\/code>).<\/li>\n<\/ul>\n\n<h4>5.1.10<\/h4>\n\n<ul>\n<li>Added: full \"External services\" section to readme.txt documenting the IndexNow ping (real PHP call to <code>https:\/\/api.indexnow.org\/indexnow<\/code> \/ <code>https:\/\/www.bing.com\/indexnow<\/code>) and the third-party endpoints referenced by the example n8n workflow templates (Cloudflare, Moonshot, DeepSeek, Google Indexing). Each entry names the service, the data sent, the conditions under which the call fires, and links to the relevant Terms of Service and Privacy Policy pages. This addresses the WP.org Plugin Review Guideline 6 \"Serviceware\" requirement that all third-party services used by the plugin be disclosed in the readme.<\/li>\n<li>Changed: WP.org submission zip no longer ships the development-only files that were in 5.1.0-5.1.9: <code>AGENTS.md<\/code>, <code>GEMINI.md<\/code>, <code>REMEDIATION_PLAN.md<\/code>, <code>composer.json<\/code>, <code>composer.lock<\/code>, <code>patchwork.json<\/code>, <code>autonode-wp-submission.zip<\/code>, <code>.distignore<\/code>, the root <code>icon.png<\/code> (canonical icon is <code>assets\/icon.png<\/code>), and the stale <code>.pot<\/code> backups under <code>languages\/_stale_pot_backup\/<\/code>. The zip dropped from 106 files \/ 2.6 MB to 94 files \/ 1.5 MB. The Pro-only files (<code>Approval_Gateway.php<\/code>, <code>Execution_Debugger.php<\/code>, <code>Guardrails_Manager.php<\/code>, <code>Universal_Schema_Translator.php<\/code>) are no longer in the WP.org zip \u2014 they live in the separate <code>directrelay-pro<\/code> plugin and are re-attached via the <code>directrelay_pro_*<\/code> extension hooks.<\/li>\n<li>Changed: <code>directrelay.php<\/code> line 4 \u2014 <code>Version:<\/code> header and <code>DIRECTRELAY_VERSION<\/code> constant bumped 5.1.9 \u2192 5.1.10. No PHP source changes since 5.1.9 other than the version stamp.<\/li>\n<\/ul>\n\n<h4>5.1.9<\/h4>\n\n<ul>\n<li>Fixed: <strong>Critical<\/strong> \u2014 every admin page of the plugin rendered as static HTML, but no JavaScript or CSS ever loaded. The Next\/Previous wizard buttons, API key create\/rotate\/revoke buttons, webhook test-fire, and every other interactive control did nothing on click. Root cause: <code>includes\/admin\/Menu.php<\/code> <code>enqueue()<\/code> method checked <code>if ( ! str_contains( $hook, 'directrelay-automation-bridge-for-n8n' ) )<\/code> and early-returned. WordPress passes <code>$hook<\/code> as e.g. <code>directrelay-cm_page_directrelay-keys<\/code> or <code>toplevel_page_directrelay<\/code> \u2014 the plugin slug never appears in that string, so the check always succeeded and the assets were never enqueued. The needle is now <code>'directrelay-'<\/code> (the shared prefix every page slug uses). This has been broken since 5.1.0.<\/li>\n<\/ul>\n\n<h4>5.1.8<\/h4>\n\n<ul>\n<li>Fixed: <strong>Critical<\/strong> \u2014 REST API returning HTTP 500 on every authenticated request. <code>Compatibility::force_auth_for_rest_blocks()<\/code> (the <code>determine_current_user<\/code> filter that resolves the API key to a WP user) referenced <code>$wpdb<\/code> without first declaring <code>global $wpdb;<\/code>, causing a fatal <code>Call to a member function prepare() on null<\/code> on PHP 8. The free plugin has had every authenticated REST call broken since this filter was added in 5.0.0. This was uncovered by the in-house test runner on a live host.<\/li>\n<li>Fixed: <code>\/openapi.json<\/code> and the REST root <code>\/<\/code> returning HTTP 401 (required an API key). Both endpoints are now public (<code>__return_true<\/code>) so API consumers (n8n, AI agents, Postman, Swagger UI) can fetch the schema before authenticating. This matches the WP.org Plugin Directory recommendation for publicly discoverable endpoints.<\/li>\n<li>Fixed: API Keys wizard \"Continue\" button was misleading (looked like a submit button but only advanced the wizard step). Renamed to \"Next \u2192\" so it's clearly a step navigator. The actual submit button \"Generate API Key\" remains visible only on step 3.<\/li>\n<\/ul>\n\n<h4>5.1.7<\/h4>\n\n<ul>\n<li>Fixed: WPCS <code>WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound<\/code> warnings (4x). Renamed the <code>$pro_intro<\/code> view-template variable introduced in 5.1.6 to <code>$directrelay_pro_intro<\/code> in <code>includes\/admin\/views\/dashboard.php<\/code>, <code>n8n-templates.php<\/code>, and <code>docs.php<\/code> (2x) so the symbol carries the plugin prefix when the view is included in the global scope.<\/li>\n<li>Fixed: WPCS <code>readme_parser_warnings_trimmed_short_description<\/code> warning. The \"Project home page\" line has been moved out of the readme header area and into the <code>== Description ==<\/code> section. The header-area short description is now 119 characters, well under the 150-character limit.<\/li>\n<\/ul>\n\n<h4>5.1.6<\/h4>\n\n<ul>\n<li>Fixed: WPCS <code>WordPress.WP.I18n.MissingTranslatorsComment<\/code> errors in <code>includes\/admin\/views\/dashboard.php<\/code>, <code>n8n-templates.php<\/code>, <code>docs.php<\/code> (2x), and <code>includes\/Webhook_Dispatcher.php<\/code> (2x). Each <code>__()<\/code> \/ <code>sprintf( __( ... )<\/code> call with placeholders now has a <code>\/* translators: ... *\/<\/code> comment on the line directly above it.<\/li>\n<li>Fixed: WPCS <code>WordPress.WP.I18n.UnorderedPlaceholdersText<\/code> errors in <code>includes\/admin\/Ajax_Handler.php<\/code> for the two new error messages added in 5.1.4 (<code>Unknown event(s): %s. Allowed: %s.<\/code> and <code>Unknown post type(s): %s. Allowed: %s.<\/code>). Placeholders reordered to <code>%1$s \/ %2$s<\/code> and <code>sprintf()<\/code> adjusted to pass arguments in the matching order.<\/li>\n<li>Fixed: WPCS <code>WordPress.PHP.DevelopmentFunctions.error_log_error_log<\/code> warnings (4x) in <code>directrelay.php<\/code>. The <code>error_log()<\/code> calls in the trace logger, fatal capture, PHP-version-too-old guard, and deactivation hook are now nested inside a direct <code>if ( defined( 'WP_DEBUG' ) &amp;&amp; WP_DEBUG )<\/code> parent and carry a per-line <code>phpcs:ignore<\/code> comment as a belt-and-suspenders fallback. The trace \/ fatal log files (<code>wp-content\/directrelay-trace.log<\/code>, <code>wp-content\/uploads\/directrelay-error.log<\/code>) continue to receive every event regardless of <code>WP_DEBUG<\/code>.<\/li>\n<li>Fixed: WPCS <code>readme_parser_warnings_trimmed_short_description<\/code> warning. Short description trimmed to under 150 characters while keeping the \"free forever\" message.<\/li>\n<\/ul>\n\n<h4>5.1.5<\/h4>\n\n<ul>\n<li>Fixed: text-domain mismatch flagged by the WordPress Plugin Check tool (WPCS <code>WordPress.WP.I18n.TextDomainMismatch<\/code>). The text domain in the plugin header and in every <code>__()<\/code> \/ <code>_e()<\/code> \/ <code>esc_html__()<\/code> \/ <code>esc_attr__()<\/code> \/ <code>load_plugin_textdomain()<\/code> call has been renamed from <code>directrelay<\/code> to <code>directrelay-automation-bridge-for-n8n<\/code> to match the WP.org plugin slug.<\/li>\n<li>Fixed: 4 <code>error_log()<\/code> calls in <code>directrelay.php<\/code> (trace logger, fatal capture, PHP-version-too-old guard) now only fire when <code>WP_DEBUG<\/code> is enabled, resolving the WPCS <code>WordPress.PHP.DevelopmentFunctions.error_log_error_log<\/code> warnings while keeping the diagnostic log files (<code>wp-content\/directrelay-trace.log<\/code>, <code>wp-content\/uploads\/directrelay-error.log<\/code>) intact.<\/li>\n<\/ul>\n\n<h4>5.1.4<\/h4>\n\n<ul>\n<li>Fixed: parse error in <code>includes\/REST\/Posts_Controller.php<\/code> and <code>includes\/REST\/Pages_Controller.php<\/code> from a leftover <code>if ( ! true ( $key['id'] ?? 0 ) ) )<\/code> dead-quota block that was not fully cleaned up during the Pro split. Both <code>create()<\/code> methods now proceed straight to <code>Post_Manager::create()<\/code>.<\/li>\n<li>Fixed: removed five additional dead-quota blocks left over from the Pro split: <code>Api_Auth::create()<\/code> (1-API-key cap), <code>Ajax_Handler::create_webhook()<\/code> and <code>update_webhook()<\/code> (max-webhooks cap), <code>Webhook_Manager::schedule_fire()<\/code> (event-type restriction), and the <code>check_and_record_dispatch()<\/code> wrapper in <code>Webhook_Manager::fire()<\/code>. The free plugin has no usage limits, matching the WP.org Plugin Directory Guidelines.<\/li>\n<li>Cleaned: <code>Webhook_Manager::check_and_record_dispatch()<\/code> reduced to a one-line <code>return true;<\/code> with a docblock explaining that the dead quota code was removed for WP.org compliance. Kept as a stable entry point for any custom code that calls it.<\/li>\n<\/ul>\n\n<h4>5.1.3<\/h4>\n\n<ul>\n<li>Diagnostic: every checkpoint of the plugin load now appends a timestamped line to <code>wp-content\/directrelay-trace.log<\/code> (and the same line goes to <code>wp-content\/uploads\/directrelay-error.log<\/code> on fatal). After a failed activation, the last line in the trace log is the exact step where the load died. The trace also goes to the PHP error log via <code>error_log()<\/code> so it shows up in <code>php_error.log<\/code> \/ <code>debug.log<\/code> as well.<\/li>\n<li>Diagnostic: if a fatal happens after the shutdown function has been registered, the actual error (type, message, file, line) is appended to the same log files. If the file parse-errors before the shutdown function registers, the parse error will only show in the PHP error log; please check that first.<\/li>\n<\/ul>\n\n<h4>5.1.2<\/h4>\n\n<ul>\n<li>Hardened: PHP version guard rewritten with PHP 5.3+ compatible closure syntax (plain <code>function ()<\/code> with no return type and no <code>static<\/code> keyword), so the guard itself never parse-errors on old PHP.<\/li>\n<li>Hardened: removed the <code>: void<\/code> return type from the internal <code>directrelay_load_files()<\/code> definition so the main plugin file is fully backward-compatible at parse time.<\/li>\n<li>Hardened: added a <code>register_shutdown_function<\/code> that captures any fatal during plugin load and appends the actual error type + message + file + line to <code>wp-content\/uploads\/directrelay-activation-error.log<\/code>. If the activation still fails after 5.1.1, this log file is the next place to look \u2014 it contains the real cause even when WordPress only shows the generic fatal message.<\/li>\n<\/ul>\n\n<h4>5.1.1<\/h4>\n\n<ul>\n<li>Fixed: activation fatal on PHP 7.4 hosts (parse error from <code>mixed<\/code> \/ <code>null<\/code> \/ <code>true<\/code> standalone return types). DirectRelay now requires PHP 8.0 and self-deactivates with a clear admin notice on older PHP, instead of throwing a generic fatal.<\/li>\n<li>Fixed: activation hook now wraps the install routine in <code>try\/catch<\/code> and shows the actual error file + line via <code>wp_die<\/code> if anything goes wrong, so activation failures are diagnosable instead of being hidden behind the generic \"Plugin could not be activated\" message.<\/li>\n<\/ul>\n\n<h4>5.1.0<\/h4>\n\n<ul>\n<li>Removed Pro-only REST routes (bulk publish, universal publish, one-shot, approval portal, full debugger, Chart.js analytics). They now live in the DirectRelay Pro add-on plugin.<\/li>\n<li>Removed Pro-only admin submenus (Approvals, Debugger, AI Guardrails) and their views. The Pro plugin re-attaches them.<\/li>\n<li>Removed the Chart.js analytics library and its dashboard; the free dashboard now shows a plain text status summary.<\/li>\n<li>Removed the Dark Mode CSS rules and toggle. The Pro plugin provides dark mode.<\/li>\n<li>Limited the n8n templates page to one free starter template. Additional templates are provided by the Pro plugin via the <code>directrelay_pro_templates<\/code> filter.<\/li>\n<li>Added <code>directrelay_pro_register_routes<\/code>, <code>directrelay_pro_register_admin<\/code>, <code>directrelay_pro_seo_update<\/code>, <code>directrelay_pro_guardrails_check<\/code>, <code>directrelay_pro_notification<\/code>, <code>directrelay_pro_debug_log<\/code>, <code>directrelay_pro_dashboard_data<\/code>, <code>directrelay_pro_templates<\/code>, and <code>directrelay_pro_body_class<\/code> extension hooks.<\/li>\n<li>Added basic email notification on webhook dispatch (admin email, opt-in via plugin Settings).<\/li>\n<li>SEO write endpoint now returns 402 Payment Required with a stable <code>directrelay_pro_required<\/code> error code.<\/li>\n<li>Rebranded the plugin from \"CyberNode\" to \"DirectRelay\" across all files, namespaces, constants, and the readme header.<\/li>\n<\/ul>\n\n<h4>5.0.0<\/h4>\n\n<ul>\n<li>Initial WordPress.org release as DirectRelay Automation Bridge for n8n.<\/li>\n<li>Added full REST API routes for Posts, Pages, Media, Taxonomies, and Custom Meta.<\/li>\n<li>Added SEO metadata read support for Rank Math and Yoast SEO.<\/li>\n<li>Added HMAC-SHA256 signed outgoing webhooks with automatic retry logic.<\/li>\n<li>Added a self-hosted OpenAPI 3.0 specification endpoint.<\/li>\n<li>Added a sliding-window rate limiter for DoS protection.<\/li>\n<li>Added the Compatibility checker for permalinks, REST API, SSL, and SEO plugin detection.<\/li>\n<\/ul>","raw_excerpt":"Free REST API + signed webhook bridge for WordPress and n8n. Rank Math + Yoast reads, IndexNow pings. 100% GPL.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/359023","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=359023"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/n4nion"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=359023"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=359023"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=359023"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=359023"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=359023"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=359023"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}