Docs

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/organizations

List Organizations

Get all organizations the current user belongs to.

Endpoint

GET /api/auth/organizations

Success 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/:orgId

Success 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/organizations

Request

{
  "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/:orgId

Request

{
  "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/:orgId

Request

{
  "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/members

Query Parameters

ParameterTypeDescription
rolestringFilter by role (owner, admin, member)
searchstringSearch by name or email
limitnumberResults per page (default: 20)
offsetnumberPagination 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/invitations

Request

{
  "email": "newmember@example.com",
  "role": "member", // owner, admin, or member
  "sendEmail": true // Send invitation email
}

Roles

RolePermissions
ownerFull control, can delete organization
adminManage members, settings, billing
memberStandard 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/:memberId

Request

{
  "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/:memberId

Success Response (200)

{
  "success": true,
  "data": {
    "removed": true
  },
  "message": "Member removed successfully"
}

Accept Invitation

Accept an organization invitation.

Endpoint

POST /api/auth/organizations/invitations/:invitationId/accept

Success 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/switch

Request

{
  "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

EventDescription
organization.createdNew organization created
organization.updatedOrganization details changed
organization.deletedOrganization removed
organization.member.invitedInvitation sent
organization.member.joinedUser accepted invitation
organization.member.leftMember removed or left
organization.member.role_changedRole updated

Rate Limits

ActionLimit
Create organization5 per user per hour
Invite member20 per org per hour
Update member30 per org per hour
List members60 per minute

On this page