Table of contents
Quick summary
| If you want to… | Do this | Time | What it changes |
|---|---|---|---|
| Give an agent a reusable, on-demand capability | Write a SKILL.md with name + description frontmatter, plus any scripts or reference files it needs | 20–40 min | The agent loads the instructions only when the task matches, not on every turn |
| Make that skill findable from your website | Serve a JSON index at /.well-known/agent-skills/index.json | 15 min | An agent that lands on your domain can list what you offer before it asks |
| Cover both conventions in the wild | Also serve /.well-known/skills/index.json with the same content | 5 min | Neither a legacy-path nor a new-path client comes back empty |
| Check what's live | Scan the domain at aiscan.site (check P3) or curl both paths yourself | 2 min | Confirms the file parses, and which path answered |
Two different things are true here, and neither one is us. The SKILL.md file format is a real, published specification, maintained at agentskills.io. The two .well-known paths websites use to advertise one are not a joint standard; they're a convention several vendors converged on independently, and not on the same path. That gap is most of this article.
An AIScan scan checks this under P3, and, full disclosure up front, our own probe currently reads only the newer of the two paths. More on exactly where that leaves you further down.
Why this file exists at all
An agent that can browse the web meets thousands of sites. Most of them it will visit once. A handful, the ones it works with daily, or the one it landed on because a user pasted a link, are worth knowing more about: not a general crawl of every page, but a packaged, reusable set of instructions for doing one thing on that particular site well.
That packaged unit is a Skill. Anthropic's own description of the format, published in its developer docs, frames it, in its own words, as "organized like an onboarding guide you'd create for a new team member": not a wall of text handed over on day one, but a folder the new hire opens page by page, only when a task calls for that page. A skill for filling out expense reports doesn't need to be read before the agent has an expense report to file.
Mechanically, a skill is a directory with one required file, SKILL.md, plus whatever else it needs:
publish-agent-skills-index/
├── SKILL.md # required: frontmatter + instructions
├── scripts/ # optional: code the agent can run
├── references/ # optional: longer docs, read on demand
└── assets/ # optional: templates, schemas, examples
SKILL.md itself is YAML frontmatter followed by ordinary Markdown. The published specification sets two required fields and four optional ones:
| Field | Required | What it's for |
|---|---|---|
name | Yes | Lowercase, hyphenated, ≤64 chars, must match the folder name |
description | Yes | ≤1024 chars: what the skill does and when to use it |
license | No | A license name, or a pointer to a bundled license file |
compatibility | No | Environment needs, e.g. "requires Python 3.14+ and uv" |
metadata | No | Free-form key/value pairs for anything the spec doesn't define |
allowed-tools | No | A pre-approved tool list, marked experimental |
The description field is doing more work than its name suggests. It is the only thing an agent reads before deciding whether to open the file at all. According to Anthropic's own guidance on the format, this is what gets matched against the user's request, so a vague description ("Helps with PDFs") never triggers, while a specific one ("Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files…") does. That's the whole reason this loads in stages: name and description sit in every agent's context for every installed skill, all the time, at roughly 100 tokens each. The full SKILL.md body, recommended under 5,000 tokens, loads only once a task matches. Anything in scripts/, references/ or assets/ loads only if the body itself points there. A skill can bundle a hundred pages of reference material and cost nothing until one page is actually opened.
None of that (the folder, the frontmatter, the three-tier loading) says anything about a website. It's a convention for packaging a capability, agnostic to where the folder lives: on a coding agent's local disk, uploaded through an API, or fetched from somewhere else entirely. That "somewhere else" is where the second half of this article starts, and it's also where the convention stops being settled.
The part that isn't in the spec: how a website advertises one
If skills are meant to be reusable and an agent is meant to discover them without a human pointing it at a URL, the obvious move is the one already worn smooth by robots.txt, sitemap.xml and llms.txt: a fixed, predictable path under /.well-known/ that any agent can probe without being told it exists first.
Multiple sites now do exactly that. The trouble is they don't all probe, or serve, the same path.
We measured this directly across 52 documentation sites: /.well-known/agent-skills/index.json (the newer form) is served by 10 of them; /.well-known/skills/index.json (the older, shorter form) by 5; the union is 11, and only 4 sites answer at both. A client written against one path finds fewer than half of what's actually out there.
The split isn't random, and it isn't only in a sample from weeks ago. We re-verified it live while writing this piece. docs.stripe.com answers only the legacy path: fetched from that domain directly, it returns 200, application/json, listing skills named things like connect-recommend and connect-required-verification-information, each carrying a description and a files array pointing at its own SKILL.md and reference docs. The newer path on the same host returns Stripe's ordinary branded 404 page, confirmed against a deliberately nonexistent control path on the same host returning an identical 404 shell: this is a real absence, not a soft-200. GitBook, Pydantic, Resend, Supabase, Vercel and Prisma run the other way, answering the newer path only.
Two live files, two different shapes of the same idea. Fetched from vercel.com directly, the newer-path index carries a $schema field pointing at a discovery schema hosted under the agentskills.io domain, and its entries don't point at a SKILL.md folder at all; they point at a downloadable archive:
{
"name": "deploy-to-vercel",
"description": "Deploy applications and websites to Vercel...",
"type": "archive",
"url": "https://github.com/vercel-labs/agent-skills/releases/download/.../deploy-to-vercel.tar.gz",
"digest": "sha256:a1241958..."
}
Stripe's entries are closer to a directory listing: a name, a description, and a files array of relative paths an agent fetches individually. We checked our own site too, because the honest version of a how-to includes what we do, not only what others do: verified on aiscan.site itself, /.well-known/agent-skills/index.json answers 200, and its shape is a third variant again, a schemaVersion string, and entries carrying skillUrl and claudeMd fields that neither Stripe's nor Vercel's format uses.
| Site | Path that answers | Entry shape | Points to |
|---|---|---|---|
Stripe (docs.stripe.com) | legacy only | name, description, files[] | individual files over HTTP |
Vercel (vercel.com) | newer only | name, description, type, url, digest | one downloadable archive |
AIScan (aiscan.site) | newer only | schemaVersion, skillUrl, claudeMd | its own bespoke fields |
Three real, live files, three different sets of fields, verified live on all three domains within the same afternoon. Neither shape is wrong; the specification governs what's inside a skill folder, not how an index describes where to find one. But a scanner or a client built against one shape can misread the other, and a reader trying to copy "the" format from a single example is copying a choice, not a rule. If you're writing a client against this convention today, read the file rather than assuming its shape. If you're publishing one, that same variance is your argument for keeping it boring and close to the specification's own examples rather than inventing new fields.
Publish your own: two paths, write both
Path 1: you already have (or are ready to write) a SKILL.md. This is the every-day case: an internal process, a support workflow, an API your own agents already use, packaged once as a skill so any compatible agent can pick it up.
- Create the folder and the required frontmatter:
--- name: your-skill-name description: What this does, specifically, and when an agent should reach for it. --- # Your Skill Name ## Instructions Step-by-step guidance the agent follows. - Add
scripts/,references/orassets/only if the task genuinely needs them. An emptyreferences/folder costs nothing, but don't manufacture one just to look complete. - Validate it before publishing.
skills-ref validate ./your-skill-name, the reference checker the specification itself points to, catches frontmatter and naming mistakes (a name with a leading hyphen, a description over 1,024 characters) before an agent ever sees them. - Serve the folder somewhere fetchable: a static path on your own domain, or a release archive if you'd rather ship a single downloadable file, matching Vercel's
type: "archive"shape. - Write the index and serve it at both paths, byte-identical:
{ "skills": [ { "name": "your-skill-name", "description": "Same description as the SKILL.md frontmatter.", "files": ["SKILL.md"] } ] }/.well-known/agent-skills/index.jsonand/.well-known/skills/index.json: same file, two paths. It costs one extra static-file rule and closes the gap this article measured on 52 real sites.
Path 2: you run a documentation platform and want to know if it already does this for you. Adoption tracks the platform, not the writer. On the same 52-site sample, Starlight and GitBook sites answer 3-for-3 and 2-for-2; Mintlify answers 4 of 5; Next.js custom docs sites 11 of 13; Docusaurus and Hugo both answer 2 of 3. Hand-rolled documentation stacks, sites that built their own instead of adopting one of the above, answer only 8 of 19. If your docs run on a generated platform, check whether it already writes this file before adding your own. If you hand-rolled your stack, budget the same static-file work as Path 1, because nothing is generating it for you.
Nobody's CMS panel handles this yet, ourselves included. ThinkRank manages WordPress's robots.txt, robots meta, schema and llms.txt from one settings screen, but a skills index isn't one of its fields today, so a WordPress site adds this the same way a hand-rolled stack does: a static file dropped at both well-known paths. Same story on StoreSEO for a Shopify storefront. It generates your llms.txt from live products and pages, but a skills index today means a static asset served through a theme app extension, not a settings toggle.
Verify it actually landed
for p in agent-skills/index.json skills/index.json; do
echo "=== $p ==="
curl -sL -o /tmp/skills-check.json -w '%{http_code} %{content_type}\n' \
"https://yoursite.com/.well-known/$p"
python3 -c "import json; d=json.load(open('/tmp/skills-check.json')); print(len(d.get('skills',[])),'skill(s) parsed')" 2>/dev/null \
|| echo "did not parse as JSON, check the content-type above"
done
Pass looks like 200 application/json on the path(s) you serve, and the Python line printing a skill count rather than a parse error. Two failure modes are common, and both are silent unless you check for them. First, the file returns 200 but with content-type: text/html, meaning your host is answering with an app shell rather than the file you dropped; compare against a deliberately nonexistent path under the same .well-known/ prefix, since an identical body and byte count on both means neither is really being served. Second, it validates as JSON but every files entry 404s, because the referenced SKILL.md never got deployed alongside the index that points at it.
What AIScan checks, and what it can't
Check P3 currently probes /.well-known/agent-skills/index.json only. That's a real limitation we're disclosing rather than fixing quietly: on the 52-site sample above, a scanner reading one path finds 10 or 5 of the 11 sites actually publishing something, depending which path it picked. If your file lives only at the legacy /.well-known/skills/index.json path, as Stripe's does, a P3 pass today reads as a fail. Serving both paths, as Path 1 above recommends, sidesteps the gap entirely rather than waiting on it.
What P3 can verify: the path resolves, returns parseable JSON, and the response isn't a soft-200 app shell. What it can't verify, and what no automated check can, is whether your SKILL.md files are actually well-written enough for an agent to complete the task they describe, or whether the archive a type: "archive" entry points at is the version you meant to ship. Both are review questions, not scan questions. The skills-ref validator referenced in the specification checks format compliance, not quality.
Publishing an MCP server card is the closest sibling task: a different well-known file, a different discovery purpose (tools an agent can call, rather than instructions it can load), but the same shape of problem, a static file at a fixed path that most sites still don't serve. If you're doing one, budget time for the other.
Run the scan on your own site
npx aiscan-cli yoursite.com, or paste the URL at aiscan.site, free, no account. It runs check P3 alongside the rest of the capabilities dimension (P1, P2, P4, E2) and tells you which of the two well-known paths, if either, actually resolved on your domain. Browse the full guide library for the rest of the discovery-file series, including the MCP server card guide linked above.
Frequently asked questions
Is /.well-known/agent-skills/index.json an official standard?
No. The SKILL.md file format has a real, published specification at agentskills.io. The well-known path for advertising one from a website does not — it's a convention several vendors converged on independently, and they haven't converged on the same path. Treat the file format as settled and the discovery path as still moving; serve both known paths rather than betting on one becoming the winner.
I published my skills index, but AIScan's P3 check still shows a fail. What's wrong?
Check which path you served it at. Our P3 probe currently reads only /.well-known/agent-skills/index.json, the newer form. If your file lives at the older /.well-known/skills/index.json path only — the same choice Stripe's own documentation makes — a real, working file still reads as a 404 to this specific check. That's a known limitation on our side, not a defect in your file. Serving the same JSON at both paths is the fix that works regardless of which path any given scanner reads.
My index.json returns HTTP 200, but an agent still can't read it. What's actually wrong?
Check the content-type header first, not just the status code. A 200 with content-type: text/html usually means your host served its app shell instead of the JSON file you dropped — most catch-all routes do this for any unrecognized path. Confirm by requesting a path under the same .well-known/ prefix that has never existed; if it returns an identical body and byte count to your real file, neither is actually being served, and the fix is a routing rule, not a content change.
Do I need to publish my skill's SKILL.md file publicly, or just the index?
Both, if you want an outside agent to use the skill. The index is a pointer, not the payload — Stripe's index entries carry a files array naming each skill's actual SKILL.md and reference files, and an agent has to fetch those separately to get the instructions. An index whose linked files 404 is worse than no index: it tells an agent something exists, then fails the fetch that was supposed to prove it.
What's the difference between the files shape and the archive shape in an index entry?
Stripe's index points at individual files over HTTP — a name, a description, and a files array of relative paths, closer to a directory listing an agent reads file by file. Vercel's points at a single downloadable tarball with a type: "archive" field, a url, and a sha256 digest for integrity — closer to shipping a release artifact. Both are valid uses of the same index concept; pick whichever matches how you already distribute the rest of your skill's files.
Our documentation site is on Docusaurus. Does it publish this file for us automatically?
Not by default, and adoption varies a lot by platform even among sites that could. Across the sites we measured, Docusaurus installs answered 2 of 3, Starlight and GitBook installs answered every one we checked, and hand-rolled documentation stacks answered only 8 of 19. If you hand-rolled yours, remember that most static-site frameworks only serve files placed in a designated public or static-assets directory verbatim — a raw JSON file dropped elsewhere in the source tree gets processed or ignored rather than served as-is, so confirm the file lands at the literal URL after a full build and deploy, not just in your local dev server.
Should our WordPress or Shopify SEO plugin be managing this file?
Not yet, as far as we've found. ThinkRank manages WordPress's robots.txt, robots meta, schema and llms.txt from one screen, and StoreSEO generates a Shopify store's llms.txt from live products and pages, but neither currently has a settings field for a skills index. Until one does, this is a static file you add by hand or through your theme or deploy pipeline, on any platform.
Does a skills index replace llms.txt or a robots.txt entry for AI crawlers?
No — they answer different questions. robots.txt and Content Signals declare what a crawler may fetch; llms.txt is a curated link map into your existing content; a skills index advertises packaged, reusable task instructions an agent can load and run. A site can publish all three, and most sites that need one will eventually need the others too.
Related guides
Publish an MCP Server Card So Agents Can Find Your Tools
On 10 September 2026 we probed every host running a remote MCP server listed in the official MCP Registry: 79 domains, three requests each. Seven of them publish a parseable server card at…
Docs Sites and AI Agents: Four Markdown Routes, 52 Sites Tested
Ask an AI assistant how to configure a webhook, add a database index, or set a cache header, and it does not go looking for a blog post. It goes to the vendor's documentation. Docs are the…
Cloudflare Agent Readiness vs Chrome's Agentic Browsing Audit in 2026: 28 Checks, One Overlap
Verified 31 August 2026. Every score, status code and audit weight below was produced on this date by running both tools live. The Lighthouse figures come from Lighthouse 13.4.1 driving Chrome for…
AI Readiness Scanner Pricing in 2026: Every Price, Verified and Dated
Verified 30 August 2026. Every price below was fetched from the vendor's own pricing page on this date. Where a vendor publishes no price, that is recorded as a finding rather than filled in with a…
