tuya/tuya-openclaw-skills/muse
Claude Code and agent skill by tuya/tuya-openclaw-skills
- community
Checking that the SKILL.md file is still in the repository.
Read status and control the user's Tuya app devices through Tuya's cloud end-user API with a user-supplied API key: homes and rooms, device detail, thing-model property control, rename, weather, hourly statistics, self-send notifications, IPC cloud snapshots and real-time event subscription. Cloud only; prefer a device-specific or local-TCP skill when one matches.
Install
npx vskill@latest install tuya/tuya-openclaw-skills/museSKILL.md
Tuya Cloud Devices: End-User API Control
Use this skill when the user supplies a Tuya end-user API key (sk-...) and asks to control or query devices from their Tuya app account. Devices are reached in the cloud, so no local keys or model-specific datapoint maps are needed. For direct local-TCP control with a confirmed key and datapoint schema, prefer Tuya Wi-Fi devices.
Prerequisites
Read and follow the Home Link networking and safety rules in ~/docs/devices/home_link.md.
- A user-supplied end-user API key, applied for at https://tuya.ai/. Treat it as a credential:
pass it in a header, never log or echo it.
- Send
Authorization: Bearer <key>on every call. Pick the base URL from the first two
characters after sk-; it must match the account's region. If the prefix is not listed, ask the user to confirm the key was issued for an international account at https://tuya.ai/.
| Prefix | Base URL | |
|---|---|---|
| AZ | https://openapi.tuyaus.com | |
| EU | https://openapi.tuyaeu.com | |
| IN | https://openapi.tuyain.com | |
| UE | https://openapi-ueaz.tuyaus.com | |
| WE | https://openapi-weaz.tuyaeu.com | |
| SG | https://openapi-sg.iotbing.com |
- Responses share one envelope:
{"success": true, "result": ...}or
{"success": false, "code", "msg"}. On 1010 the key expired — ask for a fresh one. On 429 back off and honor Retry-After. List endpoints return everything in one response; there is no pagination.
Workflow
- List homes with
GET /v1.0/end-user/homes/all, and rooms with
GET /v1.0/end-user/homes/{home_id}/rooms when the user names one.
- Locate the device:
GET /v1.0/end-user/devices/all, or scope to
.../homes/{home_id}/devices or .../homes/room/{room_id}/devices. Each entry in result.devices[] carries device_id, name, category, category_name, online and, under a home scope, room_id. Match the user's words against category_name first, then fuzzy-match name. With multiple matches, list the candidates with their rooms and ask.
- Read
GET /v1.0/end-user/devices/{device_id}/detail. Anullresultmeans the device does
not exist or the key has no access — stop. online: false means report the device as offline and command nothing. result.properties maps each property code to its current value.
- Read the thing model
GET /v1.0/end-user/devices/{device_id}/model. Itsresult.modelfield
is a JSON string that needs a second parse. services[].properties[] then defines each property's code, accessMode (ro properties are read-only — say so) and typeSpec:
value:min,max,stepandscale— the real-world value is the raw number divided
by 10^scale.
enum: the issued value must be one ofrange.bool/string:true/false, andmaxlenrespectively.
Common codes are switch_led/switch (bool power), bright_value/temp_value (0–1000), temp_set (16–30) and mode (enum) — the model response is always authoritative.
- Issue
POST /v1.0/end-user/devices/{device_id}/shadow/properties/issuewith a body whose
properties value is a JSON string, not an object — it is double-serialized: {"properties": "{\"switch_led\":true}"}. An empty result object means the command was accepted, not that it took effect. For relative asks ("a bit brighter"), adjust the current value by 10% of the typeSpec range for vague amounts or by the stated amount exactly, clamp to [min, max], honor scale and round to step.
- Wait 1–2 seconds, re-read the detail and compare the mapped value before reporting success.
Also Available
- Rename:
POST /v1.0/end-user/devices/{device_id}/attributewith{"name": "..."}, max 50
characters.
- Weather:
GET /v1.0/end-user/services/weather/recentwithlat,lonandcodes— a
URL-encoded JSON array of attribute codes such as w.temp, w.humidity, w.condition, w.conditionNum, w.pressure, w.realFeel, w.uvi, w.windDir, w.windLevel, w.windSpeed, plus w.hour.N to set the forecast horizon to the next N hours. Response keys are {code}.{index} with index 0 meaning now. Home coordinates are shaped {"Value": "30.3"} — read the inner Value; ask the user for their city if the home has none set.
- Hourly statistics: confirm capability with
GET /v1.0/end-user/statistics/hour/config
(items carry dev_id, dp_code, statistic_type of SUM/COUNT/MAX/MIN), then GET .../hour/data?dev_id=..&dp_code=..&statistic_type=..&start_time=..&end_time=... Times are yyyyMMddHH and one request spans at most 24 hours; page longer ranges and aggregate. Values return as an array of {time: value} pairs with string values.
- Notifications are self-send only — they reach the key's own user, nobody else. SMS and
voice take {"message": "..."}; mail and push take {"subject": "...", "content": "..."}. Rate and duplicate limits apply (per contact: 15–30 per day, identical content more than twice within 50 seconds is refused).
- IPC cloud snapshots (cameras):
POST /v1.0/end-user/ipc/{device_id}/capture/allocate
with body {"capture_json": "<json string>"} — double-serialized like property issue. The string carries device_id, capture_type (PIC or VIDEO), optionally pic_count (clamped to 1–5), video_duration_seconds (1–60, default 10) and home_id. Allocate only reserves an upload slot: result.status is ACCEPTED or REJECTED and result carries bucket and the object key(s) (image_object_key, and for VIDEO also video_object_key / cover_image_object_key) — never a media URL; the device still has to capture and upload. Then poll POST .../capture/resolve with a resolve_json string echoing device_id, capture_type, bucket and the object key(s) from allocate, and pass user_privacy_consent_accepted: true. Resolve returns the link to hand the user: the image/video cloud storage URL in decrypt_image_url / decrypt_video_url. status: NOT_READY means keep polling — for PIC sleep 2 s, then resolve every 2 s up to 30 s, then retry up to 3 times 3 s apart; for VIDEO sleep max(5, video_duration_seconds_effective) + 2 s using the clamped duration from the allocate response, then resolve every 2 s up to 120 s, then retry up to 3 times 5 s apart. With pic_count above 1, allocate returns per-snapshot coordinates in pic_slots[] — resolve each slot's key separately. A video's cover image can lag the video itself — check message_for_user.
Real-Time Subscription
Property changes and online/offline transitions are also pushed over WebSocket. This is a long-lived process for an always-on host (a Linux gadget), not a one-shot query. Write a short Python script using the websockets package:
- URI by key prefix (same mapping as the REST table): AZ
wss://wsmsgs.iot-wus.com,
EU wss://wsmsgs.iot-eu.com, IN wss://wsmsgs.iot-ap.com, UE wss://wsmsgs.iot-eus.com, WE wss://wsmsgs.iot-weu.com, SG wss://wsmsgs.iot-sea.com.
- Auth: an
Authorization: <key>header on the handshake — the same key as REST but without
the Bearer prefix.
- Frames are JSON with two top-level fields,
eventTypeanddata. AdevicePropertyChange
carries data.devId and data.status[], each with code, value and time (ms); an onlineStatusChange carries data.devId, data.status ("online"/"offline") and data.time.
- Codes match thing-model codes. Look their meanings up via the model endpoint instead of
guessing from the code string.
- Reconnect with backoff on transient drops. Treat close codes 1002, 1003, 1008 and 1011 as
fatal, and any frame containing error, a failing errorCode, or "success": false as a server error.
- For event-driven automation, on a matching trigger issue properties to the action device as
in Workflow step 5, and confirm the trigger→action mapping with the user before running it.
Limits
- Control only basic property types (bool, enum, integer, string). Never write
raw,bitmap,
struct or array properties, or anything absent from the thing model.
- No lock/unlock, live video streaming, firmware updates, pairing or device removal — say so and
point to the Tuya app.
- Device listing takes one scope at a time (all, home or room). Rate-limit batch writes with a
short delay between commands.
- Notifications triggered by device events must cool down at least 30 minutes — property events
fire frequently.
- These are the user's own account devices behind an end-user key; never attempt another user's
devices.
Sources
- tuya-openclaw-skills — upstream skill this was adapted from.
- Tuya developer docs
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingplatforms/muse/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/tuya/tuya-openclaw-skills/muse)