On-Page SEO Audit API: Titles, Meta, Headings, Schema avatar

On-Page SEO Audit API: Titles, Meta, Headings, Schema

Pricing

from $3.00 / 1,000 page auditeds

Go to Apify Store
On-Page SEO Audit API: Titles, Meta, Headings, Schema

On-Page SEO Audit API: Titles, Meta, Headings, Schema

On-page SEO audit for a list of URLs or a capped crawl of a site: title and meta lengths, H1 to H3 outline, canonical, robots and noindex, hreflang, Open Graph, JSON-LD types, image alt text, links, redirects, word count, a 0 to 100 score and a fix list. Plain HTTP, obeys robots.txt.

Pricing

from $3.00 / 1,000 page auditeds

Rating

0.0

(0)

Developer

Itay Ozer

Itay Ozer

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 hours ago

Last modified

Share

Send a list of page URLs, or one homepage plus a crawl limit, and get an on-page SEO audit for every page: title and meta description lengths, the H1 to H3 outline, canonical and noindex checks, hreflang, Open Graph, schema.org types from JSON-LD, image alt text, link counts, redirects, response time and word count. Each page gets a 0 to 100 score and a fix list written in plain language.

It reads pages over plain HTTP, the way a search engine crawler does on its first pass. No headless browser and no proxy, so it is fast (a 100-page crawl of a docs site took under 4 seconds in our tests) and cheap: $3 per 1,000 pages. Error pages, pages blocked by robots.txt and duplicates cost nothing.

It obeys each site's robots.txt (RFC 9309) and never reads pages behind a login.

What you get

One result per page:

  • score (0 to 100) and issues: every problem found, each with a code, a severity (error, warning or notice), the points it takes off the score, and a message that says how to fix it
  • title, titleLength, metaDescription, metaDescriptionLength
  • h1 (every H1 text), h1Count, and headings: the H1 to H3 outline in page order
  • canonical, canonicalIsSelf, metaRobots, xRobotsTag (the response header) and indexable (false when noindex is set or the canonical points to another page)
  • language (from <html lang>), viewport, hreflang alternates
  • openGraph (all og: tags), twitterCard, jsonLdTypes (such as Organization, Product, BreadcrumbList, FAQPage, including types nested in @graph)
  • imagesTotal and imagesMissingAlt, linksInternal, linksExternal, linksNofollow, wordCount of visible text
  • statusCode, redirectChain (every hop), finalUrl, responseMs (time to first byte), htmlBytes, contentType
  • url, foundOn (for crawled pages: the page that links to it), status, error, fetchedAt

What the score checks

The score starts at 100 and loses the points of each issue found.

CodeSeverityPointsFires when
title-missingerror15No <title>
title-too-long / title-too-shortwarning5 / 3Title over 60 or under 30 characters
meta-description-missingerror10No meta description
meta-description-too-long / -too-shortwarning3Over 160 or under 70 characters
h1-missingerror10No H1
h1-multiplewarning3More than one H1
heading-levels-skippedwarning2For example an H3 straight after an H1
noindexerror15noindex in the robots meta tag or X-Robots-Tag header
canonical-missingwarning3No canonical link
canonical-elsewherewarning5The canonical points to another address
viewport-missingerror8No viewport meta tag (not mobile friendly)
lang-missingwarning3No lang on <html>
not-httpserror10The page is served over plain HTTP
images-missing-altwarning1 to 10Images without an alt attribute, scaled by their share
needs-javascripterror15Almost no content without JavaScript
thin-contentwarning5Under 200 words of visible text
redirect-chain / redirectedwarning / notice3 / 1Two or more redirects / one redirect before the page
slow-responsewarning3Time to first byte over 1,800 ms
json-ld-invalidwarning5A JSON-LD block that is not valid JSON
structured-data-missingnotice2No JSON-LD at all
open-graph-incompletenotice2og:title, og:description or og:image missing

Input

FieldRequiredExampleNotes
urlsyes["https://www.python.org/"]Full addresses, up to 10,000 per run. An address typed without https:// is read as https
maxCrawlPagesno1000 (default) audits only your URLs. A number makes it follow links to other pages on the same site and audit up to that many more pages per site, up to 5,000

Audit a list of pages:

{
"urls": ["https://www.python.org/", "https://www.gov.uk/"]
}

Audit a whole site, up to 500 pages:

{
"urls": ["https://www.example-shop.com/"],
"maxCrawlPages": 499
}

While crawling it stays on the same site (www. and the bare domain count as one site), skips links marked nofollow, pages that are nofollow as a whole, files such as PDFs, images and archives, and anything robots.txt disallows. Links that lead to a page already audited in the run come back as free duplicate rows, and broken links come back as free http-error rows with foundOn telling you which page links to them.

Output example

