# auth.md for ucalyptus.me

This document tells an agent (or a person reading on an agent's behalf) how to
obtain and use a credential for the one protected thing on this site: the
extended profile endpoint.

Spec reference: [https://workos.com/auth-md](https://workos.com/auth-md).

## Discover

Discovery metadata lives at fixed, well-known URLs:

- **Authorization server metadata (RFC 8414):** `/.well-known/oauth-authorization-server`
  and the anonymous credential type. The host serving this document is always the
  origin that answers it, so read it from whichever host you were given.
- **Protected resource metadata (RFC 9728):** `/.well-known/oauth-protected-resource`
  and `scopes_supported`.
- **Public keys (JWKS):** `/.well-known/jwks.json` for verifying issued tokens.
- **This file** is linked from `agent_auth.skill` in the authorization-server
  metadata, and from the API catalog: `/.well-known/api-catalog`.

## What's protected

One endpoint: `GET /api/agent/full-profile`. It returns a fuller, structured
version of the author's CV and current availability (service and leadership
history, and what he is and isn't currently open to hearing about from an
agent) than the public [`/api/site.json`](/api/site.json).

Everything else on ucalyptus.me (publications, projects, the public CV) is
already open, no token needed.

## Pick a method

This site supports **one** credential method, honestly documented: OAuth 2.0
`client_credentials` with the **anonymous** identity type.

- Identity type: **`anonymous`**. No user identity is claimed or verified. This is a
  personal site with no accounts.
- Credential type: **`client_secret`** (a client ID + derived secret).
- Token format: **ES256 JWT**, short-lived.

`identity_assertion`/JAG-style assertion tokens (auth.md's
`identity_assertion.assertion_types_supported`) and `id-jag` tokens are **not
supported** here. There is no identity to assert. Do not attempt them.

## Register

```
POST https://ucalyptus.me/oauth/register
Content-Type: application/json

{"client_name": "your-agent-name"}
```

Returns `client_id` and `client_secret` (`201`). Neither is stored server-side
— the secret is HMAC-derived from the id, recomputed on demand. Losing the id
means registering again, not recovering the old secret.

Registration is self-serve, free, and has no rate-limit on purpose (within the
Worker's coarse rate limits). No approval is needed.

## Claim

Unsupported via assertion here (no `identity_assertion`, no `id-jag`). With the
**anonymous + client_secret** method, the "credential claim" is simply
presenting the registered client ID and its derived secret at the token
exchange:

```
POST https://ucalyptus.me/oauth/token
Content-Type: application/x-www-form-urlencoded

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

Returns `access_token`, `token_type: Bearer`, `expires_in` (900s), `scope`
(`read:extended`).

## Use the credential

```
GET https://ucalyptus.me/api/agent/full-profile
Authorization: Bearer <access_token>
```

- Token TTL is **15 minutes**; re-run the token exchange when expired.
- On success you get the extended profile JSON, with a `_meta` block carrying
  the issuing client, scope, and `expires_at`.
- On failure you get a **JSON** error (never an HTML error page). See Errors.

## Errors

The API always returns JSON errors with OAuth-style fields. The protected
resource also sends a `WWW-Authenticate` header pointing at the protected
resource metadata, e.g.:

```
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer error="invalid_token", error_description="...", resource_metadata="https://ucalyptus.me/.well-known/oauth-protected-resource"
```

| Status | Error | Meaning | Resolution |
|---|---|---|---|
| 400 | `unsupported_grant_type` | Only `client_credentials` is supported | Use that grant |
| 400 | `invalid_request` | Missing `client_id`/`client_secret` | Include both |
| 401 | `invalid_client` | Secret doesn't match the id | Register again if the secret was lost |
| 401 | `invalid_token` | Missing/bad/expired/wrong-audience token | Get a fresh token via `/oauth/token` |
| 403 | `insufficient_scope` | Token lacks `read:extended` | Re-token with the registered client |
| 404 | `not_found` | Requested an API path that doesn't exist | Check `/openapi.json` for real endpoints |
| 429 | `rate_limited` | Too many requests | Respect `Retry-After` and RFC `RateLimit-*` headers |

## Revocation

There is no explicit revocation endpoint, and it is not needed: this
implementation is stateless, and tokens are **short-lived (15 min)**, so a
token expires on its own. To drop an unwanted client, simply stop using its
`client_id`; the derived secret cannot be recovered without the id, and the
site stores nothing to revoke.

## Contact

- Email: hello@ucalyptus.me
- This flow, and the Worker behind it:
  [`workers/agent-auth/`](https://github.com/ucalyptus/ucalyptus.github.io/tree/master/workers/agent-auth)
in the site's own repo. Read the source, it's short.
