# auth.md

You are an agent. This document tells you how to register a client, get a credential
for Padlet, and call the Padlet MCP server with it. Follow the steps in order.

Padlet uses standard OAuth 2.1, and every endpoint below lives at https://mcp.padlet.com

- **Audience** — agents acting for a signed-in Padlet user. There is no
  service-to-service tier: a credential with no Padlet user behind it does not exist.
- **Registration method** — open dynamic client registration, RFC 7591 (Step 1).
  Registration itself is anonymous: no account, no pre-shared secret, no approval
  queue. The client it returns cannot call anything until a human authorizes it in
  Step 2, which binds it to a Padlet user.
- **Authorization method** — authorization code grant with PKCE `S256`, RFC 6749 and
  RFC 7636 (Steps 2 and 3). A human approves your client in a browser.
- **Credential** — a bearer OAuth access token, sent in the `Authorization` header
  (Step 4). It expires after 90 days and comes with a refresh token that renews it
  without the user (see Refreshing a credential).

## Step 1 — Register a client

```http
POST /oauth/register HTTP/1.1
Host: mcp.padlet.com
Content-Type: application/json

{
  "client_name": "Your Agent",
  "redirect_uris": ["https://your-agent.example.com/callback"]
}
```

Response (201):

```json
{
  "client_id": "e1f4...",
  "client_secret": "9ab2...",
  "client_secret_expires_at": 0,
  "client_name": "Your Agent",
  "redirect_uris": ["https://your-agent.example.com/callback"],
  "token_endpoint_auth_method": "client_secret_basic",
  "grant_types": ["authorization_code","refresh_token"],
  "response_types": ["code"],
  "scope": "mcp"
}
```

What each field is:

- `client_id` — identifies your client at every endpoint below. Not a secret.
- `client_secret` — your client credential at the token endpoint. Returned **once**;
  persist it and do not log it. Send `"token_endpoint_auth_method": "none"` at
  registration if you are a public client, and no secret is issued.
- `scope` — the granted scope, always `mcp`. Echo it at Step 2.

Every call mints a **new** client. Registering again returns different credentials and
does not recover the previous ones, so store what you get.

## Step 2 — Get the user's authorization

Send the user to the authorization endpoint in a browser. PKCE is required and `S256`
is the only challenge method accepted. `scope` defaults to `mcp` when you omit it, and
`mcp` is the only scope there is.

```
https://mcp.padlet.com/oauth/authorize
  ?response_type=code
  &client_id=<client_id>
  &redirect_uri=<registered redirect_uri>
  &code_challenge=<S256 challenge>
  &code_challenge_method=S256
  &scope=mcp
```

The user signs in to Padlet and approves your client. Padlet redirects to your
`redirect_uri` with `code`. There is no headless variant of this step.

## Step 3 — Exchange the code for a credential

```http
POST /oauth/token HTTP/1.1
Host: mcp.padlet.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<code>&redirect_uri=<same redirect_uri>&code_verifier=<PKCE verifier>&client_id=<client_id>
```

Authenticate with `client_secret_basic`, `client_secret_post`, or `none` if you
registered as a public client. `authorization_code` is the only grant type that mints a
first token; `refresh_token` renews one you already hold.

Response (200):

```json
{
  "access_token": "7c5d...",
  "token_type": "Bearer",
  "expires_in": 7776000,
  "refresh_token": "0b31...",
  "scope": "mcp",
  "created_at": 1767225600
}
```

`access_token` is the credential and lasts 90 days. `refresh_token` does not expire on
its own and buys you a new pair without sending the user back through Step 2. Store
both.

## Step 4 — Call the MCP server

Present the credential in the `Authorization` header. It is the only accepted method:
a token in a query param or form field is ignored.

```http
POST / HTTP/1.1
Host: mcp.padlet.com
Authorization: Bearer <access_token>
Content-Type: application/json
```

https://mcp.padlet.com/ speaks MCP over Streamable HTTP and is the surface this credential is
scoped to. Its tools act as the approving user, with exactly that user's access to
padlets.

