Documentation
SEO Loop answers SEO questions about any public URL, over plain HTTP. No signup, no API key.
Quick start
curl seoloop.in/audit/example.com
curl -fsSL https://seoloop.in/install | sh # optional CLI
seoloop audit example.com
Targets
A target is a domain or URL: example.com, https://example.com/pricing. If you leave out the scheme, https:// is assumed. Only http and https on ports 80 and 443 are allowed, and the host must resolve to a public address. Anything else is refused with HTTP 422.
Endpoints
Every endpoint is GET /<endpoint>/<target>. The answer is short plain text by default.
| Endpoint | Returns |
|---|---|
/audit/<target> | 26 checks in 4 categories, a 0-100 score and a grade. See The audit. |
/compare/<a>?vs=<b> | Both sites audited side by side, with the checks one passes and the other fails, and the score gap. |
/indexable/<target> | indexable: yes or NO with reasons: HTTP status, X-Robots-Tag and meta noindex, robots.txt rules (longest match wins, wildcards, $), canonical pointing elsewhere. |
/links/<target> | Checks the first 30 unique links on the page (internal first) and lists broken ones. HEAD requests with a GET fallback. |
/schema/<target> | JSON-LD blocks, the types found, and required properties that are missing for common types. |
/status/<target> | HTTP status after redirects. |
/title/<target> | The page title. |
/meta/<target> | Title, description, canonical, robots, viewport, language, Open Graph, headings, image alt gaps, word count. |
/headers/<target> | Final response headers. |
/redirects/<target> | Every hop from the URL you gave to the page you land on. |
/ttfb/<target> | Time to first byte, with DNS, connect and TLS timings. |
/ssl/<target> | Certificate subject, issuer, validity, days left, trust, protocol and cipher. |
/robots/<target> | robots.txt for the site, parsed: sitemaps, rule count, whether everything is blocked. |
/sitemap/<target> | Finds the sitemap (robots.txt first, then /sitemap.xml) and counts entries. |
/ip, /ua | Your IP address and user agent. |
Output options
| Option | Effect |
|---|---|
?json · Accept: application/json · /v1/<endpoint>/<target> | Full JSON instead of text. |
?field=days_left | Just one top-level scalar value from the JSON, as text. |
?min=80 (audit) | HTTP 412 if the score is below 80. With curl -f that fails a script or build. |
?only=seo,security · ?skip=hsts,sitemap (audit) | Include or exclude categories or check ids. The score is recomputed. |
?format=csv|md (audit) | CSV rows, or Markdown for PR comments and job summaries. |
?color (audit) | ANSI colours for PASS and FAIL. |
Audits return an X-SeoLoop-Score header. Machine-readable descriptions: /openapi.json and /llms.txt.
The audit
Each check is weighted by importance (high 10, medium 5, low 2). The score is the weighted share of checks that pass; grades are A 90+, B 80+, C 70+, D 60+, otherwise F.
| Category | Check id | Importance | What it checks |
|---|---|---|---|
| seo | title | high | Title tag present |
| seo | title_length | medium | Title 10-60 characters |
| seo | description | high | Meta description present |
| seo | description_length | medium | Description 50-160 characters |
| seo | h1 | medium | Exactly one H1 |
| seo | canonical | medium | Canonical link |
| seo | indexable | high | Page is indexable |
| seo | lang | low | html lang attribute |
| seo | h2 | low | Has H2 subheadings |
| seo | img_alt | medium | Images have alt attributes |
| seo | content | medium | At least 300 words of content |
| seo | open_graph | low | Open Graph title + image |
| performance | status | high | Responds 200 OK |
| performance | ttfb | medium | TTFB under 800 ms |
| performance | viewport | high | Mobile viewport meta |
| performance | compression | medium | Compressed response (gzip/br) |
| performance | html_size | low | HTML under 500 KB |
| security | https | high | Served over HTTPS |
| security | tls_valid | high | Valid TLS certificate |
| security | http_redirect | medium | HTTP redirects to HTTPS |
| security | hsts | medium | HSTS header |
| security | nosniff | low | X-Content-Type-Options |
| security | framing | low | Clickjacking protection |
| crawlability | robots_txt | medium | robots.txt exists |
| crawlability | robots_open | high | robots.txt does not block everything |
| crawlability | sitemap | medium | XML sitemap found |
CLI
curl -fsSL https://seoloop.in/install | sh
seoloop audit example.com
seoloop audit example.com --only seo --format md
seoloop audit example.com --min 80 # exit code 3 below 80
seoloop audit example.com --diff # what changed since last run
seoloop compare a.com b.com
seoloop ssl example.com --field days_left
seoloop indexable example.com --json
Exit codes: 0 success, 1 error, 2 bad usage, 3 audit score below --min, 4 rate limited. --diff keeps its state in ~/.cache/seoloop. Set SEOLOOP_URL to use your own server.
GitHub Action
- uses: mehranshahmiri/seoloop-action@v1
with:
url: https://staging.example.com
min-score: 80
skip: sitemap # optional
only: seo,security # optional
Listed on the GitHub Marketplace. Outputs score and grade, writes the Markdown report to the job summary, and fails the job below min-score. The target must be publicly reachable from the internet, so use a deployed preview or staging URL.
MCP server
A remote MCP server (Streamable HTTP, stateless, JSON responses) exposes every endpoint as a tool, so AI agents can run the checks themselves.
claude mcp add --transport http seoloop https://seoloop.in/mcp
Most other MCP clients that support remote servers take a URL: https://seoloop.in/mcp. Tools: audit, compare, indexable, links, schema, status, title, meta, headers, redirects, ttfb, ssl, robots, sitemap. All are read-only. Results include both text and structured content.
Limits & errors
- Rate limits are per IP: 30 requests a minute in general, 6 a minute for
/auditand/compare, 20 a minute for/mcp. Over the limit you get HTTP 429. - Results are cached for 5 minutes (10 for audits), so repeating a request is fast. Look for
X-Cache: HIT. - Each request has a 25 second budget, at most 6 redirects, and a 1.5 MB response cap.
| Status | Meaning |
|---|---|
| 400 | Missing or unusable parameter (for example /compare without ?vs=). |
| 412 | Audit score below ?min=. |
| 422 | Target refused: invalid, non-public, or a disallowed port or scheme. |
| 429 | Rate limited. |
| 502 / 504 | The target could not be reached, or timed out. |
Errors are plain text (error: ...) or {"error": "...", "status": 422} as JSON.
Self-hosting
PHP 8.2+ with curl, dom, openssl, mbstring and intl, plus nginx and PHP-FPM. The repository includes example configs for nginx, a dedicated PHP-FPM pool and rate limits, and a Dockerfile.
git clone https://github.com/mehranshahmiri/seoloop
php -S 127.0.0.1:8099 -t public public/index.php # try it locally
Environment: SEOLOOP_BASE_URL (public URL), SEOLOOP_CACHE_DIR. Full steps are in the README.
Security model
The service fetches URLs that strangers type in, so all outbound requests go through one hardened function: public IPs only (every address a name resolves to is checked), the validated IP is pinned into the connection, redirects are followed manually and re-validated at every hop, ports are limited to 80 and 443, and size, time and redirect counts are capped. Found a problem? See the security policy.