The authenticated user manages themselves through /account. No resource ID needed because the token identifies who they are.
GET /account/profile - fetch your own profilePATCH /account/profile - partial update (name, etc.)DELETE /account/profile - soft delete your accountPATCH because the client sends whichever fields they want to change. Email is excluded from PATCH /account/profile because changing it requires verification through a separate flow.
Two-step flow. The user requests the change while authenticated, then confirms it from their new inbox.
POST /account/email - sends a confirmation link to the new addressPOST /auth/change-email - consumes the token from the link, swaps the emailBoth are POST because they're actions, not resource replacements. POST /account/email doesn't change the email, it triggers a confirmation. POST /auth/change-email consumes a token and has side effects (deletes old tokens, updates verified timestamp). The confirmation endpoint is unauthenticated because the token was sent to the new inbox, which may be opened on a different device.
Same two-step pattern as email change. Fully unauthenticated since the user has lost access.
POST /auth/forgot-password - sends a reset linkPOST /auth/reset-password - consumes the token, sets the new passwordBoth POST for the same reasons as email change. Action pairs always share a naming pattern: verb-noun, hyphenated (forgot-password/reset-password, change-email).
Proves the user can receive messages at their current address. Unlike email change and password reset, this uses short codes instead of links because verification can be multi-channel (email, SMS).
POST /account/verify - submit a verification codePOST /account/verify/resend - request a new codeNested under /account/verify because resending is a sub-action of the verification flow, not a standalone feature.
POST /auth/token/refresh - rotates the current Sanctum tokenPOST because it creates a new token and invalidates the old one. Not PUT because you're not replacing a known resource at a URI, you're triggering a rotation.
POST /auth/login - creates a tokenPOST /auth/logout - revokes the current tokenPOST /auth/register - creates a new accountAll actions. Login and register return tokens, logout destroys one.
Admins manage any resource. Routes are namespaced under /admin and controllers live in Controllers/Admin/. An admin middleware on the group requires admin or super role as a safety net before per-route policy checks.
GET /admin/users - list users (search, filter by role, filter trashed)GET /admin/users/{id} - show a userPATCH /admin/users/{id} - partial update (name fields)DELETE /admin/users/{id} - soft deleteDELETE /admin/users/{id}/force - hard delete or anonymize (follows prune strategy)PATCH /admin/users/{id}/restore - restore a soft-deleted userDELETE /admin/users/{id}/avatar - remove a user's avatarPATCH /admin/users/{id}/role - set or remove a user's rolePOST /admin/users/{id}/password-reset - force a password reset with a temp passwordSub-resource actions get their own controller (UserAvatarController, UserRoleController, etc.) rather than extra methods on UserController. Each controller stays small and single-purpose.
PATCH for updates because the admin sends a subset of fields. POST for password reset because it triggers side effects (generates a temp password, revokes tokens, sends an email).
/account/profile, /admin/users/auth/login, /auth/forgot-password, /auth/change-email/auth/forgot-password, not /auth/forgotPasswordforgot-password/reset-password, /account/email/change-emailPOST for actions that trigger side effects: consuming tokens, sending emails, creating sessionsPUT for full replacement of a resource at a known URIPATCH for partial updates where the client sends a subset of fieldsPOST regardless of what it eventually leads to