# auth.md

> Agent authentication for **PostExpert**.

PostExpert's API and MCP server are protected. Machine clients authenticate with
**OAuth 2.0 client credentials**. Access is limited to accounts on the **Ultra**
plan; the plan is re-checked on every request.

## 1. Register a client

A client is provisioned by an authenticated Ultra account — there is no
anonymous registration (that would bypass the paywall).

- **In the app:** Account → Manage subscription → Ultra tools → Manage API keys →
  *New key*. The `pe_live_…` value is shown once and is your `client_secret`.
- **Programmatically:** `POST https://app.postexpert.de/oauth/register` with a bearer token from a
  logged-in Ultra session (`Authorization: Bearer <session-jwt>`) or an existing
  `X-API-Key`. Returns `client_id`, `client_secret` and the token endpoint.

```http
POST https://app.postexpert.de/oauth/register
Authorization: Bearer <ultra-session-jwt>
Content-Type: application/json

{ "client_name": "my-agent" }
```

## 2. Get an access token

```http
POST https://app.postexpert.de/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=<client_id>&client_secret=<pe_live_key>&scope=api:read api:write
```

The response is a short-lived bearer token:

```json
{ "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "scope": "api:read api:write" }
```

You can also send the credentials with HTTP Basic auth (`client_id:client_secret`).

## 3. Call the API or MCP server

```http
GET https://app.postexpert.de/api/v1/usage
Authorization: Bearer <access_token>
```

The raw `X-API-Key: pe_live_…` header is accepted too, anywhere a bearer token is.

## Discovery

- Authorization server metadata: `https://app.postexpert.de/.well-known/oauth-authorization-server`
- Protected resource metadata: `https://app.postexpert.de/.well-known/oauth-protected-resource`

## Notes

- Identity type: `service_account` (machine-to-machine, no end-user consent).
- Credential type: `client_secret` (an Ultra API key).
- Revoke a client by deleting its API key in the app; revocation is immediate.
