# Authentication for MiriamSchwab.me

> How an agent gets access to this site. Short version: everything you can read is open, and there are no API keys to obtain.

## Discover

Start from these, in this order:

- [`/llms.txt`](https://miriamschwab.me/llms.txt) — what this site holds, and when it is worth reading.
- [`/openapi.json`](https://miriamschwab.me/openapi.json) — every HTTP endpoint, its parameters, and its error shape. Its root `security: []` is the machine-readable statement of what this document says in prose: no authentication is required.
- [`/.well-known/mcp.json`](https://miriamschwab.me/.well-known/mcp.json) — the MCP server, its transport, and its tools. Its `authentication.type` is `none`.
- [`/.well-known/api-catalog`](https://miriamschwab.me/.well-known/api-catalog) — every machine-readable document here, as a linkset.

There is no `/.well-known/oauth-authorization-server` and no `/.well-known/oauth-protected-resource` on this host. That is not an omission: this site runs no authorization server and exposes no protected resource, so publishing either would point you at something that does not exist.

## Pick a method

**Reading — no credential.** Every read surface on this site is anonymous: the HTML pages, the `.md` twins, `llms.txt`, `llms-full.txt`, the OpenAPI document, and the MCP server. Send no `Authorization` header. You will not receive a `401`, because there is nothing here to be unauthorized for.

**Writing — per-endpoint.** This site exposes 1 endpoint that takes an action, and each states its own requirement:

- **Send Miriam Schwab a message** — `GET, POST https://miriamschwab.me/wp-json/miriamschwab/v1/contact`
  - Requirement: none; POST needs a submit_token from GET
  - GET this URL first for the input schema and a single-use submit_token (valid 15 min), then POST it back with name, email and message. A POST without the token is rejected. Reaches a real inbox and cannot be unsent, so confirm the wording with your user first.

## Register

There is no registration step and no `register_uri`. No account, client id, or client secret exists to be issued, and nothing on this site records who is calling it beyond an ordinary request log.

## Claim a credential

No long-lived credential is issued. Where an endpoint above requires a token, that token is single-use and obtained from the endpoint itself immediately before the call — `GET` the endpoint, read the token out of the response, and send it back with your `POST`. There is no `claim_uri` separate from the endpoint you are calling.

## Use the credential

For reads, send an ordinary request. Two headers are worth setting:

- `Accept: text/markdown` on any page URL, if you would rather have Markdown than HTML.
- `Accept: application/json` if you want errors as JSON rather than as an HTML error page.
- A descriptive `User-Agent`. Nothing is gated on it, but it is what shows up in this site's request log, and an identifiable agent is a welcome one.

For the endpoints listed above, follow the requirement stated against each. A call that omits the token is refused — see Errors.

## Retrying safely

The one endpoint here that takes an action — `POST https://miriamschwab.me/wp-json/miriamschwab/v1/contact` — accepts an `Idempotency-Key` header, and you should always send one.

It matters more than usual on this site. The `submit_token` that endpoint requires is single-use, so if your POST succeeds but the response never reaches you, a plain retry is answered `401` — indistinguishable from the token having been wrong in the first place. You cannot tell whether Miriam got the message, and either choice you make is wrong half the time.

With a key, that ambiguity disappears:

- Generate a UUID and send it as `Idempotency-Key` on the POST.
- The result of the first POST carrying that key is kept for 24 hours.
- Retry with the **same** key. You get the original result back, with `Idempotency-Replayed: true` and `"replayed": true` in the body. Nothing is sent twice, and the spent token is never consulted.
- Use a **new** key for a genuinely new message. The same key with a different body is refused `422` (`ms_contact_idempotency_key_reuse`).
- A `409` (`ms_contact_idempotency_in_progress`) means your first attempt is still running. Wait a few seconds and retry with the same key.
- An attempt that failed sent nothing and recorded nothing, so you can fix the problem and retry with the same key.

Keys must be printable ASCII, no spaces, 16-255 characters. There are no accounts here to scope keys to, so a short or guessable key could collide with another caller's — a UUID avoids that without you thinking about it.

## Errors

Every error from this site is JSON when you ask for JSON, with the same shape throughout:

```json
{ "code": "rest_forbidden", "message": "...", "data": { "status": 403 } }
```

Match on `code`, not on `message`. `data.status` repeats the HTTP status.

- `401` — not used. No endpoint here challenges for credentials, so you will not see a `WWW-Authenticate: Bearer` header and there is no `resource_metadata` URL to follow.
- `403` — a required single-use token was missing, already spent, or expired. Fetch a fresh one and retry once.
- `404` — wrong URL. The body carries a `links` array pointing at this site's index, sitemap and catalog, so a wrong guess is recoverable in one more request.
- `429` — you are being rate limited. Read `RateLimit-Reset` and `Retry-After` and wait.

## Revocation

There is no `revocation_uri`, because there is no persistent credential to revoke. Single-use tokens expire on their own and cannot be reused after a successful call.

Access can still be withdrawn from this end — `robots.txt` states this site's crawler policy, and it is the document to read before assuming a bulk crawl is welcome.
