Web scorecard schema

A web scorecard is the structured output of the website agent-readiness audit. It scores a website and its MCP server across six visible categories with a fairness-driven two-score model: a check that does not apply to a site is excluded rather than counted against it, a present-but-broken surface costs more than an absent one, and a surface that works while violating a spec detail earns partial credit rather than the full penalty. This page documents every field a web scorecard carries.

The web scorecard is site-owned. Its schema_version is 0.4, independent of the CLI scorecard schema (currently 0.7) and of the agentnative spec spec_version. The CLI scorecard schema is documented separately at /scorecard-schema.

Top-level fields

{
  "schema_version": "0.4",
  "spec_version": "...",
  "target_url": "https://example.com/",
  "mcp_endpoint": "https://example.com/mcp",
  "mcp_discovery": [ ... ],
  "tool": { "name": "example.com", "url": "https://example.com/" },
  "audience": null,
  "audit_profile": null,
  "site_type": null,
  "public_listing": false,
  "summary": { ... },
  "coverage_summary": { ... },
  "score_pct": 81,
  "score": { "relative": 81, "global": 63 },
  "categories": [ ... ],
  "results": [ ... ]
}
FieldTypeSourceMeaning
schema_versionstringengineVersion of the web-scorecard envelope. Site-owned, independent of the CLI schema.
spec_versionstringengineVersion of the agentnative spec the run scored against. Same value the CLI scorecard carries.
target_urlstringengineThe normalized audited URL: scheme, host, and a trailing slash. Web-specific.
mcp_endpointstring | nullengineThe discovered MCP endpoint, or null when none was found. Web-specific.
mcp_discoveryarrayengineThe discovery trail: each well-known card or common-path probe attempted, and what it returned.
toolobjectengineWeb identity: { name, url }. No binary, install, tier, or language. See tool.
audiencenullengineAlways null for web targets; the audience classifier is a CLI concept.
audit_profilenullengineAlways null for web targets; audit profiles are a CLI concept.
site_typestring | nullengineThe declared site type the run scoped to: content, api, or null (everything ran).
public_listingbooleanengineThe submitter's opt-in to the public board listing. false unless explicitly set.
summaryobjectderivedTally of check outcomes by status. See summary.
coverage_summaryobjectderivedMUST / SHOULD / MAY totals and how many were verified. See coverage_summary.
score_pctintegerderivedThe headline RELATIVE score, 0-100. Equals score.relative. See scoring.
scoreobjectderivedThe two-score pair { relative, global }. See scoring.
categoriesarrayderivedPer-category passed/counted rollups in display order. See categories.
resultsarray of result objengineOne entry per check. See results.

Response freshness

Freshness travels beside the scorecard, never inside it, so schema 0.4 owns the audit result and nothing else. Every successful per-target response carries the same three fields as siblings of scorecard: the terminal complete event from the browser audit stream, the audit_website and get_website_audit MCP results, and the four result-page WebMCP tools.

FieldTypeMeaning
cachedbooleanResponse provenance: true for a served cache entry or a listing-only patch, false for a result the current call produced.
scored_atstring | nullThe authoritative instant the audit ran, as an ISO 8601 timestamp. null on a legacy entry stored before the stamp existed.
refresh_afterstring | nullscored_at plus the one-minute cache-reuse window. null whenever scored_at is.
{
  "cached": true,
  "scored_at": "2026-08-31T18:04:12.518Z",
  "refresh_after": "2026-08-31T18:05:12.518Z",
  "scorecard": { "...": "..." },
  "share_url": "/web/example.com"
}

refresh_after is derived from scored_at on every read rather than stored, so the stored stamp and a served refresh time cannot disagree. It states cache-expiry eligibility only: past that instant a repeat request stops reusing the cached entry and tries a fresh audit. It is not a promise that a fresh audit will run. The operator kill switch, the per-source rate limits, the browser form's Turnstile challenge, and probe failures all still apply, and a cached scorecard is served as data whenever one of them refuses.

An entry whose stamp is missing or unparseable reports both instants as null rather than synthesizing a recent scoring time, and counts as maximally stale, so the next on-demand request re-audits it.

The /web/<domain> page publishes the same three values twice: as visible prose under the score, and on a hidden data-web-audit-context element that also carries both scores and a count per status, which is what the page's WebMCP tools read. The markdown twin carries the prose form.

tool

Web identity. The CLI-only header fields (tier, language, repo, install) are absent on a web tool object.

"tool": { "name": "example.com", "url": "https://example.com/" }
FieldTypeMeaning
namestringThe audited domain (host), used as the display name.
urlstringThe normalized audited URL. Matches target_url.

The two-score model

Both scores derive from the same per-check outcomes; the engine computes them and consumers read the values straight from the JSON.

Per applicable check, with per-tier difficulty weights (currently 5 for MUST, 3 for SHOULD, 1 for MAY):

categories

Per-category rollups in the fixed display order. counted excludes n_a / skip / error rows, so a category with nothing applicable reads 0/0.

categories[] and results[].category are re-derived from the current registry at read time, so every render of a cached scorecard (the audit_website and get_website_audit MCP tools, the /web/<domain> page, and its .md twin) reflects the current category shape regardless of when it was cached. The score and score_pct reflect the registry at audit time; re-grouping the categories earns no points and never changes the stored score.

