Organizations API
Manage organizations, workspaces, members, and roles across all auth providers
Organizations API
Manage multi-tenant organizations (workspaces) with member management, role-based access control, and organization-level settings. Works across all supported auth providers.
Overview
Organizations enable multi-tenant functionality where users can belong to multiple workspaces with different roles and permissions.
Base Endpoint
/api/auth/organizationsList Organizations
Get all organizations the current user belongs to.
Endpoint
GET /api/auth/organizationsSuccess Response (200)
{
"success": true,
"data": {
"organizations": [
{
"id": "org_abc123",
"name": "Acme Inc",
"slug": "acme-inc",
"logoUrl": "https://cdn.example.com/logos/acme.png",
"role": "owner",
"memberCount": 5,
"createdAt": "2024-01-10T08:00:00Z",
"settings": {
"allowPublicProjects": true,
"require2FA": false
}
},
{
"id": "org_def456",
"name": "StartupXYZ",
"slug": "startupxyz",
"role": "member",
"memberCount": 12,
"createdAt": "2024-01-05T10:30:00Z"
}
]
}
}Get Organization
Retrieve details for a specific organization.
Endpoint
GET /api/auth/organizations/:orgIdSuccess Response (200)
{
"success": true,
"data": {
"organization": {
"id": "org_abc123",
"name": "Acme Inc",
"slug": "acme-inc",
"description": "Enterprise software solutions",
"logoUrl": "https://cdn.example.com/logos/acme.png",
"website": "https://acme.example.com",
"createdAt": "2024-01-10T08:00:00Z",
"updatedAt": "2024-01-15T14:30:00Z",
"settings": {
"allowPublicProjects": true,
"require2FA": false,
"defaultRole": "member",
"billingEmail": "billing@acme.example.com"
}
},
"membership": {
"role": "owner",
"joinedAt": "2024-01-10T08:00:00Z",
"permissions": ["*"]
}
}
}Create Organization
Create a new organization. The creator automatically becomes the owner.
Endpoint
POST /api/auth/organizationsRequest
{
"name": "My New Org",
"slug": "my-new-org", // Auto-generated if not provided
"description": "Optional description",
"logoUrl": "https://example.com/logo.png", // Optional
"website": "https://example.com" // Optional
}Slug Requirements
- 3-50 characters
- Lowercase letters, numbers, and hyphens only
- Must start with a letter
- Must be unique across the platform
Success Response (201)
{
"success": true,
"data": {
"organization": {
"id": "org_xyz789",
"name": "My New Org",
"slug": "my-new-org",
"description": "Optional description",
"createdAt": "2024-01-15T16:00:00Z",
"settings": {
"allowPublicProjects": true,
"require2FA": false,
"defaultRole": "member"
}
},
"membership": {
"role": "owner",
"joinedAt": "2024-01-15T16:00:00Z"
}
},
"message": "Organization created successfully"
}Update Organization
Update organization details (requires admin or owner role).
Endpoint
PATCH /api/auth/organizations/:orgIdRequest
{
"name": "Updated Org Name",
"description": "New description",
"logoUrl": "https://example.com/new-logo.png",
"settings": {
"allowPublicProjects": false,
"require2FA": true
}
}Success Response (200)
{
"success": true,
"data": {
"organization": {
"id": "org_abc123",
"name": "Updated Org Name",
"slug": "acme-inc", // Slug cannot be changed
"description": "New description",
"updatedAt": "2024-01-15T17:00:00Z"
}
}
}Delete Organization
Permanently delete an organization (owner only).
Endpoint
DELETE /api/auth/organizations/:orgIdRequest
{
"confirmName": "Acme Inc" // Must match org name for confirmation
}Success Response (200)
{
"success": true,
"data": {
"deleted": true
},
"message": "Organization deleted successfully"
}List Members
Get all members of an organization.
Endpoint
GET /api/auth/organizations/:orgId/membersQuery Parameters
| Parameter | Type | Description |
|---|---|---|
role | string | Filter by role (owner, admin, member) |
search | string | Search by name or email |
limit | number | Results per page (default: 20) |
offset | number | Pagination offset |
Success Response (200)
{
"success": true,
"data": {
"members": [
{
"id": "mem_123",
"userId": "usr_456",
"email": "owner@example.com",
"name": "John Owner",
"role": "owner",
"avatarUrl": "https://cdn.example.com/avatars/usr_456.jpg",
"joinedAt": "2024-01-10T08:00:00Z",
"lastActiveAt": "2024-01-15T14:30:00Z"
},
{
"id": "mem_789",
"userId": "usr_012",
"email": "member@example.com",
"name": "Jane Member",
"role": "member",
"joinedAt": "2024-01-12T10:00:00Z",
"lastActiveAt": "2024-01-14T09:00:00Z"
}
],
"pagination": {
"total": 5,
"limit": 20,
"offset": 0
}
}
}Invite Member
Invite a user to join the organization.
Endpoint
POST /api/auth/organizations/:orgId/invitationsRequest
{
"email": "newmember@example.com",
"role": "member", // owner, admin, or member
"sendEmail": true // Send invitation email
}Roles
| Role | Permissions |
|---|---|
owner | Full control, can delete organization |
admin | Manage members, settings, billing |
member | Standard access, can create content |
Success Response (201)
{
"success": true,
"data": {
"invitation": {
"id": "inv_abc123",
"email": "newmember@example.com",
"role": "member",
"status": "pending",
"expiresAt": "2024-01-22T16:00:00Z",
"invitedBy": {
"id": "usr_456",
"name": "John Owner"
}
}
},
"message": "Invitation sent successfully"
}Update Member Role
Change a member's role (owner or admin only).
Endpoint
PATCH /api/auth/organizations/:orgId/members/:memberIdRequest
{
"role": "admin"
}Success Response (200)
{
"success": true,
"data": {
"member": {
"id": "mem_789",
"role": "admin",
"updatedAt": "2024-01-15T17:30:00Z"
}
}
}Remove Member
Remove a member from the organization.
Endpoint
DELETE /api/auth/organizations/:orgId/members/:memberIdSuccess Response (200)
{
"success": true,
"data": {
"removed": true
},
"message": "Member removed successfully"
}Accept Invitation
Accept an organization invitation.
Endpoint
POST /api/auth/organizations/invitations/:invitationId/acceptSuccess Response (200)
{
"success": true,
"data": {
"membership": {
"organizationId": "org_abc123",
"role": "member",
"joinedAt": "2024-01-15T18:00:00Z"
}
}
}Switch Organization
Set the active organization for the current session.
Endpoint
POST /api/auth/organizations/switchRequest
{
"organizationId": "org_def456"
}Success Response (200)
{
"success": true,
"data": {
"activeOrganization": {
"id": "org_def456",
"name": "StartupXYZ",
"role": "member"
}
}
}Code Examples
React Hook for Organizations
import { useState, useEffect } from 'react';
interface Organization {
id: string;
name: string;
slug: string;
role: 'owner' | 'admin' | 'member';
memberCount: number;
}
export function useOrganizations() {
const [organizations, setOrganizations] = useState<Organization[]>([]);
const [activeOrg, setActiveOrg] = useState<Organization | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchOrganizations();
}, []);
const fetchOrganizations = async () => {
try {
const response = await fetch('/api/auth/organizations', {
credentials: 'include'
});
const result = await response.json();
if (result.success) {
setOrganizations(result.data.organizations);
// Set first org as active if none selected
if (result.data.organizations.length > 0 && !activeOrg) {
setActiveOrg(result.data.organizations[0]);
}
}
} finally {
setIsLoading(false);
}
};
const createOrganization = async (data: {
name: string;
slug?: string;
}) => {
const response = await fetch('/api/auth/organizations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
setOrganizations([...organizations, result.data.organization]);
}
return result;
};
const switchOrganization = async (orgId: string) => {
const response = await fetch('/api/auth/organizations/switch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ organizationId: orgId })
});
const result = await response.json();
if (result.success) {
const org = organizations.find(o => o.id === orgId);
setActiveOrg(org || null);
}
return result;
};
return {
organizations,
activeOrg,
isLoading,
createOrganization,
switchOrganization,
refresh: fetchOrganizations
};
}Organization Selector Component
export function OrganizationSelector() {
const { organizations, activeOrg, switchOrganization, isLoading } = useOrganizations();
const [isOpen, setIsOpen] = useState(false);
if (isLoading) return <div>Loading...</div>;
return (
<div className="org-selector">
<button onClick={() => setIsOpen(!isOpen)}>
{activeOrg?.name || 'Select Organization'}
<span className="dropdown-arrow">▼</span>
</button>
{isOpen && (
<div className="org-dropdown">
{organizations.map(org => (
<div
key={org.id}
className={`org-item ${org.id === activeOrg?.id ? 'active' : ''}`}
onClick={() => {
switchOrganization(org.id);
setIsOpen(false);
}}
>
<span className="org-name">{org.name}</span>
<span className="org-role">{org.role}</span>
</div>
))}
<div className="org-divider" />
<a href="/organizations/new" className="org-item">
+ Create Organization
</a>
</div>
)}
</div>
);
}Provider-Specific Behavior
BetterAuth
- Organizations stored in local database
- Customizable roles and permissions
- Full data ownership
NextAuth
- Requires custom organization implementation
- Session-based org context
- Database schema for memberships
Clerk
- Uses Clerk Organizations feature
- Built-in role management
- Automatic UI components available
AuthKit
- WorkOS organization support
- Enterprise directory sync
- SSO per organization
Webhook Events
| Event | Description |
|---|---|
organization.created | New organization created |
organization.updated | Organization details changed |
organization.deleted | Organization removed |
organization.member.invited | Invitation sent |
organization.member.joined | User accepted invitation |
organization.member.left | Member removed or left |
organization.member.role_changed | Role updated |
Rate Limits
| Action | Limit |
|---|---|
| Create organization | 5 per user per hour |
| Invite member | 20 per org per hour |
| Update member | 30 per org per hour |
| List members | 60 per minute |