miro_api

Miro API python client exposes two classes: MiroApi and Miro

MiroApi class is the stateless low-level client. It contains methods for backend-to-backend communication and to run automation scripts. It enables passing the OAuth access token once, and then reusing it in subsequent calls.

Miro is the stateful high-level client. It contains properties and methods to interact with Miro users. For example, to trigger the authorization flow. The Miro methods are related to authorization and access token management.

 1# coding: utf-8
 2"""
 3Miro API python client exposes two classes: `MiroApi` and `Miro`
 4
 5`MiroApi` class is the stateless low-level client.
 6It contains methods for backend-to-backend communication and to run automation scripts.
 7It enables passing the OAuth access token once, and then reusing it in subsequent calls.
 8
 9`Miro` is the stateful high-level client.
10It contains properties and methods to interact with Miro users. For example, to trigger the authorization flow.
11The Miro methods are related to authorization and access token management.
12"""
13
14__version__ = "2.1.2"
15
16
17from miro_api.miro_api_wrapper import MiroApi, Miro
18
19MiroApi = MiroApi
20Miro = Miro
class MiroApi(miro_api.api_extended.MiroApiExtended):
36class MiroApi(MiroApiExtended):
37    """
38    Exposes api endpoints as methods
39    """
40
41    def __init__(self, access_token: Union[str, Callable[[], str]]) -> None:
42        """
43        Initialize the client by passing an access token.
44
45        ```python
46        # Import the 'MiroApi' object
47        from miro_api import MiroApi
48
49        # Create a new instance of the 'MiroApi' object,
50        # and pass the OAuth access token as a parameter
51        api = MiroApi('<access_token>')
52
53        # Use the 'MiroApi' instance to send a request to the Miro REST API,
54        # and to create a new board in the team where the App is installed
55        boards = api.create_board()
56        ```
57        """
58        self.api_client = MiroApiClient(access_token)

Exposes api endpoints as methods

MiroApi(access_token: Union[str, Callable[[], str]])
41    def __init__(self, access_token: Union[str, Callable[[], str]]) -> None:
42        """
43        Initialize the client by passing an access token.
44
45        ```python
46        # Import the 'MiroApi' object
47        from miro_api import MiroApi
48
49        # Create a new instance of the 'MiroApi' object,
50        # and pass the OAuth access token as a parameter
51        api = MiroApi('<access_token>')
52
53        # Use the 'MiroApi' instance to send a request to the Miro REST API,
54        # and to create a new board in the team where the App is installed
55        boards = api.create_board()
56        ```
57        """
58        self.api_client = MiroApiClient(access_token)

Initialize the client by passing an access token.

# Import the 'MiroApi' object
from miro_api import MiroApi

# Create a new instance of the 'MiroApi' object,
# and pass the OAuth access token as a parameter
api = MiroApi('<access_token>')

