# AI Score — full agent documentation Product: AI Score (aiscore.getbags.app) Owner: BAGS (https://www.getbags.app) Purpose: Scan any origin for agent-readiness. Returns Level 0–5 (server ladder) and a 0–100 equal-weight pass rate over non-neutral, non-commerce checks. ## Scoring ladder Each level requires all prior gates. 1. **L1 Basic Web Presence** — pass ≥2 of `robotsTxt`, `sitemap`, `linkHeaders` 2. **L2 Bot-Aware** — also pass `robotsTxtAiRules` and `contentSignals` 3. **L3 Agent-Readable** — also pass `markdownNegotiation` (GET `/` with `Accept: text/markdown` → `text/markdown`) 4. **L4 Agent-Integrated** — also pass ≥1 of `mcpServerCard`, `a2aAgentCard`, `agentSkills`, `apiCatalog` 5. **L5 Agent-Native** — also pass ≥2 of 3 buckets: - (a) `webBotAuth` pass - (b) all four L4 integration checks - (c) auth metadata: `oauthDiscovery` OR `oauthProtectedResource` OR `authMd` `webBotAuth` is **neutral** on fail (informational; never drags the 0–100 score). ## Check catalog (21) ### Discoverability - `robotsTxt` — GET `/robots.txt` → 200, textish, ≥1 User-agent - `sitemap` — Sitemap: from robots or `/sitemap.xml` → valid XML - `linkHeaders` — GET `/` Link header with agent-useful rel (api-catalog, describedby, …) - `dnsAid` — DoH SVCB/HTTPS/TXT under `_index._agents.`, `_mcp._agents.`, `_a2a._agents.` with DNSSEC AD=true ### Content accessibility - `markdownNegotiation` — Accept: text/markdown on `/` ### Bot access control - `robotsTxtAiRules` — known AI crawler UA groups **or** `User-agent: *` with Allow/Disallow (wildcard covers AI bots; matches isitagentready.com) - `contentSignals` — Content-Signal `ai-train|search|ai-input=yes|no` directives - `webBotAuth` — `/.well-known/http-message-signatures-directory` JWKS (neutral on fail; aiscore does not publish a decorative key) ### Discovery - `apiCatalog` — `/.well-known/api-catalog` non-empty linkset[] with anchors - `oauthDiscovery` — openid-configuration or oauth-authorization-server - `oauthProtectedResource` — resource + authorization_servers, or public `bearer_methods_supported:[]` + docs - `authMd` — `/auth.md` + PRM/agent_auth/no-auth docs + registration guidance - `mcpServerCard` — live Streamable HTTP `initialize` + `tools/list` (card alone insufficient) - `a2aAgentCard` — name, version, supportedInterfaces - `agentSkills` — `/.well-known/agent-skills/index.json` skills[] - `webMcp` — Headless browser: ≥1 navigator.modelContext tool ### Commerce (only when isCommerce; never in 0–100) - `x402`, `mpp`, `ucp`, `acp`, `ap2` ## HTTP API ### OpenAPI `GET /openapi.json` — OpenAPI 3.1 document for `POST /api/scan`, `GET /api/health`, and `POST /mcp`. Canonical machine-readable contract for AgentCash / OpenAPI discovery. Also linked from `/.well-known/api-catalog` and the homepage `Link` header (`rel="service-desc"`). ### POST /api/scan ```json { "url": "https://www.getbags.app", "format": "json", "enabledChecks": ["robotsTxt", "sitemap"] } ``` Response includes `level`, `levelName`, `score`, `nextLevel.requirements[]` (with `skillUrl` fix guides), and `checks` by category. `format: "agent"` returns `text/markdown`. Cache key per host+profile (KV, 1h); `?fresh=1` or `"fresh": true` bypasses. Rate limit (free): 10 scans/min/IP (`X-RateLimit-*` headers). **Dual over-limit behaviour** (also applies to `POST /mcp`): 1. **x402 dormant (current production)** — missing `AISCORE_X402_PAY_TO` and/or `CDP_API_KEY_ID` / `CDP_API_KEY_SECRET` → **429**: ```json { "error": "Rate limit exceeded. Try again shortly.", "code": "RATE_LIMITED", "upgrade": { "x402": false, "amount": "0.01", "asset": "USDC", "network": "base", "payTo": null, "cta": "https://www.getbags.app", "message": "…" } } ``` 2. **x402 live** — all three env vars set → **402 Payment Required** with `PAYMENT-REQUIRED` header and v1 body (`x402Version`, `accepts[]`, `upgrade` with `x402: true`). Price: $0.01 USDC on Base (`maxAmountRequired: "10000"`, asset `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`, network `base` / `eip155:8453`). Facilitator: Coinbase CDP. Client retries with `PAYMENT-SIGNATURE` or `X-PAYMENT`; Worker verifies/settles then runs the scan. Ops bypass: header `X-Aiscore-Paid` matching env `AISCORE_PAID_BYPASS_SECRET` skips the IP rate limit. Under the free limit, requests stay free. Compat discovery fan-out: `GET /.well-known/x402` → `{ "version": 1, "resources": ["…/api/scan", "…/mcp"], "instructions": "…" }` (same shape as www.getbags.app). Canonical contract remains `/openapi.json`. ### GET /api/health `{ "status": "ok", "timestamp": "…" }` ### POST /api/cron/rescan Ops-only. Bearer `CRON_SECRET`. Re-scans a fixed domain set into KV cache. See `scripts/rescans.md`. ### POST /mcp (MCP Streamable HTTP) Stateless JSON-RPC. Require `Accept: application/json` or `text/event-stream`. - `initialize` → protocolVersion `2025-11-25`, serverInfo `{ name: "aiscore", version: "1.0.0" }`, capabilities `{ tools: {} }` - `notifications/initialized` → 202 - `tools/list` → `scan_site` (future: `scan_site_bulk` behind x402) - `tools/call` name `scan_site` args `{ "url": string }` → ScanResult JSON as text content CORS: `*`. ## Self-host agent surfaces (this origin) This site publishes the surfaces it scores: | Surface | Path | |---|---| | robots + Content-Signal + AI UA rules | `/robots.txt` | | sitemap | `/sitemap.xml` | | llms | `/llms.txt`, `/llms-full.txt` | | API catalog | `/.well-known/api-catalog` | | MCP card | `/.well-known/mcp/server-card.json` → `https://aiscore.getbags.app/mcp` | | A2A card | `/.well-known/agent-card.json` | | OpenAPI | `/openapi.json` | | x402 fan-out | `/.well-known/x402` | | Agent skills | `/.well-known/agent-skills/index.json` + per-check `SKILL.md` (directory `/` → index) | | OAuth PRM (public API) | `/.well-known/oauth-protected-resource` | | Auth docs | `/auth.md` | | Link headers + markdown negotiation | middleware on `/` | | MCP server | `POST /mcp` | Fix guides for failing checks live at: `https://aiscore.getbags.app/.well-known/agent-skills//SKILL.md` The skills directory URL `/.well-known/agent-skills/` redirects (308) to `/.well-known/agent-skills/index.json`. ## Deploy notes (zone + worker bindings) These cannot be satisfied by static files alone: 1. **DNS-AID** — Add DNSSEC-validated SVCB records on the getbags.app zone: - `_index._agents.aiscore` (ServiceMode) - `_mcp._agents.aiscore` → MCP endpoint - optional `_a2a._agents.aiscore` Scanner requires DNSSEC Authentic Data (`AD=true`) on DoH lookups. 2. **Headless browser binding** — Worker binding `BROWSER` (wrangler `browser.binding`) must be enabled for `webMcp`. Without it the check returns `unableToCheck`. 3. **WebMCP on homepage** — UI must register ≥1 tool via `navigator.modelContext.provideContext` / `registerTool` at parse time (inline script, not a hydration effect).