# OAuth & Authentication

This guide covers how third-party apps obtain and refresh API credentials for
the Roam API. For **which endpoints each credential type can call**, see
[Access Models](/docs/guides/access-models). For **which scopes to request**,
see the [Scopes catalog](/docs/guides/scopes).

## Choose a credential type

| Credential | When to use | Acts as |
|------------|-------------|---------|
| **API Key** | Server-to-server bots you install in one Roam (admin-issued) | Organization bot identity for that Roam |
| **OAuth app** | Multi-tenant / third-party product users install | Organization **or** Personal, chosen at consent |
| **Personal Access Token (PAT)** | Local scripts, MCP clients, single-user tools | The authorizing user (Personal only) |

API Keys and PATs are created in product UI and used as Bearer tokens immediately.
OAuth apps need the authorization-code flow below.

`client_credentials` is **not** supported. Every API call binds a user or a Roam
install; server-to-server callers use API Keys.

## Create an OAuth app

1. Open **Roam Administration → Developer** (gear icon → Settings).
2. Click **Add ApiClient** (or equivalent “create app”).
3. Choose **OAuth** as the authorization type.
4. Set a name, description, and **redirect URIs** (exact-match list — no
   wildcards; `http://localhost…` is allowed for local development if you
   register it).
5. Select the [scopes](/docs/guides/scopes) your app needs.
6. Save. Copy the **Client ID** and **Client Secret** (confidential clients).

Redirect URIs must match the `redirect_uri` you send on authorize **exactly**
(scheme, host, path, and query). Roam also allows a first-party dev callback
used by the in-product token inspector; do not rely on that for your app.

## Authorization code flow

### Endpoints

| Step | URL |
|------|-----|
| Authorize (browser) | `https://ro.am/oauth/authorize` |
| Token (server) | `https://ro.am/oauth/token` |
| Authorization-server metadata | `https://ro.am/.well-known/oauth-authorization-server` |

MCP / Dynamic Client Registration uses additional discovery documents; see
[MCP](/docs/integrations/mcp#oauth-details) for DCR and PKCE assistant setup.
Third-party web apps normally use a pre-registered confidential client.

### 1. Send the user to authorize

```http
GET https://ro.am/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fyour.app%2Foauth%2Fcallback
  &scope=chat%3Ahistory%20chat%3Asend_message%20user%3Aread
  &state=opaque-csrf-value
```

| Parameter | Required | Notes |
|-----------|----------|--------|
| `response_type` | yes | Must be `code` |
| `client_id` | yes | From Developer settings |
| `redirect_uri` | yes | Must be an exact registered URI |
| `scope` | recommended | Space-separated scope list; omit only if you want the app's default grant set |
| `state` | strongly recommended | CSRF token; echo-checked on callback |
| `code_challenge` / `code_challenge_method` | public clients | PKCE **S256** required for public clients (`token_endpoint_auth_method: none`). Confidential clients may use PKCE optionally. `plain` is rejected. |

The user signs in, picks the Roam / access model (Organization vs Personal when
both are allowed), and consents. Roam redirects to:

```text
https://your.app/oauth/callback?code=AUTH_CODE&state=opaque-csrf-value
```

### 2. Exchange the code for tokens

```http
POST https://ro.am/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https%3A%2F%2Fyour.app%2Foauth%2Fcallback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
```

Confidential clients may also send the secret via HTTP Basic
(`client_secret_basic`) instead of the form body (`client_secret_post`).

Public clients omit `client_secret` and must include:

```text
&code_verifier=THE_PKCE_VERIFIER
```

### 3. Token response

Successful responses look like:

```json
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 604800,
  "refresh_token": "…",
  "scope": "chat:history chat:send_message user:read"
}
```

| Field | Meaning |
|-------|---------|
| `access_token` | Send as `Authorization: Bearer …` on `https://api.ro.am/v1/…` |
| `expires_in` | Lifetime of the **access** token in seconds (**604800** = **7 days**) |
| `refresh_token` | Long-lived credential used only at the token endpoint (**365 days**) |
| `scope` | Granted scopes (space-separated) |

Store the refresh token securely. Access tokens alone will expire after one week.

## Refresh tokens

```http
POST https://ro.am/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=YOUR_REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
```

Refresh **cannot escalate scopes**. Requesting additional scopes on refresh is
rejected; the new access token keeps the prior grant (or a subset if you
narrow). A successful refresh returns a new `access_token` and typically a
rotated `refresh_token` — discard the old refresh token when a new one is
returned.

## Call the API

```bash
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://api.ro.am/v1/token.info
```

Use [`/token.info`](/docs/api/token-info) to inspect the grant (client, principal,
scopes). Revoke with [`/token.revoke`](/docs/api/token-revoke) (access **and**
refresh for that grant). Apps can also subscribe to lifecycle webhooks
[`token.revoked`](/docs/webhooks/token-revoked) and
[`app.uninstalled`](/docs/webhooks/app-uninstalled).

### Auth failure codes

A `401` carries a machine-readable code: `not_authed`, `invalid_token`, or
`token_revoked`. For an OAuth app the distinction that matters is that
`invalid_token` is worth a [refresh](#refresh-tokens), while `token_revoked` is
terminal — the grant is gone, so discard both tokens and send the user back
through authorization instead of retrying.

Full table: [Authentication failures](/docs/api/api#authentication-failures).
Complete catalog: [Error Codes](/docs/guides/error-codes).

## Personal Access Tokens

PATs are created under **User Settings → Developer**. They always use the
Personal access model and expand from PAT scope **groups**
(e.g. `pat:chat:write`) into concrete OAuth scopes — see
[Scopes → PAT groups](/docs/guides/scopes#personal-access-token-groups).

PATs are accepted only on endpoints in the personal-access allowlist. Each
endpoint page's **Access** line documents whether Personal is supported.

## Webhook signing secret

OAuth apps and API keys receive a per-app Standard Webhooks signing secret
(shown in Developer settings). Use it to verify delivery signatures — see the
[Webhooks](/docs/webhooks/webhooks) overview. The secret is **not** the same as
the OAuth client secret.

## Next steps

- [Scopes catalog](/docs/guides/scopes)
- [Access Models](/docs/guides/access-models)
- [API versioning](/docs/guides/api-versioning)
- [MCP OAuth / DCR](/docs/integrations/mcp#oauth-details) (assistants & public clients)