Dark green cover graphic showing two overlapping padlock key cards, one solid emerald and one outlined mint, titled OAuth Discovery for AI Agents.
Dark green cover graphic showing two overlapping padlock key cards, one solid emerald and one outlined mint, titled OAuth Discovery for AI Agents.
AI Readiness

OAuth Discovery for AI Agents: RFC 8414 and RFC 9728 Explained

RFC 8414 names your authorization server. RFC 9728 names it to an agent with no token yet. How to publish both, and what AIScan's P4 check actually grades.

AAsif Rahman 28 Sept 2026 11 min read
#OAuth#MCP#agent discovery#well-known#AI readiness

This guide covers P4 · Capabilities.

Table of contents

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 thisTimeWhat it changes
Tell an agent which authorization server handles your APIPublish RFC 9728 metadata at /.well-known/oauth-protected-resource10 minYour 401 responses point an agent at the right server
Let an agent find your authorization server's endpointsPublish RFC 8414 metadata at /.well-known/oauth-authorization-server10 minAgents get token_endpoint and authorization_endpoint without hardcoding them
Let a new agent register itself, no manual setupAdd a registration_endpoint per RFC 759115–30 minRemoves the step where a developer emails you for a client ID
Check what you already exposeRun a scan and read check P41 minTells 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:

  1. The agent calls your protected endpoint with no token and gets 401 with that header.
  2. 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.
  3. For the issuer it picks, it fetches <issuer>/.well-known/oauth-authorization-server per RFC 8414 Section 3, and validates that the issuer value inside the response is identical to the one it requested (Section 3.3 makes that check mandatory, not optional).
  4. 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 a client_id on 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."
  5. It completes the flow at authorization_endpoint, exchanges the result for a token at token_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.

FieldRFCStatusWhat it tells the agent
issuer8414 §2RequiredThe server's identity, cross-checked on fetch
authorization_endpoint8414 §2Required*Where to send the user for consent
token_endpoint8414 §2Required*Where to exchange a code for a token
registration_endpoint8414 §2 / 7591OptionalWhere a new client registers itself
resource9728 §2RequiredWhich API this file describes
authorization_servers9728 §2RecommendedWhich 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.

  1. Decide your issuer identifier: an https URL with no query string or fragment. This is the value everything else keys off.
  2. Write the authorization-server file with issuer, authorization_endpoint, token_endpoint, response_types_supported, and scopes_supported if you support scoped access.
  3. Serve it at /.well-known/oauth-authorization-server with content-type: application/json.
  4. Write the protected-resource file with resource set to your API's own identifier and authorization_servers naming the issuer from step 1.
  5. Serve it at /.well-known/oauth-protected-resource.
  6. On every 401 your API returns, add the WWW-Authenticate header 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:

StackWhere the file goesGate
Hugo, Docusaurus, Astrostatic/.well-known/ (Docusaurus also honours baseUrl)none
Next.jspublic/.well-known/, or a route handlernone
Lovable, Replitpublic/.well-known/ and client/public/.well-known/none
WordPressa real file under the web root, since .well-known is served before PHPnone
FramerSite settings, Hosting, FilesPro and above
WebflowEnterprise-only APIEnterprise
Ghostblocked: theme middleware denies .json outside /assets/n/a
Squarespace, Wixno route exists on any plann/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_servers in 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_supported or scopes_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 seeWhat it meansFirst step
Both jq -e calls exit 0 with real URLsBoth files are live and parseNothing
jq exits non-zeroA required field is missingAdd the field named in the error
content-type: text/html on either fileYour host is serving the app shell, not the fileSet the content type explicitly in the route
No www-authenticate header on the 401An agent has to already know the well-known pathAdd the header per RFC 9728 §5.1
issuer in the response doesn't match the URL you fetchedFails RFC 8414 §3.3's own validation ruleFix 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