pydexcom
A simple Python API to interact with Dexcom Share service. Used to get real-time Dexcom CGM sensor data.
Quick-start
- Download the Dexcom G7 / G6 / G5 / G4 mobile app and enable the Share service.
The Dexcom Share service requires setup of at least one follower to enable the share service, but pydexcom
will use your (or the dependent's) credentials, not the follower's or manager's.
- Install the
pydexcom
package.
pip install pydexcom
- Profit.
>>> from pydexcom import Dexcom
>>> dexcom = Dexcom(username="username", password="password") # `region="ous"` if outside of US, `region="jp"` if Japan
>>> dexcom = Dexcom(username="+11234567890", password="password") # phone number
>>> dexcom = Dexcom(username="user@email.com", password="password") # email address
>>> dexcom = Dexcom(account_id="12345678-90ab-cdef-1234-567890abcdef", password="password") # account ID (advanced)
>>> glucose_reading = dexcom.get_current_glucose_reading()
>>> print(glucose_reading)
85
>>> glucose_reading.value
85
>>> glucose_reading.mmol_l
4.7
>>> glucose_reading.trend
4
>>> glucose_reading.trend_direction
'Flat'
>>> glucose_reading.trend_description
'steady'
>>> glucose_reading.trend_arrow
'→'
>>> print(bg.datetime)
2023-08-07 20:40:58
>>> glucose_reading.json
{'WT': 'Date(1691455258000)', 'ST': 'Date(1691455258000)', 'DT': 'Date(1691455258000-0400)', 'Value': 85, 'Trend': 'Flat'}
Documentation
https://gagebenne.github.io/pydexcom/pydexcom.html
Frequently Asked Questions
Why is my password not working?
The Dexcom Share API understandably reports limited information during account validation. If anything is incorrect, the API simply reports back invalid password ( pydexcom.errors.AccountErrorEnum
). However, there could be many reasons you are getting this error:
1. Ensure your credentials are valid.
Validate your Dexcom account credentials by logging on to the Dexcom Account Management website for your region:
For users in the United States: uam1.dexcom.com. For users outside of the United States: uam2.dexcom.com. For users in the Asia-Pacific: uam.dexcom.jp.
2. Use the correct Dexcom Share API endpoint.
For users in the United States: use the default, or set region="us"
when initializing Dexcom
.
For users outside of the United States: be sure to set region="ous"
when initializing Dexcom
.
For users in Japan: be sure to set region="jp"
when initializing Dexcom
.
3. Ensure your username is correctly formatted.
Format phone numbers with a +
, your country code, then your phone number. For example, a US phone number of (123)-456-7890
would be supplied as a username="+11234567890"
.
4. Use _your_ Dexcom Share credentials, not the _follower's_ credentials.
Use the same credentials used to login to the Dexcom mobile application publishing the glucose readings.
5. Ensure you have at least one follower on Dexcom Share.
The Dexcom Share service requires setup of at least one follower to enable the service, as does this package.
6. Try using your account ID.
You can find your account ID by logging in to Dexcom Account Management website for your region. After logging in, note the UUID in the URL -- this is your account ID.
Format account IDs (UUIDs) with hyphens. For example, an account ID of 1234567890abcdef1234567890abcdef
found in the URL after logging in would be supplied as account_id="12345678-90ab-cdef-1234-567890abcdef"
.
7. Report it!
The Dexcom Share API sometimes changes. If you believe there is an issue with pydexcom
, feel free to create an issue if one has not been created yet already.
Why not use the official Dexcom Developer API?
The official Dexcom API is a great tool to view trends, statistics, and day-by-day data, but is not suitable for real time fetching of glucose readings as it is a retrospective API.
Can I use the Dexcom Stelo with this package?
No, the Dexcom Stelo isn't compatible with the Dexcom Share service, so this package can't retrieve its readings.
How can I let you know of suggestions or issues?
By all means submit a pull request if you have a feature you would like to see in the next release. Alternatively, you may create an issue if you have a suggestion or bug you'd like to report.
Where is this package being used?
Primarily this package is used in the Home Assistant Dexcom integration, but it's fantastic to see community projects involving pydexcom
:
35class Dexcom: 36 """Class for communicating with Dexcom Share API.""" 37 38 def __init__( 39 self, 40 *, 41 password: str, 42 account_id: str | None = None, 43 username: str | None = None, 44 region: Region = Region.US, 45 ) -> None: 46 """ 47 Initialize `Dexcom` with Dexcom Share credentials. 48 49 :param username: username for the Dexcom Share user, *not follower*. 50 :param account_id: account ID for the Dexcom Share user, *not follower*. 51 :param password: password for the Dexcom Share user. 52 :param region: the region to use, one of `"us"`, `"ous"`, `"jp"`. 53 """ 54 self._validate_region(region) 55 self._validate_user_ids(account_id, username) 56 57 self._base_url = DEXCOM_BASE_URLS[region] 58 self._application_id = DEXCOM_APPLICATION_IDS[region] 59 self._password = password 60 self._username: str | None = username 61 self._account_id: str | None = account_id 62 self._session_id: str | None = None 63 self._session = requests.Session() 64 self._get_session() 65 66 @property 67 def username(self) -> str | None: 68 """Get username.""" 69 return self._username 70 71 @property 72 def account_id(self) -> str | None: 73 """Get account ID.""" 74 return self._account_id 75 76 def _post( 77 self, 78 endpoint: str, 79 params: dict[str, Any] | None = None, 80 json: dict[str, Any] | None = None, 81 ) -> Any: # noqa: ANN401 82 """ 83 Send post request to Dexcom Share API. 84 85 :param endpoint: URL of the post request 86 :param params: `dict` to send in the query string of the post request 87 :param json: JSON to send in the body of the post request 88 """ 89 response = self._session.post( 90 f"{self._base_url}{endpoint}", 91 headers=HEADERS, 92 params=params, 93 json={} if json is None else json, 94 ) 95 96 try: 97 response.raise_for_status() 98 return response.json() 99 except requests.HTTPError as http_error: 100 error = self._handle_response(response) 101 if error: 102 raise error from http_error 103 _LOGGER.exception("%s", response.text) 104 raise 105 106 def _handle_response(self, response: requests.Response) -> DexcomError | None: # noqa: C901 107 error: DexcomError | None = None 108 """ 109 Parse `requests.Response` for `pydexcom.errors.DexcomError`. 110 111 :param response: `requests.Response` to parse 112 """ 113 if response.json(): 114 _LOGGER.debug("%s", response.json()) 115 code = response.json().get("Code", None) 116 message = response.json().get("Message", None) 117 if code == "SessionIdNotFound": 118 error = SessionError(SessionErrorEnum.NOT_FOUND) 119 elif code == "SessionNotValid": 120 error = SessionError(SessionErrorEnum.INVALID) 121 elif code == "AccountPasswordInvalid": # defunct 122 error = AccountError(AccountErrorEnum.FAILED_AUTHENTICATION) 123 elif code == "SSO_AuthenticateMaxAttemptsExceeded": 124 error = AccountError(AccountErrorEnum.MAX_ATTEMPTS) 125 elif code == "SSO_InternalError": 126 if message and ( 127 "Cannot Authenticate by AccountName" in message 128 or "Cannot Authenticate by AccountId" in message 129 ): 130 error = AccountError(AccountErrorEnum.FAILED_AUTHENTICATION) 131 elif code == "InvalidArgument": 132 if message and "accountName" in message: 133 error = ArgumentError(ArgumentErrorEnum.USERNAME_INVALID) 134 elif message and "password" in message: 135 error = ArgumentError(ArgumentErrorEnum.PASSWORD_INVALID) 136 elif message and "UUID" in message: 137 error = ArgumentError(ArgumentErrorEnum.ACCOUNT_ID_INVALID) 138 elif code and message: 139 _LOGGER.debug("%s: %s", code, message) 140 return error 141 142 def _validate_region(self, region: Region) -> None: 143 if region not in list(Region): 144 raise ArgumentError(ArgumentErrorEnum.REGION_INVALID) 145 146 def _validate_user_ids(self, account_id: str | None, username: str | None) -> None: 147 user_ids = sum(user_id is not None for user_id in [account_id, username]) 148 if user_ids == 0: 149 raise ArgumentError(ArgumentErrorEnum.USER_ID_REQUIRED) 150 if user_ids != 1: 151 raise ArgumentError(ArgumentErrorEnum.USER_ID_MULTIPLE) 152 153 def _validate_session_id(self) -> None: 154 """Validate session ID.""" 155 if any( 156 [ 157 not isinstance(self._session_id, str), 158 not self._session_id, 159 not valid_uuid(self._session_id), 160 ], 161 ): 162 raise ArgumentError(ArgumentErrorEnum.SESSION_ID_INVALID) 163 if self._session_id == DEFAULT_UUID: 164 raise ArgumentError(ArgumentErrorEnum.SESSION_ID_DEFAULT) 165 166 def _validate_username(self) -> None: 167 """Validate username.""" 168 if any([not isinstance(self._username, str), not self._username]): 169 raise ArgumentError(ArgumentErrorEnum.USERNAME_INVALID) 170 171 def _validate_password(self) -> None: 172 """Validate password.""" 173 if any([not isinstance(self._password, str), not self._password]): 174 raise ArgumentError(ArgumentErrorEnum.PASSWORD_INVALID) 175 176 def _validate_account_id(self) -> None: 177 """Validate account ID.""" 178 if any( 179 [ 180 not isinstance(self._account_id, str), 181 not self._account_id, 182 not valid_uuid(self._account_id), 183 ], 184 ): 185 raise ArgumentError(ArgumentErrorEnum.ACCOUNT_ID_INVALID) 186 if self._account_id == DEFAULT_UUID: 187 raise ArgumentError(ArgumentErrorEnum.ACCOUNT_ID_DEFAULT) 188 189 def _validate_minutes_max_count(self, minutes: int, max_count: int) -> None: 190 if not isinstance(minutes, int) or any([minutes < 0, minutes > MAX_MINUTES]): 191 raise ArgumentError(ArgumentErrorEnum.MINUTES_INVALID) 192 if not isinstance(max_count, int) or any( 193 [max_count < 0, max_count > MAX_MAX_COUNT], 194 ): 195 raise ArgumentError(ArgumentErrorEnum.MAX_COUNT_INVALID) 196 197 @property 198 def _authenticate_endpoint_arguments(self) -> dict[str, Any]: 199 return { 200 "endpoint": DEXCOM_AUTHENTICATE_ENDPOINT, 201 "json": { 202 "accountName": self._username, 203 "password": self._password, 204 "applicationId": self._application_id, 205 }, 206 } 207 208 @property 209 def _login_id_endpoint_arguments(self) -> dict[str, Any]: 210 return { 211 "endpoint": DEXCOM_LOGIN_ID_ENDPOINT, 212 "json": { 213 "accountId": self._account_id, 214 "password": self._password, 215 "applicationId": self._application_id, 216 }, 217 } 218 219 def _glucose_readings_endpoint_arguments( 220 self, 221 minutes: int = MAX_MINUTES, 222 max_count: int = MAX_MAX_COUNT, 223 ) -> dict[str, Any]: 224 return { 225 "endpoint": DEXCOM_GLUCOSE_READINGS_ENDPOINT, 226 "params": { 227 "sessionId": self._session_id, 228 "minutes": minutes, 229 "maxCount": max_count, 230 }, 231 } 232 233 def _get_account_id(self) -> str: 234 """ 235 Retrieve account ID from the authentication endpoint. 236 237 See `pydexcom.const.DEXCOM_AUTHENTICATE_ENDPOINT`. 238 """ 239 _LOGGER.debug("Retrieve account ID from the authentication endpoint") 240 return self._post(**self._authenticate_endpoint_arguments) 241 242 def _get_session_id(self) -> str: 243 """ 244 Retrieve session ID from the login endpoint. 245 246 See `pydexcom.const.DEXCOM_LOGIN_ID_ENDPOINT`. 247 """ 248 _LOGGER.debug("Retrieve session ID from the login endpoint") 249 return self._post(**self._login_id_endpoint_arguments) 250 251 def _get_session(self) -> None: 252 """Create Dexcom Share API session.""" 253 self._validate_password() 254 255 if self._account_id is None: 256 self._validate_username() 257 self._account_id = self._get_account_id() 258 259 self._validate_account_id() 260 self._session_id = self._get_session_id() 261 self._validate_session_id() 262 263 def _get_glucose_readings( 264 self, 265 minutes: int = MAX_MINUTES, 266 max_count: int = MAX_MAX_COUNT, 267 ) -> list[dict[str, Any]]: 268 """ 269 Retrieve glucose readings from the glucose readings endpoint. 270 271 See `pydexcom.const.DEXCOM_GLUCOSE_READINGS_ENDPOINT`. 272 """ 273 _LOGGER.debug("Retrieve glucose readings from the glucose readings endpoint") 274 self._validate_minutes_max_count(minutes, max_count) 275 276 return self._post( 277 **self._glucose_readings_endpoint_arguments(minutes, max_count), 278 ) 279 280 def get_glucose_readings( 281 self, 282 minutes: int = MAX_MINUTES, 283 max_count: int = MAX_MAX_COUNT, 284 ) -> list[GlucoseReading]: 285 """ 286 Get `max_count` glucose readings within specified number of `minutes`. 287 288 Catches one instance of a thrown `pydexcom.errors.SessionError` if session ID 289 expired, attempts to get a new session ID and retries. 290 291 :param minutes: Number of minutes to retrieve glucose readings from (1-1440) 292 :param max_count: Maximum number of glucose readings to retrieve (1-288) 293 """ 294 json_glucose_readings: list[dict[str, Any]] = [] 295 296 try: 297 # Requesting glucose reading with DEFAULT_UUID returns non-JSON empty string 298 self._validate_session_id() 299 json_glucose_readings = self._get_glucose_readings(minutes, max_count) 300 except SessionError: 301 # Attempt to update expired session ID 302 self._get_session() 303 json_glucose_readings = self._get_glucose_readings(minutes, max_count) 304 305 return [GlucoseReading(json_reading) for json_reading in json_glucose_readings] 306 307 def get_latest_glucose_reading(self) -> GlucoseReading | None: 308 """Get latest available glucose reading, within the last 24 hours.""" 309 return next(iter(self.get_glucose_readings(max_count=1)), None) 310 311 def get_current_glucose_reading(self) -> GlucoseReading | None: 312 """Get current available glucose reading, within the last 10 minutes.""" 313 return next(iter(self.get_glucose_readings(minutes=10, max_count=1)), None)
Class for communicating with Dexcom Share API.
38 def __init__( 39 self, 40 *, 41 password: str, 42 account_id: str | None = None, 43 username: str | None = None, 44 region: Region = Region.US, 45 ) -> None: 46 """ 47 Initialize `Dexcom` with Dexcom Share credentials. 48 49 :param username: username for the Dexcom Share user, *not follower*. 50 :param account_id: account ID for the Dexcom Share user, *not follower*. 51 :param password: password for the Dexcom Share user. 52 :param region: the region to use, one of `"us"`, `"ous"`, `"jp"`. 53 """ 54 self._validate_region(region) 55 self._validate_user_ids(account_id, username) 56 57 self._base_url = DEXCOM_BASE_URLS[region] 58 self._application_id = DEXCOM_APPLICATION_IDS[region] 59 self._password = password 60 self._username: str | None = username 61 self._account_id: str | None = account_id 62 self._session_id: str | None = None 63 self._session = requests.Session() 64 self._get_session()
Initialize Dexcom
with Dexcom Share credentials.
Parameters
- username: username for the Dexcom Share user, not follower.
- account_id: account ID for the Dexcom Share user, not follower.
- password: password for the Dexcom Share user.
- region: the region to use, one of
"us"
,"ous"
,"jp"
.
71 @property 72 def account_id(self) -> str | None: 73 """Get account ID.""" 74 return self._account_id
Get account ID.
280 def get_glucose_readings( 281 self, 282 minutes: int = MAX_MINUTES, 283 max_count: int = MAX_MAX_COUNT, 284 ) -> list[GlucoseReading]: 285 """ 286 Get `max_count` glucose readings within specified number of `minutes`. 287 288 Catches one instance of a thrown `pydexcom.errors.SessionError` if session ID 289 expired, attempts to get a new session ID and retries. 290 291 :param minutes: Number of minutes to retrieve glucose readings from (1-1440) 292 :param max_count: Maximum number of glucose readings to retrieve (1-288) 293 """ 294 json_glucose_readings: list[dict[str, Any]] = [] 295 296 try: 297 # Requesting glucose reading with DEFAULT_UUID returns non-JSON empty string 298 self._validate_session_id() 299 json_glucose_readings = self._get_glucose_readings(minutes, max_count) 300 except SessionError: 301 # Attempt to update expired session ID 302 self._get_session() 303 json_glucose_readings = self._get_glucose_readings(minutes, max_count) 304 305 return [GlucoseReading(json_reading) for json_reading in json_glucose_readings]
Get max_count
glucose readings within specified number of minutes
.
Catches one instance of a thrown pydexcom.errors.SessionError
if session ID
expired, attempts to get a new session ID and retries.
Parameters
- minutes: Number of minutes to retrieve glucose readings from (1-1440)
- max_count: Maximum number of glucose readings to retrieve (1-288)
307 def get_latest_glucose_reading(self) -> GlucoseReading | None: 308 """Get latest available glucose reading, within the last 24 hours.""" 309 return next(iter(self.get_glucose_readings(max_count=1)), None)
Get latest available glucose reading, within the last 24 hours.
22class GlucoseReading: 23 """Class for parsing glucose reading from Dexcom Share API.""" 24 25 def __init__(self, json_glucose_reading: dict[str, Any]) -> None: 26 """ 27 Initialize `GlucoseReading` with JSON glucose reading from Dexcom Share API. 28 29 :param json_glucose_reading: JSON glucose reading from Dexcom Share API 30 """ 31 self._json = json_glucose_reading 32 try: 33 self._value = int(json_glucose_reading["Value"]) 34 self._trend_direction: str = json_glucose_reading["Trend"] 35 # Dexcom Share API returns `str` direction now, previously `int` trend 36 self._trend: int = DEXCOM_TREND_DIRECTIONS[self._trend_direction] 37 38 match = re.match( 39 r"Date\((?P<timestamp>\d+)(?P<timezone>[+-]\d{4})\)", 40 json_glucose_reading["DT"], 41 ) 42 if match: 43 self._datetime = datetime.fromtimestamp( 44 int(match.group("timestamp")) / 1000.0, 45 tz=datetime.strptime(match.group("timezone"), "%z").tzinfo, 46 ) 47 except (KeyError, TypeError, ValueError) as error: 48 raise ArgumentError(ArgumentErrorEnum.GLUCOSE_READING_INVALID) from error 49 50 @property 51 def value(self) -> int: 52 """Blood glucose value in mg/dL.""" 53 return self._value 54 55 @property 56 def mg_dl(self) -> int: 57 """Blood glucose value in mg/dL.""" 58 return self._value 59 60 @property 61 def mmol_l(self) -> float: 62 """Blood glucose value in mmol/L.""" 63 return round(self.value * MMOL_L_CONVERSION_FACTOR, 1) 64 65 @property 66 def trend(self) -> int: 67 """ 68 Blood glucose trend information. 69 70 Value of `pydexcom.const.DEXCOM_TREND_DIRECTIONS`. 71 """ 72 return self._trend 73 74 @property 75 def trend_direction(self) -> str: 76 """ 77 Blood glucose trend direction. 78 79 Key of `pydexcom.const.DEXCOM_TREND_DIRECTIONS`. 80 """ 81 return self._trend_direction 82 83 @property 84 def trend_description(self) -> str | None: 85 """ 86 Blood glucose trend information description. 87 88 See `pydexcom.const.TREND_DESCRIPTIONS`. 89 """ 90 return TREND_DESCRIPTIONS[self._trend] 91 92 @property 93 def trend_arrow(self) -> str: 94 """Blood glucose trend as unicode arrow (`pydexcom.const.TREND_ARROWS`).""" 95 return TREND_ARROWS[self._trend] 96 97 @property 98 def datetime(self) -> datetime: 99 """Glucose reading recorded time as datetime.""" 100 return self._datetime 101 102 @property 103 def json(self) -> dict[str, Any]: 104 """JSON glucose reading from Dexcom Share API.""" 105 return self._json 106 107 def __str__(self) -> str: 108 """Blood glucose value as in mg/dL.""" 109 return str(self._value)
Class for parsing glucose reading from Dexcom Share API.
25 def __init__(self, json_glucose_reading: dict[str, Any]) -> None: 26 """ 27 Initialize `GlucoseReading` with JSON glucose reading from Dexcom Share API. 28 29 :param json_glucose_reading: JSON glucose reading from Dexcom Share API 30 """ 31 self._json = json_glucose_reading 32 try: 33 self._value = int(json_glucose_reading["Value"]) 34 self._trend_direction: str = json_glucose_reading["Trend"] 35 # Dexcom Share API returns `str` direction now, previously `int` trend 36 self._trend: int = DEXCOM_TREND_DIRECTIONS[self._trend_direction] 37 38 match = re.match( 39 r"Date\((?P<timestamp>\d+)(?P<timezone>[+-]\d{4})\)", 40 json_glucose_reading["DT"], 41 ) 42 if match: 43 self._datetime = datetime.fromtimestamp( 44 int(match.group("timestamp")) / 1000.0, 45 tz=datetime.strptime(match.group("timezone"), "%z").tzinfo, 46 ) 47 except (KeyError, TypeError, ValueError) as error: 48 raise ArgumentError(ArgumentErrorEnum.GLUCOSE_READING_INVALID) from error
Initialize GlucoseReading
with JSON glucose reading from Dexcom Share API.
Parameters
- json_glucose_reading: JSON glucose reading from Dexcom Share API
50 @property 51 def value(self) -> int: 52 """Blood glucose value in mg/dL.""" 53 return self._value
Blood glucose value in mg/dL.
55 @property 56 def mg_dl(self) -> int: 57 """Blood glucose value in mg/dL.""" 58 return self._value
Blood glucose value in mg/dL.
60 @property 61 def mmol_l(self) -> float: 62 """Blood glucose value in mmol/L.""" 63 return round(self.value * MMOL_L_CONVERSION_FACTOR, 1)
Blood glucose value in mmol/L.
65 @property 66 def trend(self) -> int: 67 """ 68 Blood glucose trend information. 69 70 Value of `pydexcom.const.DEXCOM_TREND_DIRECTIONS`. 71 """ 72 return self._trend
Blood glucose trend information.
Value of pydexcom.const.DEXCOM_TREND_DIRECTIONS
.
74 @property 75 def trend_direction(self) -> str: 76 """ 77 Blood glucose trend direction. 78 79 Key of `pydexcom.const.DEXCOM_TREND_DIRECTIONS`. 80 """ 81 return self._trend_direction
Blood glucose trend direction.
83 @property 84 def trend_description(self) -> str | None: 85 """ 86 Blood glucose trend information description. 87 88 See `pydexcom.const.TREND_DESCRIPTIONS`. 89 """ 90 return TREND_DESCRIPTIONS[self._trend]
Blood glucose trend information description.
92 @property 93 def trend_arrow(self) -> str: 94 """Blood glucose trend as unicode arrow (`pydexcom.const.TREND_ARROWS`).""" 95 return TREND_ARROWS[self._trend]
Blood glucose trend as unicode arrow (pydexcom.const.TREND_ARROWS
).
Regions.