# Use the 'MiroApi' instance to send a request to the Miro REST API,
# and to create a new board in the team where the App is installed
boards = api.create_board()
api_client
Inherited Members
miro_api.api_extended.MiroApiExtended
get_all_boards
get_all_tags_from_board
get_all_board_members
get_all_items
get_all_items_by_tag
get_all_items_within_frame
get_all_connectors
get_all_enterprise_teams
get_all_organization_members
get_all_enterprise_team_members
miro_api.api.MiroApiEndpoints
get_metrics
get_metrics_total
enterprise_get_audit_logs
enterprise_board_content_item_logs_fetch
enterprise_board_export_job_results
enterprise_board_export_job_status
enterprise_create_board_export
enterprise_dataclassification_board_get
enterprise_dataclassification_board_set
enterprise_dataclassification_organization_settings_get
enterprise_dataclassification_team_boards_bulk
enterprise_dataclassification_team_settings_get
enterprise_dataclassification_team_settings_set
create_items
create_items_in_bulk_using_file_from_device
create_shape_item_flowchart
delete_shape_item_flowchart
get_items_experimental
get_shape_item_flowchart
get_specific_item_experimental
update_shape_item_flowchart
get_all_cases
get_case
create_mindmap_nodes_experimental
delete_mindmap_node_experimental
get_mindmap_node_experimental
get_mindmap_nodes_experimental
revoke_token_v2
enterprise_get_organization_member
enterprise_get_organization_members
enterprise_get_organization
enterprise_add_project_member
enterprise_delete_project_member
enterprise_get_project_member
enterprise_get_project_members
enterprise_update_project_member
enterprise_get_project_settings
enterprise_update_project_settings
enterprise_create_project
enterprise_delete_project
enterprise_get_project
enterprise_get_projects
enterprise_update_project
enterprise_post_user_sessions_reset
enterprise_delete_team_member
enterprise_get_team_member
enterprise_get_team_members
enterprise_invite_team_member
enterprise_update_team_member
enterprise_get_default_team_settings
enterprise_get_team_settings
enterprise_update_team_settings
enterprise_create_team
enterprise_delete_team
enterprise_get_team
enterprise_get_teams
enterprise_update_team
create_board_subscription
delete_subscription_by_id
get_subscription_by_id
get_user_subscriptions
update_board_subscription
create_app_card_item
delete_app_card_item
get_app_card_item
update_app_card_item
get_board_members
get_specific_board_member
remove_board_member
share_board
update_board_member
copy_board
create_board
delete_board
get_boards
get_specific_board
update_board
create_card_item
delete_card_item
get_card_item
update_card_item
create_connector
delete_connector
get_connector
get_connectors
update_connector
create_document_item_using_file_from_device
create_document_item_using_url
delete_document_item
get_document_item
update_document_item_using_file_from_device
update_document_item_using_url
create_embed_item
delete_embed_item
get_embed_item
update_embed_item
create_frame_item
delete_frame_item
get_frame_item
update_frame_item
create_group
delete_group
get_all_groups
get_group_by_id
get_items_by_group_id
un_group
update_group
create_image_item_using_local_file
create_image_item_using_url
delete_image_item
get_image_item
update_image_item_using_file_from_device
update_image_item_using_url
delete_item
delete_item_experimental
get_items
get_items_within_frame
get_specific_item
update_item_position_or_parent
create_shape_item
delete_shape_item
get_shape_item
update_shape_item
create_sticky_note_item
delete_sticky_note_item
get_sticky_note_item
update_sticky_note_item
attach_tag_to_item
create_tag
delete_tag
get_items_by_tag
get_tag
get_tags_from_board
get_tags_from_item
remove_tag_from_item
update_tag
create_text_item
delete_text_item
get_text_item
update_text_item
revoke_token
token_info
class Miro:
 61class Miro:
 62    """
 63    This is the highlevel client
 64    """
 65
 66    _api_client = ApiClient()
 67    _client_id: str
 68    _client_secret: str
 69    _redirect_url: str
 70    _storage: Storage
 71
 72    def __init__(
 73        self,
 74        client_id: Optional[str] = None,
 75        client_secret: Optional[str] = None,
 76        redirect_url: Optional[str] = None,
 77        storage: Storage = InMemoryStorage(),
 78    ):
 79        """
 80        Initializes the Miro client with the given client id and client secret
 81
 82        Parameters are optional if the environment variables are defined
 83
 84            client_id: MIRO_CLIENT_ID
 85            client_secret: MIRO_CLIENT_SECRET
 86            redirect_url: MIRO_REDIRECT_URL
 87
 88        This method will raise an Exception if any of these are not defined.
 89
 90        Storage is used to persist state needed by the client (access token, refresh token).
 91        By default the client uses in memory storage for state. For production usage we recommend implementing one backed up by a permanent database.
 92        """
 93        self._client_id: str = client_id or os.environ.get("MIRO_CLIENT_ID") or ""
 94        self._client_secret: str = client_secret or os.environ.get("MIRO_CLIENT_SECRET") or ""
 95        self._redirect_url: str = redirect_url or os.environ.get("MIRO_REDIRECT_URL") or ""
 96
 97        if not self._client_id:
 98            raise Exception("miro-api: MIRO_CLIENT_ID environment variable or passing clientId is required")
 99        if not self._client_secret:
