Summary and recommendation
Miro exposes two distinct API surfaces for identity and user management: a REST API (base URL `https://api.miro.com/v2`) using OAuth 2.0 or static access tokens, and a SCIM 2.0 API (`https://miro.com/scim/v2`) using a separate static bearer token.
These surfaces use different base URLs, different auth tokens, and different data models - they must not be mixed in the same request flow.
Org-level operations (listing members, updating roles, removing members) require `organizations:read` or `organizations:write` scopes and a caller with Company Admin privileges. Team-level operations require `organizations:teams:read` or `organizations:teams:write`. The `users:read` and `users:write` scopes are scoped to the authenticated user's own profile only and do not grant access to arbitrary user records.
For identity graph construction - mapping users across org membership, team assignments, and board-level roles - you must join data from `GET /v2/orgs/{org_id}/members`, `GET /v2/orgs/{org_id}/teams/{team_id}/members`, and SCIM `GET /Users`.
These three sources use different identifiers: the REST API uses a numeric `member_id`, SCIM uses its own `id`, and the `org_id` is a numeric string retrieved from `GET /v2/users/me` or the Miro admin UI.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (authorization code flow) or static access token |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: OAuth 2.0 (authorization code flow) or static access token
Setup steps
- Go to https://miro.com/app/settings/user-profile/apps and create a new app
- Set the redirect URI for your application
- Select required OAuth 2.0 scopes for your app
- Use the authorization endpoint https://miro.com/oauth/authorize to obtain an authorization code
- Exchange the code for an access token at https://api.miro.com/v1/oauth/token
- Include the token in requests as: Authorization: Bearer
- For SCIM, generate a dedicated SCIM token from Company Settings > Security > SCIM (Enterprise only)
Required scopes
| Scope | Description | Required for |
|---|---|---|
| organizations:read | Read organization details and membership | List organization members, get org info |
| organizations:teams:read | Read teams within an organization | List teams, get team members |
| organizations:teams:write | Create, update, delete teams and manage team membership | Add/remove team members, create teams |
| users:read | Read the current user's profile | Get current user info |
| users:write | Update the current user's profile | Update user profile fields |
| organizations:write | Manage organization-level settings and members | Update org member roles, remove members |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique Miro user identifier | system-generated | immutable | Used as path param in user endpoints |
| name | string | Full display name of the user | set by user | updatable | |
| string | Primary email address | required | not updatable via API | Email changes require user action | |
| role | string | Organization role: company_admin, member, non_team_member, viewer | assigned on invite | updatable by admin | Enum values vary by plan |
| type | string | Object type, always 'user' | system-generated | immutable | |
| createdAt | string (ISO 8601) | Timestamp when the user joined the organization | system-generated | immutable | |
| active | boolean | Whether the user account is active (SCIM field) | defaults true | updatable via SCIM | Setting false deactivates the user |
| teamRole | string | Role within a specific team: admin, member, non_team_member | set on team invite | updatable | Returned in team-member context only |
Core endpoints
Get current user
- Method: GET
- URL:
https://api.miro.com/v2/users/me - Watch out for: Returns only the authenticated user's own profile; cannot retrieve arbitrary users via REST without org-level scopes.
Request example
GET /v2/users/me
Authorization: Bearer <token>
Response example
{
"id": "3458764517517852417",
"name": "Jane Doe",
"email": "jane@example.com",
"type": "user"
}
Get organization members
- Method: GET
- URL:
https://api.miro.com/v2/orgs/{org_id}/members - Watch out for: Requires organizations:read scope and the caller must be a Company Admin or have org-level access.
Request example
GET /v2/orgs/3458764517517852417/members?limit=10&cursor=
Authorization: Bearer <token>
Response example
{
"data": [{"id":"...","name":"Jane","role":"member"}],
"cursor": "next_cursor_value",
"total": 120
}
Get organization member by ID
- Method: GET
- URL:
https://api.miro.com/v2/orgs/{org_id}/members/{member_id} - Watch out for: org_id must be the numeric organization ID, not the org name or slug.
Request example
GET /v2/orgs/3458764517517852417/members/3458764517517852418
Authorization: Bearer <token>
Response example
{
"id": "3458764517517852418",
"name": "John Smith",
"email": "john@example.com",
"role": "member"
}
Update organization member role
- Method: PATCH
- URL:
https://api.miro.com/v2/orgs/{org_id}/members/{member_id} - Watch out for: Only Company Admins can promote/demote roles. Role values are plan-dependent.
Request example
PATCH /v2/orgs/{org_id}/members/{member_id}
Content-Type: application/json
{"role": "company_admin"}
Response example
{
"id": "3458764517517852418",
"role": "company_admin"
}
Remove organization member
- Method: DELETE
- URL:
https://api.miro.com/v2/orgs/{org_id}/members/{member_id} - Watch out for: Removing a member does not delete their Miro account; it only removes them from the organization.
Request example
DELETE /v2/orgs/{org_id}/members/{member_id}
Authorization: Bearer <token>
Response example
HTTP 204 No Content
Get team members
- Method: GET
- URL:
https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members - Watch out for: Requires organizations:teams:read scope.
Request example
GET /v2/orgs/{org_id}/teams/{team_id}/members?limit=10
Authorization: Bearer <token>
Response example
{
"data": [{"id":"...","name":"Jane","teamRole":"admin"}],
"cursor": "abc123"
}
Add team member
- Method: POST
- URL:
https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members - Watch out for: User must already be an org member before being added to a team.
Request example
POST /v2/orgs/{org_id}/teams/{team_id}/members
Content-Type: application/json
{"memberId": "3458764517517852418", "role": "member"}
Response example
{
"id": "3458764517517852418",
"teamRole": "member"
}
Remove team member
- Method: DELETE
- URL:
https://api.miro.com/v2/orgs/{org_id}/teams/{team_id}/members/{member_id} - Watch out for: Removes user from team only; does not remove from organization.
Request example
DELETE /v2/orgs/{org_id}/teams/{team_id}/members/{member_id}
Authorization: Bearer <token>
Response example
HTTP 204 No Content
Rate limits, pagination, and events
- Rate limits: Miro enforces per-app rate limits. The REST API allows up to 1000 requests per minute per app token. SCIM API has separate limits. Rate limit details are returned in response headers.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: Headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset are returned. On 429, use Retry-After header value.
- Pagination method: cursor
- Default page size: 10
- Max page size: 100
- Pagination pointer: cursor
| Plan | Limit | Concurrent |
|---|---|---|
| All plans (REST API) | 1000 requests/minute per app | 0 |
| Enterprise (SCIM) | Not publicly specified; subject to standard enterprise limits | 0 |
- Webhooks available: Yes
- Webhook notes: Miro supports webhooks for board-level events (item created, updated, deleted, etc.) but does not offer dedicated user-management or org-membership webhook events as of the current API version.
- Alternative event strategy: Poll GET /v2/orgs/{org_id}/members on a schedule to detect membership changes. Use SCIM provisioning for real-time user lifecycle events triggered by your IdP.
- Webhook events: board:created, board:updated, board:deleted, item:created, item:updated, item:deleted
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://miro.com/scim/v2
Supported operations: GET /Users - list users, GET /Users/{id} - get user by ID, POST /Users - provision new user, PUT /Users/{id} - replace user attributes, PATCH /Users/{id} - update user attributes (including active=false to deactivate), DELETE /Users/{id} - deprovision user, GET /Groups - list teams/groups, POST /Groups - create group, PATCH /Groups/{id} - update group membership, DELETE /Groups/{id} - delete group
Limitations:
- Requires Enterprise plan
- SAML SSO must be configured before SCIM can be enabled
- Only Company Admins can generate the SCIM token in Company Settings
- SCIM token is a static bearer token, not an OAuth token
- Deactivating a user via SCIM (active=false) suspends them but does not delete the account
- IdP-initiated group pushes map to Miro Teams
- Supported IdPs include Okta, Microsoft Entra ID (Azure AD), Google Workspace, and OneLogin
Common scenarios
Three primary automation scenarios are supported by the provided data.
Deprovision a leaver via SCIM: Locate the user with GET /scim/v2/Users?filter=userName eq "user@example.com", then send PATCH /scim/v2/Users/{id} with active=false. Use PATCH over DELETE - DELETE is irreversible and removes content ownership. SCIM is Enterprise-only; the token is generated from Company Settings > Security > SCIM.
Assign an existing org member to a team via REST: Confirm org membership with GET /v2/orgs/{org_id}/members/{member_id}, then POST /v2/orgs/{org_id}/teams/{team_id}/members with {"memberId": "<member_id>", "role": "member"}. The user must already be an org member - adding a non-member returns a 400 or 404.
Bulk member audit via cursor pagination: Call GET /v2/orgs/{org_id}/members?limit=100, extract the cursor field from each response, and repeat until no cursor is returned. Max page size is 100; pagination is cursor-based only - offset-based page numbers are not supported. At 1,000 requests/minute, large orgs will require backoff logic.
Deprovision a leaver via SCIM
- Ensure SAML SSO and SCIM are enabled in Miro Company Settings (Enterprise plan required)
- Obtain the SCIM bearer token from Company Settings > Security > SCIM
- Find the user's SCIM ID: GET https://miro.com/scim/v2/Users?filter=userName eq "user@example.com"
- Deactivate the user: PATCH https://miro.com/scim/v2/Users/{id} with body {"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"active","value":false}]}
- Verify the user is suspended in Miro Company Settings
Watch out for: Use PATCH active=false rather than DELETE to preserve board content and audit history. DELETE permanently removes the user.
Assign a user to a team via REST API
- Authenticate with OAuth 2.0 and obtain a token with organizations:teams:write scope
- Retrieve the org_id from GET /v2/users/me or Miro admin settings
- List teams to find the target team_id: GET /v2/orgs/{org_id}/teams
- Confirm the user is an org member: GET /v2/orgs/{org_id}/members/{member_id}
- Add the user to the team: POST /v2/orgs/{org_id}/teams/{team_id}/members with body {"memberId": "
", "role": "member"}
Watch out for: The user must already be an organization member before they can be added to a team. Adding a non-member returns a 404 or 400 error.
Bulk-list all org members for an audit
- Authenticate with a token holding organizations:read scope
- Call GET /v2/orgs/{org_id}/members?limit=100
- Check the response for a 'cursor' field; if present, call GET /v2/orgs/{org_id}/members?limit=100&cursor={cursor}
- Repeat until no cursor is returned in the response
- Aggregate all member objects for the audit report
Watch out for: Max page size is 100. For large organizations, cursor pagination may require many sequential requests; respect rate limits (1000 req/min) and implement backoff.
Why building this yourself is a trap
The REST API does not support user invitation - POST /Users is a SCIM-only operation, and SCIM requires Enterprise. Any workflow that needs to provision net-new users via API is blocked below Enterprise tier, with no REST equivalent.
OAuth access tokens expire and require refresh token handling; the static SCIM token does not expire but is a separate credential that must be rotated independently. Conflating the two auth flows or reusing tokens across API surfaces will produce auth failures that are not immediately obvious from error messages.
Miro's webhook events cover board-level activity only (item created/updated/deleted, board created/updated/deleted). There are no webhook events for org membership changes, user deactivation, or team assignment.
Any identity graph that needs to stay current must poll GET /v2/orgs/{org_id}/members on a schedule or rely on IdP-side SCIM push events - there is no push-based notification path for user lifecycle changes via the REST API.
Automate Miro workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.