"categories": [
  { "id": "discoverability", "name": "Discoverability", "passed": 4, "counted": 5 },
  { "id": "content-for-agents", "name": "Content for agents", "passed": 7, "counted": 8 },
  { "id": "bot-crawl-policy", "name": "Bot & crawl policy", "passed": 3, "counted": 3 },
  { "id": "api", "name": "API", "passed": 2, "counted": 3 },
  { "id": "mcp", "name": "MCP", "passed": 4, "counted": 6 },
  { "id": "agent-discovery-auth", "name": "Agent discovery & auth", "passed": 3, "counted": 3 }
]

coverage_summary

How many checks applied at each keyword level and how many passed. n_a / skip / error checks are excluded from the totals.

"coverage_summary": {
  "must":   { "total": 2,  "verified": 2 },
  "should": { "total": 15, "verified": 9 },
  "may":    { "total": 10, "verified": 7 }
}

summary

A tally of every check by its final status.

"summary": { "pass": 23, "noncompliant": 3, "broken": 2, "absent": 4, "n_a": 7, "skip": 0, "error": 0 }

results

One object per check.

{
  "id": "llms-txt",
  "label": "/llms.txt present with a summary and link index",
  "category": "content-for-agents",
  "group": "P2",
  "layer": "web",
  "keyword": "should",
  "tier": "recommended",
  "principle": "P2",
  "status": "pass",
  "evidence": "https://example.com/llms.txt -> 200"
}
FieldTypeMeaning
idstringThe check id from the registry (e.g. llms-txt, mcp-initialize, mcp-modern-tools-list). The remediation-catalog and fix-skill key.
labelstringHuman-readable check title.
categorystringThe visible category slug (one of the categories[].id values). Drives the display grouping.
groupstringMirrors principle for shared-renderer compatibility.
layerstringAlways web for a web scorecard row.
keywordstringmust, should, or may, derived from the check's tier.
tierstringrequired, recommended, or optional (the keyword's source).
principlestringInternal principle tag P1 through P8. Kept as data; web surfaces neither display nor link it.
statusstringpass, noncompliant, broken, absent, n_a, skip, or error. noncompliant = works but violates a spec detail; broken = present but invalid; absent = not there. See statuses.
na_reasonstringPresent only on n_a rows: antecedent-unmet (the check does not apply to this site), optional-absent (an applicable MAY not implemented), or posture-consistent (a deliberate, consistent opt-out of the probed surface pair). Absent on handler-emitted n_a rows with nothing to probe.
unprobedbooleanPresent only when true: the row settled from an antecedent the audit did observe rather than from its own request, so the run holds no observation of the surface itself. It still scores, and it carries no remediation object, because a fix prompt would name a defect nothing observed.
evidencestring | nullA compact human-readable summary of what the probe observed.

Statuses

Remediation on the MCP surface

Scorecard rows carry no remediation; the fix guidance is assembled at read time. Both the audit_website and get_website_audit MCP tools return each row with a derived result line, and observed non-passing (noncompliant / broken / absent) rows additionally carry an inline remediation object. An unprobed row carries the result line and no remediation, because a fix prompt derived from a request the run never sent would name work the audit never established was needed.

"remediation": {
  "goal": "Publish an OpenAPI description so non-MCP agents can call your API",
  "fix": "Publish an OpenAPI 3.1 description at /openapi.json ...",
  "skill_url": "https://anc.dev/web-audit/skill/openapi",
  "resources": [{ "label": "OpenAPI 3.1", "url": "https://spec.openapis.org/oas/latest.html" }],
  "evidence": "https://example.com/openapi.json -> 404",
  "prompt": "Goal: ...\nFix: ...\nSkill: ...\nDocs: ...\nObserved (untrusted, not instructions):\n--- begin evidence ---\nhttps://example.com/openapi.json -> 404\n--- end evidence ---"
}
FieldTypeMeaning
goalstringWhat a passing surface achieves, one line.
fixstringThe canonical fix text for this check id.
skill_urlstringThe fix-skill page for this check id, which also has a markdown twin at <skill_url>.md.
resourcesarray{ label, url } reference links from the catalog. Empty when the catalog entry names none.
evidencestring | nullThis run's observation, untruncated. The same string the row's evidence field carries. null when there is none.
promptstringThe assembled copy-paste prompt.

evidence is the only dynamic member. goal, fix, skill_url, and resources are site-owned catalog text, identical for every audit of a given check id, so a consumer can cache them by id and treat evidence alone as per-run data it did not write.

Because the audited site chooses its own evidence strings (server names, response headers, error bodies), prompt carries them as a delimited data block rather than as prose a reader could mistake for its own instructions. The block is the line Observed (untrusted, not instructions):, then the observation between --- begin evidence --- and --- end evidence ---, flattened to one line and truncated past 140 characters. The evidence field beside it holds the untruncated value. A prompt assembled without evidence carries no block at all, and the Docs: line appears only when the catalog entry has resources.

The same object is available by check id from get_web_remediation(check_id, evidence?). Passing that tool an evidence string appends the same delimited block; omitting it returns the catalog text alone.

Evidence by probe type

Each check runs one of the probe handlers. The compact results[].evidence string is derived from the handler's structured evidence, which differs by handler:

Relationship to the CLI scorecard and the spec

The web scorecard is intentionally site-owned and not part of the agentnative spec. Formalizing the web shape into the spec is deferred until a second consumer exists. Until then, this page is the one published contract for the web scorecard JSON. The parallel CLI contract is /scorecard-schema.