'Implement ClickUp Enterprise SSO, OAuth 2.0 multi-workspace access,
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill clickup-enterprise-rbac --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Clickup Enterprise Rbac?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-clickup-enterprise-rbac-tons-of-skills-marketplace)More formats (shields.io, HTML) on the badges page.
---
name: clickup-enterprise-rbac
description: 'Implement ClickUp Enterprise SSO, OAuth 2.0 multi-workspace access,
role-based permissions, and organization management via API v2.
Trigger: "clickup SSO", "clickup RBAC", "clickup enterprise",
"clickup roles", "clickup permissions", "clickup OAuth app", "clickup multi-workspace".
'
allowed-tools: Read, Write, Edit
version: 1.6.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- productivity
- clickup
compatibility: Designed for Claude Code
---
# ClickUp Enterprise RBAC
## Overview
Enterprise access patterns for ClickUp API v2. ClickUp's role system is built into the workspace, and the API surfaces roles via member objects. OAuth 2.0 enables multi-workspace apps where each user authorizes their own workspaces.
## Prerequisites
- Named workspace owner and approved role/access model
- Scoped OAuth/API identities with documented integration ownership
- Inventory of spaces/lists, data sensitivity, groups, and privileged actions
- Offboarding, access-review, token-rotation, and incident procedures
## Instructions
Evaluate effective workspace and list permissions before every privileged API
action, apply only the minimum role/group change, then verify the resulting
access with a representative least-privilege check. Keep production automation
on dedicated identities and expire exceptional access; never rely solely on a
displayed role name when resource-level sharing may differ.
## ClickUp Role Model
ClickUp workspace members have role IDs in the API:
| Role ID | Role | Permissions |
|---------|------|-------------|
| 1 | Owner | Full control, billing, workspace settings |
| 2 | Admin | Manage members, spaces, integrations |
| 3 | Member | Create/edit tasks, spaces (per permission) |
| 4 | Guest | Limited access to shared items only |
```typescript
// Get workspace members with roles
async function getWorkspaceMembers(teamId: string) {
const data = await clickupRequest(`/team/${teamId}`);
return data.team.members.map((m: any) => ({
userId: m.user.id,
username: m.user.username,
email: m.user.email,
role: m.user.role, // 1=owner, 2=admin, 3=member, 4=guest
roleLabel: { 1: 'owner', 2: 'admin', 3: 'member', 4: 'guest' }[m.user.role],
}));
}
// Check if user can perform admin operations
function canAdminister(member: { role: number }): boolean {
return member.role <= 2; // Owner or Admin
}
```
## OAuth 2.0 Multi-Workspace App
Build apps that access multiple ClickUp workspaces on behalf of users.
```typescript
// Step 1: Redirect user to ClickUp authorization
function getOAuthUrl(state: string): string {
return `https://app.clickup.com/api?client_id=${process.env.CLICKUP_CLIENT_ID}&redirect_uri=${encodeURIComponent(process.env.CLICKUP_REDIRECT_URI!)}&state=${state}`;
}
// Step 2: Exchange code for token
async function handleOAuthCallback(code: string) {
const response = await fetch('https://api.clickup.com/api/v2/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.CLICKUP_CLIENT_ID,
client_secret: process.env.CLICKUP_CLIENT_SECRET,
code,
}),
});
const { access_token } = await response.json();
// Step 3: Discover which workspaces user authorized
const teamsResponse = await fetch('https://api.clickup.com/api/v2/team', {
headers: { 'Authorization': access_token },
});
const { teams } = await teamsResponse.json();
return {
token: access_token, // Doesn't expire (but can be revoked)
workspaces: teams.map((t: any) => ({ id: t.id, name: t.name })),
};
}
// Step 4: Store per-user tokens
interface UserClickUpAuth {
userId: string;
clickupToken: string; // Encrypt at rest
authorizedWorkspaces: string[];
connectedAt: Date;
}
```
## Permission Middleware
```typescript
// Express middleware that checks ClickUp workspace access
function requireClickUpAccess(requiredRole: number = 3) {
return async (req: any, res: any, next: any) => {
const userToken = req.user.clickupToken;
const teamId = req.params.teamId || req.body.teamId;
if (!userToken) {
return res.status(401).json({ error: 'ClickUp not connected' });
}
// Verify user still has access to this workspace
const teamsRes = await fetch('https://api.clickup.com/api/v2/team', {
headers: { 'Authorization': userToken },
});
if (!teamsRes.ok) {
return res.status(401).json({ error: 'ClickUp token expired or revoked' });
}
const { teams } = await teamsRes.json();
const workspace = teams.find((t: any) => t.id === teamId);
if (!workspace) {
return res.status(403).json({ error: 'No access to this ClickUp workspace' });
}
// Check role level
const userMember = workspace.members.find(
(m: any) => m.user.id === req.user.clickupUserId
);
if (!userMember || userMember.user.role > requiredRole) {
return res.status(403).json({
error: `Requires role ${requiredRole} or higher`,
});
}
req.clickupWorkspace = workspace;
next();
};
}
// Usage
app.delete('/api/clickup/:teamId/space/:spaceId',
requireClickUpAccess(2), // Admin required
async (req, res) => { /* ... */ }
);
```
## User Groups (API v2 "Teams")
```
GET /api/v2/group Get User Groups
POST /api/v2/team/{team_id}/group Create User Group
PUT /api/v2/group/{group_id} Update User Group
DELETE /api/v2/group/{group_id} Delete User Group
```
```typescript
// Create a user group for engineering team
await clickupRequest(`/team/${teamId}/group`, {
method: 'POST',
body: JSON.stringify({
name: 'Engineering',
member_ids: [183, 456, 789],
}),
});
```
## Audit Trail
```typescript
interface ClickUpAuditEntry {
timestamp: string;
userId: number;
workspaceId: string;
action: string;
resource: string;
resourceId: string;
success: boolean;
}
function logClickUpAction(entry: Omit<ClickUpAuditEntry, 'timestamp'>): void {
const log: ClickUpAuditEntry = {
...entry,
timestamp: new Date().toISOString(),
};
console.log(JSON.stringify({ level: 'audit', service: 'clickup', ...log }));
}
```
## Error Handling
| Issue | Cause | Solution |
|-------|-------|----------|
| OAUTH_023/027 | Workspace not authorized | User must re-authorize via OAuth flow |
| Role check fails | User role changed in ClickUp | Re-fetch member data from API |
| Token revoked | User disconnected app | Handle 401, prompt re-auth |
| Guest access denied | Endpoint requires member+ | Check `role` field before API call |
## Output
Publish a redacted access-review record with workspace/list scope, effective
roles/groups, integration identities, privileged actions, audit evidence,
exceptions and expiry, token owner, and remediation status. Do not export
unnecessary member profiles, private tasks, or live authentication material.
## Examples
For a new engineering integration, create or approve a scoped service identity,
verify that it can update only its designated list and cannot read a restricted
list, then capture the result in the access review. If a former employee or
over-broad group remains active, remove access, test dependent automation, and
document the minimum remediation.
## Resources
- [ClickUp Authentication](https://developer.clickup.com/docs/authentication)
- [ClickUp Get Access Token](https://developer.clickup.com/reference/getaccesstoken)
- [ClickUp API Terminology](https://developer.clickup.com/docs/general-v2-v3-api)
## Next Steps
For major migrations, see `clickup-migration-deep-dive`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!