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 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: ['[email protected]', '[email protected]'],
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)
// 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: ['[email protected]'],
role: 'member', // 'member' | 'admin' | 'non_team'
});
Organization Management (Enterprise)
// 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).
// 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: '[email protected]',
name: { givenName: 'New', familyName: 'User' },
emails: [{ value: '[email protected]', 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:
// 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)
// 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:
type BoardRole = 'viewer' | 'commenter' | 'editor' | 'coowner' | 'owner';
const ROLE_HIERARCHY: Record<BoardRole, number> = {
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