100            raise Exception("miro-api: MIRO_CLIENT_SECRET environment variable or passing clientSecret is required")
101        if not self._redirect_url:
102            raise Exception("miro-api: MIRO_REDIRECT_URL environment variable or passing redirectUrl is required")
103
104        self._storage = storage
105
106    @property
107    def api(self) -> MiroApi:
108        """Returns an instance of MiroApi using access token from given storage"""
109        return MiroApi(lambda: self.access_token)
110
111    @property
112    def is_authorized(self) -> bool:
113        """Returns True if there's a valid access token.
114        If this is False then you will need to run Miro.exchange_code_for_access_token(code) with code received through OAuth 2 flow.
115        """
116        try:
117            return bool(self.access_token)
118        except Exception:
119            return False
120
121    @property
122    def auth_url(self) -> str:
123        """Returns a URL that user should be redirected to in order to authorize the application"""
124        return self.get_auth_url()
125
126    def get_auth_url(self, state=None, team_id=None) -> str:
127        """Returns a URL that user should be redirected to in order to authorize the application, accepts an optional state argument and a teamId that will be used as a default"""
128        auth = {
129            "client_id": self._client_id,
130            "redirect_uri": self._redirect_url,
131            "response_type": "code",
132        }
133        if state:
134            auth["state"] = state
135        if team_id:
136            auth["team_id"] = team_id
137
138        return f"https://miro.com/oauth/authorize?{urllib.parse.urlencode(auth)}"
139
140    def exchange_code_for_access_token(self, url_or_code) -> str:
141        """Exchanges the authorization code for an access token by calling the token endpoint It will store the token information in storage for later reuse"""
142        code = url_or_code
143        if "?" in url_or_code:
144            url = urllib.parse.urlparse(url_or_code)
145            params = urllib.parse.parse_qs(url.query)
146            if code_param := params.get("code", [""]):
147                code = code_param[0]
148
149        if not code:
150            raise Exception("No code provided")
151
152        return self._get_token(code=code)
153
154    def revoke_token(self) -> None:
155        """Revokes currently stored access token"""
156        self.api.revoke_token(self.access_token)
157        self._storage.set(None)
158
159    def _get_token(self, code: str = "", refresh_token: str = "") -> str:
160        token_url = "https://api.miro.com/v1/oauth/token"
161        params = {
162            "client_id": self._client_id,
163            "client_secret": self._client_secret,
164        }
165        if code:
166            params.update(
167                {
168                    "code": code,
169                    "redirect_uri": self._redirect_url,
170                    "grant_type": "authorization_code",
171                }
172            )
173        elif refresh_token:
174            params.update({"refresh_token": refresh_token, "grant_type": "refresh_token"})
175
176        token_url = f"{token_url}?{urllib.parse.urlencode(params)}"
177
178        response = self._api_client.call_api("POST", token_url)
179        payload = json.loads(response.read().decode("utf-8"))
180
181        state = State(
182            payload["access_token"],
183            payload.get("refresh_token"),
184            (
185                (datetime.datetime.now() + datetime.timedelta(seconds=payload["expires_in"] - 120))
186                if payload.get("payload")
187                else None
188            ),
189        )
190
191        self._storage.set(state)
192
193        return state.access_token
194
195    @property
196    def access_token(self) -> str:
197        """Returns currently stored access token. If it has expired the client will request a new access token using the stored refresh token."""
198        state = self._storage.get()
199
200        if not state or not state.access_token:
201            raise Exception("No access token stored, run exchangeCodeForAccessToken() first")
202        if state.refresh_token and state.token_expires_at and state.token_expires_at < datetime.datetime.now():
203            return self._get_token(refresh_token=state.refresh_token)
204
205        return state.access_token

This is the highlevel client

Miro( client_id: Optional[str] = None, client_secret: Optional[str] = None, redirect_url: Optional[str] = None, storage: miro_api.storage.Storage = <miro_api.storage.InMemoryStorage object>)
 72    def __init__(
 73        self,
 74        client_id: Optional[str] = None,
 75        client_secret: Optional[str] = None,
 76        redirect_url: Optional[str] = None,
 77        storage: Storage = InMemoryStorage(),
 78    ):
 79        """
 80        Initializes the Miro client with the given client id and client secret
 81
 82        Parameters are optional if the environment variables are defined
 83
 84            client_id: MIRO_CLIENT_ID
 85            client_secret: MIRO_CLIENT_SECRET
 86            redirect_url: MIRO_REDIRECT_URL
 87
 88        This method will raise an Exception if any of these are not defined.
 89
 90        Storage is used to persist state needed by the client (access token, refresh token).
 91        By default the client uses in memory storage for state. For production usage we recommend implementing one backed up by a permanent database.
 92        """
 93        self._client_id: str = client_id or os.environ.get("MIRO_CLIENT_ID") or ""
 94        self._client_secret: str = client_secret or os.environ.get("MIRO_CLIENT_SECRET") or ""
 95        self._redirect_url: str = redirect_url or os.environ.get("MIRO_REDIRECT_URL") or ""
 96
 97        if not self._client_id:
 98            raise Exception("miro-api: MIRO_CLIENT_ID environment variable or passing clientId is required")
 99        if not self._client_secret:
