# auth.md

How an automated client authenticates with Tavern (https://gettavern.app).

## Who this is for

Scripts, bots, assistants and MCP clients acting **on behalf of a person who
already has a Tavern account**. There is no machine identity and no
anonymous tier: every authorised call is a *seat* at a *campaign*, and a seat
belongs to an account a person made.

## Discovery

| | |
| --- | --- |
| Protected resource metadata | `https://gettavern.app/.well-known/oauth-protected-resource` (and `…/mcp`, `…/api/v1`) |
| Authorization server metadata | `https://gettavern.app/.well-known/oauth-authorization-server` |
| REST API | `https://gettavern.app/api/v1` |
| MCP server | `https://gettavern.app/mcp` |

A call without a credential answers `401` with
`WWW-Authenticate: Bearer resource_metadata="…"`, which is where an MCP client
starts on its own.

## Supported methods

### `api_key`: a personal key

For your own scripts. The person makes a key in Tavern's settings, under
API and assistants, picks what it may do (read; read and write; or read, write
and delete) and when it expires, and copies it once: `tvn_…`. It is stored
only as a hash and can be revoked from the same screen at any time.

### `oauth2`: an app acting as somebody

The authorization code grant with PKCE (S256), for public clients. No client
secret, ever.

- **Client identity.** Either a Client ID Metadata Document (the `client_id`
  is an https url serving the client's own metadata), or dynamic registration
  at `POST https://gettavern.app/oauth/register` (RFC 7591).
- **Authorize.** Send the person to `https://gettavern.app/oauth/authorize`
  with `response_type=code`, `client_id`, `redirect_uri`, `state`,
  `code_challenge`, `code_challenge_method=S256`, `scope` (`read write delete`,
  any of them) and `resource` (RFC 8707). They sign in if they need to, see who
  is asking and for what, and say yes or no. The answer carries `iss` (RFC 9207).
- **Token.** `POST https://gettavern.app/oauth/token`, form-encoded. An access
  token (`tvo_…`) lasts an hour; the refresh token (`tvr_…`) rotates on every
  use, and one used twice ends the whole grant.
- **Revoke.** `POST https://gettavern.app/oauth/revoke` (RFC 7009), or the person
  disconnects the app in their settings.

Signing in with a password to get the app's own session token is not a
supported method: a key or a connected app replaces the password, and neither
ever handles one.

## Using the credential

Send it as a bearer credential on every request:

```http
Authorization: Bearer <secret>
```

Every REST route names its campaign in its path, by slug.

## What a credential opens

A token is one account, and an account is only ever its own seats. It reaches
the campaigns that account has a seat at, with the seat it has at each, and
nothing else: no other table's journal, none of the GM's powers at a table
where the account is a player, and no other person's private notes, which are
sealed to their author and are not readable through any route at all. No
credential carries the operator's back office, and the API sends no email.

## What is public without one

The front page, the sign-in page, the docs, the developer page, the two legal
documents, the icon packs, public profiles and any memorial a GM made public.
Ask for the app's own pages with `Accept: text/markdown` and they answer in
markdown.

## Contact

hello@gettavern.app
