xiaomi-mimo
An unofficial, typed Python client for the Xiaomi MiMo platform. It packages the browser's Mi Account SSO exchange, persists the resulting cookies securely, and refreshes the short-lived MiMo session automatically.
This project is not affiliated with Xiaomi. The MiMo web API is undocumented and can change without notice. Use it only with accounts and data you are authorized to access, and review Xiaomi's applicable terms.
Authentication model
The MiMo console uses four required cookies:
| Cookie | Purpose observed in the web client |
|---|---|
api-platform_serviceToken |
MiMo service session |
api-platform_slh |
MiMo service session companion value |
api-platform_ph |
MiMo session value and POST query parameter |
userId |
MiMo user identity |
The console describes these as 24-hour cookies. They are not refresh tokens. The apparently automatic browser refresh works because the browser also has a longer-lived Xiaomi Account session. The minimal reusable seed for that session is normally the userId and passToken cookies from account.xiaomi.com.
When a MiMo request is unauthorized, the library:
- asks
/api/v1/genLoginUrlfor a signed login URL with a safe GET follow-up; - exchanges the saved Xiaomi Account
userIdandpassTokenat/pass/serviceLogin?_json=true; - computes Xiaomi's
clientSignwhen required; - visits the signed MiMo
/stscallback, which replaces the four platform cookies; - verifies
/api/v1/userProfile; and - retries the original API request once.
No browser or JavaScript engine is needed while the Xiaomi Account session remains valid. Xiaomi can still expire or revoke passToken, or require CAPTCHA/MFA. No client can guarantee refresh forever in those cases; this library raises ReauthenticationRequired instead of attempting to bypass interactive verification.
Install
pip install xiaomi-mimo
One-time setup
- Sign in normally at
https://account.xiaomi.comin your browser. - Open Developer Tools → Application/Storage → Cookies →
https://account.xiaomi.com. - Copy only
userIdandpassToken. - Provide them once and choose a cookie file.
import os
from xiaomi_mimo import MimoClient
with MimoClient(
account_cookie={
"userId": os.environ["XIAOMI_USER_ID"],
"passToken": os.environ["XIAOMI_PASS_TOKEN"],
},
cookie_file="~/.config/xiaomi-mimo/cookies.json",
) as mimo:
print(mimo.token_plan_usage())
The cookie file is created atomically with mode 0600 on POSIX systems. On later runs, the seed does not need to be supplied again:
from xiaomi_mimo import MimoClient
with MimoClient(cookie_file="~/.config/xiaomi-mimo/cookies.json") as mimo:
profile = mimo.user_profile()
usage = mimo.token_plan_usage()
Treat both the environment variables and the cookie file like passwords. Never commit either one.
A raw Cookie header is also accepted:
mimo = MimoClient(
account_cookie="userId=...; passToken=...",
cookie_file="~/.config/xiaomi-mimo/cookies.json",
)
Finding passToken when it is not visible
passToken is a Xiaomi Account cookie, not a MiMo platform cookie. It will not appear in a copied request to platform.xiaomimimo.com.
- Open
https://account.xiaomi.comin the same browser profile that opens MiMo without another sign-in. - Open Developer Tools on that tab.
- Select Application/Storage → Cookies.
- Inspect
https://account.xiaomi.comand rows whose Domain is.account.xiaomi.comor.xiaomi.com. - Filter for
userIdandpassToken.
The cookie is normally HttpOnly. It is therefore absent from document.cookie, but remains visible in Developer Tools. If the Application panel does not show it:
- Enable Preserve log in the Network panel.
- Open an expired MiMo
loginUrlor revisit a protected MiMo console page. - Select the first request to
https://account.xiaomi.com/pass/serviceLogin. - Open Cookies → Request Cookies and find
userIdandpassToken. - Alternatively, use Copy → Copy as cURL and inspect the Cookie header locally.
Never paste either value into an issue, log, source file, or chat. A posted passToken should be revoked by signing out the corresponding Xiaomi Account session. The persisted cookie file contains the same sensitive capability and should not be synchronized or committed.
Start from an existing MiMo session
The four cookies from a copied MiMo curl request can be loaded as platform_cookie. This permits immediate API use, but those cookies alone cannot renew themselves. Supply the Xiaomi Account seed as well if automatic refresh is required.
with MimoClient(
account_cookie="userId=...; passToken=...",
platform_cookie=(
"userId=...; api-platform_serviceToken=...; "
"api-platform_slh=...; api-platform_ph=..."
),
cookie_file="~/.config/xiaomi-mimo/cookies.json",
) as mimo:
print(mimo.token_plan_usage())
Analytics cookies, browser client-hint headers, cookie-preferences, and a browser user agent are not required.
Captured endpoint helpers
Every MiMo API endpoint observed in ref.har and ref2.har has a convenience method:
| Client method | HTTP endpoint |
|---|---|
user_profile() |
GET /userProfile |
balance_alert_config() |
GET /balanceAlertConfig |
ab_test_experiments() |
GET /abtest/experiments |
invitation_eligible() |
GET /invitation/eligible |
token_plans() |
GET /tokenPlan/list |
token_plan_detail() |
GET /tokenPlan/detail |
token_plan_usage(user_id=...) |
GET /tokenPlan/usage |
token_plan_api_key() |
GET /tokenPlan/apiKey |
token_plan_api_key_raw() |
GET /tokenPlan/apiKey/raw |
token_plan_management_url() |
GET /tokenPlan/managementUrl |
usage() |
GET /usage |
token_plan_usage_records(year=..., month=...) |
POST /usage/token-plan/list |
usage_details(year=..., month=...) |
POST /usage/detail/list |
plugin_usage(year=..., month=...) |
POST /usage/plugin/list |
api_keys(with_deleted=...) |
GET /apiKeys |
create_api_key(name) |
POST /apiKeys |
delete_api_key(id) |
DELETE /apiKeys/{id} |
refundable_amount() |
GET /refund/refundableAmount |
balance() |
GET /balance |
recharge_or_refund_records() |
GET /rechargeOrRefund |
accumulated_recharge_amount() |
GET /accumulatedRechargeAmount |
plugins() |
GET /plugins |
email_bind_info() |
GET /email/bindInfo |
The API-key methods handle secrets. token_plan_api_key_raw() and create_api_key() can return a complete API key; do not log their results. delete_api_key() permanently deletes the selected key.
summary = mimo.usage()
records = mimo.usage_details(year=2026, month=8)
keys = mimo.api_keys(with_deleted=False)
Generic requests
Paths are relative to /api/v1:
result = mimo.request_json("GET", "/tokenPlan/usage")
result = mimo.request_json(
"POST",
"/usage/detail/list",
json={"pageNum": 1, "pageSize": 20},
)
For POST requests, api-platform_ph is copied from the cookie jar into the query string to match the MiMo web client. API envelopes with a data field are unwrapped by default:
full_envelope = mimo.request_json("GET", "/userProfile", unwrap=False)
response = mimo.request("GET", "/userProfile")
Automatic retry supports replayable JSON, form mappings, and str or bytes content. Streaming upload bodies are intentionally not accepted by the high-level method because replay after a 401 would be unsafe.
Explicit refresh and status
state = mimo.auth_state
print(state.authenticated, state.renewable, state.expires_at)
state = mimo.refresh()
Refresh is serialized across threads. If another thread already replaced the session, waiting requests reuse it instead of running a second SSO exchange.
Errors
from xiaomi_mimo import APIError, ReauthenticationRequired
try:
print(mimo.token_plan_usage())
except ReauthenticationRequired as exc:
print("Sign in to Xiaomi Account again and replace userId/passToken")
print(exc.login_url)
except APIError as exc:
print(exc.code, str(exc))
ReauthenticationRequired.login_url can be opened for normal interactive sign-in. After signing in, copy the new Xiaomi Account userId and passToken into a new client invocation so the saved store is updated.
Development
uv sync --extra test
uv run ruff check .
uv run mypy src
uv run pytest
uv build
Release files for xiaomi-mimo 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| xiaomi_mimo-0.2.0.tar.gz | 46.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| xiaomi_mimo-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.2 kB
Release files / xiaomi_mimo-0.2.0.tar.gz
| Download URL | xiaomi_mimo-0.2.0.tar.gz |
|---|---|
| Size | 46.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e579654c4f6503489e285dbb6addeac9eb0c879f2f93aecf60826562dc3003d7
|
|
BLAKE2b-256 checksum How to use checksums |
3c90963b62b099e54b4e0d7a13a52f1ad67bc0497ee38a9702ca9260dc46063a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.0
|
Release files / xiaomi_mimo-0.2.0-py3-none-any.whl
| Download URL | xiaomi_mimo-0.2.0-py3-none-any.whl |
|---|---|
| Size | 15.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a08685a26bc425ede954c4be413aebb1599934c179b2d30d549956800697a397
|
|
BLAKE2b-256 checksum How to use checksums |
d3fb292bba482de9a18dc3027e6cf575bb7c15992bef6dc4fb0175f0a37cad85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.0
|