# Authentication: OAuth and API keys

## OAuth 2.1 (for AI apps)

Tandem implements the MCP authorization spec:

1. The client calls `https://api.tandemcrm.app/mcp` without a token and gets `401` with a `WWW-Authenticate` header pointing to the protected-resource metadata.
2. **Protected resource metadata** (RFC 9728): `https://api.tandemcrm.app/.well-known/oauth-protected-resource/mcp`
3. **Authorization server metadata** (RFC 8414): `https://app.tandemcrm.app/.well-known/oauth-authorization-server/api/auth`, and OpenID configuration at `https://app.tandemcrm.app/.well-known/openid-configuration/api/auth`. The issuer is `https://app.tandemcrm.app/api/auth`.
4. **Dynamic client registration** (RFC 7591): `POST https://app.tandemcrm.app/api/auth/oauth2/register`. Clients register as public clients and use PKCE.
5. The person signs in, **chooses a workspace**, and approves the scopes on a consent screen.
6. The token is bound to the resource `https://api.tandemcrm.app/mcp` and to that one workspace.

| | |
| --- | --- |
| Scopes | `openid`, `profile`, `email`, `offline_access`, `crm:read`, `crm:suggest`, `crm:write`, `crm:delete` |
| Default scopes at registration | `openid profile offline_access crm:read crm:write` |
| Access token lifetime | 15 minutes; refresh with `offline_access` |
| Revocation | Under Agents in the app, or `POST https://app.tandemcrm.app/api/auth/oauth2/revoke`. Takes effect immediately. |

## API keys (for scripts and self-hosted agents)

Workspace owners and admins create keys under **Agents** (or `POST /v1/api-keys` while signed in), choosing a name and scopes. A key looks like `crm_sk_…`, belongs to one workspace, and acts on behalf of the person who created it. The secret is shown once; Tandem stores only its hash.

Send it as a bearer token, over MCP or REST:

```
Authorization: Bearer crm_sk_…
```

Revoke a key under Agents; it stops working on its next call.
