# auth.md

This document tells autonomous agents how to register on behalf of a user (or on their own) for the Business.Digital API and MCP server. It is the prose companion to the machine-readable OAuth metadata under `/.well-known/`.

- **Resource:** `https://business.digital/mcp`
- **Resource name:** Business.Digital MCP
- **Authorization server (issuer):** `https://business.digital`
- **Audience:** Autonomous agents acting for end users, plus first-party MCP clients.
- **Supported flows:** anonymous registration (client credentials), identity_assertion (id-jag, verified_email).

## Discover

Agents discover authentication metadata in two hops.

**Hop 1 — Protected Resource Metadata.** Either parse the `WWW-Authenticate: Bearer resource_metadata="…"` header on a 401 response from any protected endpoint, or fetch directly:

```http
GET https://business.digital/.well-known/oauth-protected-resource
Accept: application/json
```

Relevant fields:

- `resource` — canonical resource URI to use as the `aud` claim
- `resource_name` — human-readable label
- `authorization_servers` — list of issuers to query in Hop 2
- `scopes_supported` — scopes the resource will accept
- `bearer_methods_supported` — always `["header"]` for this service

**Hop 2 — Authorization Server Metadata.** Fetch the issuer's discovery document:

```http
GET https://business.digital/.well-known/oauth-authorization-server
Accept: application/json
```

Read the full `agent_auth` block. It declares `register_uri`, `claim_uri`, `revocation_uri`, the supported `identity_types_supported`, and the credential and assertion types each type accepts.

## Pick a method

Choose the first method that matches the agent's capability:

| Capability the agent has                                     | Method                                       |
| ------------------------------------------------------------ | -------------------------------------------- |
| Identity provider can issue an ID-JAG for `aud=https://business.digital/mcp` | `identity_assertion` + `id-jag`              |
| Verified email available, no ID-JAG                          | `identity_assertion` + `verified_email` (requires claim) |
| Neither — agent acts standalone                              | `anonymous` (returns API key)                |

## Register

All registration calls hit a single endpoint and select the flow via the `identity_type` field.

### Anonymous

For agents that act standalone or on behalf of a not-yet-identified user. Returns a confidential client (`client_id` + `client_secret`) that can mint MCP read tokens via `client_credentials`.

```http
POST https://business.digital/api/oauth/register
Content-Type: application/json

{
  "client_name": "Acme Travel Agent",
  "identity_type": "anonymous",
  "credential_type": "api_key",
  "scopes": ["mcp.read"]
}
```

Response (`201 Created`):

```json
{
  "client_id": "mcp-…",
  "client_secret": "…",
  "token_endpoint_auth_method": "client_secret_post",
  "grant_types": ["client_credentials"],
  "scope": "mcp.read",
  "client_id_issued_at": 1748390400
}
```

Use the returned credentials at `https://business.digital/api/oauth/token` to obtain a bearer token for the MCP server.

### Identity Assertion (ID-JAG)

For agent platforms whose identity provider can vouch for the user with a JWT-secured Authorization Grant whose `aud` matches `https://business.digital/mcp`.

```http
POST https://business.digital/api/oauth/register
Content-Type: application/json

{
  "client_name": "Acme Travel Agent",
  "identity_type": "identity_assertion",
  "assertion_type": "urn:ietf:params:oauth:token-type:id-jag",
  "assertion": "<signed id-jag JWT>",
  "credential_type": "access_token",
  "scopes": ["mcp.read"]
}
```

Response (`201 Created`):

```json
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mcp.read"
}
```

### Identity Assertion (verified_email)

For agents that have a verified email for the user but cannot present an ID-JAG. Returns a pending credential that the user must claim via OTP.

```http
POST https://business.digital/api/oauth/register
Content-Type: application/json

{
  "client_name": "Acme Travel Agent",
  "identity_type": "identity_assertion",
  "assertion_type": "verified_email",
  "email": "user@example.com",
  "credential_type": "access_token",
  "scopes": ["mcp.read"]
}
```

Response (`202 Accepted`):

```json
{
  "claim_token": "…",
  "claim_uri": "https://business.digital/api/oauth/register",
  "expires_in": 600
}
```

## Claim ceremony

Only required when registration returned a `claim_token`. The user receives a one-time code by email; the agent then claims the credential on the user's behalf.

```http
POST https://business.digital/api/oauth/register
Content-Type: application/json

{
  "claim_token": "…",
  "otp": "123456"
}
```

Response (`200 OK`):

```json
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mcp.read"
}
```

## Use the credential

Present the credential as a bearer token in the `Authorization` header on every request:

```http
POST https://business.digital/mcp
Authorization: Bearer <access_token-or-api_key>
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"tools/list"}
```

Tokens expire after `expires_in` seconds. Refresh by exchanging the client credentials at the token endpoint again, or by re-running the assertion flow.

## Errors

| `error` code            | Meaning                                                       |
| ----------------------- | ------------------------------------------------------------- |
| `invalid_request`       | Malformed body or missing required field                      |
| `invalid_assertion`     | Assertion signature, issuer, or audience invalid              |
| `audience_mismatch`     | Assertion `aud` does not equal the protected resource URI     |
| `credential_expired`    | Bearer token or assertion past `exp`                          |
| `claim_expired`         | `claim_token` past its TTL                                    |
| `otp_invalid`           | OTP wrong or already used                                     |
| `replay_detected`       | Same assertion JTI presented twice                            |
| `rate_limited`          | Too many registration or claim attempts; back off and retry   |
| `invalid_scope`         | Requested scope outside `scopes_supported`                    |

All errors follow the RFC 6749 shape: `{"error": "…", "error_description": "…"}`.

## Revocation

Agents and users can revoke a credential at any time:

```http
POST https://business.digital/api/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=<access_token-or-api_key>&token_type_hint=access_token
```

For `identity_assertion` credentials, the upstream identity provider can also push a revocation event matching `https://schemas.workos.com/events/agent/auth/identity/assertion/revoked`. The service treats the credential as invalid the moment such an event is received.

For account or data deletion requests beyond credential revocation, contact `datenschutz@business.digital`.
