{
  "openapi": "3.1.0",
  "info": {
    "title": "Royal Plugins Public API",
    "version": "1.0.0",
    "summary": "Marketing-site scan tools, badges, and links to the per-install Royal MCP OAuth-authenticated tool surface.",
    "description": "Two API surfaces are declared here:\n\n1. **Marketing-site public API** (hosted at royalplugins.com) — free security scanners, embeddable badges, and a rate-limited fetch proxy. Public, no authentication, rate-limited per IP.\n\n2. **Royal MCP tool API** (hosted per-customer-site at `{customer-wordpress-site}/wp-json/royal-mcp/v1/mcp`) — 209 MCP tools with OAuth 2.1 authorization. Scopes and security scheme are declared below in `components.securitySchemes.royalMcpOAuth`. The tool surface itself is documented at https://royalplugins.com/support/royal-mcp/ and via the MCP `tools/list` protocol call on any installed plugin.",
    "contact": {
      "name": "Royal Plugins Support",
      "url": "https://royalplugins.com/support/",
      "email": "support@royalplugins.com"
    },
    "license": {
      "name": "Marketing-site API — proprietary. Royal MCP plugin — GPL-2.0-or-later",
      "identifier": "GPL-2.0-or-later"
    }
  },
  "servers": [
    {
      "url": "https://royalplugins.com",
      "description": "Marketing-site production endpoint"
    },
    {
      "url": "https://{customer-wordpress-site}",
      "description": "Royal MCP tool API — hosted per-customer-site after Royal MCP plugin installation",
      "variables": {
        "customer-wordpress-site": {
          "default": "your-wordpress-site.example.com",
          "description": "Any WordPress site with the Royal MCP plugin installed and activated"
        }
      }
    }
  ],
  "externalDocs": {
    "description": "llms.txt agent-facing site index",
    "url": "https://royalplugins.com/llms.txt"
  },
  "tags": [
    {
      "name": "Scan Tools",
      "description": "Public security scanners — WordPress site checks, plugin code analysis, security headers, SSL certificates"
    },
    {
      "name": "Badges",
      "description": "Embeddable SVG badges for scan results — used by the marketing site + third-party sites showing Royal Plugins scan status"
    },
    {
      "name": "Fetch",
      "description": "Rate-limited HTTP proxy for browser-side tools (SSRF-safe)"
    },
    {
      "name": "Signup",
      "description": "Newsletter and founder-cohort signup endpoints"
    },
    {
      "name": "Royal MCP",
      "description": "Per-install MCP tool endpoint. Documented here as OpenAPI for discoverability; actual surface is served by the Royal MCP WordPress plugin"
    }
  ],
  "paths": {
    "/api/scan/wordpress": {
      "get": {
        "summary": "Scan a WordPress site for common security issues",
        "operationId": "scanWordpress",
        "tags": ["Scan Tools"],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "URL of the WordPress site to scan (must be publicly reachable)",
            "schema": {"type": "string", "format": "uri"}
          }
        ],
        "responses": {
          "200": {"description": "Scan complete", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ScanResult"}}}},
          "400": {"description": "Invalid URL"},
          "429": {"description": "Rate limited"},
          "502": {"description": "Site unreachable"}
        },
        "security": []
      }
    },
    "/api/scan/plugin-code": {
      "get": {
        "summary": "Static security analysis of a WordPress plugin from the wordpress.org plugin directory",
        "operationId": "scanPluginCode",
        "tags": ["Scan Tools"],
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "description": "wordpress.org plugin slug (e.g. `royal-mcp`)",
            "schema": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,60}$"}
          }
        ],
        "responses": {
          "200": {"description": "Scan complete", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PluginScanResult"}}}},
          "400": {"description": "Invalid slug"},
          "404": {"description": "Plugin not found on wordpress.org"},
          "429": {"description": "Rate limited"}
        },
        "security": []
      }
    },
    "/api/scan/headers": {
      "get": {
        "summary": "Check security headers on a given URL",
        "operationId": "scanHeaders",
        "tags": ["Scan Tools"],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "URL to check headers on",
            "schema": {"type": "string", "format": "uri"}
          }
        ],
        "responses": {
          "200": {"description": "Header analysis complete", "content": {"application/json": {"schema": {"type": "object"}}}},
          "400": {"description": "Invalid URL"},
          "429": {"description": "Rate limited"},
          "502": {"description": "URL unreachable"}
        },
        "security": []
      }
    },
    "/api/scan/ssl": {
      "get": {
        "summary": "Check SSL/TLS certificate details for a domain",
        "operationId": "scanSsl",
        "tags": ["Scan Tools"],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": false,
            "description": "Domain to check (mutually exclusive with `url`)",
            "schema": {"type": "string"}
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "URL to extract domain from (mutually exclusive with `domain`)",
            "schema": {"type": "string", "format": "uri"}
          }
        ],
        "responses": {
          "200": {"description": "SSL check complete", "content": {"application/json": {"schema": {"type": "object"}}}},
          "400": {"description": "Invalid domain or URL"},
          "429": {"description": "Rate limited"},
          "502": {"description": "SSL handshake failed"}
        },
        "security": []
      }
    },
    "/api/fetch": {
      "get": {
        "summary": "Rate-limited HTTP proxy fetch for browser-side tools",
        "description": "Streams a remote URL back to the caller. SSRF-protected against private IP ranges. Used by browser tools (SERP Preview, Meta Tag Checker, Schema Validator) that need to fetch public URLs without CORS blocking.",
        "operationId": "fetchProxy",
        "tags": ["Fetch"],
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Publicly reachable URL to fetch (private IPs blocked)",
            "schema": {"type": "string", "format": "uri"}
          }
        ],
        "responses": {
          "200": {"description": "Streamed response body", "content": {"*/*": {}}},
          "400": {"description": "Invalid URL or blocked host"},
          "429": {"description": "Rate limited"}
        },
        "security": []
      }
    },
    "/api/badges/security": {
      "get": {
        "summary": "SVG security badge for a plugin scan result",
        "operationId": "badgeSecurity",
        "tags": ["Badges"],
        "parameters": [
          {"name": "slug", "in": "query", "required": true, "schema": {"type": "string"}},
          {"name": "grade", "in": "query", "required": true, "schema": {"type": "string", "enum": ["A", "B", "C", "D", "F"]}},
          {"name": "token", "in": "query", "required": true, "description": "HMAC-SHA256 signature over slug+grade — prevents grade spoofing", "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "SVG badge", "content": {"image/svg+xml": {"schema": {"type": "string"}}}},
          "400": {"description": "Missing or invalid params"},
          "403": {"description": "Invalid token"}
        },
        "security": []
      }
    },
    "/api/badges/plugintests": {
      "get": {
        "summary": "SVG badge showing WordPress plugin test status for a wp.org slug",
        "operationId": "badgePluginTests",
        "tags": ["Badges"],
        "parameters": [
          {"name": "slug", "in": "query", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "SVG badge", "content": {"image/svg+xml": {"schema": {"type": "string"}}}},
          "400": {"description": "Missing slug"}
        },
        "security": []
      }
    },
    "/api/newsletter-signup": {
      "post": {
        "summary": "Add an email to the Royal Plugins newsletter",
        "operationId": "newsletterSignup",
        "tags": ["Signup"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": {"type": "string", "format": "email"},
                  "firstName": {"type": "string"},
                  "honeypot": {"type": "string", "description": "Anti-bot honeypot — must be empty"},
                  "utm_source": {"type": "string"},
                  "utm_medium": {"type": "string"},
                  "utm_campaign": {"type": "string"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Signup successful"},
          "400": {"description": "Invalid email or honeypot triggered"},
          "429": {"description": "Rate limited"}
        },
        "security": []
      }
    },
    "/wp-json/royal-mcp/v1/mcp": {
      "post": {
        "summary": "Royal MCP tool endpoint (per-install, hosted on customer WordPress site)",
        "description": "Not hosted on royalplugins.com. Every WordPress site with the Royal MCP plugin installed publishes a JSON-RPC MCP tool endpoint at this path. Streamable HTTP + SSE transports supported. Accepts standard MCP protocol calls (initialize, tools/list, tools/call, notifications, etc.). Documented here as OpenAPI for agent discoverability; actual endpoint responds to the JSON-RPC MCP protocol, not REST.",
        "operationId": "royalMcpTool",
        "tags": ["Royal MCP"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request per Model Context Protocol specification"
              }
            }
          }
        },
        "responses": {
          "200": {"description": "JSON-RPC 2.0 response (or SSE stream)"},
          "401": {"description": "Missing or invalid OAuth bearer token"},
          "403": {"description": "Token scope insufficient for requested tool"}
        },
        "security": [
          {"royalMcpOAuth": ["mcp:full"]},
          {"royalMcpOAuth": ["mcp:read"]},
          {"royalMcpOAuth": ["mcp:write"]},
          {"royalMcpOAuth": ["mcp:destructive"]},
          {"royalMcpOAuth": ["mcp:admin"]}
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "ScanResult": {
        "type": "object",
        "description": "Structured WordPress security scan result",
        "properties": {
          "success": {"type": "boolean"},
          "domain": {"type": "string"},
          "grade": {"type": "string", "enum": ["A", "B", "C", "D", "F"]},
          "score": {"type": "integer", "minimum": 0, "maximum": 100},
          "checks": {"type": "array", "items": {"type": "object"}},
          "wordpress": {"type": "object", "properties": {
            "detected": {"type": "boolean"},
            "version": {"type": ["string", "null"]},
            "version_exposed": {"type": "boolean"}
          }}
        }
      },
      "PluginScanResult": {
        "type": "object",
        "description": "Static security analysis result for a wordpress.org plugin",
        "properties": {
          "success": {"type": "boolean"},
          "slug": {"type": "string"},
          "grade": {"type": "string", "enum": ["A", "B", "C", "D", "F"]},
          "score": {"type": "integer", "minimum": 0, "maximum": 100},
          "issues": {"type": "array", "items": {"type": "object", "properties": {
            "severity": {"type": "string", "enum": ["critical", "high", "medium", "low", "info"]},
            "file": {"type": "string"},
            "line": {"type": "integer"},
            "message": {"type": "string"}
          }}}
        }
      }
    },
    "securitySchemes": {
      "royalMcpOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE (S256) — per-install authorization server hosted by Royal MCP plugin at each customer WordPress site. Discovery via RFC 8414 at `{customer-wordpress-site}/.well-known/oauth-authorization-server`. Marketing-site metadata document at https://royalplugins.com/.well-known/oauth-authorization-server documents the capability model.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://{customer-wordpress-site}/wp-json/royal-mcp/v1/oauth/authorize",
            "tokenUrl": "https://{customer-wordpress-site}/wp-json/royal-mcp/v1/oauth/token",
            "refreshUrl": "https://{customer-wordpress-site}/wp-json/royal-mcp/v1/oauth/token",
            "scopes": {
              "mcp:full": "Full access to every MCP tool the plugin exposes (default and current-shipping scope).",
              "mcp:read": "Read-only MCP tools — site inventory, content queries, health checks, analytics reads. No writes.",
              "mcp:write": "Non-destructive writes — create/update draft content, meta updates, safe configuration changes. Excludes deletes.",
              "mcp:destructive": "Destructive writes — deletes, bulk updates that overwrite data, irreversible operations. Requires undo-token support in the plugin (Pro tier).",
              "mcp:admin": "Full administrative access including WordPress capability escalations and plugin activation/deactivation."
            }
          }
        }
      }
    }
  }
}
