# auth.md: agenticplug.ai agent authentication

You are an agent. agenticplug.ai supports **agentic registration** (the
auth.md profile): discover, register, optionally claim, exchange for an access
token, call the API, handle revocation. Everything here is on one host, which
is both the resource server and the authorization server:

    https://agenticplug.ai

What you get access to is our Model Context Protocol (MCP) server: public
knowledge tools and consultation tools (request a consultation and, where
enabled, see open times, book one, and move or cancel it with the booking
reference the booking returns). There is **no human login or consent gate**,
because nothing behind it is private: the knowledge is public, a request only
reaches the agenticplug.ai team, and a booking sends a standard invitation with
no caller-written text to the address given. Booking is limited per client and
per IP, to one upcoming consultation per address, and to a few per day.

## Step 1: Discover

A 401 from the MCP server carries this header, and repeats the links below in
its JSON body:

```http
WWW-Authenticate: Bearer resource_metadata="https://agenticplug.ai/.well-known/oauth-protected-resource/api/mcp"
```

- Protected Resource Metadata (RFC 9728), for the MCP server: https://agenticplug.ai/.well-known/oauth-protected-resource/api/mcp
- Protected Resource Metadata for the site itself: https://agenticplug.ai/.well-known/oauth-protected-resource
- Authorization Server metadata (RFC 8414), with the agent_auth block: https://agenticplug.ai/.well-known/oauth-authorization-server
- JWKS (token verification keys): https://agenticplug.ai/.well-known/jwks.json
- MCP Server Card (SEP-2127): https://agenticplug.ai/.well-known/mcp/server-card.json

The agent_auth block in the Authorization Server metadata:

```json
{
  "agent_auth": {
    "skill": "https://agenticplug.ai/auth.md",
    "register_uri": "https://agenticplug.ai/agent/identity",
    "identity_endpoint": "https://agenticplug.ai/agent/identity",
    "claim_uri": "https://agenticplug.ai/agent/identity/claim",
    "claim_endpoint": "https://agenticplug.ai/agent/identity/claim",
    "identity_types_supported": [
      "anonymous"
    ],
    "anonymous": {
      "credential_types_supported": [
        "access_token"
      ]
    }
  }
}
```

register_uri and identity_endpoint are the same URL, as are claim_uri and
claim_endpoint: both generations of the auth.md field names are published.

## Step 2: Pick a method

The only method is **anonymous**. There is no ID-JAG trust list here, so an
identity_assertion registration is refused with issuer_not_enabled, and
service_auth (email first) with service_auth_not_enabled. To tie an agent to a
person, register anonymously and claim it (Step 4).

## Step 3: Register

```http
POST https://agenticplug.ai/agent/identity
Content-Type: application/json

{ "type": "anonymous" }
```

```json
{
  "registration_id": "reg_...",
  "registration_type": "anonymous",
  "identity_assertion": "<service-signed JWT>",
  "assertion_expires": "2026-09-28T12:00:00.000Z",
  "credential_type": "access_token",
  "credential": "<access token>",
  "credential_expires": "2026-09-27T13:00:00.000Z",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scopes": ["mcp:read"],
  "pre_claim_scopes": ["mcp:read"],
  "post_claim_scopes": ["mcp:read"],
  "claim_url": "https://agenticplug.ai/agent/identity/claim",
  "claim_token": "clm_...",
  "claim_token_expires": "2026-09-28T12:00:00.000Z"
}
```

credential is ready to use now (Step 6). identity_assertion lasts a day and
mints fresh access tokens (Step 5). claim_token is returned once: hold it in
memory until you claim or the day is up. Registrations are limited per IP.

## Step 4: Claim (optional)

A claim binds your registration to a person's email address. There are no
accounts here and one scope, so it unlocks no extra tools. What it changes: the
new identity assertion carries the verified address and lasts 90 days
instead of one, and every pre-claim credential stops working. Claim only when
the person you act for asks to own the agent.

### 4a. Ask for a code

```http
POST https://agenticplug.ai/agent/identity/claim
Content-Type: application/json

{ "claim_token": "clm_...", "email": "person@example.com" }
```

```json
{
  "registration_id": "reg_...",
  "status": "initiated",
  "delivery": "email",
  "expires_at": "2026-09-27T12:10:00.000Z",
  "otp_expires_in": 600,
  "complete_url": "https://agenticplug.ai/agent/identity/claim/complete"
}
```

We email a 6-digit code to that address. The email contains only the code and
fixed text. Codes expire in 10 minutes. A registration can ask for
3 codes, and an inbox receives at most
3 a day, whichever agents ask (name+tag and Gmail dot
variants count as the same inbox). Only the latest code works, and only for
the address it was sent to.

