Command-line client for the Aqara Open Cloud API. Lists homes, devices, rooms, and scenes — renames and moves devices, manages rooms, runs scenes, refreshes tokens — straight from the terminal.
Unlike the Aqara MCP server, which
only exposes a fixed set of read tools, this CLI talks to the full developer
API and can mutate names + room assignments. Verified intent quirks (the
"dids vs did" footgun, the 403 you get from config.position.name, the
singular-positionId delete) are baked into the wrapper.
brew install omarshahine/tap/aqara-cliUpgrades land via brew update && brew upgrade aqara-cli.
pipx install git+https://github.com/omarshahine/aqara-cli.gitgit clone https://github.com/omarshahine/aqara-cli && cd aqara-cli
pip install -e ".[dev]"Setting up Aqara API access is genuinely fiddly — the docs are scattered and some endpoints expect quirky payloads. The CLI gets you to a working state in about 10 minutes, and a recurring launchd refresher means you never have to think about it again.
-
Go to https://developer.aqara.com/ and sign in with the same Aqara account you use in the mobile app (same email/phone — this is what gives your dev app permission to see your homes and devices).
-
Console → Cloud Development → Project Management → Add Project.
- Name: anything you'll recognize later (e.g.
aqara-cli). - Region: pick the one your account lives in. Most US accounts are
USA. - "Whether to access the cloud-cloud open platform service": yes.
- Name: anything you'll recognize later (e.g.
-
Once the project is created, open its detail page. You'll see:
- AppId — alphanumeric.
- KeyId — alphanumeric.
- AppKey — under "Application key". Reveal it; copy the full string.
-
Add a Redirect URI. In your app settings, register
http://localhost:8765/callbackas an authorized Redirect URI. The CLI's default bootstrap flow (aqara auth browser-flow) needs this — the alternative email-code flow is unreliable enough that we don't recommend it. Skip this step and the browser flow won't work. -
Note the region. Each region has its own endpoint:
usa→open-usa.aqara.com(default)cn→open-cn.aqara.comeu→open-ger.aqara.comru→open-ru.aqara.comkr→open-kr.aqara.com
Pick the one that matches your Aqara account. If your devices live in the US, use
usaeven if you're physically elsewhere.
Two options. Pick one.
aqara auth set-app \
--app-id "<your-AppId>" \
--app-key "<your-AppKey>" \
--key-id "<your-KeyId>" \
--region usaThis writes ~/.config/aqara-cli/credentials.json with mode 0600. The CLI
will read from this file unless overridden by env vars (see Option B).
Export the following in your shell rc / dotenv / secret store:
export AQARA_OPEN_APP_ID="<your-AppId>"
export AQARA_OPEN_APP_KEY="<your-AppKey>"
export AQARA_OPEN_KEY_ID="<your-KeyId>"
export AQARA_OPEN_REGION="usa" # one of: usa cn eu ru kr — default: usaEnv vars always win over credentials.json. Lower-case variants
(aqara_open_app_id, …) are also accepted for legacy shell setups.
Aqara's API uses per-user OAuth on top of the app credentials. There are two ways to get the first set of tokens; the browser flow is the one that actually works. Aqara's email-code flow looks simpler in the docs but their email delivery is unreliable — many users (including this project's author) never receive the code. Skip the frustration:
Prerequisite, one-time, easy to miss: In your developer.aqara.com app
settings, add http://localhost:8765/callback as an authorized Redirect URI.
Without it, the authorize page shows a generic error and you'll think the
whole thing is broken.
Then:
aqara auth browser-flowWhat happens:
- The CLI starts a local HTTP listener on
localhost:8765. - Your browser opens
https://open-<region>.aqara.com/v3.0/open/authorize?.... - You sign in with the same Aqara account that owns your homes/devices.
- You click Authorize; Aqara redirects to
localhost:8765/callback?code=.... - The CLI captures the code, exchanges it for an AccessToken + RefreshToken
at
/v3.0/open/access_token, and writes both to credentials.json.
Done. aqara info should now show auth_ok: true.
Flags worth knowing:
--port <N>if 8765 is taken (you'll need to re-register the redirect URI with the new port on the developer portal).--no-browserto print the URL without opening it (useful over SSH — forward the port locally, then click the link).--no-saveto print tokens without writing credentials.json (for users who keep secrets in their own manager).
Token lifetimes (as of 2026-05):
- AccessToken: 7 days (or whatever you passed via
--validityon the email-code path; the browser flow uses Aqara's default). - RefreshToken: 30 days, rotated on every successful refresh.
Fallback — email verification code (only if browser flow truly won't work for you, e.g. no browser available at all):
aqara auth request-code [email protected] # Aqara emails a code
aqara auth get-token [email protected] 123456 # exchange itIn practice, the email is slow, lands in spam, or never arrives. If you're
seeing nothing after 5 minutes, switch to aqara auth browser-flow.
aqara infoYou should see auth_ok: true and a list of your homes. If auth_ok: false,
the error message tells you what's missing.
aqara homes # see them
aqara home set "Home" # persist to ~/.config/aqara-cli/config.jsonAfter this, aqara devices, aqara rooms, and aqara scenes are scoped to
that home. Pass --home <name|id> per-call to override, or --all-homes
on devices for everything.
Refresh tokens expire in 30 days. To make the install long-term unattended:
aqara auth install-refresherThis writes a launchd plist at
~/Library/LaunchAgents/com.shahine.aqara-cli.refresh.plist that runs
aqara refresh every 5 days (well inside both the 7-day access window and
the 30-day refresh window). Logs land at
~/Library/Logs/aqara-cli/refresh.log.
Check on it any time:
aqara auth statusUninstall:
aqara auth uninstall-refresherOn Linux, set up a systemd timer or cron entry that runs aqara refresh
every 5 days. The CLI persists the refreshed tokens to credentials.json
automatically; nothing else needs to know.
# Read
aqara homes
aqara rooms
aqara devices
aqara device-status lumi.example1234abcd
aqara scenes
# Rename a device
aqara rename lumi.example1234abcd "Front Door Sensor"
# Move a device to a different room
aqara move lumi.example1234abcd real2.exampleRoomId
# Manage rooms
aqara room rename real2.exampleLivingRoom "Living Room"
aqara room create "Mudroom" # under the default home
aqara room delete real2.foo # API refuses if devices still present
# Run a scene
aqara scenes
aqara scene-run <scene-id>
# Manual refresh
aqara refreshEvery write supports --dry-run — prints the exact intent + payload without
touching the network.
For any intent the CLI doesn't wrap yet:
aqara call query.position.info --data '{"pageNum":1,"pageSize":50}'
aqara call config.auth.refreshToken
aqara call -v query.device.subInfo --data '{"did":"lumi.example1234abcd"}'Pass -v to print the outbound request (URL + headers minus secrets).
Found while building this; documented in code so the next person doesn't re-discover them:
config.device.positionrequiresdids(an array of device ids), not the natural-looking singulardid. Calling withdidreturnscode=302, msg="Param not valid: dids 不能为空".- The position-rename intent is
config.position.update, notconfig.position.name(that returns 403). config.position.deleterequires singularpositionId. The pluralpositionIds: [list]returns 302.
Aqara documents both paths as equivalent; in practice the email-code flow is unreliable enough that I built this CLI's onboarding around the browser flow. The reasons people get stuck on email-code:
- Aqara's transactional email frequently lands in spam or doesn't arrive at all.
- The code is rate-limited and there's no clear per-account dashboard for retries — you just sit there hoping.
- It hides errors. The browser flow surfaces "your redirect URI isn't registered" or "wrong region" within seconds; the email flow just stays silent.
aqara auth browser-flow is a verbatim port of the script that successfully
bootstrapped this account when nothing else worked.
auth_ok: falseonaqara info: missing one of AppId/AppKey/KeyId. Runaqara auth set-appor check your env vars.code=108/109on any call: AccessToken expired. The CLI auto- refreshes; if you see this persisting, your RefreshToken expired too — re-run Step 3.code=302 msg="Param not valid: dids ..."when callingmoveor a rawconfig.device.position: you passed singulardidinstead ofdids(an array). The CLI does it right; only happens if you're using the rawaqara callescape hatch.code=403on a read intent: usually a region mismatch (e.g. account is onusabutregion=cn). Re-check Step 1.5.- Email never arrives in Step 3a: Aqara's email delivery is sometimes slow or filtered. Wait 5 minutes, check spam, and if still nothing, fall back to the browser OAuth path (see above).
aqara-cli/
├── src/aqara_cli/
│ ├── api.py # signed HTTPS client, token refresh, typed wrappers
│ ├── config.py # ~/.config/aqara-cli/{config,credentials}.json
│ └── main.py # Click commands
├── tests/test_cli.py # smoke tests (no network)
└── pyproject.toml
MIT — see LICENSE.