Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Configure Miro Enterprise features: organization management, SCIM provisioning,
board-level access control, audit logs, and SSO integration via REST API v2.
Trigger with phrases like "miro SSO", "miro RBAC",
"miro enterprise", "miro SCIM", "miro permissions", "miro organization".
allowed-tools
Read, Write, Edit
version
1.6.0
license
MIT
author
Jeremy Longshore <jeremy@intentsolutions.io>
tags
["saas","miro","enterprise","rbac","scim"]
compatibility
Designed for Claude Code
Miro Enterprise RBAC
Overview
Enterprise-grade access control for Miro REST API v2: organization and team management, SCIM user provisioning, board sharing with role-based permissions, and audit log access. Requires Miro Enterprise plan.
// List teams in organization// GET https://api.miro.com/v2/orgs/{org_id}/teams (Enterprise)const teams = awaitmiroFetch(`/v2/orgs/${orgId}/teams?limit=50`);
// Get team details// GET https://api.miro.com/v2/teams/{team_id}const team = awaitmiroFetch(`/v2/teams/${teamId}`);
// List team members// GET https://api.miro.com/v2/teams/{team_id}/membersconst teamMembers = awaitmiroFetch(`/v2/teams/${teamId}/members?limit=100`);
// Invite user to team// POST https://api.miro.com/v2/teams/{team_id}/membersawaitmiroFetch(`/v2/teams/${teamId}/members`, 'POST', {
emails: ['newdev@company.com'],
role: 'member', // 'member' | 'admin' | 'non_team'
});
Organization Management (Enterprise)
// Get organization info// GET https://api.miro.com/v2/orgs/{org_id}const org = awaitmiroFetch(`/v2/orgs/${orgId}`);
// List organization members// GET https://api.miro.com/v2/orgs/{org_id}/membersconst orgMembers = awaitmiroFetch(`/v2/orgs/${orgId}/members?limit=100`);
SCIM User Provisioning (Enterprise)
Miro supports SCIM 2.0 for automated user lifecycle management from identity providers (Okta, Azure AD, OneLogin).
// SCIM Base URL: https://miro.com/api/v1/scim/v2// Create user via SCIM// POST https://miro.com/api/v1/scim/v2/Usersconst scimUser = awaitfetch('https://miro.com/api/v1/scim/v2/Users', {
method: 'POST',
headers: {
'Authorization': `Bearer ${scimToken}`,
'Content-Type': 'application/scim+json',
},
body: JSON.stringify({
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
userName: 'newuser@company.com',
name: { givenName: 'New', familyName: 'User' },
emails: [{ value: 'newuser@company.com', type: 'work', primary: true }],
active: true,
}),
});
// List users via SCIM// GET https://miro.com/api/v1/scim/v2/Usersconst users = awaitfetch('https://miro.com/api/v1/scim/v2/Users?filter=active eq true', {
headers: { 'Authorization': `Bearer ${scimToken}` },
});
// Deactivate user (deprovision)// PATCH https://miro.com/api/v1/scim/v2/Users/{user_id}awaitfetch(`https://miro.com/api/v1/scim/v2/Users/${scimUserId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${scimToken}`,
'Content-Type': 'application/scim+json',
},
body: JSON.stringify({
schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
Operations: [{ op: 'replace', path: 'active', value: false }],
}),
});
// Manage team membership via SCIM Groups// GET https://miro.com/api/v1/scim/v2/Groups// POST/PATCH Groups to add/remove team members
Board Sharing Policies
Control how boards can be shared at creation time:
// Create board with restrictive sharingawaitmiroFetch('/v2/boards', 'POST', {
name: 'Confidential Strategy Board',
policy: {
sharingPolicy: {
access: 'private', // Only invited membersinviteToAccountAndBoardLinkAccess: 'no_access',
organizationAccess: 'private', // Not visible to orgteamAccess: 'private', // Not visible to team
},
permissionsPolicy: {
collaborationToolsStartAccess: 'all_editors',
copyAccess: 'team_members', // Only team can copysharingAccess: 'owners_and_coowners', // Only owners can share
},
},
});
// Create board with open team accessawaitmiroFetch('/v2/boards', 'POST', {
name: 'Team Brainstorming',
teamId: teamId,
policy: {
sharingPolicy: {
access: 'edit', // Team can edit by defaultteamAccess: 'edit',
},
permissionsPolicy: {
sharingAccess: 'team_members_and_collaborators',
},
},
});
Audit Logs (Enterprise)
// Get audit logs โ requires 'auditlogs:read' scope// GET https://api.miro.com/v2/orgs/{org_id}/audit-logsconst logs = awaitmiroFetch(
`/v2/orgs/${orgId}/audit-logs?limit=100&createdAfter=${startDate}`
);
// Log entries include:// - User actions (board created, item modified, member added)// - Admin actions (team created, user deactivated, settings changed)// - API actions (OAuth token issued, SCIM provisioning)for (const entry of logs.data) {
console.log({
action: entry.action,
actor: entry.actor?.email,
target: entry.context?.boardId ?? entry.context?.teamId,
timestamp: entry.createdAt,
});
}
Access Control Middleware
Enforce board-level permissions in your application:
typeBoardRole = 'viewer' | 'commenter' | 'editor' | 'coowner' | 'owner';
constROLE_HIERARCHY: Record<BoardRole, number> = {
viewer: 0,
commenter: 1,
editor: 2,
coowner: 3,
owner: 4,
};
functionhasMinimumRole(userRole: BoardRole, requiredRole: BoardRole): boolean {
returnROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole];
}
asyncfunctionrequireBoardRole(boardId: string, userId: string, minRole: BoardRole) {
const members = awaitmiroFetch(`/v2/boards/${boardId}/members?limit=100`);
const user = members.data.find((m: any) => m.id === userId);
if (!user) {
thrownewError('User is not a board member');
}
if (!hasMinimumRole(user.role, minRole)) {
thrownewError(`Requires ${minRole} role, user has ${user.role}`);
}
}
// UsageawaitrequireBoardRole(boardId, userId, 'editor');
// Throws if user doesn't have editor or higher role