Claude
Skills
Sign in
Back

miro-api

Included with Lifetime
$97 forever

Miro whiteboard automation using REST API v2 and Python SDK for creating boards, frames, shapes, connectors, and collaborative visual workflows

communicationmirowhiteboardcollaborationvisualdiagramssticky-notesapipython

What this skill does


# Miro API Skill

Master Miro whiteboard automation using the REST API v2 and Python SDK. This skill covers board creation, widget management, frames, shapes, connectors, templates, and real-time collaboration patterns for building automated visual workflows.

## When to Use This Skill

### USE when:
- Automating sprint retrospective board creation
- Building visual project status dashboards
- Creating automated architecture diagrams
- Setting up templated workshop boards
- Integrating Miro with CI/CD pipelines
- Automating user story mapping workflows
- Creating visual incident response boards
- Building automated onboarding boards
- Generating meeting facilitation templates
- Syncing data from external systems to Miro

### DON'T USE when:
- Simple text-based collaboration (use Slack or Teams)
- Document-focused workflows (use Notion or Confluence)
- Code-focused diagramming (use Mermaid or PlantUML)
- Real-time whiteboarding without persistence
- Personal note-taking (use Obsidian)

## Prerequisites

### Miro App Setup

```bash
# 1. Create a Miro App at https://miro.com/app/settings/user-profile/apps
# 2. Choose REST API 2.0
# 3. Configure OAuth 2.0 scopes

# Required OAuth Scopes:
# - boards:read          - Read board data
# - boards:write         - Create and modify boards
# - team:read            - Read team information
# - identity:read        - Read user identity
# - microphone:read      - Read board audio (optional)
# - screen_recording     - Screen recording (optional)

# App Permissions for different use cases:
# Team App:   boards:read, boards:write, team:read
# User App:   boards:read, boards:write, identity:read
# Admin App:  All scopes + organization management

# Get Access Token via OAuth 2.0:
# 1. Redirect user to: https://miro.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI
# 2. Exchange code for token at: https://api.miro.com/v1/oauth/token
```

### Python Environment Setup

```bash
# Create virtual environment
python -m venv miro-env
source miro-env/bin/activate  # Linux/macOS
# miro-env\Scripts\activate   # Windows

# Install Miro API SDK
pip install miro-api

# Install additional dependencies
pip install python-dotenv requests httpx aiohttp

# Create requirements.txt
cat > requirements.txt << 'EOF'
miro-api>=2.0.0
python-dotenv>=1.0.0
requests>=2.31.0
httpx>=0.25.0
aiohttp>=3.9.0
Pillow>=10.0.0
EOF

# Environment variables
cat > .env << 'EOF'
MIRO_ACCESS_TOKEN=your-access-token
MIRO_CLIENT_ID=your-client-id
MIRO_CLIENT_SECRET=your-client-secret
MIRO_TEAM_ID=your-team-id
EOF
```

### API Authentication

```python
# auth.py
# ABOUTME: Miro API authentication utilities
# ABOUTME: Handles OAuth2 token management and refresh

import os
from dotenv import load_dotenv
import requests
from datetime import datetime, timedelta

load_dotenv()

class MiroAuth:
    """Miro OAuth2 authentication handler"""

    BASE_URL = "https://api.miro.com"
    AUTH_URL = "https://miro.com/oauth/authorize"
    TOKEN_URL = "https://api.miro.com/v1/oauth/token"

    def __init__(self):
        self.client_id = os.environ.get("MIRO_CLIENT_ID")
        self.client_secret = os.environ.get("MIRO_CLIENT_SECRET")
        self.access_token = os.environ.get("MIRO_ACCESS_TOKEN")
        self.refresh_token = os.environ.get("MIRO_REFRESH_TOKEN")
        self.token_expires = None

    def get_authorization_url(self, redirect_uri: str, state: str = None) -> str:
        """Generate OAuth2 authorization URL"""
        params = {
            "response_type": "code",
            "client_id": self.client_id,
            "redirect_uri": redirect_uri,
        }
        if state:
            params["state"] = state

        query = "&".join(f"{k}={v}" for k, v in params.items())
        return f"{self.AUTH_URL}?{query}"

    def exchange_code_for_token(self, code: str, redirect_uri: str) -> dict:
        """Exchange authorization code for access token"""
        response = requests.post(
            self.TOKEN_URL,
            data={
                "grant_type": "authorization_code",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                "code": code,
                "redirect_uri": redirect_uri,
            },
        )
        response.raise_for_status()
        token_data = response.json()

        self.access_token = token_data["access_token"]
        self.refresh_token = token_data.get("refresh_token")
        self.token_expires = datetime.now() + timedelta(
            seconds=token_data.get("expires_in", 3600)
        )

        return token_data

    def refresh_access_token(self) -> dict:
        """Refresh the access token"""
        response = requests.post(
            self.TOKEN_URL,
            data={
                "grant_type": "refresh_token",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                "refresh_token": self.refresh_token,
            },
        )
        response.raise_for_status()
        token_data = response.json()

        self.access_token = token_data["access_token"]
        self.refresh_token = token_data.get("refresh_token", self.refresh_token)

        return token_data

    def get_headers(self) -> dict:
        """Get authorization headers for API requests"""
        return {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json",
        }
```

## Core Capabilities

### 1. Board Management

```python
# boards.py
# ABOUTME: Miro board management operations
# ABOUTME: Create, read, update, delete boards

import os
from miro_api import Miro
from dotenv import load_dotenv

load_dotenv()

# Initialize Miro client
miro = Miro(access_token=os.environ.get("MIRO_ACCESS_TOKEN"))


def create_board(name: str, description: str = "", team_id: str = None) -> dict:
    """Create a new Miro board"""
    team_id = team_id or os.environ.get("MIRO_TEAM_ID")

    board = miro.boards.create(
        name=name,
        description=description,
        team_id=team_id,
        policy={
            "permissionsPolicy": {
                "collaborationToolsStartAccess": "all_editors",
                "copyAccess": "anyone",
                "sharingAccess": "team_members_with_editing_rights",
            },
            "sharingPolicy": {
                "access": "private",
                "inviteToAccountAndBoardLinkAccess": "editor",
                "organizationAccess": "private",
                "teamAccess": "edit",
            },
        },
    )

    return {
        "id": board.id,
        "name": board.name,
        "view_link": board.view_link,
        "created_at": board.created_at,
    }


def get_board(board_id: str) -> dict:
    """Get board details"""
    board = miro.boards.get(board_id)

    return {
        "id": board.id,
        "name": board.name,
        "description": board.description,
        "view_link": board.view_link,
        "created_at": board.created_at,
        "modified_at": board.modified_at,
    }


def list_boards(team_id: str = None, limit: int = 50) -> list:
    """List all boards in a team"""
    team_id = team_id or os.environ.get("MIRO_TEAM_ID")

    boards = miro.boards.get_all(team_id=team_id, limit=limit)

    return [
        {
            "id": board.id,
            "name": board.name,
            "view_link": board.view_link,
        }
        for board in boards
    ]


def update_board(board_id: str, name: str = None, description: str = None) -> dict:
    """Update board properties"""
    update_data = {}
    if name:
        update_data["name"] = name
    if description:
        update_data["description"] = description

    board = miro.boards.update(board_id, **update_data)

    return {"id": board.id, "name": board.name, "description": board.description}


def delete_board(board_id: str) -> bool:
    """Delete a board"""
    miro.boards.delete(board_id)
    return True


def copy_board(board_id: str, n

Related in communication