Team Member Management
Comprehensive guide to managing team members and invitations in your OEC.SH organization.
Overview
Team member management in OEC.SH allows you to collaborate with your team by inviting members, assigning roles, and controlling access to your organization's resources. The platform supports both organization-wide and project-specific access through a flexible invitation system.
Key Features
- Flexible Invitations: Invite members via email with organization-wide or project-specific access
- Role-Based Access Control: 4 organization roles (Owner, Admin, Developer, Viewer) + permission matrix
- Invitation Codes: 6-character codes or secure link tokens for easy onboarding
- Multi-Use Invitations: Create invitations that can be used by multiple team members
- Email Integration: Automatic invitation emails with onboarding links
- Real-Time Updates: SSE events for instant UI updates when members join or leave
- Audit Trail: Complete tracking of member additions, role changes, and removals
Organization Member Roles
OEC.SH uses a hierarchical role system for organization members. Each role has specific permissions and capabilities.
Role Hierarchy
OWNER > ADMIN > DEVELOPER > VIEWERRole Definitions
Owner
- Full Control: Complete access to all organization features
- Billing Management: Can manage subscriptions, payment methods, and billing settings
- Organization Deletion: Only role that can delete the organization
- Ownership Transfer: Can transfer ownership to another member
- Member Management: Add, remove, and change roles of any member
- Resource Quotas: Manage organization-level resource quotas
- Unique: Only one owner per organization (automatic on creation)
Permissions Include:
- All Admin permissions
org.delete- Delete organizationorg.billing.manage- Manage billing and subscriptionsorg.ownership.transfer- Transfer ownership
Admin
- Full Management: Manage all organization resources except billing and deletion
- Team Management: Invite, remove members, and change roles (except Owner)
- Settings Control: Update organization settings, DNS, storage configurations
- Project Management: Create, update, delete projects
- Server Management: Add and manage servers
Permissions Include:
- All Developer permissions
org.settings.update- Update organization settingsorg.members.invite- Invite new membersorg.members.remove- Remove membersorg.members.update_role- Change member rolesorg.servers.create- Add new serversorg.servers.delete- Remove serversorg.dns.manage- Manage DNS configurationsorg.storage.manage- Manage storage providers
Developer
- Standard Access: Create and manage projects, deploy environments
- Project Operations: Full CRUD on projects and environments
- Deployment: Deploy, update, and manage application deployments
- Backups: Create and restore backups
- Environment Management: Create, configure, and delete environments
Permissions Include:
- All Viewer permissions
org.projects.create- Create new projectsorg.projects.update- Update project settingsorg.projects.delete- Delete projects (own projects)org.environments.create- Create environmentsorg.environments.deploy- Deploy environmentsorg.environments.update- Update environment configurationsorg.environments.delete- Delete environmentsorg.backups.create- Create backupsorg.backups.restore- Restore from backups
Viewer
- Read-Only Access: View organization resources and configurations
- No Modifications: Cannot create, update, or delete resources
- Monitoring: View logs, metrics, and deployment status
- Reports: Access usage reports and analytics
Permissions Include:
org.view- View organization detailsorg.members.list- View team membersorg.projects.list- View projectsorg.environments.list- View environmentsorg.logs.view- View application logsorg.monitoring.view- View metrics and monitoring data
Viewing Team Members
List Organization Members
View all active members of your organization with their roles and status.
Endpoint: GET /api/v1/organizations/{org_id}/members
Required Permission: org.members.list
Response:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"role": "ADMIN",
"org_role": "org_admin",
"managed_project_ids": [],
"is_active": true,
"user": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"email": "admin@example.com",
"name": "Jane Admin",
"avatar_url": "https://gravatar.com/avatar/..."
},
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"user_id": "8d0e7780-8536-51ef-b058-f18fd3c4f91b",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"role": "DEVELOPER",
"org_role": "org_manager",
"managed_project_ids": [
"a1b2c3d4-e5f6-4321-a987-123456789abc"
],
"is_active": true,
"user": {
"id": "8d0e7780-8536-51ef-b058-f18fd3c4f91b",
"email": "developer@example.com",
"name": "John Developer",
"avatar_url": null
},
"created_at": "2024-02-20T14:45:00Z"
}
]Member Information Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Unique membership identifier |
user_id | UUID | User ID of the member |
organization_id | UUID | Organization ID |
role | String | Member's role (OWNER, ADMIN, DEVELOPER, VIEWER) |
org_role | String | Internal org role (org_admin, org_manager, org_user) |
managed_project_ids | Array | Project IDs this member can manage (for DEVELOPER role) |
is_active | Boolean | Whether membership is active |
user | Object | User details (email, name, avatar) |
created_at | ISO 8601 | When the member joined |
Filtering and Search
The frontend typically provides:
- Role Filter: Filter by OWNER, ADMIN, DEVELOPER, VIEWER
- Status Filter: Active vs. Suspended members
- Search: Search by name or email
- Sort: By join date, name, or role
Inviting Team Members
OEC.SH supports two invitation scopes:
- Organization-Wide: Member gets access to all projects in the organization
- Project-Specific: Member gets access only to specified projects
Create Organization Invitation
Endpoint: POST /api/v1/invitations/organizations/{organization_id}
Required Permission: org.members.invite
Request Body:
{
"email": "newmember@example.com",
"scope": "organization",
"role": "DEVELOPER",
"expires_in_days": 7,
"max_uses": 1,
"message": "Welcome to our team! We're excited to have you join us."
}Request Schema:
{
email?: string | null; // Restrict to specific email (optional)
scope: "organization" | "project"; // Invitation scope
// Organization-scope fields
role: "OWNER" | "ADMIN" | "DEVELOPER" | "VIEWER"; // For org-wide invites
// Project-scope fields (when scope = "project")
project_ids?: string[]; // Required for project-scope
project_role?: "admin" | "developer" | "viewer"; // Role within projects
// Common fields
expires_in_days: number; // 1-30 days (default: 7)
max_uses?: number | null; // 1-100 or null for unlimited (default: 1)
message?: string | null; // Optional message (max 500 chars)
}Response:
{
"id": "inv-123e4567-e89b-12d3-a456-426614174000",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "newmember@example.com",
"scope": "organization",
"role": "DEVELOPER",
"org_role": "org_manager",
"project_ids": null,
"project_role": null,
"projects": null,
"code": "ABC123",
"link_token": "veryLongSecureTokenWith64CharactersForInvitationLinkSecurity",
"status": "pending",
"expires_at": "2024-12-25T10:30:00Z",
"max_uses": 1,
"use_count": 0,
"remaining_uses": 1,
"is_valid": true,
"invited_by": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"inviter_name": "Jane Admin",
"message": "Welcome to our team! We're excited to have you join us.",
"created_at": "2024-12-18T10:30:00Z",
"updated_at": "2024-12-18T10:30:00Z"
}Invitation Codes
Every invitation has two ways to be used:
-
6-Character Code: Short code for manual entry (e.g.,
ABC123)- Uses characters:
ABCDEFGHJKMNPQRSTUVWXYZ23456789 - Excludes confusing characters:
0, O, I, L - Case-insensitive
- Uses characters:
-
64-Character Link Token: Secure token for invitation links
- Used in URL:
https://app.oec.sh/onboarding?token=... - More secure than code
- Only shown once on creation
- Used in URL:
Invitation States
| State | Description |
|---|---|
pending | Waiting to be accepted |
accepted | Successfully accepted (when max_uses reached) |
expired | Past expiration date |
revoked | Manually cancelled by admin |
Email Invitation Workflow
When an invitation is created with an email address and email service is configured:
- Invitation Created: Backend generates code + link token
- Email Queued: Task enqueued via ARQ for sending
- Email Sent: Email service sends invitation with:
- Organization name
- Inviter name
- Invitation link with token
- Expiration date
- Optional message from inviter
- Scope and role information
- User Receives Email: Contains clickable link to onboarding page
- User Clicks Link: Redirected to
/onboarding?token=... - Validation: Frontend validates token via API
- Acceptance: User registers/logs in and accepts invitation
Project-Specific Invitations
Invite users to specific projects only (not organization-wide access):
Request:
{
"email": "contractor@example.com",
"scope": "project",
"project_ids": [
"a1b2c3d4-e5f6-4321-a987-123456789abc",
"b2c3d4e5-f6a1-5432-b098-234567890bcd"
],
"project_role": "developer",
"expires_in_days": 14,
"max_uses": 1,
"message": "Here's access to the Client XYZ project. Let me know if you need help!"
}Response:
{
"id": "inv-234f5678-f90c-23e4-b567-537725285111",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "contractor@example.com",
"scope": "project",
"role": "VIEWER",
"org_role": "org_user",
"project_ids": [
"a1b2c3d4-e5f6-4321-a987-123456789abc",
"b2c3d4e5-f6a1-5432-b098-234567890bcd"
],
"project_role": "developer",
"projects": [
{
"id": "a1b2c3d4-e5f6-4321-a987-123456789abc",
"name": "Client XYZ Website",
"slug": "client-xyz-website"
},
{
"id": "b2c3d4e5-f6a1-5432-b098-234567890bcd",
"name": "Client XYZ Backend",
"slug": "client-xyz-backend"
}
],
"code": "DEF456",
"link_token": "anotherVeryLongSecureTokenWith64CharactersForProjectInvite",
"status": "pending",
"expires_at": "2025-01-01T10:30:00Z",
"max_uses": 1,
"use_count": 0,
"remaining_uses": 1,
"is_valid": true,
"invited_by": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"inviter_name": "Jane Admin",
"message": "Here's access to the Client XYZ project. Let me know if you need help!",
"created_at": "2024-12-18T10:30:00Z",
"updated_at": "2024-12-18T10:30:00Z"
}Accepting Invitations
Validate Invitation (Public)
Before accepting, users can validate an invitation code or token.
Endpoint: POST /api/v1/invitations/validate
Authentication: Not required (public endpoint)
Request:
{
"code": "ABC123"
}OR
{
"token": "veryLongSecureTokenWith64Characters..."
}Response (Valid):
{
"valid": true,
"organization_name": "Acme Corp",
"organization_slug": "acme-corp",
"email_restricted": true,
"restricted_email": "newmember@example.com",
"expires_at": "2024-12-25T10:30:00Z",
"message": "Welcome to our team!",
"scope": "organization",
"projects": null,
"project_role": null,
"error": null
}Response (Invalid):
{
"valid": false,
"organization_name": "Acme Corp",
"organization_slug": "acme-corp",
"email_restricted": true,
"restricted_email": "newmember@example.com",
"expires_at": "2024-12-18T10:30:00Z",
"message": null,
"scope": "organization",
"projects": null,
"project_role": null,
"error": "Invitation has expired"
}Accept Invitation
Endpoint: POST /api/v1/invitations/accept
Authentication: Required
Request:
{
"code": "ABC123"
}Response (Organization-Scope):
{
"success": true,
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_name": "Acme Corp",
"organization_slug": "acme-corp",
"scope": "organization",
"role": "DEVELOPER",
"org_role": "org_manager",
"project_access": null,
"message": "Welcome to Acme Corp!"
}Response (Project-Scope):
{
"success": true,
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_name": "Acme Corp",
"organization_slug": "acme-corp",
"scope": "project",
"role": null,
"org_role": null,
"project_access": [
{
"project_id": "a1b2c3d4-e5f6-4321-a987-123456789abc",
"project_name": "Client XYZ Website",
"role": "developer"
},
{
"project_id": "b2c3d4e5-f6a1-5432-b098-234567890bcd",
"project_name": "Client XYZ Backend",
"role": "developer"
}
],
"message": "You now have access to 2 project(s) in Acme Corp!"
}Acceptance Flow
- User Receives Invitation: Via email or shared link/code
- User Navigates: To
/onboarding?token=...or enters code manually - Validation: Frontend validates invitation (public endpoint)
- Registration/Login: User registers if new, or logs in if existing
- Acceptance: Authenticated user accepts invitation
- Membership Created:
- Organization-scope: Creates
OrganizationMemberrecord - Project-scope: Creates
ProjectMemberrecord(s)
- Organization-scope: Creates
- Use Count Incremented:
use_count++on invitation - Status Update: If
use_count >= max_uses, status →accepted - Welcome Email: Sent via task queue
- Redirect: User redirected to organization dashboard
Email Restrictions
If email field is set on invitation:
- Only that specific email can accept
- Case-insensitive matching
- Prevents invitation sharing
- Error if wrong email tries to accept:
"This invitation is restricted to user@example.com"
Multi-Use Invitations
Create invitations for team onboarding:
{
"email": null, // No email restriction
"scope": "organization",
"role": "DEVELOPER",
"max_uses": 10, // Can be used 10 times
"expires_in_days": 30
}Use Cases:
- Onboarding multiple team members
- Conference/event access codes
- Partner access
- Temporary contractor access
Tracking:
use_count: Number of times acceptedremaining_uses:max_uses - use_countis_valid:falsewhenuse_count >= max_uses
Managing Member Roles
Update Member Role
Change a member's organization role.
Endpoint: PATCH /api/v1/organizations/{org_id}/members/{user_id}
Required Permission: org.members.update_role
Request:
{
"role": "ADMIN",
"managed_project_ids": []
}Response:
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"user_id": "8d0e7780-8536-51ef-b058-f18fd3c4f91b",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"role": "ADMIN",
"org_role": "org_admin",
"managed_project_ids": [],
"is_active": true,
"user": {
"id": "8d0e7780-8536-51ef-b058-f18fd3c4f91b",
"email": "developer@example.com",
"name": "John Developer",
"avatar_url": null
},
"created_at": "2024-02-20T14:45:00Z"
}Role Change Restrictions
Cannot Change Owner's Role:
// Error response
{
"detail": "Cannot change the owner's legacy role"
}Permission Required:
- Must have
org.members.update_rolepermission - Typically limited to OWNER and ADMIN roles
Managed Projects (DEVELOPER Role)
Developers can be restricted to specific projects:
{
"role": "DEVELOPER",
"managed_project_ids": [
"a1b2c3d4-e5f6-4321-a987-123456789abc",
"b2c3d4e5-f6a1-5432-b098-234567890bcd"
]
}Use Cases:
- Client-specific access
- Department separation
- Contractor isolation
- Security isolation
Removing Team Members
Remove Member from Organization
Deactivate a member's access to the organization.
Endpoint: DELETE /api/v1/organizations/{org_id}/members/{user_id}
Required Permission: org.members.remove
Response:
{
"message": "Member removed successfully"
}Removal Restrictions
Cannot Remove Owner:
{
"detail": "Cannot remove the organization owner"
}Cannot Remove Self:
- Use "Leave Organization" endpoint instead
- Prevents accidental self-removal
Soft Delete Behavior
Member removal is a soft delete:
- Sets
is_active = falseonOrganizationMemberrecord - Preserves audit trail and resource ownership
- Can be reactivated by adding member again
- Member loses all access immediately
Resource Ownership Transfer
Before removing a member who owns resources:
- Check Ownership: Verify if member owns projects, environments, servers
- Transfer Ownership: Reassign resources to another member
- Remove Member: Proceed with removal once resources are transferred
Leaving an Organization
Members can leave an organization voluntarily (except Owner).
Endpoint: POST /api/v1/organizations/{org_id}/leave
Authentication: Required
Response:
{
"message": "You have left the organization successfully"
}Leave Restrictions
Owner Cannot Leave:
{
"detail": "Organization owner cannot leave. Transfer ownership first."
}Ownership Transfer Requirement:
- Owner must transfer ownership to another ADMIN or OWNER
- Once transferred, original owner becomes ADMIN
- Then they can leave the organization
Suspending Members
Temporarily disable member access without removing them.
Endpoint: PATCH /api/v1/organizations/{org_id}/members/{user_id}/suspend
Required Permission: org.members.update
Request:
{
"suspended": true,
"reason": "Security review in progress"
}Suspension vs. Removal
| Action | Effect | Reversible | Audit Trail | Resource Ownership |
|---|---|---|---|---|
| Suspend | Temporary access loss | Yes (reactivate) | Preserved | Maintained |
| Remove | Permanent access loss | Yes (re-invite) | Preserved | Transferred |
Use Cases for Suspension
- Security Incidents: Immediate access revocation during investigation
- Offboarding Process: Gradual access reduction before full removal
- Policy Violations: Temporary suspension pending review
- Inactive Accounts: Disable unused accounts without deletion
- Leave of Absence: Maintain membership but disable access
Reactivating Suspended Members
Endpoint: PATCH /api/v1/organizations/{org_id}/members/{user_id}/suspend
Request:
{
"suspended": false
}Invitation Management
List Pending Invitations
View all invitations for an organization.
Endpoint: GET /api/v1/invitations/organizations/{organization_id}
Required Permission: org.invitations.list
Query Parameters:
status(optional): Filter bypending,accepted,expired,revokedinclude_expired(boolean): Include expired invitations (default:false)
Response:
{
"invitations": [
{
"id": "inv-123e4567-e89b-12d3-a456-426614174000",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "newmember@example.com",
"scope": "organization",
"role": "DEVELOPER",
"org_role": "org_manager",
"code": "ABC123",
"link_token": null,
"status": "pending",
"expires_at": "2024-12-25T10:30:00Z",
"max_uses": 1,
"use_count": 0,
"remaining_uses": 1,
"is_valid": true,
"invited_by": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"inviter_name": "Jane Admin",
"message": "Welcome to our team!",
"created_at": "2024-12-18T10:30:00Z",
"updated_at": "2024-12-18T10:30:00Z"
}
],
"total": 1
}Get Specific Invitation
Endpoint: GET /api/v1/invitations/organizations/{organization_id}/{invitation_id}
Required Permission: org.invitations.list
Resend Invitation Email
Send invitation email again to the same recipient.
Endpoint: POST /api/v1/invitations/organizations/{organization_id}/{invitation_id}/resend
Required Permission: org.members.invite
Restrictions:
- Only
pendinginvitations can be resent - Invitation must not be expired
- Invitation must have an email address
- Email service must be configured
Response:
{
"id": "inv-123e4567-e89b-12d3-a456-426614174000",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "newmember@example.com",
"scope": "organization",
"role": "DEVELOPER",
"code": "ABC123",
"status": "pending",
"expires_at": "2024-12-25T10:30:00Z",
"is_valid": true,
"message": "Invitation email resent successfully"
}Revoke Invitation
Cancel a pending invitation.
Endpoint: DELETE /api/v1/invitations/organizations/{organization_id}/{invitation_id}
Required Permission: org.invitations.revoke
Restrictions:
- Only
pendinginvitations can be revoked - Once revoked, invitation cannot be accepted
Response: 204 No Content
Cleanup Expired Invitations
Delete all expired and revoked invitations from an organization.
Endpoint: DELETE /api/v1/invitations/organizations/{organization_id}/cleanup
Required Permission: org.invitations.revoke
Response:
{
"deleted_count": 5
}User's Received Invitations
List My Pending Invitations
View all invitations sent to the current user's email.
Endpoint: GET /api/v1/invitations/me
Authentication: Required
Response:
{
"invitations": [
{
"id": "inv-234f5678-f90c-23e4-b567-537725285111",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "myemail@example.com",
"scope": "organization",
"role": "DEVELOPER",
"code": "XYZ789",
"status": "pending",
"expires_at": "2025-01-15T10:30:00Z",
"is_valid": true,
"invited_by": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"inviter_name": "Jane Admin",
"message": "Join our team!",
"created_at": "2024-12-10T10:30:00Z"
}
],
"total": 1
}Filters:
- Only shows invitations restricted to user's email
- Only shows
pendingstatus - Only shows non-expired invitations
- Only shows invitations not at max uses
Member Activity Tracking
Last Active Timestamp
Track when members last accessed the platform:
{
user: {
id: string;
email: string;
name: string;
last_login_at: string; // ISO 8601 timestamp
}
}Activity Audit Trail
All member actions are logged in the audit system:
- Member Added:
ORG_MEMBER_ADDED - Role Changed:
ORG_MEMBER_ROLE_CHANGED - Member Removed:
ORG_MEMBER_REMOVED - Invitation Created:
INVITATION_CREATED - Invitation Accepted:
INVITATION_ACCEPTED - Invitation Revoked:
INVITATION_REVOKED - Invitation Resent:
INVITATION_RESENT
Example Audit Entry:
{
"id": "log-123",
"action": "org_member_added",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"organization_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"resource_type": "organization_member",
"resource_id": "660e8400-e29b-41d4-a716-446655440001",
"resource_name": "developer@example.com",
"details": {
"member_user_id": "8d0e7780-8536-51ef-b058-f18fd3c4f91b",
"member_email": "developer@example.com",
"role": "DEVELOPER",
"org_role": "org_manager"
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2024-12-18T10:30:00Z"
}Permission System Integration
OEC.SH uses a dual permission system:
1. Legacy Role-Based Checks
Organization Member Roles:
- Checked via
OrganizationMember.rolecolumn - Simple enum:
OWNER,ADMIN,DEVELOPER,VIEWER
2. Permission Matrix (Sprint 2E21)
Granular Permissions:
- 55+ granular permissions with
resource.actionformat - Example:
org.members.invite,org.projects.create - Supports custom roles and permission overrides
- Redis-cached abilities (5-minute TTL)
Common Member Management Permissions:
| Permission | Description | Default Roles |
|---|---|---|
org.members.list | View team members | All |
org.members.invite | Send invitations | OWNER, ADMIN |
org.members.update_role | Change member roles | OWNER, ADMIN |
org.members.remove | Remove members | OWNER, ADMIN |
org.invitations.list | View pending invitations | OWNER, ADMIN, DEVELOPER |
org.invitations.revoke | Cancel invitations | OWNER, ADMIN |
Permission Checks in Code
Backend:
from core.permissions import check_permission
has_permission = await check_permission(
db=db,
user=current_user,
permission_code="org.members.invite",
organization_id=organization_id,
)
if not has_permission:
raise HTTPException(403, "You don't have permission to invite members.")Frontend:
import { useAbilities } from '@/hooks/useAbilities';
function MemberManagement({ organizationId }) {
const { can } = useAbilities({ organizationId });
return (
<>
{can('members', 'invite') && (
<Button onClick={onInvite}>Invite Member</Button>
)}
</>
);
}SSE Real-Time Events
Member changes trigger Server-Sent Events for real-time UI updates.
Event Types
permissions_changed
Broadcasted when a user's permissions are updated.
{
event_type: "permissions_changed",
data: {
user_id: "8d0e7780-8536-51ef-b058-f18fd3c4f91b",
message: "Your permissions have been updated"
}
}Frontend Handling:
useEffect(() => {
if (event?.event_type === 'permissions_changed') {
// Refresh user abilities
mutateAbilities();
toast.info('Your permissions have been updated');
}
}, [event]);role_permissions_changed
Broadcasted when a role's permissions are modified.
{
event_type: "role_permissions_changed",
data: {
role_id: "role-123",
role_name: "Developer",
message: "Permissions for role 'Developer' have been updated"
}
}SSE Connection
Endpoint: GET /api/v1/events/stream
Query Parameters:
organization_id(optional): Filter events to specific organization
Connection Headers:
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-aliveEvent Stream Format:
event: message
data: {"event_type":"permissions_changed","data":{...}}
event: message
data: {"event_type":"role_permissions_changed","data":{...}}API Reference
Complete Endpoint List
Organization Members
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /organizations/{org_id}/members | org.members.list | List all members |
POST | /organizations/{org_id}/members | org.members.invite | Add member directly |
PATCH | /organizations/{org_id}/members/{user_id} | org.members.update_role | Update member role |
DELETE | /organizations/{org_id}/members/{user_id} | org.members.remove | Remove member |
POST | /organizations/{org_id}/leave | - | Leave organization |
Invitations - Organization Context
| Method | Endpoint | Permission | Description |
|---|---|---|---|
POST | /invitations/organizations/{org_id} | org.members.invite | Create invitation |
GET | /invitations/organizations/{org_id} | org.invitations.list | List invitations |
GET | /invitations/organizations/{org_id}/{inv_id} | org.invitations.list | Get invitation |
POST | /invitations/organizations/{org_id}/{inv_id}/resend | org.members.invite | Resend invitation |
DELETE | /invitations/organizations/{org_id}/{inv_id} | org.invitations.revoke | Revoke invitation |
DELETE | /invitations/organizations/{org_id}/cleanup | org.invitations.revoke | Cleanup expired |
Invitations - Public
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST | /invitations/validate | No | Validate invitation |
POST | /invitations/accept | Yes | Accept invitation |
GET | /invitations/me | Yes | List my invitations |
Request/Response Schemas
OrganizationMemberCreate
{
email: string; // User email to add
role: "OWNER" | "ADMIN" | "DEVELOPER" | "VIEWER";
managed_project_ids?: string[]; // For DEVELOPER role
}OrganizationMemberUpdate
{
role?: "OWNER" | "ADMIN" | "DEVELOPER" | "VIEWER";
managed_project_ids?: string[];
}InvitationCreate
{
email?: string | null;
scope: "organization" | "project";
// Organization-scope
role: "OWNER" | "ADMIN" | "DEVELOPER" | "VIEWER";
// Project-scope
project_ids?: string[];
project_role?: "admin" | "developer" | "viewer";
// Common
expires_in_days: number; // 1-30
max_uses?: number | null; // 1-100 or null
message?: string | null; // Max 500 chars
}InvitationValidate
{
code?: string; // 6 characters
token?: string; // 64 characters
}InvitationAccept
{
code?: string; // 6 characters
token?: string; // 64 characters
}Best Practices
Security
-
Principle of Least Privilege
- Assign minimum necessary role
- Use DEVELOPER for most team members
- Reserve ADMIN for trusted leadership
- Limit OWNER to one or two people
-
Regular Access Reviews
- Monthly review of active members
- Quarterly review of member roles
- Remove inactive members
- Audit permission changes
-
Invitation Hygiene
- Use email-restricted invitations for known users
- Set short expiration (7 days) for security-sensitive orgs
- Single-use invitations for individual onboarding
- Cleanup expired invitations regularly
-
Multi-Factor Authentication
- Require 2FA for OWNER and ADMIN roles
- Encourage 2FA for all members
- Enforce 2FA via organization policy
Onboarding Workflow
-
Prepare Resources
- Create necessary projects before inviting
- Set up project-specific roles if needed
- Document team guidelines and processes
-
Send Invitation
- Use organization-scope for full access
- Use project-scope for contractors/external
- Include welcome message with next steps
- Set appropriate expiration (7-14 days)
-
Track Acceptance
- Monitor pending invitations
- Resend if not accepted within 3 days
- Contact directly if invitation expires
-
Verify Access
- Confirm member can access resources
- Check role permissions are correct
- Provide onboarding documentation
Offboarding Workflow
-
Transfer Ownership
- Identify resources owned by departing member
- Reassign project ownership
- Transfer server management
-
Revoke Access
- Remove from organization
- Verify all access revoked
- Check for remaining API keys or tokens
-
Audit Trail
- Document removal in audit log
- Review recent activity
- Archive member's work if needed
Role Assignment Guidelines
| Team Member Type | Recommended Role | Reason |
|---|---|---|
| Founder/CEO | OWNER | Full control, billing |
| CTO/Tech Lead | ADMIN | Management, no billing |
| Developer | DEVELOPER | Deploy, manage projects |
| Designer | DEVELOPER | Manage design projects |
| DevOps | ADMIN | Server + project management |
| QA/Tester | DEVELOPER | Access to test environments |
| Project Manager | VIEWER | Monitor progress |
| Client/Stakeholder | VIEWER | Read-only monitoring |
| Contractor | Project-scope DEVELOPER | Limited to specific projects |
| External Partner | Project-scope VIEWER | Read-only project access |
Troubleshooting
Invitation Not Received
Problem: User didn't receive invitation email
Solutions:
- Check spam/junk folder
- Verify email address is correct in invitation
- Check email service is configured on platform
- Use "Resend Invitation" button
- Share invitation code/link manually
- Check email delivery logs in audit trail
Cannot Remove Member
Problem: "Cannot remove the organization owner"
Solutions:
- Transfer ownership to another ADMIN first
- Only OWNER can transfer ownership
- After transfer, original owner becomes ADMIN
- Then remove the (now) ADMIN member
Problem: "Cannot remove member - last owner"
Solutions:
- Must have at least one OWNER
- Add another OWNER first
- Then remove the original owner
Permission Denied Errors
Problem: "You don't have permission to invite members"
Solutions:
- Check your role (need OWNER or ADMIN)
- Contact organization owner
- Verify
org.members.invitepermission - Check permission cache (wait 5 minutes for refresh)
Problem: "Cannot access members from other organizations"
Solutions:
- Ensure you're in the correct organization context
- Verify
organization_idin request - Check you're a member of that organization
- Don't try to access cross-org members
Invitation Validation Errors
Problem: "Invitation has expired"
Solutions:
- Request new invitation from admin
- Admin can create new invitation with same email
- Set longer expiration (up to 30 days)
Problem: "Invitation has reached maximum uses"
Solutions:
- Multi-use invitation is exhausted
- Admin needs to create new invitation
- Check
use_countandmax_usesfields
Problem: "This invitation is restricted to email@example.com"
Solutions:
- You must use the specified email address
- Register/login with correct email
- Request new invitation for your email
Email Delivery Issues
Problem: Email service not configured
Solutions:
- Platform admin must configure SMTP settings
- Set
EMAIL_ENABLED=truein environment - Configure email templates
- Test email delivery
Problem: Email goes to spam
Solutions:
- Configure SPF, DKIM, DMARC records
- Use reputable email service (SendGrid, Mailgun)
- Whitelist sender domain
- Use custom domain for emails
Related Documentation
- Permission Matrix System - Detailed permission management
- Organization Settings - Organization configuration
- Project Management - Managing projects
- Audit Logs - Activity tracking and compliance
- SSE Events - Real-time event streaming
- Security Best Practices - Platform security guidelines
Last Updated: December 2024 Sprint: 2E21 - Permission Matrix System