DEV Community

Tidy Tools
Tidy Tools

Posted on

An on-page SEO audit API: a 0–100 score and a fix list in about 2 seconds

I wanted an SEO check I could run inside a CI job: one URL in, a number out, and a list of things to fix. No dashboard, no crawl that takes ten minutes, no AI guessing. Rules only, so the same page gives the same score every time.

So I built one. Here is what it returned for two well-known sites on 1 October 2026 (real calls, real responses), and how to wire it into a deploy pipeline.

One call

curl --request GET \
  --url 'https://seo-audit-api15.p.rapidapi.com/api/v1/seo/audit?url=https://htmx.org/' \
  --header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY' \
  --header 'X-RapidAPI-Host: seo-audit-api15.p.rapidapi.com'
Enter fullscreen mode Exit fullscreen mode

1.32 s later:

{
  "success": true,
  "url": "https://htmx.org/",
  "statusCode": 200,
  "renderedWith": "http",
  "score": 90,
  "grade": "A",
  "summary": { "critical": 0, "warnings": 1, "notices": 5, "total": 6 },
  "categoryScores": {
    "indexing": 100, "technical": 100, "meta": 97, "headings": 85, "content": 100, "links": 100,
    "images": 97, "social": 94, "structured-data": 97, "performance": 100, "mobile": 100, "international": 100
  },
  "issues": [
    {
      "severity": "warning",
      "code": "h1-multiple",
      "category": "headings",
      "message": "The page has 2 H1 headings.",
      "fix": "Keep a single main H1 and turn the others into H2 headings."
    },
    {
      "severity": "notice",
      "code": "description-long",
      "category": "meta",
      "message": "The meta description is 338 characters (~2168px) and will likely be cut (about 920px fit).",
      "fix": "Shorten the meta description to about 160 characters."
    },
    {
      "severity": "notice",
      "code": "img-dimensions-missing",
      "category": "images",
      "message": "44 of 44 images have no width/height attributes.",
      "fix": "Add width and height attributes to images so the layout does not shift while they load (CLS)."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

(Three of the six issues shown; the other three were missing Open Graph tags, no twitter:card, and no structured data.)

The two H1s are "Platinum Sponsor" and "Gold Sponsor" — exactly the kind of thing nobody notices until a tool points at it. The response also includes the details behind each finding: the full heading outline, title and description pixel widths, the robots.txt rule that matched Googlebot (Allow: /), and the sitemap it found through robots.txt (188 URLs, and it lists this page).

Checking links too

Add checkLinks=10 and it also requests up to 10 links from the page (internal first) and follows their redirects. For https://svelte.dev/ (1.55 s, score 88 / B):

"links": {
  "total": 121, "internal": 16, "external": 105, "unique": 70,
  "checked": { "requested": 10, "checked": 10, "broken": 0, "redirected": 6 }
}
Enter fullscreen mode Exit fullscreen mode

Six of the ten navigation links redirect — /docs/svelte → /docs/svelte/overview, /playground → /playground/hello-world?show=input, and so on — so the audit adds a notice to link to the final URLs. It also flagged a missing canonical, no XML sitemap (none in robots.txt, /sitemap.xml is 404), a 34-character meta description, and 6 render-blocking stylesheets.

None of these are disasters; both sites are in good shape. That is the point of a score: you can see that a page is fine, and see the moment it gets worse.

What it checks

  • Indexing: status code, redirects, canonical (and whether its target works), meta robots and X-Robots-Tag, robots.txt verdict for Googlebot, sitemap.
  • Meta and headings: title and description length in characters and pixels, H1 count, skipped levels, the full outline.
  • Content and images: word count, readability, alt text (empty alt="" counts as decorative, not missing), missing width/height.
  • Social and structured data: Open Graph, Twitter cards, JSON-LD and Microdata validated against rich-result rules.
  • Speed hints from the response itself: HTML size, compression, cache headers, render-blocking resources. (This is not Lighthouse; there are no lab Core Web Vitals here.)
  • Mobile and international: viewport, zoom, lang, hreflang.

It uses a plain HTTP request first and only opens a real browser if the site blocks the request or the HTML is an empty JavaScript shell.

Errors are explicit. A page that does not exist is not "score 0":

{ "success": false, "error": { "code": "target_error", "message": "The page does not exist (HTTP 404).",
  "details": { "targetStatus": 404, "finalUrl": "https://htmx.org/this-page-does-not-exist" } } }
Enter fullscreen mode Exit fullscreen mode

Use it as a CI gate

Fail the deploy if a key page drops below a score, or if any critical issue appears:

# seo_gate.py — run after deploying a preview
import os, sys, requests

HOST = "seo-audit-api15.p.rapidapi.com"
PAGES = ["https://staging.example.com/", "https://staging.example.com/pricing"]
MIN_SCORE = 85

failed = False
for url in PAGES:
    r = requests.get(f"https://{HOST}/api/v1/seo/audit", params={"url": url, "checkLinks": 20},
                     headers={"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"], "X-RapidAPI-Host": HOST}, timeout=60)
    a = r.json()
    if not a.get("success"):
        print(f"FAIL {url}: {a['error']['code']}"); failed = True; continue
    critical = [i for i in a["issues"] if i["severity"] == "critical"]
    print(f"{a['score']:>3} {a['grade']} {url}")
    for i in a["issues"]:
        print(f"    [{i['severity']}] {i['message']} -> {i['fix']}")
    if a["score"] < MIN_SCORE or critical:
        failed = True

sys.exit(1 if failed else 0)
Enter fullscreen mode Exit fullscreen mode
# .github/workflows/seo.yml (excerpt)
- name: SEO gate
  run: python seo_gate.py
  env:
    RAPIDAPI_KEY: ${{ secrets.RAPIDAPI_KEY }}
Enter fullscreen mode Exit fullscreen mode

Note that the API has to reach the page, so staging must be publicly reachable; private and local addresses are refused on purpose.

Price

RapidAPI plans as of 1 October 2026:

Plan Per month Audits Overage
Basic $0 200 (hard limit) —
Pro $7.99 10,000 $0.001 each
Ultra $24.99 50,000 $0.0008 each
Mega $69.99 200,000 $0.0005 each

The free plan covers a CI check on a handful of pages per deploy for a small project. RapidAPI charges its own bandwidth fee above 10 GB a month.

Try it: https://rapidapi.com/tidytools/api/seo-audit-api15

Need a whole site, not one page? The same rules run as a crawler on Apify, with site-wide checks (duplicate titles, sitemap URLs that fail, internal links to redirects) and an optional PageSpeed measurement: https://apify.com/tidytools/seo-audit-crawler ($5 per 1,000 pages).


Disclosure: I built this API and the Apify crawler and I earn money when people use them. The scores above are what the API returned on 1 October 2026; the sites may have changed since. htmx and Svelte were picked as well-known public examples, not because anything is wrong with them.

Top comments (0)