keywordseverywhere_api.py calls a removed endpoint — 404 with a valid key (v2.3.1)

Author: models118Created Sep 11, 2026Updated Sep 11, 2026

Summary

The Keywords Everywhere / Open PageRank source added in v2.3.1 cannot work as shipped: scripts/keywordseverywhere_api.py requests an endpoint that no longer exists. Verified with a valid opr_live_… key created today in the Keywords Everywhere dashboard — the legacy path returns HTTP 404, while the same key succeeds against the current API.

This lines up with the caveat already in the v2.3.1 changelog: "The live API path is unverified: landing this required no Keywords Everywhere account, and none was available to exercise the real endpoint end to end (#262)."

What the code does

scripts/keywordseverywhere_api.py:37

python
KWE_BASE = "https://openpagerank.keywordseverywhere.com/api/v1.0/getPageRank"

scripts/keywordseverywhere_api.py:70

python
headers = {"API-OPR": api_key}

That is the legacy DomCop Open PageRank shape. After Open PageRank moved to Keywords Everywhere, the service exposes a different path and a different auth scheme.

Evidence

Four requests, same host, same valid opr_live_… key (value redacted), all within one minute:

# Request Result
A GET /api/v1.0/getPageRank?domains[]=example-domain, header API-OPR: <valid key> 404, HTML error page
B same path, header Authorization: Bearer <valid key> 404, HTML error page
C POST /v1/domains/bulk, Authorization: Bearer <valid key>, body {"domains":["example-domain"]} 200, JSON payload
D GET /v1/domains/example-domain, Bearer 404 but JSON: {"error":{"type":"not_found_error","message":"Unknown endpoint: GET /v1/domains/…"}}

C is the positive control: the key is valid, so A and B are not authentication failures. D shows that unknown routes under /v1/ return structured JSON, whereas /api/v1.0/... returns an HTML 404 — that prefix is not served by the API at all.

Shape of the successful response (C):

json
{
  "as_of": "2026-08-01",
  "count": 1,
  "results": [
    {
      "domain": "…",
      "found": true,
      "open_page_rank": 1.16,
      "rank": 10836226,
      "referring_domains": 8,
      "history": []
    }
  ]
}

Current API

Per https://openpagerank.keywordseverywhere.com/docs:

  • Auth: Authorization: Bearer opr_live_…
  • Bulk lookup: POST /v1/domains/bulk with {"domains": [...]}, up to 100 domains per call
  • The payload now carries referring_domains and a monthly history array (2018 → present), neither of which the legacy API returned

Worth noting: legacy DomCop keys stop working on 2026-09-30, so the legacy shape has no remaining lifetime even if some compatibility route existed.

Suggested fix

  • Point KWE_BASE at https://openpagerank.keywordseverywhere.com/v1/domains/bulk
  • Switch from GET + repeated domains[] params to POST with a JSON body
  • Replace the API-OPR header with Authorization: Bearer
  • Map results[] (the legacy response[] key is gone), and treat status_code/error accordingly

Since the response now includes referring_domains, this source could contribute to the referring-domain factor in seo-backlinks scoring instead of being documented as "0-10 domain rank only" (skills/seo-backlinks/SKILL.md:54).

There is also an official MCP server at https://openpagerank.keywordseverywhere.com/mcp (streamable HTTP, same Bearer auth) exposing get_domain_rank, get_domain_history and bulk_domain_rank. Its initialize and tools/list respond without a key, which makes it straightforward to smoke-test the integration in CI without holding an account.

Environment

  • claude-seo 2.3.1 (9279553)
  • Keywords Everywhere free plan (30,000 domains/month)
  • Python 3.13, Windows 11