From klaviyo-pack
Configures Klaviyo enterprise RBAC with scoped API keys and OAuth for granular read/write permissions on profiles, events, campaigns, and more.
How this skill is triggered — by the user, by Claude, or both
Slash command
/klaviyo-pack:klaviyo-enterprise-rbacThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Enterprise access control for Klaviyo: API key scoping with granular read/write permissions, OAuth app authorization flows, and application-level RBAC built on top of Klaviyo's scope system.
Enterprise access control for Klaviyo: API key scoping with granular read/write permissions, OAuth app authorization flows, and application-level RBAC built on top of Klaviyo's scope system.
Klaviyo uses scoped API keys and OAuth for access control. There are no built-in "roles" in Klaviyo's API -- you implement RBAC by creating multiple API keys with different scopes.
| Scope | Read | Write | What It Controls |
|---|---|---|---|
accounts | Account info | N/A | Organization name, timezone |
campaigns | List campaigns | Create/send campaigns | Email, SMS, push campaigns |
catalogs | Browse items | CRUD catalog items | Product catalog management |
coupons | List coupons | Create coupons | Coupon/discount codes |
data-privacy | N/A | Delete profiles | GDPR/CCPA deletion requests |
events | Query events | Track events | Server-side event tracking |
flows | List flows | Create/update flows | Flow automation |
images | List images | Upload images | Email template images |
lists | List lists | CRUD lists/members | List management |
metrics | Query metrics | N/A | Metric aggregations |
profiles | Read profiles | Create/update profiles | Profile management |
segments | Read segments | N/A | Segment queries |
tags | Read tags | CRUD tags | Resource tagging |
templates | Read templates | Create/update templates | Email templates |
webhooks | List webhooks | CRUD webhooks | Webhook subscriptions |
Create separate API keys per service/role in Klaviyo dashboard (Settings > API Keys):
// Example: different keys for different services
// Profile Sync Service -- only needs profiles + lists
// Key scopes: profiles:read, profiles:write, lists:read, lists:write
const profileSyncSession = new ApiKeySession(process.env.KLAVIYO_KEY_PROFILE_SYNC!);
// Event Tracking Service -- only needs events + profiles
// Key scopes: events:write, profiles:read, profiles:write
const eventTrackingSession = new ApiKeySession(process.env.KLAVIYO_KEY_EVENT_TRACKER!);
// Reporting Dashboard -- read-only
// Key scopes: campaigns:read, metrics:read, segments:read, profiles:read
const reportingSession = new ApiKeySession(process.env.KLAVIYO_KEY_REPORTING!);
// Admin Service -- full access (use sparingly)
// Key scopes: all scopes
const adminSession = new ApiKeySession(process.env.KLAVIYO_KEY_ADMIN!);
// src/klaviyo/rbac.ts
enum AppRole {
Admin = 'admin',
Marketer = 'marketer',
Developer = 'developer',
Viewer = 'viewer',
Service = 'service',
}
interface KlaviyoPermissions {
canReadProfiles: boolean;
canWriteProfiles: boolean;
canDeleteProfiles: boolean;
canSendCampaigns: boolean;
canManageLists: boolean;
canTrackEvents: boolean;
canViewReports: boolean;
canManageWebhooks: boolean;
}
const ROLE_PERMISSIONS: Record<AppRole, KlaviyoPermissions> = {
admin: {
canReadProfiles: true, canWriteProfiles: true, canDeleteProfiles: true,
canSendCampaigns: true, canManageLists: true, canTrackEvents: true,
canViewReports: true, canManageWebhooks: true,
},
marketer: {
canReadProfiles: true, canWriteProfiles: false, canDeleteProfiles: false,
canSendCampaigns: true, canManageLists: true, canTrackEvents: false,
canViewReports: true, canManageWebhooks: false,
},
developer: {
canReadProfiles: true, canWriteProfiles: true, canDeleteProfiles: false,
canSendCampaigns: false, canManageLists: true, canTrackEvents: true,
canViewReports: true, canManageWebhooks: true,
},
viewer: {
canReadProfiles: true, canWriteProfiles: false, canDeleteProfiles: false,
canSendCampaigns: false, canManageLists: false, canTrackEvents: false,
canViewReports: true, canManageWebhooks: false,
},
service: {
canReadProfiles: true, canWriteProfiles: true, canDeleteProfiles: false,
canSendCampaigns: false, canManageLists: false, canTrackEvents: true,
canViewReports: false, canManageWebhooks: false,
},
};
export function checkPermission(role: AppRole, permission: keyof KlaviyoPermissions): boolean {
return ROLE_PERMISSIONS[role][permission];
}
// Map roles to API keys with appropriate scopes
const ROLE_API_KEYS: Record<AppRole, string> = {
admin: process.env.KLAVIYO_KEY_ADMIN!,
marketer: process.env.KLAVIYO_KEY_MARKETER!,
developer: process.env.KLAVIYO_KEY_DEVELOPER!,
viewer: process.env.KLAVIYO_KEY_VIEWER!,
service: process.env.KLAVIYO_KEY_SERVICE!,
};
export function getSessionForRole(role: AppRole): ApiKeySession {
const key = ROLE_API_KEYS[role];
if (!key) throw new Error(`No API key configured for role: ${role}`);
return new ApiKeySession(key);
}
// src/middleware/klaviyo-auth.ts
import { checkPermission, AppRole, KlaviyoPermissions } from '../klaviyo/rbac';
export function requireKlaviyoPermission(permission: keyof KlaviyoPermissions) {
return (req: any, res: any, next: any) => {
const userRole = req.user?.klaviyoRole as AppRole;
if (!userRole) return res.status(401).json({ error: 'No Klaviyo role assigned' });
if (!checkPermission(userRole, permission)) {
return res.status(403).json({
error: 'Forbidden',
message: `Role '${userRole}' does not have permission: ${permission}`,
});
}
next();
};
}
// Usage in routes
app.get('/api/klaviyo/profiles',
requireKlaviyoPermission('canReadProfiles'),
profilesHandler
);
app.post('/api/klaviyo/campaigns/send',
requireKlaviyoPermission('canSendCampaigns'),
campaignSendHandler
);
app.delete('/api/klaviyo/profiles/:id',
requireKlaviyoPermission('canDeleteProfiles'),
profileDeleteHandler
);
// OAuth flow for Klaviyo apps (marketplace integrations)
// Reference: https://developers.klaviyo.com/en/docs/set_up_oauth
const OAUTH_CONFIG = {
clientId: process.env.KLAVIYO_OAUTH_CLIENT_ID!,
clientSecret: process.env.KLAVIYO_OAUTH_CLIENT_SECRET!,
redirectUri: 'https://your-app.com/auth/klaviyo/callback',
authorizationUrl: 'https://www.klaviyo.com/oauth/authorize',
tokenUrl: 'https://a.klaviyo.com/oauth/token',
// Only request scopes your app needs
scopes: ['profiles:read', 'profiles:write', 'events:write', 'lists:read'],
};
// Step 1: Redirect user to Klaviyo authorization
function getAuthorizationUrl(state: string): string {
const params = new URLSearchParams({
response_type: 'code',
client_id: OAUTH_CONFIG.clientId,
redirect_uri: OAUTH_CONFIG.redirectUri,
scope: OAUTH_CONFIG.scopes.join(' '),
state,
});
return `${OAUTH_CONFIG.authorizationUrl}?${params}`;
}
// Step 2: Exchange code for access token
async function exchangeCodeForToken(code: string): Promise<{
accessToken: string;
refreshToken: string;
expiresIn: number;
}> {
const response = await fetch(OAUTH_CONFIG.tokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
client_id: OAUTH_CONFIG.clientId,
client_secret: OAUTH_CONFIG.clientSecret,
redirect_uri: OAUTH_CONFIG.redirectUri,
}),
});
const data = await response.json();
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
};
}
// src/klaviyo/audit.ts
interface KlaviyoAuditEntry {
timestamp: Date;
userId: string;
role: AppRole;
action: string;
resource: string;
success: boolean;
klaviyoEndpoint: string;
ipAddress?: string;
}
async function logKlaviyoAccess(entry: KlaviyoAuditEntry): Promise<void> {
// Store in audit database
await db.auditLog.create({ data: entry });
// Alert on suspicious activity
if (entry.action === 'DELETE' && entry.resource.includes('profile')) {
await alertSecurityTeam(`Profile deletion by ${entry.userId} (${entry.role})`);
}
}
# Per-role API keys (create in Klaviyo dashboard with specific scopes)
KLAVIYO_KEY_ADMIN=pk_admin_*** # All scopes
KLAVIYO_KEY_MARKETER=pk_marketer_*** # campaigns:*, lists:*, profiles:read, segments:read
KLAVIYO_KEY_DEVELOPER=pk_dev_*** # profiles:*, events:*, lists:*, webhooks:*, templates:*
KLAVIYO_KEY_VIEWER=pk_viewer_*** # *:read only
KLAVIYO_KEY_SERVICE=pk_service_*** # events:write, profiles:read/write
# OAuth (for marketplace apps)
KLAVIYO_OAUTH_CLIENT_ID=your_client_id
KLAVIYO_OAUTH_CLIENT_SECRET=your_client_secret
| Error | Status | Cause | Solution |
|---|---|---|---|
permission_denied | 403 | API key missing required scope | Create new key with correct scopes |
| OAuth code expired | 400 | User took too long to authorize | Retry authorization flow |
| Token refresh failed | 401 | Refresh token revoked | Re-authorize the app |
| Role not assigned | 401 | User missing klaviyoRole | Assign role in your user management |
For major migrations, see klaviyo-migration-deep-dive.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin klaviyo-packApplies Klaviyo security best practices for API key management, OAuth scopes, webhook HMAC-SHA256 verification, and secret rotation in integrations.
Implements RBAC for Apollo.io API via Express middleware, TypeScript permission matrix for roles like viewer/sales_rep, and scoped keys to restrict data access.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.