api
POST /api/public/audit
Run a one-shot anonymous SEO audit with no API key. Synchronous response in under 60s, capped at 1 audit per IP per 24 hours, no Core Web Vitals.
What this endpoint does
POST /api/public/audit runs a synchronous SEO audit on any public URL with no API key. It returns the full audit envelope in the same response: total score, per-module scores, and rule-level findings. PageSpeed Insights is skipped to keep the call cheap and the response under 60 seconds.
- Synchronous: blocks until the audit is done, no polling.
- Anonymous: identified by client IP, capped at 1 audit per IP per 24 hours.
- Skips Core Web Vitals (LCP, CLS, INP). For PSI data, use the authenticated
POST /api/audits. - The result is not persisted to a user account: a rate-limit row is stored on the audits table with
user_id = NULL.
Why it matters
The anonymous audit is the path of least friction for the home-page “free audit” widget and for third-party tools that want to demo MetricSpot’s score before asking a user to sign up. No OAuth, no key minting, no plan: just an HTTPS POST.
Concrete workflows:
- A landing page builder embeds a “test your site” form that calls the endpoint directly from the user’s browser and renders the score back inline.
- A Slack bot accepts
/audit example.comfrom any teammate and posts the score back without anyone in the workspace having to authenticate against MetricSpot.
How to use it
Send a POST with a JSON body containing url. The URL must be absolute (https://...), parseable, and at most 2000 characters.
Request
POST /api/public/audit HTTP/1.1
Host: app.metricspot.com
Content-Type: application/json
{ "url": "https://example.com" }
curl
curl -X POST https://app.metricspot.com/api/public/audit \
-H "content-type: application/json" \
-d '{"url": "https://example.com"}'
Node fetch
const res = await fetch("https://app.metricspot.com/api/public/audit", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ url: "https://example.com" }),
});
const { audit } = await res.json();
console.log(`Score ${audit.total_score} for ${audit.domain}`);
Python httpx
import httpx
r = httpx.post(
"https://app.metricspot.com/api/public/audit",
json={"url": "https://example.com"},
timeout=90.0,
)
print(r.json()["audit"]["total_score"])
Response
200 OK with the full audit envelope:
{
"audit": {
"url": "https://example.com",
"domain": "example.com",
"total_score": 78,
"module_scores": {
"technical": 92,
"onpage": 71,
"performance": null,
"ai": 65,
"modern_seo": 80,
"social": 88,
"accessibility": 74,
"privacy": 82,
"readability": 70,
"tech_stack": 100,
"organic_traffic": 100
},
"rules": [
{
"module": "onpage",
"rule_id": "onpage.meta-description",
"passed": false,
"severity": "major",
"title": "Missing meta description",
"recommendation": "Add a 140-160 character meta description summarizing the page.",
"data": {}
}
]
},
"note": "Anonymous audits are limited and do not include Core Web Vitals. Sign up free for full audits and PDF reports."
}
Fields:
audit.url: the URL audited, echoed back.audit.domain: hostname extracted from the URL.audit.total_score: weighted score across all modules, 0 to 100.audit.module_scores: per-module score 0 to 100, ornullwhen the module was skipped (e.g.performanceis alwaysnullfor anonymous audits).audit.rules: array of every rule evaluated. Each entry hasmodule,rule_id,passed(boolean),severity(info|minor|major|critical),title, optionalrecommendation, anddata(rule-specific payload).note: a string explaining the anonymous limits, for UI display.
Error envelope
{ "error": "Rate limit reached", "message": "Sign up free for more reports." }
Common errors
| Code | When | Action |
|---|---|---|
INVALID_URL (400) | URL not parseable, missing scheme, or > 2000 chars | Pass an absolute https:// URL |
RATE_LIMITED (429) | IP already audited in the last 24 hours | Wait 24h, or sign up for a Free plan (10 audits/mo) |
UPSTREAM_FAILED (502) | Crawler upstream blip | Retry once with a short backoff |
INTERNAL (500) | Unexpected backend error | Retry; if persistent, sign in and use POST /api/audits for a queued retry |
The endpoint does not return 401, 402, or 404: it’s unauthenticated by design and has no resources to look up.
Frequently asked questions
Why is performance always null?
The anonymous endpoint skips Google PageSpeed Insights to keep total time under 60 seconds and avoid burning PSI quota on un-throttled traffic. For Core Web Vitals (LCP, CLS, INP), use the authenticated POST /api/audits, which runs PSI in parallel with the crawler.
How is the rate limit enforced?
A row is inserted into the audits table on every anonymous run with user_id = NULL and ip_address set to the caller’s IP. Subsequent calls from the same IP within 24 hours return 429. The IP is read from the standard reverse-proxy headers (X-Forwarded-For), so audits behind a shared proxy may collide.
Can I use this from a browser?
Yes. The endpoint sets permissive CORS for metricspot.com and accepts cross-origin POSTs. For your own domain, run a serverless function as a thin proxy: it lets you add per-tenant rate limiting and avoids exposing your visitors’ IPs to MetricSpot directly.
Is the result persisted anywhere?
A row exists for rate-limit purposes, but it is not attached to any user account and is not visible in any dashboard. To save and revisit an audit, sign up free and call POST /api/audits instead.
Sources
Last updated 2026-05-14