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
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
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()
Inherited Members
- miro_api.api_extended.MiroApiExtended
- get_all_boards
- 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_all_legal_holds
- get_case
- get_legal_hold
- get_legal_hold_content_items
- 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
- 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
- remove_tag_from_item
- update_tag
- create_text_item
- delete_text_item
- get_text_item
- update_text_item
- revoke_token
- token_info
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
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.
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
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
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
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
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
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.