First batch of real Wave 1 content for the v5.0 overhaul. These pages establish the tone and cross-linking pattern for the rest of the wave. - index.md: routing landing with intent-based cards - getting-started/quick-start.md: 15-min docker-compose happy path, reusing the existing compose snippet and asciinema cast - install/docker-compose.md: canonical production reference with volume table, env var table, and hardening checklist - install/image-variants.md: slim vs full matrix + what's actually different between them - install/reverse-proxy.md: Caddy / nginx / Traefik / NPM recipes with ROMM_BASE_URL guidance - administration/users-and-roles.md: full 19-scope matrix across Viewer/Editor/Admin, invite flow, OIDC hooks - developers/api-authentication.md: session / Basic / OAuth2 / Client API Token / OIDC flows with error semantics Builds clean with --strict.
5.5 KiB
| title | description |
|---|---|
| API Authentication | How to authenticate to the RomM REST API — session cookies, Basic, OAuth2 tokens, client tokens, and OIDC. |
API Authentication
RomM's REST API accepts four authentication modes. Pick the one that matches your client:
| Mode | Who it's for | How the credential is carried |
|---|---|---|
| Session cookie | Browser UI | Cookie: session=… after POST /api/auth/login |
| HTTP Basic | Quick scripts, curl one-liners | Authorization: Basic <base64(user:pass)> |
| OAuth2 Bearer | Automation, CI, third-party apps | Authorization: Bearer <jwt> |
| Client API Token | Companion apps (Argosy, Grout, Playnite, custom scripts) | Authorization: Bearer rmm_<token> |
| OIDC session | Users who sign in via SSO | Same as session cookie, but issued after OIDC callback |
All of them resolve to the same scope model — see the scope matrix in Users & Roles. A request is allowed if the active identity holds all scopes the endpoint requires.
Base URL
https://<your-instance>/api
When RomM is behind a reverse proxy (as it should be in production), that's your public URL. When running locally without a proxy, the container listens on port 8080.
Session login (browsers)
POST /api/auth/login
Content-Type: application/x-www-form-urlencoded
username=alice&password=s3cret
Response sets a session cookie. Subsequent requests from the same browser are authenticated automatically.
Log out:
POST /api/auth/logout
For OIDC logins, hitting /api/auth/logout also triggers RP-Initiated Logout if your OIDC provider supports it (configured via OIDC_END_SESSION_ENDPOINT).
HTTP Basic
Fine for quick scripts. Avoid in shared environments — the credentials are sent on every request.
curl -u alice:s3cret https://romm.example.com/api/roms
import requests
from requests.auth import HTTPBasicAuth
r = requests.get("https://romm.example.com/api/roms",
auth=HTTPBasicAuth("alice", "s3cret"))
OAuth2 Bearer token
The RomM backend implements the OAuth2 password grant. Exchange credentials for a short-lived access token and a refresh token:
POST /api/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=alice&password=s3cret&scope=roms.read%20roms.write
{
"access_token": "eyJhbGciOi...",
"token_type": "bearer",
"expires_in": 900,
"refresh_token": "eyJhbGciOi..."
}
Access tokens are HS256-signed JWTs valid for ~15 minutes. Send them as:
Authorization: Bearer eyJhbGciOi...
Refresh before expiry:
POST /api/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=eyJhbGciOi...
Request only the scopes you need — RomM will issue a token with the intersection of what you asked for and what the user has.
Client API tokens (for companion apps)
For anything long-lived — a running companion app, a cron job, a CI integration — use Client API Tokens instead of OAuth2. They're issued per-user from Administration → Client API Tokens, carry a subset of the user's scopes, and don't expire unless you set an expiry.
Token format: rmm_ + 40 hex chars. Use it as a bearer:
curl -H "Authorization: Bearer rmm_abcdef0123456789..." \
https://romm.example.com/api/roms
Each user gets up to 25 active tokens. Tokens can be paired with a device via the pairing flow — useful when you don't want to type a long token on a handheld.
OIDC
Users signing in through an OIDC provider get a regular RomM session, same as username/password login. For the API side this means you can't use an OIDC access token directly — authenticate the user through the browser first (they'll be redirected to the OIDC provider, then back to RomM), then use the resulting session cookie, or mint a Client API Token for programmatic use.
OIDC provider setup lives in Administration → OIDC.
Which scopes do I need?
Every endpoint in the API Reference lists its required scopes. The short version:
- Read-ish endpoints want the matching
*.readscope. - Write-ish endpoints want
*.write. - Admin endpoints (anything under
/api/usersbeyondme,/api/tasks/run) wantusers.read,users.write, ortasks.run.
A token that holds users.write also implicitly grants lesser scopes like users.read or me.read — RomM doesn't require you to list them all. But a token that holds only roms.read can't write, even if the underlying user is an Admin.
Errors
| HTTP | Meaning |
|---|---|
401 Unauthorized |
No credential, expired credential, bad credential. |
403 Forbidden |
Authenticated, but the identity lacks a required scope. |
404 Not Found |
The resource doesn't exist — or, for privacy, the identity can't see it. |
When debugging a 403, check:
- The user's role in Administration → Users.
- The token's scopes (for OAuth2/Client API Tokens) — scopes are narrower than the user's role by default.
- The endpoint's scope requirements in the API Reference.
OpenAPI
The full machine-readable schema is served at /openapi.json. It's the source of truth for generated clients, Postman collections, and the in-docs API Reference.
curl https://romm.example.com/openapi.json > romm-openapi.json
See Consuming OpenAPI for codegen tips.