應用端 API
讀完你會知道:登入拿到哪兩種 token、資料在哪個環境、常用的 API 有哪些。
App、網頁或第三方服務都走這套。平台只認使用者的 token,設備的事情由平台代為處理。
登入在一個地方,資料在各自的環境
帳號與登入統一在 identity 服務。登入成功後拿到兩個 token:
- access token:短期(1 小時),打 API 用。
- refresh token:30 天,換新的 access token;每換一次舊的就失效(rotation)。
POST https://<identity 主機名>/v1/auth/login
{ "client_id": "<你的 client id>", "email": "...", "password": "..." }
client_id 決定帳號池與品牌。同一個帳號池的帳號用哪個品牌的 App 都直接登入,不會因為第一次用這個品牌而被擋,也沒有「加入品牌」這一步(舊的 join_brand / JOIN_BRAND_REQUIRED 已經拿掉,送了也沒作用)。
品牌條款
登入、註冊、refresh 的回應與 GET /v1/me 都帶 memberships 與 tos_required:
"tos_required": { "brand": "<品牌>", "current_tos_version": "2026-09", "url": "https://…", "accepted_tos_version": "2025-12" }
null:這個品牌的現行條款已經同意過(或品牌沒設條款),照常使用。- 不是
null:使用者還沒同意這個品牌的現行條款(第一次用這個品牌,或條款改版了;accepted_tos_version是之前同意的舊版,沒有就是從沒同意過)。顯示url的條款,使用者同意後:
POST https://<identity 主機名>/v1/me/brands/<品牌>/accept-terms
Authorization: Bearer <access token>
{ "tos_version": "<畫面上顯示的 current_tos_version>" }
成功回 200 {membership, memberships, tos_required: null}。同一版重送不會改同意時間。
409 TOS_VERSION_STALE:你顯示的那一版已經不是現行版(剛好改版)。回應的detail帶新的current_tos_version與url,用新版重新顯示、再請使用者同意。403 BRAND_MISMATCH:只能同意發這顆 token 的 App 自己品牌的條款。- 註冊或登入前已經讓使用者看過條款的話,請求帶
tos_version就直接記同意,回應的tos_required會是null。
tos_required 不擋任何 API;要不要在同意前限制使用由 App 決定(本 portal 是先顯示條款頁,不同意就登出)。
家戶就是環境
GET https://<identity 主機名>/v1/me
回應包含使用者的家戶清單,每個家戶帶著它所屬的環境與端點集。你要操作某個家戶的設備,就打那個家戶端點集裡的 API,不是固定一個網址。
切家戶等於切環境,不用重新登入。
常用端點
家戶與設備(打家戶所屬環境的 api):
| 端點 | 用途 |
|---|---|
POST /v1/homes | 建家戶 |
GET /v1/homes / GET /v1/homes/{id} | 家戶清單與細節 |
POST /v1/homes/{id}/members | 邀請成員 |
GET /v1/homes/{id}/things/{device} | 讀 Thing 與 shadow |
PUT /v1/homes/{id}/things/{device}/shadow/desired | 寫 desired(控制設備) |
DELETE /v1/homes/{id}/things/{device} | 解除綁定 |
POST /v1/homes/{id}/things/claim | 接管既有設備(舊平台設備用 DID,其他品牌原廠設備用 MAC) |
所有 thing 操作都會先驗你是不是這個家戶的成員。
控制設備 = 寫 desired
控制不是「呼叫設備」,是寫下你要的狀態:
PUT /v1/homes/{home}/things/{device}/shadow/desired
{ "kinio_motion": { "enabled": false } }
平台把差異推給設備,設備收斂後回報 reported。UI 要顯示「收斂中」而不是假裝馬上成功 —— 設備可能離線,等它上線才會套用。
配對一台新設備
POST /v1/pairing/sessions { "home_id": "...", "device_ref": "...", "method": "ble|ap|qr" }
→ { "binding_id": "bind-…", "nonce": "…", "expires_at": "…" }
nonce 只回這一次。App 透過 BLE、SoftAP 或 QR 把 binding_id 與 nonce 交給設備;設備用 AID 私鑰簽 sha256(binding_id + "." + nonce),把憑證與簽章交回 App:
POST /v1/pairing/sessions/{id}/redeem { "cert_pem": "...", "signature": "..." }
平台驗憑證鏈、驗 CN 等於該 AID、驗這是目前有效的憑證、驗簽章,然後把設備綁進家戶並回傳要派發給設備的端點集。
這代表只有真的握有私鑰的設備能完成配對,App 沒辦法憑空把一台設備綁走。
即時影像
影像走 WebRTC。App 打平台的 signaling(WSS),平台負責把 offer/answer 傳給設備,並依當下負載派中繼節點。你不需要自己管 TURN 憑證。
錯誤
錯誤一律回 {"error": "<UPPER_SNAKE>", "message": "…", "detail": {…}}:程式只比對 error;message 一定有,可以直接給人看;機器可讀的補充放 detail(沒有就不帶),例如 403 REAUTH_REQUIRED 的 detail.max_age_seconds。遇到 401 先試 refresh;403 代表權限不足,重試沒用。限流回 429,照回應的建議時間退避。
列表回 {"<資源複數名>": [...], "next_cursor": "…"},下一頁帶 ?cursor=;沒有下一頁時不帶 next_cursor。