Agent skill
hubspot-enterprise-rbac
Configure HubSpot enterprise access control with OAuth scopes and team permissions. Use when implementing role-based access, configuring per-team HubSpot scopes, or setting up multi-user access patterns for HubSpot integrations. Trigger with phrases like "hubspot RBAC", "hubspot roles", "hubspot enterprise", "hubspot permissions", "hubspot team access", "hubspot OAuth scopes".
Install this agent skill to your Project
npx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/main/plugins/saas-packs/hubspot-pack/skills/hubspot-enterprise-rbac
SKILL.md
HubSpot Enterprise RBAC
Overview
Implement role-based access control for HubSpot integrations using OAuth scopes, multiple private apps with different permissions, and application-level authorization.
Prerequisites
- HubSpot Enterprise subscription (for team-level permissions)
- Understanding of HubSpot OAuth scopes
- Multiple private apps or OAuth app configured
Instructions
Step 1: Scope-Based Access Model
HubSpot's permission model is scope-based. Create separate private apps for different access levels:
| Role | Private App | Scopes | Use Case |
|---|---|---|---|
| Reader | hubspot-readonly |
crm.objects.contacts.read, crm.objects.deals.read, crm.objects.companies.read |
Dashboards, reports |
| Writer | hubspot-readwrite |
Above + .write variants |
CRM operations |
| Admin | hubspot-admin |
All CRM scopes + crm.schemas.*.read |
Schema management |
| Sync | hubspot-sync |
crm.objects.contacts.read, crm.objects.contacts.write, crm.import |
Data sync jobs |
| Webhook | hubspot-webhook |
automation |
Event handling only |
Step 2: Multi-Token Client Factory
import * as hubspot from '@hubspot/api-client';
type AccessLevel = 'reader' | 'writer' | 'admin' | 'sync';
const TOKEN_MAP: Record<AccessLevel, string> = {
reader: process.env.HUBSPOT_READER_TOKEN!,
writer: process.env.HUBSPOT_WRITER_TOKEN!,
admin: process.env.HUBSPOT_ADMIN_TOKEN!,
sync: process.env.HUBSPOT_SYNC_TOKEN!,
};
const clientCache = new Map<AccessLevel, hubspot.Client>();
export function getClientForRole(role: AccessLevel): hubspot.Client {
if (!clientCache.has(role)) {
const token = TOKEN_MAP[role];
if (!token) {
throw new Error(`No token configured for role: ${role}`);
}
clientCache.set(role, new hubspot.Client({
accessToken: token,
numberOfApiCallRetries: 3,
}));
}
return clientCache.get(role)!;
}
// Usage
const readClient = getClientForRole('reader'); // can only read
const writeClient = getClientForRole('writer'); // can read and write
Step 3: Application-Level Permission Middleware
import { Request, Response, NextFunction } from 'express';
interface AppPermissions {
contacts: { read: boolean; write: boolean; delete: boolean };
deals: { read: boolean; write: boolean; delete: boolean };
companies: { read: boolean; write: boolean; delete: boolean };
}
const ROLE_PERMISSIONS: Record<string, AppPermissions> = {
sales_rep: {
contacts: { read: true, write: true, delete: false },
deals: { read: true, write: true, delete: false },
companies: { read: true, write: false, delete: false },
},
marketing: {
contacts: { read: true, write: true, delete: false },
deals: { read: true, write: false, delete: false },
companies: { read: true, write: false, delete: false },
},
admin: {
contacts: { read: true, write: true, delete: true },
deals: { read: true, write: true, delete: true },
companies: { read: true, write: true, delete: true },
},
readonly: {
contacts: { read: true, write: false, delete: false },
deals: { read: true, write: false, delete: false },
companies: { read: true, write: false, delete: false },
},
};
function requirePermission(
objectType: keyof AppPermissions,
action: 'read' | 'write' | 'delete'
) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role || 'readonly';
const permissions = ROLE_PERMISSIONS[userRole];
if (!permissions || !permissions[objectType]?.[action]) {
return res.status(403).json({
error: 'Forbidden',
message: `Role "${userRole}" lacks ${action} permission for ${objectType}`,
});
}
next();
};
}
// Usage
app.get('/api/contacts', requirePermission('contacts', 'read'), listContacts);
app.post('/api/contacts', requirePermission('contacts', 'write'), createContact);
app.delete('/api/contacts/:id', requirePermission('contacts', 'delete'), deleteContact);
Step 4: OAuth 2.0 for Multi-Portal Access
// For public apps accessing multiple HubSpot portals
interface PortalCredentials {
portalId: string;
accessToken: string;
refreshToken: string;
expiresAt: Date;
}
class MultiPortalManager {
private credentials = new Map<string, PortalCredentials>();
async getClient(portalId: string): Promise<hubspot.Client> {
let creds = this.credentials.get(portalId);
if (!creds) {
throw new Error(`No credentials for portal ${portalId}. User must authorize.`);
}
// Refresh token if expired
if (new Date() >= creds.expiresAt) {
creds = await this.refreshToken(creds);
this.credentials.set(portalId, creds);
}
return new hubspot.Client({ accessToken: creds.accessToken });
}
private async refreshToken(creds: PortalCredentials): Promise<PortalCredentials> {
const tempClient = new hubspot.Client();
const response = await tempClient.oauth.tokensApi.create(
'refresh_token',
undefined, undefined,
process.env.HUBSPOT_CLIENT_ID!,
process.env.HUBSPOT_CLIENT_SECRET!,
creds.refreshToken
);
return {
...creds,
accessToken: response.accessToken,
refreshToken: response.refreshToken,
expiresAt: new Date(Date.now() + response.expiresIn * 1000),
};
}
}
Step 5: Audit Trail
interface HubSpotAuditEntry {
timestamp: string;
userId: string;
role: string;
action: string;
objectType: string;
objectId: string;
success: boolean;
hubspotCorrelationId?: string;
}
async function auditHubSpotAction(
userId: string, role: string, action: string,
objectType: string, objectId: string, success: boolean,
correlationId?: string
): Promise<void> {
const entry: HubSpotAuditEntry = {
timestamp: new Date().toISOString(),
userId, role, action, objectType, objectId, success,
hubspotCorrelationId: correlationId,
};
// Store in your audit database
await db.auditLog.insert(entry);
// Alert on suspicious activity
if (!success && action === 'delete') {
console.warn('Failed delete attempt:', { userId, role, objectType, objectId });
}
}
Output
- Scope-based access model with separate private apps per role
- Multi-token client factory for role-based HubSpot access
- Application-level permission middleware
- Multi-portal OAuth management for public apps
- Audit trail for all HubSpot operations
Error Handling
| Issue | Cause | Solution |
|---|---|---|
403 MISSING_SCOPES |
Token lacks required scope | Use the correct role's token |
| Permission denied in app | User role too restrictive | Check ROLE_PERMISSIONS mapping |
| Token refresh fails | Client secret changed | Update client secret in env |
| Audit gaps | Async logging failed | Add retry to audit log writes |
Resources
Next Steps
For major migrations, see hubspot-migration-deep-dive.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
dockerfile-generator
Dockerfile Generator - Auto-activating skill for DevOps Basics. Triggers on: dockerfile generator, dockerfile generator Part of the DevOps Basics skill category.
branch-naming-helper
Branch Naming Helper - Auto-activating skill for DevOps Basics. Triggers on: branch naming helper, branch naming helper Part of the DevOps Basics skill category.
readme-generator
Readme Generator - Auto-activating skill for DevOps Basics. Triggers on: readme generator, readme generator Part of the DevOps Basics skill category.
makefile-generator
Makefile Generator - Auto-activating skill for DevOps Basics. Triggers on: makefile generator, makefile generator Part of the DevOps Basics skill category.
gitignore-generator
Gitignore Generator - Auto-activating skill for DevOps Basics. Triggers on: gitignore generator, gitignore generator Part of the DevOps Basics skill category.
pre-commit-hook-setup
Pre Commit Hook Setup - Auto-activating skill for DevOps Basics. Triggers on: pre commit hook setup, pre commit hook setup Part of the DevOps Basics skill category.
Didn't find tool you were looking for?