Endpoints

Three endpoints cover the whole surface: run a scan, get an instant report, or fetch a previously persisted one. All responses are JSON unless you ask for Markdown.

GET/api/agent-scan

Runs a full AIEO scan for a domain and returns the v2 report as JSON, or as Markdown with ?format=md. A successful scan is persisted to KV for 7 days (fail-open: storage errors are noted in meta.warnings, never fail the scan). Rate limited per IP per hour — 12/hour by default, 20/hour with ?source=compare.

Query parameters
ParamTypeRequiredNotes
domainstringYesBare domain to scan, e.g. example.com (a scheme, if present, is stripped).
formatstringNo"json" or "md" explicitly selects the response format. Otherwise Accept weights select JSON or Markdown; ties and unsupported preferences return JSON.
POST/api/demo

Same engine and v2 report contract as /api/agent-scan. Body: { "domain": "example.com" }. Rate limited 10/IP/hour. Persists the report to KV for 7 days on success.

demo.sh
curl -X POST https://aieo.fyi/api/demo \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'
GET/api/report/[id]

Reads back a report saved by a prior /api/agent-scan or /api/demo call. Not rate limited — no scan runs. Ids are 12 lowercase Crockford base32 characters and expire after 7 days.

report.sh
curl "https://aieo.fyi/api/report/9f3ak2m7pqrs"

# Markdown download
curl "https://aieo.fyi/api/report/9f3ak2m7pqrs?format=md"

The JSON contract

Every scan — from /api/agent-scan, /api/demo, or a stored /api/report/[id] — returns the same shape: an overall score, four weighted group scores, a flat list of individual checks, and a short top-5 fix list ordered by points recoverable.

This excerpt contains selected fields and items from an offline test fixture, not a scan of a real business. The full response includes all checks and metadata; report links and rate-limit details are omitted here. A title fix can recover readiness points; DMARC is an informative security finding worth zero readiness points.

agent-scan.json
{
  "version": 2,
  "domain": "poor.example",
  "scores": {
    "overall": 29,
    "traditionalSEO": 25,
    "aiDiscoverability": 75,
    "schemaCoverage": 0,
    "agentInteraction": 0
  },
  "groups": [
    { "id": "traditionalSEO", "label": "Traditional SEO", "weight": 40, "score": 25 },
    { "id": "aiDiscoverability", "label": "AI Discoverability", "weight": 25, "score": 75 },
    { "id": "schemaCoverage", "label": "Schema Coverage", "weight": 15, "score": 0 },
    { "id": "agentInteraction", "label": "Agent Interaction (WebMCP)", "weight": 20, "score": 0 }
  ],
  "checks": [
    {
      "id": "title",
      "group": "traditionalSEO",
      "category": "seo",
      "title": "Page title",
      "severity": "fail",
      "scoreMode": "scored",
      "detail": "No <title> tag found.",
      "why": "Search engines truncate long titles and pad short ones; both cost click-through.",
      "learnMoreUrl": "/methodology#title",
      "scope": "page",
      "order": 1,
      "pointsRecoverable": 4,
      "fix": "Add a descriptive <title> tag (50–60 characters) containing your main keyword.",
      "pageUrl": "https://poor.example/",
      "pagesAffected": 1
    },
    {
      "id": "dmarc",
      "group": "security",
      "category": "security",
      "title": "DMARC record",
      "severity": "fail",
      "scoreMode": "informative",
      "detail": "No DMARC record. Email spoofing has no enforcement.",
      "why": "DMARC turns SPF/DKIM alignment into an enforceable anti-spoofing policy.",
      "learnMoreUrl": "/methodology#dmarc",
      "scope": "site",
      "order": 2,
      "pointsRecoverable": 0,
      "fix": "Add TXT at _dmarc.yourdomain: \"v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain\""
    }
  ],
  "topFixes": [
    {
      "id": "title",
      "title": "Page title",
      "severity": "fail",
      "fix": "Add a descriptive <title> tag (50–60 characters) containing your main keyword.",
      "group": "traditionalSEO",
      "pageUrl": "https://poor.example/",
      "pointsRecoverable": 4,
      "pagesAffected": 1
    }
  ],
  "security": { "grade": "D", "score": 45, "checks": 10 }
}