A real result for python.org, shortened:

{
"url": "https://www.python.org/",
"finalUrl": "https://www.python.org/",
"status": "ok",
"statusCode": 200,
"redirectChain": [],
"responseMs": 11,
"score": 88,
"issues": [
{ "code": "title-too-short", "severity": "warning", "points": 3, "message": "The title is 21 characters. Use 30 to 60 characters to describe the page." },
{ "code": "meta-description-too-short", "severity": "warning", "points": 3, "message": "The meta description is 52 characters. Use 70 to 160 to describe the page." },
{ "code": "h1-multiple", "severity": "warning", "points": 3, "message": "The page has 5 H1 headings. Keep one H1 and use H2 for sections." },
{ "code": "canonical-missing", "severity": "warning", "points": 3, "message": "No canonical link. Add <link rel=\"canonical\" href=\"...\"> with the preferred address of this page." }
],
"title": "Welcome to Python.org",
"titleLength": 21,
"metaDescription": "The official home of the Python Programming Language",
"metaDescriptionLength": 52,
"h1": ["Functions Defined", "All the Flow You’d Expect", "Compound Data Types", "Intuitive Interpretation", "Quick & Easy to Learn"],
"h1Count": 5,
"headings": [{ "level": 1, "text": "Functions Defined" }, { "level": 1, "text": "All the Flow You’d Expect" }],
"canonical": null,
"canonicalIsSelf": null,
"metaRobots": null,
"indexable": true,
"language": "en",
"viewport": "width=device-width, initial-scale=1.0",
"hreflang": [],
"openGraph": { "type": "website", "title": "Welcome to Python.org", "image": "https://www.python.org/static/opengraph-icon-200x200.png", "url": "https://www.python.org/" },
"jsonLdTypes": ["WebSite", "SearchAction"],
"imagesTotal": 1,
"imagesMissingAlt": 0,
"linksInternal": 126,
"linksExternal": 79,
"linksNofollow": 0,
"wordCount": 1066,
"fetchedAt": "2026-10-31T17:00:25.605Z"
}

Every URL gets a row, so you always know what happened:

statusMeaningCharged
okHTML page auditedyes
http-errorThe page answered 4xx or 5xx (for example 404 or 403)no
not-htmlA file (PDF, image, archive), not a web pageno
blocked-by-robotsThe site's robots.txt disallows it, or robots.txt had a server errorno
too-many-redirectsMore than 5 redirectsno
duplicateCrawl only: leads to a page already audited in this runno
fetch-failedNo answer (timeout, unknown host)no
invalid-urlNot a public http or https addressno

Pricing

Pay per event:

EventPriceWhen
Page audited$0.003Every result with status ok
Actor start$0.00005Once per run

Example: a 500-page site crawl where 470 pages are audited and 30 are broken links costs 470 x $0.003 + $0.00005 = $1.41005.

The actor checks your run's maximum charge before it fetches anything and stops at what that covers, so a run never goes over the limit you set.

Limits

  • Pages are read without running JavaScript, like a crawler's first pass. Pages that only show content after JavaScript runs get the needs-javascript issue. There are no Core Web Vitals or Lighthouse scores.
  • Sites that block bots (a 403 page or a challenge wall) return http-error. There is no proxy rotation or unblocking.
  • It does not check whether outgoing links work, except the same-site links it visits while crawling.
  • HTML is read up to 4 MB per page. Up to 5 pages are fetched at a time. When a site answers 429 or 503 (too busy), it waits up to 10 seconds (following Retry-After) and tries once more.
  • The score is a quick on-page health check, not a ranking prediction. Thresholds follow common SEO practice (titles up to about 60 characters, descriptions up to about 160).

FAQ

Can it audit a whole website? Yes. Put the homepage in urls and set maxCrawlPages. It follows links between pages of the same site, breadth first, and stops at your limit or your maximum charge, whichever comes first.

Does it respect robots.txt? Always, and it cannot be turned off. It identifies itself as SeoPageAuditBot with a link to this page, reads each site's robots.txt once per run and follows RFC 9309. Pages it may not read come back as free blocked-by-robots rows.

Why did my own site come back with http-error 403? Your firewall or CDN blocks automated requests. Allow the SeoPageAuditBot user agent, or audit a staging copy. Nothing is charged for blocked pages.

Can I call it from an AI agent, MCP client, Make, Zapier or the API? Yes. The input is a list of URLs, every output field has a fixed type, and the fix list is plain text an agent can act on. It runs with limited permissions, so it cannot touch your other Apify data.

Is there personal data in the results? The results describe page structure (titles, headings, tags, counts). The actor targets no site by default and adds nothing of its own. You need the right to audit the pages you send.

Something looks wrong in an audit. What do I do? Open an issue on the Issues tab with the run link and the URL.