---
name: agent-ready-website
description: Build or improve websites to be agent-ready, measured by their Ora score (ora.ai). Use when building a new site that AI agents should be able to use, or when asked to make an existing site work for AI agents, improve an Ora score/grade, or remove agent-hostile patterns (bot walls, JS-only content, missing llms.txt, undocumented APIs). Drives a scan -> fix -> rescan loop against Ora's public REST API.
---

# Agent-Ready Website

Websites get two kinds of visitors now: humans and AI agents. This skill makes a
site work for both, measured by its Ora score (https://ora.ai — 0-100, graded A+
to F). This skill is served dynamically: agent-readiness standards evolve, so
always work from a freshly fetched copy rather than a memorized one.

Two ways to use it:

- **Building a new site?** Apply the fix playbook (Step 4) as build requirements
  from the start — server-rendered content, no bot walls, discovery files,
  semantic HTML, documented APIs. Then verify with a single scan (Steps 1–2) and
  close any remaining gaps.
- **Improving an existing site?** Run the full loop: **scan → read failed checks
  → apply the smallest fix for the highest-impact check → rescan**. Never rewrite
  the site; fix it one check at a time.

## Step 1 — Get a scannable URL

Ora scans public URLs. For a local dev site, open a tunnel:

```bash
# site must be running locally first (e.g. http://localhost:3000)
cloudflared tunnel --url http://localhost:3000
# → prints e.g. https://random-words.trycloudflare.com
```

Keep the tunnel process running for the whole session. The tunnel URL is your scan
target; the domain (e.g. `random-words.trycloudflare.com`) is your score key.

## Step 2 — Baseline scan

```bash
curl -s -X POST https://ora.ai/api/scan \
  -H 'content-type: application/json' \
  -d '{"url": "https://<your-tunnel-domain>"}'
```

- A `200` response is a complete `ScanResult`.
- A `202` response means analysis is still running; poll `GET
  https://ora.ai/api/score/{domain}` every ~20s until `analysisStatus` is complete.
- Rate limit: 10 scans/min/IP. Don't rescan more than once per fix batch.

Save the full JSON (e.g. to `/tmp/ora-scan-1.json`). Record `score`, `grade`, and
every check with `status: "fail"` — check id, layer, and `details` (the details
usually say exactly what was expected).

## Step 3 — Filter what to fix (important on tunnel URLs)

Ora scores 4 layers: **Discovery** (20, off-site findability), **Accessibility**
(30, can agents reach and parse your content), **Usability** (40, can agents
operate your APIs/UI), **Payments** (10, agentic payment rails).

On a throwaway tunnel hostname (`*.trycloudflare.com`), Ora infers the "brand" from
the domain, so **ignore Discovery/off-site checks entirely** (brand search,
Wikipedia, npm/PyPI SDKs, registries — they read Cloudflare, not you). Work only on
checks keyed to the page and site itself: content, robots/bot access, discovery
files, semantic HTML, and API checks. Those read truthfully and move honestly as
you fix them.

## Step 4 — Fix playbook

Work highest-weight layer first (Usability 40 > Accessibility 30), smallest change
first. Verify each fix locally (curl the page/file) before rescanning.

### Content readable without JS (`content-no-js`, `content-efficiency`, `markdown-*`)
Agents and crawlers read raw HTML. If the page is a client-rendered SPA shell,
server-render the primary content: real `<h1>`, product/service description, and
key facts present in the initial HTML response. **Look for a hydration gate
first**: many apps render a splash/loader until client JS runs (a provider that
returns a spinner until "hydrated") — removing that gate is often a one-line fix
that unlocks the entire page for crawlers. Verify with
`curl -s https://<site>/ | grep -i <expected text>`.
Also serve a markdown mirror: `/index.md`, honor `Accept: text/markdown` on the
homepage (rewrite to the markdown, set `Vary: Accept`), and support
`?mode=agent` as a dedicated agent view of the same content.

### Bot access (`bot-detection`, `agent-crawler-reachability`, `robots-ai-policy-quality`)
- Remove or allowlist AI agents in bot walls (middleware/WAF rules that 403
  ClaudeBot, GPTBot, PerplexityBot, curl, headless browsers).
