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 e
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'
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)."
}
]
}
(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 }
}
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" } } }
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)
# .github/workflows/seo.yml (excerpt)
- name: SEO gate
run: python seo_gate.py
env:
RAPIDAPI_KEY: ${{ secrets.RAPIDAPI_KEY }}
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.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.