Skip to content

Access & login

EnvironmentURLNotes
Local devhttp://localhost:3100Started by npm run admin from repo root.
Staging / prod (planned)https://admin.twistcue.comBehind Apache + Let’s Encrypt on the same box as the API.

The admin is a standalone Next.js 16 app. It talks to the API at https://api.twistcue.connectorme.com/v1 server-side (https://api.twistcue.local/v1 in dev): you never call the admin API from the browser directly.

Admin users are managed by anyone with admins:write (the built-in superadmin role). Ask them to run:

Terminal window
# From the admin panel: System → Admin users → New
# Fields: email, name, role, password (temporary, user must reset on first login)

…or if you’re bootstrapping the very first admin, seed the database (see Local dev setup).

  1. Go to /login.
  2. Enter your email and password.
  3. On success, the API returns a JWT that’s set as an HttpOnly cookie (tc_admin_token). The token expires after 12 hours.
  4. You’re redirected to the Dashboard (/).
  • 12h expiry: you’re logged out automatically. Re-authenticate.
  • IP change: the guard will re-auth on IP mismatch (planned; WebAuthn column already in schema for phase 2).
  • Concurrent sessions allowed: no single-session lock.
  • Logout: clears the cookie server-side; JWT is stateless so any un-expired token is technically still valid until token_version bumps.
ErrorMeaningFix
insufficient_permissions (403)Your role lacks the required permission for that endpoint. Response includes the required perm array.Ask a superadmin to grant you the perm, or use a role that has it.
invalid_credentials (401)Wrong email/password.Reset password from the admin user detail page.
admin_disabled (403)The admin record is inactive.Have a superadmin re-enable it.
token_expired / token_invalid (401)12h TTL hit, or token_version bumped.Log in again.

The sidebar and inline buttons are permission-aware: routes and actions you can’t perform are hidden. The UI is not the security boundary: the API is. The PermissionsGuard server-side re-checks every mutating endpoint against your JWT claims. Hiding a button just prevents accidental 403s.