User Management
Foundation owners can invite team members, define custom roles with granular permissions, and manage the team roster. Miranda supports both a legacy role enum and a new foundation-scoped Role model with fine-grained permission arrays.
Team overview
Section titled “Team overview”The team page (GET /api/team) returns two lists:
Members
Section titled “Members”Active team members with their assigned roles:
{ "members": [ { "id": "uuid", "name": "Kathy Hoff", "email": "kathy@mirandafaith.org", "role_name": "Owner", "is_owner": true, "created_at": "2026-03-15T00:00:00Z" } ]}Pending invitations
Section titled “Pending invitations”Outstanding invitations that have not been accepted:
{ "invites": [ { "id": "uuid", "email": "jane@example.com", "role": { "id": "uuid", "name": "Program Director" }, "expires_at": "2026-03-22T00:00:00Z" } ]}Invitations expire after 7 days.
Inviting team members
Section titled “Inviting team members”POST /api/team/invite{ "email": "jane@example.com", "role_id": "uuid-of-role"}The invite process:
- Validation — Checks the role belongs to the foundation, the email is not already a member, and no pending invite exists
- Supabase account — Creates a Supabase auth account for the invitee (or updates their password if one exists)
- Passphrase generation — Creates a human-readable temporary passphrase (three random words)
- Email — Sends a
TeamInviteMailwith the join link and temporary credentials - Invite record — Creates a
TeamInvitewith a 64-character random token, expiring in 7 days
Accepting invitations
Section titled “Accepting invitations”Invitees can accept via two endpoints:
POST /api/team/invites/{token}/accept— Creates a User record; requires the invitee to authenticate separatelyPOST /api/team/invites/{token}/accept-and-login— Creates a User record and returns Supabase session tokens for immediate login
Both endpoints create a User with the role specified in the invitation.
Role management
Section titled “Role management”Listing roles
Section titled “Listing roles”GET /api/team/rolesIf no roles exist for the foundation, this endpoint automatically seeds default roles based on the foundation’s org_type:
- Owner role (always created first, slug
owner) - Additional roles from
config/org-types.phpwith preset permissions
Creating custom roles
Section titled “Creating custom roles”POST /api/team/roles{ "name": "Program Director", "permissions": [ "contacts.view", "contacts.create", "scholarships.view", "scholarships.create", "scholarships.review" ]}The slug is auto-generated from the name with a random suffix for uniqueness.
Updating roles
Section titled “Updating roles”PUT /api/team/roles/{roleId}{ "name": "Program Director", "permissions": ["contacts.view", "contacts.create", "scholarships.view", "scholarships.create", "scholarships.review", "donations.view"]}The owner role cannot be modified.
Deleting roles
Section titled “Deleting roles”DELETE /api/team/roles/{roleId}When a role is deleted, all users assigned to that role have their role_id set to null. The owner role cannot be deleted.
Permission reference
Section titled “Permission reference”Permissions are grouped by feature area. See Roles & Permissions for the complete list.
| Group | Permissions |
|---|---|
| People | contacts.view, contacts.create, contacts.edit, contacts.delete |
| Donations | donations.view, donations.create, donation_pages.view, donation_pages.create, donation_pages.edit |
| Communications | campaigns.view, campaigns.create, campaigns.edit, campaigns.send, sequences.view, sequences.create, sequences.edit |
| Programs | scholarships.view, scholarships.create, scholarships.review |
| Tools | import.use |
| Administration | settings.view, settings.edit, billing.manage, team.view, team.invite, team.remove |
Changing a member’s role
Section titled “Changing a member’s role”PUT /api/team/members/{userId}/role{ "role_id": "uuid-of-new-role"}Cannot change the role of a user with the owner legacy role.
Removing a team member
Section titled “Removing a team member”DELETE /api/team/members/{userId}Guardrails:
- Cannot remove yourself
- Cannot remove a user with the
ownerlegacy role - Deletes the
Userrecord (the Supabase auth account is not affected)
Viewing available permissions
Section titled “Viewing available permissions”GET /api/team/permissionsReturns the full permission structure from config/permissions.php:
{ "groups": { "people": { "label": "People", "permissions": { "contacts.view": "View contacts", "contacts.create": "Create contacts", "contacts.edit": "Edit contacts", "contacts.delete": "Delete contacts" } } }}This powers the role editor UI, where admins can toggle individual permissions for each custom role.