Padlet also has a REST API at https://api.padlet.dev/, but **nothing on this page
applies to it**: it takes a user-generated API key in an `x-api-key` header, not an
OAuth token, and the key is issued from Padlet's developer settings by a paying user
rather than by any endpoint here. See https://docs.padlet.dev/reference/introduction

## Refreshing a credential

Before the 90 days are up — or as soon as a call returns 401 `invalid_token` — trade
the refresh token for a fresh pair. No browser and no user are involved:

```http
POST /oauth/token HTTP/1.1
Host: mcp.padlet.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<refresh_token>&client_id=<client_id>
```

Identify yourself the same way you did in Step 3: a public client sends `client_id`
only, a confidential one authenticates with its secret. The response has the same shape
as Step 3, with a new access token and a new refresh token, scoped and lived exactly
like the pair it replaces.

Rotation is immediate and there is no grace period: the moment this call succeeds, the
old access token and the old refresh token are both revoked. Persist the new pair
before you discard the old one, and never refresh twice concurrently — replaying a
spent refresh token returns 400 `invalid_grant`, and a client that loses the response
has lost both credentials and has to start again at Step 2.

## Revoking a credential

To discard a token you hold (RFC 7009). Authenticate as the client the token was
issued to — a request carrying only `token` is refused, whichever client type you
registered as:

```http
POST /oauth/revoke HTTP/1.1
Host: mcp.padlet.com
Authorization: Basic <base64 of client_id:client_secret>
Content-Type: application/x-www-form-urlencoded

token=<access_token>&token_type_hint=access_token
```

A public client has no secret to send, but still has to identify itself — put
`client_id=<client_id>` in the body and send no `Authorization` header. Either way the
token must be one this client owns.

The user can also revoke your client's access at any time from Padlet. Either way the
next call returns 401 `invalid_token`.

## Errors

| Status | Where | Meaning | What to do |
| --- | --- | --- | --- |
| 400 `invalid_client_metadata` | /oauth/register | A `redirect_uris` entry is missing, malformed, or contains whitespace; or an unsupported `token_endpoint_auth_method` | Fix the metadata and re-register. Do not retry the same payload |
| 401 `invalid_client` | /oauth/token | Wrong `client_secret`, or the auth method does not match the one you registered | Re-check credentials; register again if lost |
| 400 `invalid_grant` | /oauth/token | Code expired, already used, or `redirect_uri`/`code_verifier` mismatch; or a spent, revoked or foreign refresh token | Restart at Step 2 |
| 401 `invalid_token` | / | Credential expired or revoked | Refresh it; if that also fails, restart at Step 2. Do not stash and retry |
| 403 `unauthorized_client` | /oauth/revoke | The request carried no client credentials, or the token belongs to a different client | Retry with this client's credentials. The token is **not** revoked |
| 429 | any | Rate limited | Exponential backoff, retry |
| 5xx | any | Transient server error | Exponential backoff, retry the same request |

## Endpoints

- Register a client — https://mcp.padlet.com/oauth/register
- Authorize a user — https://mcp.padlet.com/oauth/authorize
- Exchange a code — https://mcp.padlet.com/oauth/token
- Revoke a credential — https://mcp.padlet.com/oauth/revoke
- Call MCP tools — https://mcp.padlet.com/

## Discovery

- Protected resource metadata — https://padlet.com/.well-known/oauth-protected-resource
- Authorization server metadata, carrying the `agent_auth` block — https://mcp.padlet.com/.well-known/oauth-authorization-server
- MCP server card — https://padlet.com/.well-known/mcp/server-card.json
- Agent skills — https://padlet.com/.well-known/agent-skills/index.json

## What Padlet does not implement

So you do not spend a request finding out:

- There is no identity endpoint, and no ID-JAG or other identity-assertion exchange.
- Every credential is bound to a Padlet user by Step 2, and nothing else binds one, so
  there is nothing left to claim afterwards.
- Only the registration call in Step 1 is anonymous. Step 2 is mandatory. Every
  credential is bound to a Padlet user, and a token that no Padlet user approved does
  not exist. If you are looking for the Auth.md anonymous tier, where the credential
  itself works with no user behind it, Padlet does not have one.
- Revocation is not notified out of band. Treat a 401 `invalid_token` as the signal.

Step 2 is therefore not optional, and it is the only place a human is required.