### 4b. Ask the person for the code

"Check your email and tell me the 6-digit code from agenticplug.ai."

### 4c. Submit it

```http
POST https://agenticplug.ai/agent/identity/claim/complete
Content-Type: application/json

{ "claim_token": "clm_...", "otp": "123456" }
```

```json
{
  "registration_id": "reg_...",
  "status": "claimed",
  "email": "person@example.com",
  "identity_assertion": "<new service-signed JWT with email and email_verified>",
  "assertion_expires": "2026-12-26T12:00:00.000Z",
  "credential_type": "access_token",
  "credential": "<new access token>",
  "credential_expires": "2026-09-27T13:00:00.000Z",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scopes": ["mcp:read"]
}
```

Drop the pre-claim assertion and token and use these. A code can be tried
5 times; after that, or once it expires, ask for a new one (4a).

## Step 5: Exchange the assertion

```http
POST https://agenticplug.ai/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=<identity_assertion>
```

The response is a standard token response (access_token, token_type Bearer,
expires_in 3600, scope mcp:read). An optional resource parameter must be the
MCP server's URL. Re-use the same assertion whenever a token expires. There is
no refresh_token. invalid_grant means the assertion expired or was revoked:
register again.

## Step 6: Call the MCP server

It speaks MCP over Streamable HTTP, so every call is a JSON-RPC 2.0 POST, and
both Accept types are required. Start by listing the tools:

```http
POST https://agenticplug.ai/api/mcp
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json, text/event-stream

{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }
```

Then call one with `"method": "tools/call"` and
`"params": { "name": "<tool>", "arguments": { ... } }`. The server is
stateless, so no `initialize` handshake or session id is needed. A 401 on a
token that worked: exchange your assertion once (Step 5); if that returns
invalid_grant, register again.

While booking is enabled, `get_consultation_availability` returns every open
slot as `structuredContent` (`start`, `end`, a readable `display`), with a
Markdown summary as text. Give `book_consultation` a `retryKey` you
generate: a retry with the same key returns the first call's booking and its
reference. The same open times are also public, with no token, at
https://agenticplug.ai/api/consultation/availability

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| issuer_not_enabled, service_auth_not_enabled | /agent/identity | Register with type anonymous. |
| unsupported_credential_type | /agent/identity | The only credential type is access_token. |
| invalid_request | any | Fix the body; the description says what is wrong. |
| invalid_claim_token | /agent/identity/claim, /agent/identity/claim/complete | Register again. |
| previously_claimed | /agent/identity/claim, /agent/identity/claim/complete | Already claimed; use the credentials the claim returned. |
| claim_expired (410) | /agent/identity/claim, /agent/identity/claim/complete | The day is up. Register again. |
| otp_invalid | /agent/identity/claim/complete | Ask the person to read the code again. |
| otp_expired | /agent/identity/claim/complete | Ask for a new code (4a). |
| invalid_grant | /oauth/token | Register again. |
| invalid_target | /oauth/token | Omit resource, or send the MCP server's URL. |
| rate_limited (429) | any | Wait for Retry-After, then retry. |

5xx: back off and retry the same request. 4xx: do not resend the same payload.

## Revocation

Revoke with RFC 7009, form-encoded, no client authentication:

```http
POST https://agenticplug.ai/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=<access_token or identity_assertion>
```

Revoking an **access token** ends every access token issued to the
registration so far; the assertion survives, so exchange it for a new one
(Step 5). Revoking the **identity assertion** ends the registration and
everything issued from it. The response is 200 either way, and for a token we
do not recognise. Tokens from the client_credentials route below cannot be
revoked (unsupported_token_type); they expire within the hour.

## Also supported: OAuth client registration

Before auth.md, agents registered an OAuth client. That still works:

1. Register (RFC 7591 Dynamic Client Registration):

   ```http
   POST https://agenticplug.ai/oauth/register
   Content-Type: application/json

   { "client_name": "my-agent", "grant_types": ["client_credentials"] }
   ```

   The response includes client_id and client_secret.

2. Get a token (client_credentials grant):

   ```http
   POST https://agenticplug.ai/oauth/token
   Content-Type: application/x-www-form-urlencoded

   grant_type=client_credentials&client_id=...&client_secret=...
   ```

A browser or interactive agent may instead use the **authorization_code +
PKCE** grant at https://agenticplug.ai/oauth/authorize (register with
token_endpoint_auth_method "none" and a redirect_uris entry).

Every route issues the same kind of token: the mcp:read scope, which covers
every tool including the consultation tools, valid only for the MCP server at
https://agenticplug.ai/api/mcp
