Skip to main content

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:

  1. asks /api/v1/genLoginUrl for a signed login URL with a safe GET follow-up;
  2. exchanges the saved Xiaomi Account userId and passToken at /pass/serviceLogin?_json=true;
  3. computes Xiaomi's clientSign when required;
  4. visits the signed MiMo /sts callback, which replaces the four platform cookies;
  5. verifies /api/v1/userProfile; and
  6. 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

  1. Sign in normally at https://account.xiaomi.com in your browser.
  2. Open Developer Tools → Application/Storage → Cookies → https://account.xiaomi.com.
  3. Copy only userId and passToken.
  4. 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.

  1. Open https://account.xiaomi.com in the same browser profile that opens MiMo without another sign-in.
  2. Open Developer Tools on that tab.
  3. Select Application/Storage → Cookies.
  4. Inspect https://account.xiaomi.com and rows whose Domain is .account.xiaomi.com or .xiaomi.com.
  5. Filter for userId and passToken.

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:

  1. Enable Preserve log in the Network panel.
  2. Open an expired MiMo loginUrl or revisit a protected MiMo console page.
  3. Select the first request to https://account.xiaomi.com/pass/serviceLogin.
  4. Open Cookies → Request Cookies and find userId and passToken.
  5. 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)

Source distribution for xiaomi-mimo 0.2.0
File Size Uploaded
xiaomi_mimo-0.2.0.tar.gz 46.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xiaomi-mimo 0.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page