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. For which scopes to request, see the Scopes catalog.
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
- Open Roam Administration → Developer (gear icon → Settings).
- Click Add ApiClient (or equivalent “create app”).
- Choose OAuth as the authorization type.
- Set a name, description, and redirect URIs (exact-match list — no
wildcards;
http://localhost…is allowed for local development if you register it). - Select the scopes your app needs.
- 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 for DCR and PKCE assistant setup. Third-party web apps normally use a pre-registered confidential client.
1. Send the user to authorize
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:
https://your.app/oauth/callback?code=AUTH_CODE&state=opaque-csrf-value
2. Exchange the code for tokens
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:
&code_verifier=THE_PKCE_VERIFIER
3. Token response
Successful responses look like:
{
"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
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
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
https://api.ro.am/v1/token.info
Use /token.info to inspect the grant (client, principal,
scopes). Revoke with /token.revoke (access and
refresh for that grant). Apps can also subscribe to lifecycle webhooks
token.revoked and
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, 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. Complete catalog: 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.
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 overview. The secret is not the same as the OAuth client secret.
Next steps
- Scopes catalog
- Access Models
- API versioning
- MCP OAuth / DCR (assistants & public clients)