{"openapi":"3.1.0","info":{"title":"Rankwyn Universal Connector API","version":"1.0.0","description":"Platform-agnostic REST API for connecting any website (WordPress, Shopify, headless, or hand-rolled) to Rankwyn — SEO metadata push/pull, site audits, internal-linking suggestions, sitemap sync, and AI-assisted content tools. Every route (except the public `/api/sites/{siteId}/**` content endpoints) is authenticated with the per-site connector API key issued when a site is created in the Rankwyn dashboard. See also GraphQL at `/api/graphql` for the same read surface over a single endpoint.","contact":{"name":"Rankwyn","url":"https://rankwyn.com"}},"servers":[{"url":"https://app.rankwyn.com","description":"Current deployment"}],"tags":[{"name":"Handshake","description":"Connector setup / connection status"},{"name":"Metadata","description":"Per-page SEO metadata pull/push"},{"name":"Audit","description":"Whole-site SEO audit"},{"name":"Pages","description":"Page inventory"},{"name":"Internal Linking","description":"Internal link suggestions and orphan detection"},{"name":"Sync","description":"Sitemap / URL inventory sync"},{"name":"IndexNow","description":"IndexNow submission logging"},{"name":"Settings","description":"Read-only site settings pull"},{"name":"AI Tools","description":"Bulk AI-assisted content generation (BYO provider key)"},{"name":"Public Content","description":"Unauthenticated, crawler-facing site content"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Connector API key issued by `sites.create` in the Rankwyn dashboard. May also be sent as the `X-Rankwyn-Api-Key` header or `?api_key=` query param. Rankwyn stores only the SHA-256 hash of this key — treat it as a secret, it cannot be recovered if lost."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"OpenGraph":{"type":"object","properties":{"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image":{"type":"string","nullable":true}}},"Issue":{"type":"object","properties":{"factor":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]},"message":{"type":"string"}}},"SchemaItem":{"type":"object","additionalProperties":true,"description":"A single JSON-LD structured-data item, e.g. { \"@type\": \"Article\", ... }."}}},"paths":{"/api/agnostic/handshake":{"post":{"tags":["Handshake"],"summary":"Complete the connector handshake","description":"Verifies the API key and flips the site's connectionStatus to \"connected\". Call once during connector setup (WP plugin activation, JS snippet init, etc.).","responses":{"200":{"description":"Connected","content":{"application/json":{"schema":{"type":"object","properties":{"connected":{"type":"boolean"},"siteId":{"type":"string"},"platform":{"type":"string","enum":["wordpress","shopify","agnostic"]},"url":{"type":"string"},"connectionStatus":{"type":"string"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/meta":{"get":{"tags":["Metadata"],"summary":"Get the latest SEO metadata for a page","parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string"},"description":"Full page URL (get-or-creates the page row)."}],"responses":{"200":{"description":"Page metadata","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"pageId":{"type":"string"},"generated":{"type":"boolean"},"title":{"type":"string","nullable":true},"metaDescription":{"type":"string","nullable":true},"canonical":{"type":"string","nullable":true},"og":{"type":"object","properties":{"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image":{"type":"string","nullable":true}}},"schemaTypes":{"type":"array","items":{"type":"string"}},"schema":{"type":"array","items":{"$ref":"#/components/schemas/SchemaItem"}},"score":{"type":"number","nullable":true},"issues":{"type":"array","items":{"type":"object","properties":{"factor":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]},"message":{"type":"string"}}}},"recommendations":{"type":"array","items":{"type":"object"}},"lastGeneratedAt":{"type":"number","nullable":true}}}}}},"400":{"description":"Missing url param","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"tags":["Metadata"],"summary":"Push SEO metadata for a page","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string"},"wpPostId":{"type":"string"},"title":{"type":"string"},"metaDescription":{"type":"string"},"canonical":{"type":"string"},"robots":{"type":"string"},"focusKeywords":{"type":"array","items":{"type":"string"}},"og":{"type":"object","properties":{"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image":{"type":"string","nullable":true}}},"schema":{"type":"array","items":{"$ref":"#/components/schemas/SchemaItem"}},"contentType":{"type":"string","enum":["page","post","product","category"]}}}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"type":"object","properties":{"pageId":{"type":"string"},"url":{"type":"string"},"score":{"type":"number","nullable":true},"issues":{"type":"array","items":{"type":"object","properties":{"factor":{"type":"string"},"severity":{"type":"string","enum":["critical","warning","info"]},"message":{"type":"string"}}}}}}}}},"400":{"description":"Invalid body / missing url","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/audit":{"get":{"tags":["Audit"],"summary":"Run (or fetch) the whole-site SEO audit","description":"0-100 score + letter grade + Basic SEO / Advanced SEO / Content / Performance / Security category breakdown.","responses":{"200":{"description":"Audit report","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"502":{"description":"Audit computation failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/analyzer/audits":{"post":{"tags":["On-Page Analyzer"],"summary":"Queue a first-party audit of a URL on this site","description":"Fetches the URL as Googlebot and as a browser, optionally renders it, runs the versioned check library (evidence-tagged), scores it against its page type and generates fixes. Poll the returned URL until status is complete or failed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Full URL on this site"},"targetKeyword":{"type":"string"},"pageType":{"type":"string","description":"Override auto-detection (homepage, product, article, service, category, …)"}}}}}},"responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","properties":{"auditId":{"type":"string"},"status":{"type":"string"},"poll":{"type":"string"}}}}}},"400":{"description":"Invalid URL or URL not on this site","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited (60 audits per site per hour)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"get":{"tags":["On-Page Analyzer"],"summary":"List recent audits","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Audits","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/agnostic/analyzer/audits/{auditId}":{"get":{"tags":["On-Page Analyzer"],"summary":"Get an audit with its checks and fixes","parameters":[{"name":"auditId","in":"path","required":true,"schema":{"type":"string"}},{"name":"view","in":"query","required":false,"schema":{"type":"string","enum":["full","summary","checks","fixes"],"default":"full"}}],"responses":{"200":{"description":"Audit","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/analyzer/fixes":{"get":{"tags":["On-Page Analyzer"],"summary":"List approved fixes waiting to be applied","description":"CMS connectors pull these, apply them, then report back with state=applied. The next audit verifies them.","responses":{"200":{"description":"Approved fixes","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/agnostic/analyzer/fixes/{fixId}":{"post":{"tags":["On-Page Analyzer"],"summary":"Move a fix through its lifecycle","description":"Connectors apply the fix's `operation` (set_title, set_canonical, add_jsonld, redirect, …) for approved fixes, then report state=applied with `before` (the previous value) for rollback. Roll back by restoring `before` and posting state=suggested.","parameters":[{"name":"fixId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["state"],"properties":{"state":{"type":"string","enum":["approved","rejected","applied","suggested"]},"before":{"type":"string","description":"Previous value, captured before applying"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid transition","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/analyzer/topical-map":{"get":{"tags":["On-Page Analyzer"],"summary":"Latest site topical map","description":"Clusters, pillars, link integrity, cannibalisation, outliers, orphans and content gaps vs the reference corpus.","responses":{"200":{"description":"Topical map","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/agnostic/analyzer/link-opportunities":{"get":{"tags":["On-Page Analyzer"],"summary":"Internal-link insertions","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string"},"description":"Only links from or to this URL"}],"responses":{"200":{"description":"Opportunities (source, target, anchor, sentence)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/agnostic/analyzer/reference-corpus":{"post":{"tags":["On-Page Analyzer"],"summary":"Add reference/competitor pages","description":"Fetched first-party (robots.txt honoured); only extracted text is kept. Max 40 per site.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"},"skipped":{"type":"integer"}}}}}}}}},"/api/agnostic/analyzer/site-health":{"get":{"tags":["On-Page Analyzer"],"summary":"Latest site health","description":"Site-level modules: click depth, template-grouped issues, sitemap/robots health, duplicates, scaled content, decay, migration diff, pruning, GSC overlay and the traffic-weighted site score.","responses":{"200":{"description":"Site health","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/agnostic/analyzer/calibration":{"get":{"tags":["On-Page Analyzer"],"summary":"Weight calibration and fix outcomes","description":"The workspace's calibration (PRD §11): active and proposed calibrations (category multipliers on the prior weights, [L]-check demotions, per-group verdicts), pre-registered holdout experiments with measured Search Console lift and 95% intervals, and this site's fix metrics (time to first approval, adoption, verified resolution, automated-fix share, per-check false-positive rate). Fixes for an experiment's control pages are withheld from GET /fixes until the experiment ends.","responses":{"200":{"description":"Calibration report","content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"object","nullable":true,"additionalProperties":true},"proposed":{"type":"object","nullable":true,"additionalProperties":true},"due":{"type":"boolean"},"experiments":{"type":"array","items":{"type":"object","additionalProperties":true}},"metrics":{"type":"object","additionalProperties":true},"note":{"type":"string"}}}}}}}}},"/api/agnostic/pages/recent":{"get":{"tags":["Pages"],"summary":"List recently updated pages with their latest SEO score","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10}}],"responses":{"200":{"description":"Recent pages","content":{"application/json":{"schema":{"type":"object","properties":{"pages":{"type":"array","items":{"type":"object","properties":{"pageId":{"type":"string"},"url":{"type":"string"},"wpPostId":{"type":"string","nullable":true},"score":{"type":"number","nullable":true},"updatedAt":{"type":"number","nullable":true}}}}}}}}}}}},"/api/agnostic/internal-links":{"get":{"tags":["Internal Linking"],"summary":"Get pending link suggestions for a page, or the site-wide orphan summary","parameters":[{"name":"sourcePageUrl","in":"query","required":false,"schema":{"type":"string"}},{"name":"summary","in":"query","required":false,"schema":{"type":"string","enum":["1"]},"description":"Pass summary=1 for the site-wide orphan/suggestion counts instead of per-page suggestions."}],"responses":{"200":{"description":"Suggestions or summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"post":{"tags":["Internal Linking"],"summary":"Record links discovered on a page during a crawl, or mark a suggestion as inserted","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sourceUrl":{"type":"string"},"wpPostId":{"type":"string"},"links":{"type":"array","items":{"type":"object","properties":{"targetUrl":{"type":"string"},"anchorText":{"type":"string"}}}},"markInserted":{"type":"string","description":"A link suggestion id to mark as spliced into content."}}}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Missing sourceUrl or markInserted","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/sync":{"post":{"tags":["Sync"],"summary":"Register a batch of page URLs (from a sitemap URL or explicit list)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sitemapUrl":{"type":"string"},"urls":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Sync result","content":{"application/json":{"schema":{"type":"object","properties":{"siteId":{"type":"string"},"discovered":{"type":"integer"},"registered":{"type":"integer"},"failed":{"type":"integer"},"truncated":{"type":"boolean"}}}}}},"400":{"description":"Missing sitemapUrl and urls","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Sitemap fetch/parse failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/indexnow":{"post":{"tags":["IndexNow"],"summary":"Log an IndexNow submission made directly by the connector","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["urls","status"],"properties":{"urls":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["submitted","failed"]},"responseCode":{"type":"integer"}}}}}},"responses":{"200":{"description":"Logged","content":{"application/json":{"schema":{"type":"object","properties":{"logged":{"type":"string"}}}}}},"400":{"description":"Missing/invalid urls or status","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/settings":{"get":{"tags":["Settings"],"summary":"Pull read-only site settings (title format, IndexNow key, sitemap mode, social verification)","responses":{"200":{"description":"Settings","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/agnostic/image-alt":{"post":{"tags":["AI Tools"],"summary":"Generate alt text for a single newly uploaded image (BYO AI provider key)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pageUrl","imageUrl","provider","apiKey"],"properties":{"pageUrl":{"type":"string"},"imageUrl":{"type":"string"},"pageContext":{"type":"string"},"provider":{"type":"string","enum":["openai","anthropic","gemini","openrouter"]},"apiKey":{"type":"string","description":"Caller's own AI provider key — never stored by Rankwyn."},"modelId":{"type":"string"}}}}}},"responses":{"200":{"description":"Generated alt text","content":{"application/json":{"schema":{"type":"object","properties":{"pageId":{"type":"string"},"imageUrl":{"type":"string"},"alt":{"type":"string"}}}}}},"400":{"description":"Missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Generation failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/ai/bulk-alt-fill":{"post":{"tags":["AI Tools"],"summary":"Bulk-fill alt text for every image on the site still missing it (BYO AI provider key)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["provider","apiKey"],"properties":{"provider":{"type":"string","enum":["openai","anthropic","gemini","openrouter"]},"apiKey":{"type":"string"},"modelId":{"type":"string"},"limit":{"type":"integer"}}}}}},"responses":{"200":{"description":"Results","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Bulk fill failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/agnostic/ai/bulk-optimize":{"post":{"tags":["AI Tools"],"summary":"Bulk-generate title or meta-description suggestions for a set of pages (BYO AI provider key)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pages","provider","apiKey","field"],"properties":{"pages":{"type":"array","items":{"type":"object","properties":{"pageId":{"type":"string"},"url":{"type":"string"}}}},"provider":{"type":"string","enum":["openai","anthropic","gemini","openrouter"]},"apiKey":{"type":"string"},"modelId":{"type":"string"},"field":{"type":"string","enum":["title","meta_description"]}}}}}},"responses":{"200":{"description":"Results","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Bulk optimize failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/sites/{siteId}/robots.txt":{"get":{"tags":["Public Content"],"summary":"Rankwyn-generated robots.txt for a site","security":[],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"robots.txt","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/api/sites/{siteId}/sitemap.xml":{"get":{"tags":["Public Content"],"summary":"Rankwyn-generated sitemap.xml for a site","security":[],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","enum":["basic","complete","ecommerce","segmented"]}}],"responses":{"200":{"description":"sitemap.xml","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/api/sites/{siteId}/llms.txt":{"get":{"tags":["Public Content"],"summary":"Rankwyn-generated llms.txt (AI/agent-readable site summary) for a site","security":[],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"llms.txt","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/api/sites/{siteId}/indexnow-key.txt":{"get":{"tags":["Public Content"],"summary":"The site's IndexNow key file (served verbatim for IndexNow domain-ownership verification)","security":[],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bare hex key","content":{"text/plain":{"schema":{"type":"string"}}}}}}}}}