The auth.md file
auth.md is a single Markdown file an application publishes at a well-known location to tell agents how to register. This page walks through where to host it, the sections it should contain, and how agents are expected to parse it.
Example
See the example auth.md for a complete template you
can adapt.
Where to host it
Serve the file at your service root: https://service.example.com/auth.md. Agents
discover it the same way humans do — by reading documentation, following SDK
pointers, or by examining a WWW-Authenticate header you return on 401
responses. The structured Protected Resource Metadata (PRM) at
/.well-known/oauth-protected-resource remains the authoritative source for
endpoint URLs and supported flows; auth.md is the prose companion that
points back at it.
Sections in order
A well-formed auth.md is organized as a numbered walkthrough an agent
follows top to bottom. Here is what each section should cover:
Title and intro
A one-line title (# auth.md) followed by a short preamble addressed to
the agent. State that the service supports agentic registration and name
the real hostnames you'll use throughout the file — your resource server
(the API base, e.g. https://api.yourservice.example.com) and your authorization
server (where registration and claim live, e.g.
https://auth.yourservice.example.com) — so the agent knows which host each
example targets.
Discover
A two-hop discovery walkthrough. Show the agent how to:
- Pull the PRM URL from the
WWW-Authenticate: Bearer resource_metadata="…"header on a 401, or fall back to the conventional/.well-known/oauth-protected-resourcepath. - Fetch the Protected Resource Metadata and read
resource,resource_name,authorization_servers,scopes_supported, andbearer_methods_supported. - Fetch the Authorization Server metadata at
/.well-known/oauth-authorization-serverand read both the standard OAuth fields —issuer,token_endpoint,revocation_endpoint,grant_types_supported— and theagent_authblock in full:skill,identity_endpoint,claim_endpoint,events_endpoint,identity_types_supported,identity_assertion.assertion_types_supported, andevents_supported.
Document every field. The agent uses these to decide what to call next.
Pick a method
A decision tree mapping what the agent has on hand to the right registration method:
- A session it can exchange for an ID-JAG bound to this service's
audience →
identity_assertion. - Only the user's email →
service_auth(claim ceremony required). - Neither →
anonymous(claim ceremony optional, deferred until the user wants to take ownership).
For identity_assertion, tell the agent to cross-check its assertion type
against identity_assertion.assertion_types_supported before sending — if
it isn't listed, pick another method or stop. For service_auth and
anonymous, identity_types_supported is informational; the agent sends
the body and falls back on the *_not_enabled error if the service opted
out.
Register
One subsection per registration type your service accepts. For each:
show the exact POST /agent/identity body and the success response in
full. Use fenced http and json code blocks so the agent can extract
the templates directly. Every path returns a service-signed
identity_assertion (never a credential directly) that the agent
exchanges at /oauth2/token for an access_token.
- identity_assertion — when
(iss, sub)is known or the user is JIT-provisioned, completes immediately; the response carriesidentity_assertion,assertion_expires, andscopes. When the verified email matches an existing account, the service returns401 interaction_requiredwith aclaimblock (first-link step-up); whenauth_timeis stale,401 login_required. - service_auth — body is
{ "type": "service_auth", "login_hint": "<email>" }. Returns aclaim_tokenand aclaimblock (user_code,verification_uri) but no assertion yet; the assertion is minted when the user completes the ceremony. - anonymous — returns a pre-claim
identity_assertionplus aclaim_token, so the agent can exchange for an access_token and start working atpre_claim_scopesimmediately.
Claim ceremony
The device-authorization-shaped ceremony (RFC 8628
vocabulary), broken into three substeps. The service never emails a
code — the user_code travels from the agent to the user, and the user
authenticates on a page the service owns.
- 4a. Get the ceremony materials —
service_authregistrations already have theclaimblock from the registration response. Foranonymous,POST /agent/identity/claimwith theclaim_tokenand the user's email to mint aclaim_attemptblock. - 4b. Hand off to the user — the agent surfaces
verification_urianduser_codein one message. The user opens the link, signs in (or signs up) at the service, lands on the claim page, and types the code there — not back to the agent. - 4c. Poll for completion —
POST /oauth2/tokenwithgrant_type=urn:workos:agent-auth:grant-type:claimand theclaim_token. Returnsauthorization_pendingwhile waiting; on success a standard token response plus anidentity_assertion. If theuser_codewindow lapses (expired_token), re-call/agent/identity/claimfor a fresh code.
Exchange the assertion
Show the RFC 7523
JWT-bearer exchange at token_endpoint:
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>,
with an optional resource to pin the token to the API. The response is a
standard OAuth token envelope (access_token, token_type, expires_in,
scope) with no refresh_token. The same assertion can be
re-exchanged until it expires.
Use the access_token
Show that the access_token is presented as
Authorization: Bearer <access_token>. Document refresh: when the
access_token expires, re-exchange the same identity_assertion; when the
assertion itself expires or /oauth2/token returns invalid_grant,
restart at registration. There is no OAuth refresh_token — the two-step
pattern (assertion → access_token) replaces it.
Errors
A table mapping each error code to the endpoint that returns it and the
action the agent should take. Cover the /agent/identity codes
(invalid_issuer, invalid_signature, expired, replay_detected,
invalid_audience, invalid_client_id, missing_verified_email,
invalid_request, the *_not_enabled family, and the 401s
interaction_required and login_required); the /agent/identity/claim
codes (invalid_claim_token, claimed_or_in_flight, claim_expired);
and the /oauth2/token OAuth codes (invalid_grant, invalid_client,
unsupported_grant_type, and the claim grant's authorization_pending,
expired_token, slow_down).
Revocation
Spell out the two independent layers that can kill what the agent holds:
- Credential layer (RFC 7009,
revocation_endpoint) — agent-callable.POSTthe access_token to/oauth2/revoketo kill it; theidentity_assertionsurvives, so the agent can re-exchange for a fresh access_token. - Registration layer (RFC 8935
SET delivery,
events_endpoint) — provider-driven. The provider POSTs asecevent+jwtto the service, invalidating the assertion and every access_token derived from it. The agent discovers this asinvalid_granton the next exchange and restarts at registration.
How agents parse it
Agents are expected to treat auth.md as both prose and structured data:
- Scan headings to find sections relevant to the current step (registration vs. operation).
- Read the Discovery section first to learn which flows are supported before committing to one.
- Extract code blocks as templates for HTTP requests. Use fenced blocks
with language hints (
http,json) so the agent can identify request shapes without ambiguity. - Treat the PRM as authoritative if anything in this file conflicts with it. The PRM is the runtime source of truth.
Keep the file conservative in length and high in signal. Anything an agent
doesn't need to register or operate against your API belongs in your main
documentation, not in auth.md.