Roles
plym has a built-in IAM system for different kinds of users. This page is a comprehensive guide to roles and access levels.
Updated Aug 04, 2026
On this page
Every plym account holds one of three roles — reader, editor, or administrator — and the role decides which endpoints and admin screens that account can use.
Each API route names the roles it accepts, so the set of roles is fixed in the build rather than configured. Unlike most CMS permission systems, plym has no groups, no per-post ownership rules, and no way to change an account's role once the account exists. The role is copied into the access token at sign-in and travels with every request as a bearer token.
Choosing a role
Grant editor to everyone who writes. Grant administrator only to the one or two people who create accounts, reset passwords, and read leads. Grant reader to an account that must sign in but must never publish.
| Role | Covers |
|---|---|
reader |
Sign in, read published content, read site settings, list accounts, manage own profile. |
editor |
Everything a reader can do, plus posts, categories, FAQs, media, and the search index. |
administrator |
Everything an editor can do, plus account management and leads. |
The roles are cumulative. A route that accepts editor accepts administrator too, so an administrator never needs a second account to publish.
Privileges by action
Anonymous means a request with no Authorization header.
| Action | Endpoint | Anonymous | reader |
editor |
administrator |
|---|---|---|---|---|---|
| Sign in, renew a session, log out | POST /api/auth/login, /refresh, /logout |
✓ | ✓ | ✓ | ✓ |
| List published posts | GET /api/posts |
✓ | ✓ | ✓ | ✓ |
| Read a published post | GET /api/posts/{id} |
✓ | ✓ | ✓ | ✓ |
| List tags, categories, FAQs | GET /api/tags, /api/categories, /api/faqs |
✓ | ✓ | ✓ | ✓ |
| Submit a lead | POST /api/collect |
✓ | ✓ | ✓ | ✓ |
| Change own password | POST /api/auth/change-password |
— | ✓ | ✓ | ✓ |
| Read and update own profile | GET, PATCH /api/users/me |
— | ✓ | ✓ | ✓ |
| Read site settings | GET /api/config |
— | ✓ | ✓ | ✓ |
| List every account | GET /api/users |
— | ✓ | ✓ | ✓ |
| List drafts, filter by status, search all posts | GET /api/posts?include_drafts=true |
— | — | ✓ | ✓ |
| Read a draft | GET /api/posts/{id} |
— | — | ✓ | ✓ |
| Create, update, delete, re-render, preview posts | POST, PATCH, DELETE /api/posts… |
— | — | ✓ | ✓ |
| Create, update, delete categories | /api/categories |
— | — | ✓ | ✓ |
| Create, update, delete FAQs | /api/faqs |
— | — | ✓ | ✓ |
| Upload, list, delete media | /api/media |
— | — | ✓ | ✓ |
| Rebuild the search index | POST /api/index |
— | — | ✓ | ✓ |
| Create an account | POST /api/users |
— | — | — | ✓ |
| Reset another account's password | POST /api/users/{id}/reset-password |
— | — | — | ✓ |
| Deactivate or reactivate an account | /api/users/{id}/deactivate, /reactivate |
— | — | — | ✓ |
| List leads | GET /api/submissions |
— | — | — | ✓ |
What every signed-in account can see
Any signed-in account, reader included, can call GET /api/users and read every account's email address, role, and active state. A reader account is a trusted account with read-only reach, not a public one. No setting narrows this.
Authorization errors
| Status | Code | Cause |
|---|---|---|
| 401 | auth.token_invalid |
Missing, malformed, or expired bearer token. |
| 403 | auth.insufficient_role |
Valid token, but the route does not accept that role. |
| 403 | auth.inactive_user |
Correct password, deactivated account. |
| 404 | posts.not_found |
A draft requested without an editor token. plym hides drafts rather than reporting 403. |
Every error carries the same body shape:
{
"detail": {
"code": "auth.insufficient_role",
"message": "Insufficient role for this action"
}
}
The access token
POST /api/auth/login returns an access token and a refresh token:
curl -X POST https://flapico.com/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"sam@flapico.com","password":"a-real-password"}'
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "8f3c1d9b7a2e4f6c...",
"token_type": "bearer"
}
The access token is a JWT holding the account id and its role, and it expires 900 seconds after issue (PLYM_JWT_ACCESS_TTL_SECONDS). The refresh token lasts 2592000 seconds — 30 days — and is consumed each time you exchange it.
{ "sub": "3", "role": "editor", "iat": 1785368410, "exp": 1785369310, "typ": "access" }
A request is authorized against the role claim inside the token, not against the account row. Deactivating an account therefore does not invalidate a token it already holds; see managing-users.
Roles in the CLI and the MCP server
The CLI reads PLYM_SUPERUSER_EMAIL and PLYM_SUPERUSER_PASSWORD from .env, so plym rebuild, plym update, plym template install, and plym set url act as that administrator. The MCP server authenticates with the email and password it was configured with, and that account's role applies unchanged — create_post on a reader account fails with This account cannot create posts (editor role required).
Limits
- Three roles. No custom roles, no groups, no per-post or per-category permissions.
- One role per account.
- A role cannot be changed after the account is created. No endpoint updates another account's role, and
PATCH /api/users/meignores arolefield. - Published posts, tags, categories, and FAQs are readable without a token. No setting puts a published site behind sign-in.
- An access token cannot be revoked before it expires.