--- name: miro-enterprise-rbac description: '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.0.0 license: MIT author: Jeremy Longshore 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. ## Miro Access Hierarchy ``` Organization (Enterprise) ├── Team 1 │ ├── Board A (sharing: team only) │ │ ├── Owner (full control) │ │ ├── Co-owner (full control, can't delete board) │ │ ├── Editor (can add/edit items) │ │ ├── Commenter (can add comments only) │ │ └── Viewer (read-only) │ └── Board B ├── Team 2 │ └── Board C └── Projects └── Project 1 (groups boards) ``` ## Board Roles & Permissions | Role | View | Comment | Edit Items | Share | Delete Board | |------|------|---------|------------|-------|-------------| | Viewer | Yes | No | No | No | No | | Commenter | Yes | Yes | No | No | No | | Editor | Yes | Yes | Yes | No | No | | Co-owner | Yes | Yes | Yes | Yes | No | | Owner | Yes | Yes | Yes | Yes | Yes | ## Board Member Management ```typescript // List board members // GET https://api.miro.com/v2/boards/{board_id}/members const members = await miroFetch(`/v2/boards/${boardId}/members?limit=50`); for (const member of members.data) { console.log(`${member.name} (${member.id}): role=${member.role}`); } // Share board with users // POST https://api.miro.com/v2/boards/{board_id}/members await miroFetch(`/v2/boards/${boardId}/members`, 'POST', { emails: ['dev@company.com', 'pm@company.com'], role: 'editor', // 'viewer' | 'commenter' | 'editor' | 'coowner' message: 'You have been added to the sprint board', }); // Update member role // PATCH https://api.miro.com/v2/boards/{board_id}/members/{member_id} await miroFetch(`/v2/boards/${boardId}/members/${memberId}`, 'PATCH', { role: 'commenter', }); // Remove member from board // DELETE https://api.miro.com/v2/boards/{board_id}/members/{member_id} await miroFetch(`/v2/boards/${boardId}/members/${memberId}`, 'DELETE'); ``` ## Team Management (Enterprise) ```typescript // List teams in organization // GET https://api.miro.com/v2/orgs/{org_id}/teams (Enterprise) const teams = await miroFetch(`/v2/orgs/${orgId}/teams?limit=50`); // Get team details // GET https://api.miro.com/v2/teams/{team_id} const team = await miroFetch(`/v2/teams/${teamId}`); // List team members // GET https://api.miro.com/v2/teams/{team_id}/members const teamMembers = await miroFetch(`/v2/teams/${teamId}/members?limit=100`); // Invite user to team // POST https://api.miro.com/v2/teams/{team_id}/members await miroFetch(`/v2/teams/${teamId}/members`, 'POST', { emails: ['newdev@company.com'], role: 'member', // 'member' | 'admin' | 'non_team' }); ``` ## Organization Management (Enterprise) ```typescript // Get organization info // GET https://api.miro.com/v2/orgs/{org_id} const org = await miroFetch(`/v2/orgs/${orgId}`); // List organization members // GET https://api.miro.com/v2/orgs/{org_id}/members const orgMembers = await miroFetch(`/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). ```typescript // SCIM Base URL: https://miro.com/api/v1/scim/v2 // Create user via SCIM // POST https://miro.com/api/v1/scim/v2/Users const scimUser = await fetch('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/Users const users = await fetch('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} await fetch(`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: ```typescript // Create board with restrictive sharing await miroFetch('/v2/boards', 'POST', { name: 'Confidential Strategy Board', policy: { sharingPolicy: { access: 'private', // Only invited members inviteToAccountAndBoardLinkAccess: 'no_access', organizationAccess: 'private', // Not visible to org teamAccess: 'private', // Not visible to team }, permissionsPolicy: { collaborationToolsStartAccess: 'all_editors', copyAccess: 'team_members', // Only team can copy sharingAccess: 'owners_and_coowners', // Only owners can share }, }, }); // Create board with open team access await miroFetch('/v2/boards', 'POST', { name: 'Team Brainstorming', teamId: teamId, policy: { sharingPolicy: { access: 'edit', // Team can edit by default teamAccess: 'edit', }, permissionsPolicy: { sharingAccess: 'team_members_and_collaborators', }, }, }); ``` ## Audit Logs (Enterprise) ```typescript // Get audit logs — requires 'auditlogs:read' scope // GET https://api.miro.com/v2/orgs/{org_id}/audit-logs const logs = await miroFetch( `/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: ```typescript type BoardRole = 'viewer' | 'commenter' | 'editor' | 'coowner' | 'owner'; const ROLE_HIERARCHY: Record = { viewer: 0, commenter: 1, editor: 2, coowner: 3, owner: 4, }; function hasMinimumRole(userRole: BoardRole, requiredRole: BoardRole): boolean { return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole]; } async function requireBoardRole(boardId: string, userId: string, minRole: BoardRole) { const members = await miroFetch(`/v2/boards/${boardId}/members?limit=100`); const user = members.data.find((m: any) => m.id === userId); if (!user) { throw new Error('User is not a board member'); } if (!hasMinimumRole(user.role, minRole)) { throw new Error(`Requires ${minRole} role, user has ${user.role}`); } } // Usage await requireBoardRole(boardId, userId, 'editor'); // Throws if user doesn't have editor or higher role ``` ## Required OAuth Scopes | Feature | Required Scope | |---------|---------------| | Board members | `boards:read` (list) / `boards:write` (manage) | | Team management | `team:read` / `team:write` | | Organization | `organizations:read` | | Audit logs | `auditlogs:read` | | SCIM provisioning | SCIM token (separate from OAuth) | ## Error Handling | Error | Status | Cause | Solution | |-------|--------|-------|----------| | `insufficientPermissions` | 403 | Missing scope | Add required scope in app settings | | `memberNotFound` | 404 | User not on board | Invite user first | | `teamNotFound` | 404 | Wrong team ID or no access | Verify org/team hierarchy | | `orgNotFound` | 404 | Not Enterprise plan | Upgrade to Enterprise | | `scimTokenInvalid` | 401 | Wrong SCIM token | Generate new token in admin console | ## Resources - [Miro Board Members](https://developers.miro.com/docs/rest-api-reference-guide) - [Permission Scopes](https://developers.miro.com/reference/scopes) - [SCIM API Introduction](https://developers.miro.com/reference/scim-introduction) - [Teams API Guide](https://developers.miro.com/docs/teams-api-securely-manage-boards-teams-at-scale) - [SCIM Groups (Teams)](https://developers.miro.com/docs/groups) ## Next Steps For major migrations, see `miro-migration-deep-dive`.