Markdown export

Add ?format=md to /api/agent-scan or /api/report/[id] to get a clean text/markdown report: YAML front-matter with the domain and scores, one section per score group, and a - [ ] fix checklist you can paste straight into an issue. The export_report WebMCP tool below produces the same output client-side.

Examples

The same request in curl, JavaScript and Python.

curl
curl "https://aieo.fyi/api/agent-scan?domain=example.com"

# Markdown, for pasting into a PR or an agent's context window
curl "https://aieo.fyi/api/agent-scan?domain=example.com&format=md"
fetch.js
const res = await fetch(
  "https://aieo.fyi/api/agent-scan?domain=example.com"
);
const report = await res.json();
console.log(report.scores.overall, "/ 100");
scan.py
import requests

r = requests.get(
    "https://aieo.fyi/api/agent-scan",
    params={"domain": "example.com"},
)
report = r.json()
print(report["scores"]["overall"], "/ 100")

Rate limits, CORS and headers

Limits are per IP, per hour, tracked in Cloudflare KV. Every API response carries rate-limit headers so an agent can pace itself without guessing.

Rate limits
EndpointLimitNotes
GET /api/agent-scan12 / IP / hourCORS open; JSON or ?format=md
POST /api/demo10 / IP / hourInstant scan, no email
GET /api/report/[id]not rate-limitedReads a persisted report (7-day TTL)
Headers
HeaderNotes
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix timestamp in seconds when the window resets; JSON rateLimit.resetAt uses ISO-8601
Retry-AfterSeconds to wait, sent only on a 429 response

CORS is open (Access-Control-Allow-Origin: *) on every /api/* route — this is public scan data, meant to be read from anywhere, including from inside another site's agent.

OpenAPI schema

A machine-readable schema for the REST endpoints, including the 429 error shape and its headers, lives at /api/openapi.json. Point your codegen or client SDK at it directly.

View openapi.json

Connect your AI assistant

The remote MCP beta is at /mcp on this deployment. Add its full HTTPS URL to an assistant that supports Streamable HTTP MCP, with no authentication. It exposes three tools:

Remote MCP tools
ToolWhat it does
scan_siteCheck a public website domain and return findings, fixes and a shareable report.
get_reportRetrieve every check in an existing report by its 12-character ID.
explain_checkExplain a check ID: its rule, scoring role, generic fix and methodology.

Try “Check mybusiness.co.uk and explain the three most useful fixes.” The score measures technical readiness; it does not establish rankings, traffic or actual AI mentions and citations. Recoverable points rank changes to this score, not business impact.

Scans share the public API's 12-per-source-IP hourly allowance. Assistant providers may share an IP between users. Discovery, explanations and report retrieval do not spend a scan. Reports are kept for seven days when storage is available, and anyone with the report link can read them. Read the data policy.

For ChatGPT, use its developer-mode connection flowto test a personal plugin. This beta has not been submitted to the public directory. These remote tools are separate from the browser WebMCP tools below.

WebMCP tools on this site

aieo.fyi registers its own WebMCP tools so an AI agent can run a scan for you directly on the page, no API key required. There are seven:

document.modelContext tools
ToolWhat it does
get_site_overviewWhat AIEO is, how the tools fit together, the privacy and rate-limit terms. No network call; call it first.
run_scanRuns a full AIEO scan for a domain, renders the score card in the page and returns scores, top fixes and the 7-day report link.
get_scan_resultReturns the JSON result of the most recent scan run on this page.
explain_checkExplains a single check by id: what it measures, why it matters, how to fix it.
list_checksLists every check in the catalogue, grouped by score group.
show_full_reportRenders the full report for the last scan (or a given domain) in the page — the same report /scan shows.
export_reportExports the last scan, a stored report by id, or a given domain as Markdown or JSON, with a Copy and Download panel; the download starts in-page.

Ask your agent to open the homepage and "scan example.com" — it calls run_scan, reads the result back with get_scan_result, and can hand you a Markdown copy with export_report. Tools follow the 2026-09 Web Model Context draft: document.modelContext.registerTool(tool, { signal }), plain-JSON results (failures are { ok: false, error, hint }), and annotations — every tool is read-only and the ones that return scanned-site text carry untrustedContentHint.