- `robots.txt`: explicitly allow reputable AI crawlers (or state a deliberate,
  documented policy). Verify: `curl -s -A "ClaudeBot" https://<site>/` returns 200
  and real content.

### Discovery files (`llms-txt`, `sitemap`, `json-ld`, `metadata-completeness`, `trust-anchors`, `openapi-spec`)
- `/llms.txt` — markdown map of the site for LLMs: what the product is, key pages,
  API/docs links. Include an explicit **"When to use"** section with use cases —
  agent-instruction checks look for that guidance in llms.txt itself.
- `/sitemap.xml` — all public routes; reference it from robots.txt.
- JSON-LD (`<script type="application/ld+json">`) — schema.org type matching the
  business. Include `Organization` explicitly (an array `@type` like
  `["Airline", "Organization"]` works); adding a real `FAQPage` node broadens
  schema breadth cheaply.
- Real `<title>`, meta description, canonical, `<html lang>`, and OG tags per page.
- Trust anchors: reachable `/about`, `/contact`, `/privacy`, `/pricing` pages
  (machine-readable `/pricing.md` too).
- Agent guidance file: `/agents.md` with when-to-use / when-NOT-to-use sections.
- If you have an API: publish `/openapi.json`, link it from llms.txt, publish an
  RFC 9727 catalog at `/.well-known/api-catalog` (content-type **must** be
  `application/linkset+json`), and emit an RFC 8288 `Link` header on the
  homepage (`</llms.txt>; rel="describedby"`).

### Semantic HTML & operability (`ax-document-structure`, `ax-native-controls`, `ax-accessible-names`, `ax-form-labeling`)
Agents drive the accessibility tree. Use native elements: `<a href>` for
navigation, `<button>` for actions, `<select>`/`<input>` with real `<label>`s
instead of custom div widgets. Landmarks (`<main>`, `<nav>`, `<header>`), one
`<h1>` per page, heading hierarchy. Don't convey state by color alone.

### Agent-operable flows and APIs (`public-api`, `api-error-model`, `rate-limit-headers`, `idempotency-key-support`, `json-error-responses`)
- Make read endpoints plain GET with descriptive params; don't gate public data
  behind client-minted session tokens.
- Errors: structured JSON with a human-readable message and machine code, correct
  HTTP status. **Unknown API paths must return JSON errors too** — scanners and
  agents probe invented paths under `/api`; an HTML 404 from the API base reads
  as "no JSON error support". Add a catch-all JSON 404 for the API namespace.
- Serve JSON index responses at the API roots (`/api`, `/api/v1`) listing
  endpoints, spec URL, and docs URL — probes hit the roots, not just your
  documented paths.
- Send `RateLimit-*` headers on every API response (including errors and the
  roots); accept an `Idempotency-Key` on mutating endpoints, replay the original
  response for a repeated key, and document the header in the OpenAPI spec.
- Put flow state in the URL (query params), so every step is deep-linkable — an
  agent that lands on `/checkout` cold should be able to resume, not bounce.
- Avoid CAPTCHAs before critical actions, or offer an agent-verifiable
  alternative. Avoid short session timeouts that expire mid-task.

## Step 5 — Rescan and report

After each fix batch, rescan (same `POST /api/scan` call — new scans overwrite the
cached score) and compare against the previous JSON. Report progress as:

```
## Ora score: {score}/100 (grade {grade})  [was {prev}/100, {prev_grade}]
Fixed since last scan: {check ids}
Still failing (in scope): {check ids}
Next up: {check id} — {one-line plan}
```

Stop when the user's target is reached (default: grade C or better on a tunnel
domain — off-site checks cap what a throwaway hostname can score) or when only
out-of-scope checks remain. Full report anytime at `https://ora.ai/score/{domain}`.

## Step 6 — Agent feedback (optional)

Read what other agents experienced with any product/domain:

```bash
curl -s https://ora.ai/api/feedback/{domain}
```

Submitting first-person feedback requires agent verification (HATCHA) and is only
available through Ora's own MCP server at `https://ora.ai/api/mcp`
(`get_verification_challenge` then `submit_feedback`) — use it if that server is
connected; otherwise skip.
