OmniRank
One page. Every engine.
Score a site's SEO, AEO and GEO signals against its real HTML, then generate the machine-readable ground truth AI engines can cite.
OmniRank scores a website's live HTML against the concrete rules that search engines,
answer engines and generative AI systems use to crawl, quote and cite content, then
generates llms.txt, llms-full.txt and facts.json so the page becomes machine-readable
ground truth an AI engine can cite directly.

Real terminal output from omnirank audit https://example.com — nothing staged, nothing cropped.
SEO, AEO and GEO read the same HTML
Three audiences parse one document. A crawlable, well-marked-up, machine-readable page wins all three at once — that is the entire bet OmniRank makes, and why it audits one artifact (your rendered HTML) instead of three separate tools.
| Layer | Audience | What it wants | What OmniRank does |
|---|---|---|---|
| SEO | Googlebot, Bingbot | Crawlable, canonical, correctly sized metadata, valid structured data | Checks h1, canonical, title/description length, OpenGraph, JSON-LD |
| AEO | AI Overviews, Copilot, voice assistants | A short, liftable, factual answer near the top of the page | Checks for a 40–60 word AnswerBlock, an FAQ section, and speakable markup |
| GEO | ChatGPT, Claude, Perplexity, Gemini | Machine-ingestible ground truth plus explicit permission to cite | Generates and verifies llms.txt, llms-full.txt, facts.json with a citation licence |
The table above is not aspirational copy — it is also, deliberately, how this README is
written. It has one <h1>, an AnswerBlock-shaped opening paragraph, an FAQ section, and
descriptive headings. If OmniRank asked your page to do these things, it does them too.
Audit any site in one command
OmniRank is not published to PyPI, npm or a container registry — install it from source
into a virtual environment. Homebrew and other PEP 668-managed Python installs refuse a
bare pip install, so activate a venv first:
git clone https://github.com/bemoshiur/OmniRank.git && cd OmniRank
python3 -m venv .venv && source .venv/bin/activate && make install
python3 -m omnirank.cli audit https://example.com
Real output, captured against https://example.com:
OmniRank 0.2.0 — https://example.com
overall 76/100 aeo 80 geo 60 perf 100 seo 67
1 URLs checked, 10 findings
[FAIL] seo.canonical.missing https://example.com/
observed: no rel=canonical
fix: Add <link rel="canonical" href="https://example.com/"> to <head>.
[FAIL] seo.description.missing https://example.com/
observed: no meta description
fix: Add a meta description summarising the page.
[WARN] seo.og.missing https://example.com/
observed: missing og:title, og:image
fix: Add the missing OpenGraph tags so social unfurls render.
[FAIL] aeo.answer-block.missing https://example.com/
observed: no element matching '.answer-block'
fix: Add <div class="answer-block" data-speakable> with a 40-60 word plain-prose answer.
[FAIL] aeo.faq.too-few https://example.com/
observed: 0 FAQ pairs
fix: Add FAQs as semantic <dl>/<dt>/<dd> or <details>, mirrored by FAQPage JSON-LD.
[FAIL] seo.schema.absent https://example.com/
observed: no application/ld+json blocks
fix: Emit JSON-LD describing this page and cross-reference the site organisation by stable @id.
[FAIL] geo.llms.missing https://example.com/llms.txt
observed: HTTP 404 at llms.txt
fix: Generate llms.txt at build time and serve it as a static file.
[FAIL] geo.llms-full.missing https://example.com/llms-full.txt
observed: HTTP 404 at llms-full.txt
fix: Generate llms-full.txt at build time and serve it as a static file.
[FAIL] geo.facts.missing https://example.com/facts.json
observed: HTTP 404 at facts.json
fix: Generate facts.json at build time and serve it as a static file.
[FAIL] geo.ai-allowlist.missing https://example.com/robots.txt
observed: HTTP 404 at /robots.txt
fix: Publish a robots.txt that explicitly allows AI crawlers.
report: .omnirank/reports/2026-08-03-audit.json
example.com ships nothing but a static placeholder page, so this is close to a worst
case — real sites usually clear a handful of these on the first pass. Every finding
carries observed, expected, and fix — enough to act on without opening the code.
For a configured repo, copy the starter config and gate a build on specific gates:
cp templates/omnirank.config.example.json omnirank.config.json
python3 -m omnirank.cli audit --config omnirank.config.json --fail-on h1 canonical schema
python3 -m omnirank.cli geo --config omnirank.config.json --out public
Exit codes: 0 clean · 1 a --fail-on gate failed · 2 usage or config error. The
non-zero exit is what makes it a CI gate, not just a report — see
Use OmniRank as a CI gate below.
What ships in v0.4.0
| Skill | Status | What it does |
|---|---|---|
audit |
Shipped | Scores SEO, AEO, GEO, perf, security and structured-data gates against a site's real HTML — including a cross-URL pass over the whole crawled set — and reports observed / expected / fix for every gap |
geo-artifacts |
Shipped | Generates llms.txt, llms-full.txt and facts.json, each with an explicit citation licence |
fix (preview) |
Shipped | Resolves a finding's URL to the source file that owns it and prints the unified diff it would apply for the four mechanical findings. Writes nothing — there is no --write flag |
Still exactly two skills. v0.4.0 adds 18 new finding ids across four groups: a new
security layer (HSTS, nosniff, CSP absence, Referrer-Policy, mixed content, and a
missing HTTP→HTTPS redirect); indexability contradictions provable from a site's own
declarations (a sitemap URL its own robots.txt disallows, a canonical pointing at a
noindexed/redirecting/missing page, a hreflang alternate that is itself noindexed);
Google's required rich-result properties for eight structured-data types; and on-page
accessibility overlap (missing alt, skipped heading levels, empty or generic link
text, missing lang). It also normalises the score model so a layer's budget scales
with its own gate count instead of a flat constant — scores from 0.3.0 and 0.4.0 are
not comparable. omnirank fix still writes nothing to your project; file
modification ships once the locator is proven against real repositories and the write
guarantees in docs/research/2026-08-04-automation-architecture.md §2.5 are
implemented and tested. Everything under Roadmap below is not present in v0.4.0.
Roadmap
| Skill | Target | What it will do |
|---|---|---|
aeo-onpage |
v0.2 | Emit JSON-LD by entity type; draft AnswerBlocks and FAQs; Next.js codegen |
indexing |
v0.3 | IndexNow, GSC URL Inspection, Bing Submit, Wayback, hash-based freshness |
offsite-entity |
v0.4 | sameAs gap analysis, peer mention-gap detection, outreach drafts for human review |
measure |
v0.5 | Rank tracking plus real AI-citation testing across engines |
smm-content |
v0.6 | Repurpose published pages into platform-native assets |
smm-publish |
v0.7 | Gated publishing — dry-run by default, human approval required |
aeo-onpage's "v0.2" target above predates this plan and has already slipped: 0.2.0
shipped as a site-gates/AnswerBlock-bands/perf release instead, folded into the existing
audit skill rather than a new one. Treat every target version in this table as
directional, not a commitment — none has a firm date. Adapters for WordPress, Jekyll,
Shopify, Astro, Vue and Svelte land at v1.0. None of the skills in this table exist in the
installed package today — asking Claude Code to "write our JSON-LD" or "submit this URL to
Google" will not trigger anything, because aeo-onpage and indexing are not built yet.
Every gate OmniRank checks, grouped by layer
42 gate names exist in the config schema; 21 can fail a build (error-capable) and 21
cannot — 16 are warning-only, 4 (the security header gates) are info-only, and one mixes
warning and info. (As of v0.2.1, crawl-hygiene — a gate name that could never actually
fire, since its only source was never called from audit_site() — was removed from the
schema rather than shipped as a config option that silently did nothing;
hygiene.check_sitemap() was wired into sitemap-health instead, so a redirecting or
dead sitemap URL now gets its own dedicated finding.) Full detail, including which gates
can never trip --fail-on, is in audit-guide.md
and
ci-integration.md.
SEO
| Gate | Rule | Severity |
|---|---|---|
h1 |
Exactly one <h1> |
error |
canonical |
Present, absolute, self-referencing | error |
title-length |
Present, ≤60 characters | error if missing, warning if over |
description-length |
Present, ≤160 characters | error if missing, warning if over |
og |
og:title and og:image present |
warning |
hreflang |
If any hreflang, an x-default exists |
warning |
image-dims |
Every <img> has width and height |
warning |
AEO
| Gate | Rule | Severity |
|---|---|---|
answer-block |
40–60 word plain-prose element exists, no lists inside | error |
faq |
≥3 pairs as <dl>/<dt>/<dd> or <details> |
warning |
speakable |
Every speakable.cssSelector resolves to real markup |
error |
GEO
| Gate | Rule | Severity |
|---|---|---|
llms-txt |
/llms.txt returns 200 |
error |
llms-full |
/llms-full.txt returns 200 |
error |
facts-json |
/facts.json returns 200 and parses |
error |
ai-allowlist |
robots.txt does not Disallow: / any AI crawler |
error |
citation-licence |
llms.txt contains a licence or attribution statement |
warning |
Structured data
| Gate | Rule | Severity |
|---|---|---|
schema |
At least one valid application/ld+json block with @type |
error |
schema-fabrication |
AggregateRating has a real ratingCount; every Review has an author |
error |
schema-required |
Google's rich-result properties (headline/image/datePublished, offers, mainEntity, …) are present for the types that need them | warning |
Security — new in v0.4.0; four header gates are info-only inventory, never graded
| Gate | Rule | Severity |
|---|---|---|
hsts |
Strict-Transport-Security present, max-age ≥ 180 days |
info |
nosniff |
X-Content-Type-Options: nosniff present |
info |
csp |
A Content-Security-Policy exists, by header or meta |
info |
referrer-policy |
A Referrer-Policy header exists |
info |
mixed-content |
An https:// page requests a blockable subresource (script, iframe, stylesheet/preload) over http:// |
error |
mixed-content |
An https:// page requests a passive subresource (img, favicon) over http:// — browsers auto-upgrade these |
warning |
https-redirect |
The site's http:// origin 3xx-redirects to https:// |
error |
Site-level (cross-URL) — new in 0.2.0; needs the whole crawled set, not one page
| Gate | Rule | Severity |
|---|---|---|
duplicate-title |
Two or more crawled pages share a <title> |
warning |
duplicate-description |
Two or more crawled pages share a meta description | warning |
noindex-in-sitemap |
A crawled noindex page is also listed in sitemap.xml |
error |
canonical-cluster |
A canonical points at a page that itself canonicalises elsewhere | warning |
hreflang-reciprocity |
An hreflang alternate does not link back |
warning |
Indexability contradictions — new in v0.4.0; 100% precision, both halves of every contradiction come from the site's own declarations
| Gate | Rule | Severity |
|---|---|---|
robots-sitemap |
A sitemap URL is Disallow-ed by the site's own robots.txt |
error |
canonical-target |
A canonical points at a page that is noindexed, 404s/410s, or redirects | error / warning |
hreflang-noindex |
An hreflang alternate points at a page that is noindexed |
error |
Performance — new in 0.2.0; derived from one HTTP response, no browser involved
| Gate | Rule | Severity |
|---|---|---|
response-time |
Full response ≥ 2000 ms (warning) or ≥ 5000 ms (error) — OmniRank's own tunable thresholds. This is the complete time for OmniRank's own request (DNS through the last byte of the body), not time-to-first-byte; it measures a full download, not server think-time | warning / error |
page-weight |
Raw HTML exceeds 500,000 bytes before any subresource | warning |
compression |
No gzip/deflate content-encoding — the only encodings OmniRank's client ever requests, so br/zstd are matched if present but never verifiable |
warning |
render-blocking |
More than 2 blocking <script> tags in <head> |
warning |
perf never measures Largest Contentful Paint, Cumulative Layout Shift, Interaction to
Next Paint, or a Lighthouse score — OmniRank has no browser. See
audit-guide.md for the named threshold
constants and faq.md for the full
answer.
On-page — new in v0.4.0; body markup, not the <head> indexing signals above
| Gate | Rule | Severity |
|---|---|---|
image-alt |
Every <img> has an alt attribute (an empty alt="" is never flagged) |
warning |
heading-order |
The heading outline never skips more than one level | warning |
link-text |
Every link has an accessible name, and it isn't a generic phrase like "click here" | warning / info |
lang |
<html> declares a non-empty lang |
error |
Download and install OmniRank
Option 1 — packaged skill for Claude Code, no clone required:
curl -LO https://github.com/bemoshiur/OmniRank/releases/latest/download/omnirank-skill.zip
unzip omnirank-skill.zip -d ~/.claude/plugins/
The archive is built by scripts/build-skill-zip.sh and
published on every v* tag. It contains the plugin manifest, both skills, the Python
package, JSON Schemas and the config template — everything needed to run the CLI and have
both skills register, without the tests, CI config or Node sources. Build it yourself
instead of downloading it:
./scripts/build-skill-zip.sh # -> dist/omnirank-skill-<version>.zip
Option 2 — clone, for CLI use or contributing:
git clone https://github.com/bemoshiur/OmniRank.git ~/.claude/plugins/omnirank
This gets the full repository — tests, CI config, and the Node GEO-artifacts generator —
and lets you git pull to update instead of re-downloading a release archive. See
claude-code-setup.md for confirming the skills registered and
the real trigger phrases for each.
Use OmniRank as a CI gate
omnirank audit --fail-on <gates> exits 1 when a named gate has an error-severity
finding and 0 otherwise — that is the entire integration surface:
name: OmniRank audit
on:
pull_request:
push:
branches: [main]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install OmniRank
run: pip install "omnirank @ git+https://github.com/bemoshiur/OmniRank.git#subdirectory=scripts/py"
- name: Audit
run: |
omnirank audit --config omnirank.config.json --out omnirank-report.json \
--fail-on h1 canonical schema llms-txt llms-full facts-json ai-allowlist
# Fail the build while a mechanical fix is outstanding. Writes nothing.
python3 -m omnirank.cli fix --config omnirank.config.json --root .
Picking --fail-on h1 canonical schema (structural baseline) is a better starting point
than listing all 42 gate names — 21 of them are warning- or info-only and can never fail
a build.
The full reasoning, plus a GitLab CI job and a generic shell script, is in
ci-integration.md.
Design principles
Real-only. No fabricated statistics, ratings, reviews or testimonials. facts.statistics
carries only entries explicitly marked published; AggregateRating without a real
ratingCount is reported as an error, not a warning.
Never claim an unevaluated gate passed. An unreachable URL is an error. A layer whose gates did not run is absent from the score map rather than scored 100.
Secrets are pointers. Config holds env:NAME only. A missing variable fails loudly —
a skipped submission is otherwise indistinguishable from a successful one in logs.
Audit diagnoses; it never edits. geo-artifacts writes the three GEO artifacts it
generates and nothing else — neither skill touches existing site source.
What OmniRank does not do
- It does not force rankings. Nothing can. It scores the signals search and answer engines are known to use, and hands you a prioritised list of gaps to close.
- It does not send cold email. Outreach text is drafted for human review and manual send, never dispatched automatically.
- It does not abuse the Google Indexing API. That API covers
JobPostingandBroadcastEventonly; misuse earns a manual action. - It does not generate AI slop. Every generated page expects a human edit pass before publishing.
- It does not ship thin programmatic pages. Matrices are pruned to real demand signals, not generated to fill a sitemap.
Frequently asked questions
No — nothing can guarantee a ranking, since search and answer engines rank content using signals no third-party tool controls. OmniRank scores a site against the concrete, checkable signals those engines are known to use and returns a prioritised list of gaps; closing them improves the odds without touching what OmniRank cannot see, such as backlink profile or content quality relative to competitors. See faq.md for the full answer.
No — OmniRank runs entirely on your machine or CI runner and only makes outbound HTTP
requests to the URL you point it at, fetching pages, sitemap.xml, robots.txt,
llms.txt, llms-full.txt and facts.json from that one site. There is no telemetry and
no third-party API call in the audit or geo-artifacts code paths.
audit reads a site and reports what's wrong, with no side effects. geo-artifacts writes
three new files (llms.txt, llms-full.txt, facts.json) and nothing else. They compose:
run geo-artifacts to generate the files, deploy them, then run audit to confirm they
actually serve in production — the GEO layer checks exactly that.
No — audit only diagnoses; SKILL.md states directly that "Audit only diagnoses. It
never edits the site." geo-artifacts does write files, but only the three GEO artifacts
it generates into the output directory you specify, never existing site source.
No, not as of v0.2.0. Install the Python CLI from source — clone the repository and pip install -e ./scripts/py inside a virtual environment, or install directly from git with
pip install "omnirank @ git+https://github.com/bemoshiur/OmniRank.git#subdirectory=scripts/py".
The Node generator is likewise unpublished; import scripts/node/src/generate.ts directly
or copy it into your project.
Python 3.11 or newer is required for the CLI and both skills (pyproject.toml sets
requires-python = ">=3.11"; CI tests 3.11, 3.12 and 3.13). Node 22 or newer is needed only
if you use the in-repo Node GEO-artifacts generator instead of the Python crawl path — Node
is not required at all for the CLI.
No — publishing llms.txt, llms-full.txt and facts.json follows the community
llms.txt convention and grants an explicit citation licence, which
removes a real barrier to a model quoting the content, but no engine's retrieval or
citation behaviour is under OmniRank's control. Publishing them is a low-cost,
evidence-backed bet, not a guarantee.
Yes, for both commands — omnirank audit <url> and omnirank geo <url> both work with
just a URL, using an in-memory default config. Since the in-memory default config has no
geo section, a bare omnirank geo <url> has no configured geo.license either — as of
v0.2.1 that generates the artifacts anyway, stating plainly that no reuse licence is
granted, and prints a one-line notice to stderr saying so (a config's generated files are
published to your site, so OmniRank tells you rather than silently choosing "no rights"
on your behalf). Write a config with geo.license set to a real licence and pass it with
--config to actually grant reuse rights, or set it to "none" explicitly to make that
choice permanent and silence the notice; see
geo-artifacts-guide.md. Beyond
that, a config file is also needed for CI gating with a committed audit.failOn,
first-party facts (nap, identifiers, statistics), or anything the roadmap skills
will eventually read from secrets.
Each GATE's contribution to its layer is capped at GATE_CAP = 15
(min(15, 10*errors + 3*warnings)), so one gate failing on every page of a large site
can no longer alone drag a layer to 0 — that would conflate issue COUNT with issue
SEVERITY (one broken template is one problem, not fifty). As of v0.4.0, the summed capped
cost is then divided by the layer's own scoring SURFACE — how many distinct gates could
move that layer's score — so a layer reaches 0 only when every one of its registered
gates is maxed, not after a fixed number of them. seo ships 24 scoring gates, and
measured directly: 7 maxed gates score 71, 12 score 50, 16 score 33, and only all
24 reach 0. A small layer still floors easily — security ships only 2 scoring gates
(mixed-content, https-redirect; its four header gates are info and excluded from
the surface entirely) — but a big layer like seo no longer floors on a handful of
broken gates the way it used to.
More questions, including secrets handling and where the JSON report schema lives, are answered in docs/faq.md.
Documentation and guides
Every page is verified against the v0.2.0 source, generated from the JSON Schema where applicable, and every command shown was actually run.
| Guide | Covers |
|---|---|
| docs/README.md | Documentation index and reading order by task |
| getting-started.md | Zero-to-first-audit: install, run, read the result, exit codes |
| configuration.md | Every omnirank.config.json field, with a complete example |
| audit-guide.md | The audit skill in depth: every gate, the scoring formula, a worked example |
| geo-artifacts-guide.md | Generating the three GEO artifacts and the OpenNext/CloudFront 403 trap |
| ci-integration.md | GitHub Actions, GitLab CI, shell examples, and choosing --fail-on gates |
| claude-code-setup.md | Installing as a Claude Code plugin and the real trigger phrases per skill |
| troubleshooting.md | Real error text for likely failures, with the fix for each |
| faq.md | 17 direct, honest answers, including what OmniRank does not do |
Contributing
Read CONTRIBUTING.md before opening a pull request — it covers
the non-negotiables (real-only data, never claiming an unevaluated gate passed, env:
secrets only) and the test-driven workflow this project enforces on itself.
If OmniRank caught something on your site that you'd otherwise have shipped broken, a star helps the next person searching "AEO audit tool" find it too — that's the only ask.
Credits & Standards
OmniRank implements and builds on public standards and published research:
- schema.org — structured-data vocabulary
- IndexNow — instant indexing protocol
- llms.txt — the proposal for AI-readable site indexes
- Sitemaps XML and the Robots Exclusion Protocol (RFC 9309)
- arXiv 2311.09735 — GEO: Generative Engine Optimization (Princeton, KDD 2024). Source of the finding that statistics, quotations and cited primary sources lift AI visibility.
- arXiv 2509.10762 — GEO-16, generative-engine optimisation criteria
- Ahrefs — AI-search and brand-mention correlation studies
- Seer Interactive — ChatGPT / Bing citation-overlap analysis
Correlations from that research are observational, drawn largely from English-language B2B datasets, and are treated as directional. Validate empirically per site.
Built and maintained by S M Moshiur Rahman at TICON System Limited, Dhaka.
Contact
S M Moshiur Rahman — Director, Business & Operations, TICON System Limited
| in/in-moshiur | |
| [email protected] | |
| 🌐 Web | ticonsys.com |
| 💻 GitHub | @bemoshiur |
For bugs and feature requests, use Issues rather than direct message — it keeps the answer searchable for the next person.
Licence
Code is MIT. Documentation and the content corpus exposed via llms.txt and
llms-full.txt are CC BY 4.0.
No comments yet
Be the first to share your take.