Table of contents
- Quick summary
- Why an agent needs two files, not one
- The request an agent actually makes
- What each file actually contains
- Path one: hand-write both files
- Path two: framework-native routes
- Who doesn't need this yet
- Keep it correct after anything changes
- A worked example already live
- Verify it worked
- Where AIScan's P4 check fits, and where it stops
An agent calls your API cold, with no token. Your server returns 401, and the agent has nowhere to go from there unless something on your domain names the authorization server it should talk to. Two RFCs solve two different halves of that problem, and a site that ships only one of them still leaves an agent stuck.
Quick summary
| If you want to… | Do this | Time | What it changes |
|---|---|---|---|
| Tell an agent which authorization server handles your API | Publish RFC 9728 metadata at /.well-known/oauth-protected-resource | 10 min | Your 401 responses point an agent at the right server |
| Let an agent find your authorization server's endpoints | Publish RFC 8414 metadata at /.well-known/oauth-authorization-server | 10 min | Agents get token_endpoint and authorization_endpoint without hardcoding them |
| Let a new agent register itself, no manual setup | Add a registration_endpoint per RFC 7591 | 15–30 min | Removes the step where a developer emails you for a client ID |
| Check what you already expose | Run a scan and read check P4 | 1 min | Tells you whether the resource-side file is live today |
RFC 8414's own abstract states its purpose plainly: it "defines a metadata format that an OAuth 2.0 client can use to obtain the information needed to interact with an OAuth 2.0 authorization server." RFC 9728 does the parallel job one layer up, for "an OAuth 2.0 client or authorization server" that needs "the information needed to interact with an OAuth 2.0 protected resource." Read together, one file says who handles authentication here, the other says what that authentication server's own front door looks like.
Why an agent needs two files, not one
Picture two different documents on a building. Taped to the front door is a sign: "Badge questions go to Suite 400." That sign doesn't hand you a badge, and it doesn't tell you Suite 400's own process. It only tells you where to ask. That's /.well-known/oauth-protected-resource. It names an authorization_servers array: the issuer identifiers your API trusts, nothing more.
Suite 400 has its own intake form pinned to its door: which window to submit a form at, which window hands back a completed badge. That's /.well-known/oauth-authorization-server. It carries the actual working parts: authorization_endpoint, token_endpoint, the response_types_supported the server will accept.
An agent holding only the front-door sign knows who to ask but not how to ask. An agent that already trusts Suite 400 but showed up at the wrong building has no sign telling it Suite 400 covers this one at all. A working discovery flow needs both files to exist, and it needs them to point at each other correctly.
The request an agent actually makes
RFC 9728 Section 5.1 defines the exact signal a protected resource sends back on a 401: a resource_metadata parameter inside the WWW-Authenticate header, naming the metadata URL directly rather than making the agent guess the well-known convention:
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"
From there the sequence is mechanical:
- The agent calls your protected endpoint with no token and gets 401 with that header.
- It fetches the named URL (or falls back to the well-known path) and reads
authorization_servers, an array of issuer identifiers, per RFC 9728 Section 2. - For the issuer it picks, it fetches
<issuer>/.well-known/oauth-authorization-serverper RFC 8414 Section 3, and validates that theissuervalue inside the response is identical to the one it requested (Section 3.3 makes that check mandatory, not optional). - If the agent has never registered as a client with that server, and the metadata carries a
registration_endpoint, it uses RFC 7591 dynamic client registration to get aclient_idon the spot rather than a human doing it by email. RFC 7591's own Section 1 names exactly why this matters for agent software: client developers "are unable to predict aspects of the software, such as the deployment URLs, at compile time." - It completes the flow at
authorization_endpoint, exchanges the result for a token attoken_endpoint, and retries the original request.
Skip either file and the chain breaks at a different point. No protected-resource metadata: the agent never learns which server to trust in the first place. No authorization-server metadata: it knows the right server but has to have its endpoints hardcoded somewhere, which is exactly the brittleness this pair of specs exists to remove.
What each file actually contains
RFC 8414 Section 2 marks three fields REQUIRED: issuer (an https URL with no query or fragment), response_types_supported, and authorization_endpoint (the last one only unless no supported grant type uses it). token_endpoint is required unless the server supports only the implicit grant. scopes_supported is RECOMMENDED. registration_endpoint and jwks_uri are OPTIONAL, and Section 3 requires the whole document to be served over https at exactly /.well-known/oauth-authorization-server.
RFC 9728 Section 2 marks one field REQUIRED: resource, the protected resource's own identifier URL. authorization_servers, bearer_methods_supported and resource_documentation are the fields a client actually reads to act on the file. The default location, per Section 3, is /.well-known/oauth-protected-resource.
| Field | RFC | Status | What it tells the agent |
|---|---|---|---|
issuer | 8414 §2 | Required | The server's identity, cross-checked on fetch |
authorization_endpoint | 8414 §2 | Required* | Where to send the user for consent |
token_endpoint | 8414 §2 | Required* | Where to exchange a code for a token |
registration_endpoint | 8414 §2 / 7591 | Optional | Where a new client registers itself |
resource | 9728 §2 | Required | Which API this file describes |
authorization_servers | 9728 §2 | Recommended | Which issuer(s) this API trusts |
*Required unless the server structurally has no use for it (see RFC 8414 §2 for the exact conditions).
Path one: hand-write both files
Best when your API doesn't run inside a framework with its own router, or when your authorization server is a separate service you already operate.
- Decide your issuer identifier: an
httpsURL with no query string or fragment. This is the value everything else keys off. - Write the authorization-server file with
issuer,authorization_endpoint,token_endpoint,response_types_supported, andscopes_supportedif you support scoped access. - Serve it at
/.well-known/oauth-authorization-serverwithcontent-type: application/json. - Write the protected-resource file with
resourceset to your API's own identifier andauthorization_serversnaming the issuer from step 1. - Serve it at
/.well-known/oauth-protected-resource. - On every 401 your API returns, add the
WWW-Authenticateheader from RFC 9728 §5.1 naming the file's URL directly, so an agent doesn't have to already know the well-known convention to find it.
Path two: framework-native routes
Best when your API already lives inside a router that owns everything under your domain, so the well-known files deploy with the rest of the app instead of as separate static assets.
Every router handles a literal dot in a path segment differently, and getting this wrong is the single most common reason a "published" file 404s. This project's own MCP server runs on TanStack Start, and its two routes are src/routes/[.]well-known.oauth-authorization-server.ts and src/routes/[.well-known]/oauth-protected-resource.ts. The bracket escaping around the leading dot is TanStack Start's own file-based routing convention, verified against this codebase on 1 September 2026, and it is not a convention every router shares. Check your framework's own routing documentation for how it escapes a literal . in a path segment before assuming the same syntax applies.
Whichever router you use, the route handler still has to set the content type explicitly. A handler that returns a plain object and lets the framework guess often ships text/html on an error path, and a strict client will refuse to parse it as metadata.
Where the file itself is allowed to live also depends on the platform, and this is the same well-known-path question our MCP server card guide measured for a different file at the same kind of path:
| Stack | Where the file goes | Gate |
|---|---|---|
| Hugo, Docusaurus, Astro | static/.well-known/ (Docusaurus also honours baseUrl) | none |
| Next.js | public/.well-known/, or a route handler | none |
| Lovable, Replit | public/.well-known/ and client/public/.well-known/ | none |
| WordPress | a real file under the web root, since .well-known is served before PHP | none |
| Framer | Site settings, Hosting, Files | Pro and above |
| Webflow | Enterprise-only API | Enterprise |
| Ghost | blocked: theme middleware denies .json outside /assets/ | n/a |
| Squarespace, Wix | no route exists on any plan | n/a |
On Squarespace, Wix, Ghost or a basic Webflow plan, neither file can be self-hosted at all. That only matters if your site itself exposes an authenticated API, which is rare on those builders in the first place. Skip this check honestly rather than forcing a workaround your platform doesn't support.
Who doesn't need this yet
If your site has no authenticated API at all, P4 being not applicable is the correct answer, not a gap to fill. A contact form or a webhook receiver that only sends callouts isn't a protected resource in this sense either. RFC 9728 describes the side that receives and checks a credential, not the side that fires one out.
And if your authorization server is a hosted identity provider rather than code you run yourself, the RFC 8414 half is often already done. Auth0, WorkOS, Okta and most other managed providers publish their own authorization-server metadata by default, at their own domain rather than yours. Confirm it once at that provider's own well-known path rather than assuming either way.
Keep it correct after anything changes
Both files are static JSON, so nothing on your server alerts you when they drift from reality.
- Rotate authorization servers → update
authorization_serversin the resource-side file. Leave the old issuer listed and an agent keeps trusting a server you no longer use. - Add a grant type or a scope → update
response_types_supportedorscopes_supported. An agent reading a stale file never learns the new path exists. - Move a route or migrate frameworks → re-run the three verification calls below. A silently renamed route is the most common way one of these files stops being served without anyone noticing.
A worked example already live
This site's own MCP server card, fetched fresh from /.well-known/mcp/server-card.json on 28 September 2026, names the pattern directly in its auth block:
"auth": {
"type": "oauth2",
"dynamic_client_registration": true,
"protected_resource_metadata": "https://aiscan.site/.well-known/oauth-protected-resource"
}
The card doesn't repeat the authorization-server details inline. It points an agent at the RFC 9728 file, and that file's authorization_servers array is what tells the agent where to go next. As the server-card guide put it, a card pointing at an endpoint with no OAuth metadata behind it "leaves an agent one step short." The card alone was never meant to finish the job.
Verify it worked
Three requests settle both files:
curl -sS https://yourapi.com/.well-known/oauth-authorization-server \
| jq -e '.issuer, .authorization_endpoint, .token_endpoint, .response_types_supported'
curl -sS https://yourapi.com/.well-known/oauth-protected-resource \
| jq -e '.resource, .authorization_servers'
curl -sSI https://yourapi.com/your-protected-endpoint \
| grep -i 'www-authenticate'
| What you see | What it means | First step |
|---|---|---|
Both jq -e calls exit 0 with real URLs | Both files are live and parse | Nothing |
jq exits non-zero | A required field is missing | Add the field named in the error |
content-type: text/html on either file | Your host is serving the app shell, not the file | Set the content type explicitly in the route |
No www-authenticate header on the 401 | An agent has to already know the well-known path | Add the header per RFC 9728 §5.1 |
issuer in the response doesn't match the URL you fetched | Fails RFC 8414 §3.3's own validation rule | Fix the issuer value, don't relax the client |
Where AIScan's P4 check fits, and where it stops
Run npx aiscan-cli yoursite.com, or paste the URL at aiscan.site; no signup, no charge. P4 is one of five capability checks, weighted alongside P1 (API catalog, RFC 9727), P2 (MCP server card), P3 (Agent Skills index) and E2 (machine-readable API descriptions). Its own description states the scope directly: "For authenticated APIs, agents need to know which OAuth authorisation server to talk to. RFC 9728 defines /.well-known/oauth-protected-resource for exactly that." The check is marked not applicable when a site has no authenticated APIs at all.
Worth knowing plainly: P4 grades the RFC 9728 file only. It doesn't probe /.well-known/oauth-authorization-server or verify anything RFC 8414 requires. That's a reasonable scope for a site scan (your authorization server is often a separate service, sometimes one you don't operate at all), but it means a clean P4 pass is not proof an agent can finish the flow this article just walked through. If your authorization server is third-party, confirm it publishes RFC 8414 metadata separately; the check here can't see that far.
If you'd rather check by hand than run the scan, the three curl calls above cover exactly what P4 and the RFC 8414 side both need. Every other discovery surface this dimension covers is documented on the capabilities checks page, and the rest of our platform walkthroughs are collected at aiscan.site/guides.
Frequently asked questions
What is OAuth discovery for AI agents?
It is the pair of well-known JSON files, defined by RFC 8414 and RFC 9728, that let an agent find your authorization server's endpoints and confirm which server your API trusts, without either value being hardcoded into the agent's own code. RFC 8414 covers the authorization server itself (its authorization_endpoint and token_endpoint). RFC 9728 covers the protected resource, naming which authorization server(s) it accepts tokens from.
Do I need both RFC 8414 and RFC 9728, or just one?
You need whichever one describes the service you actually run. If you operate the API an agent calls, publish RFC 9728's /.well-known/oauth-protected-resource so agents know which authorization server to trust. If you also operate that authorization server, publish RFC 8414's /.well-known/oauth-authorization-server so agents know its actual endpoints. Many sites only run the API and rely on a third-party identity provider for the authorization-server half.
AIScan's report shows check P4 as not applicable. Does that mean something is broken?
No. P4 is marked not applicable when a site has no authenticated APIs at all, and that is the correct, expected result for most marketing sites, blogs and content platforms. There is nothing to fix. If your site does expose an authenticated API and P4 still reads not applicable or fails, that is when to check the file with the verification steps in this guide.
I published /.well-known/oauth-protected-resource but agents still get a plain 401 with nothing pointing at it. What's missing?
Your 401 response itself needs a WWW-Authenticate header carrying a resource_metadata parameter that names the file's URL directly, per RFC 9728 Section 5.1. Without that header, an agent has to already know the well-known convention exists and guess the path. Add the header, then confirm it with curl -sSI against your protected endpoint and grep for www-authenticate.
Do I need to run my own authorization server?
No. Most sites use a third-party identity provider such as Auth0, WorkOS or Okta, and those providers publish their own RFC 8414 metadata at their own domain by default. Your job is usually only the RFC 9728 half: naming that provider's issuer inside your own protected-resource file's authorization_servers array.
My authorization server has no registration_endpoint. Do agents just fail?
No. registration_endpoint is optional in RFC 8414, and RFC 7591 dynamic client registration is a convenience for agents that have never talked to your server before, not a requirement. Without it, a new client still authenticates, it just needs a client_id issued through whatever manual process your authorization server normally uses, the same as any other OAuth client.
Where do these files go on WordPress or a site builder like Wix or Squarespace?
On WordPress, both files are real files placed under the web root, since .well-known is served before PHP runs. On Squarespace, Wix and Ghost, there is no route for a custom file at .well-known on any plan, which only matters if that platform is also hosting the authenticated API in the first place, and that is rare. Framer requires a Pro plan or above; Webflow requires Enterprise.
My route returns the right JSON but agents still reject it. What's wrong?
Check the content type first. A route handler that lets the framework guess the response type often ships text/html on an error path, and a strict client won't parse that as metadata even if the body looks correct. Set content-type: application/json explicitly, then re-run curl -sS <url> | jq -e '.issuer' (or .resource for the protected-resource file) to confirm it parses.
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…
WebMCP in 2026: Do You Actually Need It Yet? 85 Sites Measured
Verified 2 September 2026. Every number below was measured on this date against live sites. The honest answer to "do I need WebMCP yet" turned out to depend on a question nobody asks first: is it…