100            raise Exception("miro-api: MIRO_CLIENT_SECRET environment variable or passing clientSecret is required")
101        if not self._redirect_url:
102            raise Exception("miro-api: MIRO_REDIRECT_URL environment variable or passing redirectUrl is required")
103
104        self._storage = storage

Initializes the Miro client with the given client id and client secret

Parameters are optional if the environment variables are defined

client_id: MIRO_CLIENT_ID
client_secret: MIRO_CLIENT_SECRET
redirect_url: MIRO_REDIRECT_URL

This method will raise an Exception if any of these are not defined.

Storage is used to persist state needed by the client (access token, refresh token). By default the client uses in memory storage for state. For production usage we recommend implementing one backed up by a permanent database.

api: MiroApi
106    @property
107    def api(self) -> MiroApi:
108        """Returns an instance of MiroApi using access token from given storage"""
109        return MiroApi(lambda: self.access_token)

Returns an instance of MiroApi using access token from given storage

is_authorized: bool
111    @property
112    def is_authorized(self) -> bool:
113        """Returns True if there's a valid access token.
114        If this is False then you will need to run Miro.exchange_code_for_access_token(code) with code received through OAuth 2 flow.
115        """
116        try:
117            return bool(self.access_token)
118        except Exception:
119            return False

Returns True if there's a valid access token. If this is False then you will need to run Miro.exchange_code_for_access_token(code) with code received through OAuth 2 flow.

auth_url: str
121    @property
122    def auth_url(self) -> str:
123        """Returns a URL that user should be redirected to in order to authorize the application"""
124        return self.get_auth_url()

Returns a URL that user should be redirected to in order to authorize the application

def get_auth_url(self, state=None, team_id=None) -> str:
126    def get_auth_url(self, state=None, team_id=None) -> str:
127        """Returns a URL that user should be redirected to in order to authorize the application, accepts an optional state argument and a teamId that will be used as a default"""
128        auth = {
129            "client_id": self._client_id,
130            "redirect_uri": self._redirect_url,
131            "response_type": "code",
132        }
133        if state:
134            auth["state"] = state
135        if team_id:
136            auth["team_id"] = team_id
137
138        return f"https://miro.com/oauth/authorize?{urllib.parse.urlencode(auth)}"

Returns a URL that user should be redirected to in order to authorize the application, accepts an optional state argument and a teamId that will be used as a default

def exchange_code_for_access_token(self, url_or_code) -> str:
140    def exchange_code_for_access_token(self, url_or_code) -> str:
141        """Exchanges the authorization code for an access token by calling the token endpoint It will store the token information in storage for later reuse"""
142        code = url_or_code
143        if "?" in url_or_code:
144            url = urllib.parse.urlparse(url_or_code)
145            params = urllib.parse.parse_qs(url.query)
146            if code_param := params.get("code", [""]):
147                code = code_param[0]
148
149        if not code:
150            raise Exception("No code provided")
151
152        return self._get_token(code=code)

Exchanges the authorization code for an access token by calling the token endpoint It will store the token information in storage for later reuse

def revoke_token(self) -> None:
154    def revoke_token(self) -> None:
155        """Revokes currently stored access token"""
156        self.api.revoke_token(self.access_token)
157        self._storage.set(None)

Revokes currently stored access token

access_token: str
195    @property
196    def access_token(self) -> str:
197        """Returns currently stored access token. If it has expired the client will request a new access token using the stored refresh token."""
198        state = self._storage.get()
199
200        if not state or not state.access_token:
201            raise Exception("No access token stored, run exchangeCodeForAccessToken() first")
202        if state.refresh_token and state.token_expires_at and state.token_expires_at < datetime.datetime.now():
203            return self._get_token(refresh_token=state.refresh_token)
204
205        return state.access_token

Returns currently stored access token. If it has expired the client will request a new access token using the stored refresh token.