ha-api

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Home Assistant API Integration

Home Assistant API集成

Master Home Assistant's REST and WebSocket APIs for external integration, state management, and real-time communication.
掌握Home Assistant的REST和WebSocket API,实现外部集成、状态管理与实时通信。

⚠️ BEFORE YOU START

⚠️ 开始之前

This skill prevents 5 common API integration errors and saves ~30% token overhead.
AspectDetails
Common Errors Prevented5+ (auth, WebSocket lifecycle, state format, error handling)
Token Savings~30% vs. manual API discovery
Setup Time2-5 minutes vs. 15-20 minutes manual
该技能可避免5种常见API集成错误,减少约30%的令牌开销。
方面详情
可避免的常见错误5种以上(认证、WebSocket生命周期、状态格式、错误处理等)
令牌开销节省相比手动API发现减少约30%
设置时间2-5分钟(手动设置需15-20分钟)

Known Issues This Skill Prevents

该技能可避免的已知问题

  1. Incorrect authentication headers - Bearer tokens must be prefixed with "Bearer " in Authorization header
  2. WebSocket lifecycle management - Missing auth_required handling or improper state subscription
  3. State format mismatches - Confusing state vs. attributes; incorrect JSON payload structure
  4. Error response handling - Not distinguishing between 4xx client errors and 5xx server errors
  5. Service call domain/service mismatch - Incorrect routing to service endpoints
  1. 认证头错误 - Bearer令牌必须在Authorization头中添加前缀“Bearer ”
  2. WebSocket生命周期管理问题 - 缺失auth_required处理或状态订阅不当
  3. 状态格式不匹配 - 混淆状态与属性;JSON负载结构错误
  4. 错误响应处理不当 - 未区分4xx客户端错误与5xx服务器错误
  5. 服务调用的域/服务不匹配 - 服务端点路由错误

Quick Start

快速开始

Step 1: Authentication Setup

步骤1:认证设置

bash
undefined
bash
undefined

In Home Assistant UI:

In Home Assistant UI:

Settings → My Home → Create Long-Lived Access Token

Settings → My Home → Create Long-Lived Access Token

Store token securely (never commit to git)

Store token securely (never commit to git)

export HA_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." export HA_URL="http://192.168.1.100:8123"

**Why this matters:** All API requests require Bearer token authentication. Creating a dedicated token for external apps allows you to revoke access without changing your password.
export HA_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." export HA_URL="http://192.168.1.100:8123"

**重要性说明:** 所有API请求都需要Bearer令牌认证。为外部应用创建专用令牌可让您在不修改密码的情况下撤销访问权限。

Step 2: Test REST API Connectivity

步骤2:测试REST API连通性

bash
undefined
bash
undefined

Get all entity states

Get all entity states

curl -X GET "${HA_URL}/api/states"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"

**Why this matters:** Verifies your Home Assistant instance is accessible and your token is valid before building complex integrations.
curl -X GET "${HA_URL}/api/states"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"

**重要性说明:** 在构建复杂集成之前,验证您的Home Assistant实例是否可访问以及令牌是否有效。

Step 3: Choose API Type

步骤3:选择API类型

  • REST API: For one-time requests, state queries, service calls (HTTP polling)
  • WebSocket API: For real-time events, continuous subscriptions, lower latency (~50ms vs. seconds)
Why this matters: Different use cases require different APIs. WebSocket excels at real-time apps; REST is simpler for occasional requests.
  • REST API:适用于一次性请求、状态查询、服务调用(HTTP轮询)
  • WebSocket API:适用于实时事件、持续订阅、低延迟场景(延迟约50ms,相比REST的数秒)
重要性说明: 不同的使用场景需要不同的API。WebSocket擅长实时应用;REST则更适合偶尔的请求。

Critical Rules

关键规则

✅ Always Do

✅ 务必遵守

  • ✅ Store access tokens in environment variables or secure vaults (never hardcode)
  • ✅ Include "Bearer " prefix in Authorization header (exact case and spacing required)
  • ✅ Validate WebSocket auth_required messages before sending other commands
  • ✅ Handle HTTP errors (401 = token invalid/expired, 404 = entity not found, 502 = HA unavailable)
  • ✅ Specify full domain/service for service calls (e.g., "light/turn_on" not just "turn_on")
  • ✅ 将访问令牌存储在环境变量或安全密钥管理库中(切勿硬编码)
  • ✅ 在Authorization头中包含“Bearer ”前缀(大小写和空格必须完全匹配)
  • ✅ 在发送其他命令之前验证WebSocket的auth_required消息
  • ✅ 处理HTTP错误(401 = 令牌无效/过期,404 = 实体不存在,502 = Home Assistant不可用)
  • ✅ 服务调用时指定完整的域/服务(例如“light/turn_on”而非仅“turn_on”)

❌ Never Do

❌ 切勿操作

  • ❌ Commit access tokens to git or share in logs
  • ❌ Skip the initial WebSocket auth handshake
  • ❌ Mix Bearer token authentication with username/password
  • ❌ Assume state values are always strings (can be "on", 123, or null)
  • ❌ Call service endpoints with entity_id as path parameter (use JSON payload instead)
  • ❌ 将访问令牌提交到git或在日志中共享
  • ❌ 跳过初始WebSocket认证握手
  • ❌ 将Bearer令牌认证与用户名/密码混合使用
  • ❌ 假设状态值始终为字符串(可以是“on”、123或null)
  • ❌ 使用entity_id作为路径参数调用服务端点(改用JSON负载)

Common Mistakes

常见错误示例

❌ Wrong - Missing Bearer prefix:
bash
curl -X GET "http://ha:8123/api/states" \
  -H "Authorization: ${HA_TOKEN}"  # Missing "Bearer "
✅ Correct - Bearer prefix required:
bash
curl -X GET "http://ha:8123/api/states" \
  -H "Authorization: Bearer ${HA_TOKEN}"
Why: Home Assistant's API uses standard Bearer token authentication. The "Bearer " prefix tells the server this is a token-based auth scheme, not a username/password.
❌ 错误示例 - 缺失Bearer前缀:
bash
curl -X GET "http://ha:8123/api/states" \
  -H "Authorization: ${HA_TOKEN}"  # Missing "Bearer "
✅ 正确示例 - 必须添加Bearer前缀:
bash
curl -X GET "http://ha:8123/api/states" \
  -H "Authorization: Bearer ${HA_TOKEN}"
原因: Home Assistant的API使用标准Bearer令牌认证。“Bearer ”前缀告知服务器这是基于令牌的认证方案,而非用户名/密码认证。

REST API Endpoints Reference

REST API端点参考

States Endpoint

状态端点

Get all states:
bash
GET /api/states
Authorization: Bearer {token}
Response (200 OK):
json
[
  {
    "entity_id": "light.living_room",
    "state": "on",
    "attributes": {
      "brightness": 255,
      "color_mode": "color_temp",
      "friendly_name": "Living Room Light"
    },
    "last_changed": "2025-12-31T18:00:00+00:00",
    "last_updated": "2025-12-31T18:05:00+00:00"
  }
]
Get single entity state:
bash
GET /api/states/{entity_id}
Authorization: Bearer {token}
Create/update entity state:
bash
POST /api/states/{entity_id}
Authorization: Bearer {token}
Content-Type: application/json

{
  "state": "on",
  "attributes": {
    "friendly_name": "Custom Entity",
    "custom_attribute": "value"
  }
}
Response (201 Created or 200 OK):
json
{
  "entity_id": "sensor.custom_sensor",
  "state": "on",
  "attributes": { ... }
}
获取所有状态:
bash
GET /api/states
Authorization: Bearer {token}
响应(200 OK):
json
[
  {
    "entity_id": "light.living_room",
    "state": "on",
    "attributes": {
      "brightness": 255,
      "color_mode": "color_temp",
      "friendly_name": "Living Room Light"
    },
    "last_changed": "2025-12-31T18:00:00+00:00",
    "last_updated": "2025-12-31T18:05:00+00:00"
  }
]
获取单个实体状态:
bash
GET /api/states/{entity_id}
Authorization: Bearer {token}
创建/更新实体状态:
bash
POST /api/states/{entity_id}
Authorization: Bearer {token}
Content-Type: application/json

{
  "state": "on",
  "attributes": {
    "friendly_name": "Custom Entity",
    "custom_attribute": "value"
  }
}
响应(201 Created或200 OK):
json
{
  "entity_id": "sensor.custom_sensor",
  "state": "on",
  "attributes": { ... }
}

Services Endpoint

服务端点

Get all available services:
bash
GET /api/services
Authorization: Bearer {token}
Response (200 OK):
json
[
  {
    "domain": "light",
    "services": {
      "turn_on": {
        "description": "Turn on light(s)",
        "fields": {
          "entity_id": {
            "description": "The entity_id of the light(s)",
            "example": ["light.living_room", "light.bedroom"]
          },
          "brightness": {
            "description": "Brightness 0-255",
            "example": 180
          }
        }
      },
      "turn_off": { ... }
    }
  }
]
Call a service:
bash
POST /api/services/{domain}/{service}
Authorization: Bearer {token}
Content-Type: application/json

{
  "entity_id": "light.living_room",
  "brightness": 180,
  "transition": 2
}
Response (200 OK):
json
[
  {
    "entity_id": "light.living_room",
    "state": "on",
    "attributes": { ... }
  }
]
获取所有可用服务:
bash
GET /api/services
Authorization: Bearer {token}
响应(200 OK):
json
[
  {
    "domain": "light",
    "services": {
      "turn_on": {
        "description": "Turn on light(s)",
        "fields": {
          "entity_id": {
            "description": "The entity_id of the light(s)",
            "example": ["light.living_room", "light.bedroom"]
          },
          "brightness": {
            "description": "Brightness 0-255",
            "example": 180
          }
        }
      },
      "turn_off": { ... }
    }
  }
]
调用服务:
bash
POST /api/services/{domain}/{service}
Authorization: Bearer {token}
Content-Type: application/json

{
  "entity_id": "light.living_room",
  "brightness": 180,
  "transition": 2
}
响应(200 OK):
json
[
  {
    "entity_id": "light.living_room",
    "state": "on",
    "attributes": { ... }
  }
]

Events Endpoint

事件端点

Get all events:
bash
GET /api/events
Authorization: Bearer {token}
Fire an event:
bash
POST /api/events/{event_type}
Authorization: Bearer {token}
Content-Type: application/json

{
  "custom_data": "value"
}
获取所有事件:
bash
GET /api/events
Authorization: Bearer {token}
触发事件:
bash
POST /api/events/{event_type}
Authorization: Bearer {token}
Content-Type: application/json

{
  "custom_data": "value"
}

History Endpoint

历史记录端点

Get entity history:
bash
GET /api/history/period/{timestamp}?filter_entity_id={entity_id}
Authorization: Bearer {token}
Response (200 OK):
json
[
  [
    {
      "entity_id": "sensor.temperature",
      "state": "22.5",
      "attributes": { ... },
      "last_changed": "2025-12-31T12:00:00+00:00"
    }
  ]
]
获取实体历史记录:
bash
GET /api/history/period/{timestamp}?filter_entity_id={entity_id}
Authorization: Bearer {token}
响应(200 OK):
json
[
  [
    {
      "entity_id": "sensor.temperature",
      "state": "22.5",
      "attributes": { ... },
      "last_changed": "2025-12-31T12:00:00+00:00"
    }
  ]
]

Config Endpoint

配置端点

Get Home Assistant configuration:
bash
GET /api/config
Authorization: Bearer {token}
Response (200 OK):
json
{
  "latitude": 52.3,
  "longitude": 4.9,
  "elevation": 0,
  "unit_system": {
    "length": "km",
    "mass": "kg",
    "temperature": "°C",
    "volume": "L"
  },
  "time_zone": "Europe/Amsterdam",
  "components": ["light", "switch", "sensor", ...]
}
获取Home Assistant配置:
bash
GET /api/config
Authorization: Bearer {token}
响应(200 OK):
json
{
  "latitude": 52.3,
  "longitude": 4.9,
  "elevation": 0,
  "unit_system": {
    "length": "km",
    "mass": "kg",
    "temperature": "°C",
    "volume": "L"
  },
  "time_zone": "Europe/Amsterdam",
  "components": ["light", "switch", "sensor", ...]
}

Template Endpoint

模板端点

Render a template:
bash
POST /api/template
Authorization: Bearer {token}
Content-Type: application/json

{
  "template": "{{ states('sensor.temperature') }}"
}
Response (200 OK):
json
{
  "template": "{{ states('sensor.temperature') }}",
  "result": "22.5"
}
渲染模板:
bash
POST /api/template
Authorization: Bearer {token}
Content-Type: application/json

{
  "template": "{{ states('sensor.temperature') }}"
}
响应(200 OK):
json
{
  "template": "{{ states('sensor.temperature') }}",
  "result": "22.5"
}

WebSocket API Reference

WebSocket API参考

Connection Flow

连接流程

  1. Open WebSocket connection to
    /api/websocket
  2. Receive auth_required message (must respond within 10 seconds)
  3. Send auth message with token
  4. Receive auth_ok confirmation
  5. Send subscriptions/commands
  6. Receive responses and events in real-time
  1. 打开到
    /api/websocket
    的WebSocket连接
  2. 接收auth_required消息(必须在10秒内响应)
  3. 发送带令牌的认证消息
  4. 接收auth_ok确认
  5. 发送订阅/命令
  6. 实时接收响应与事件

WebSocket Commands

WebSocket命令

Authentication:
javascript
// Message 1: Server sends (automatically)
{
  "type": "auth_required",
  "ha_version": "2025.1.0"
}

// Message 2: Client responds
{
  "type": "auth",
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

// Message 3: Server confirms
{
  "type": "auth_ok",
  "ha_version": "2025.1.0"
}
Subscribe to events:
javascript
{
  "id": 1,
  "type": "subscribe_events",
  "event_type": "state_changed"
}

// Responses come as:
{
  "id": 1,
  "type": "event",
  "event": {
    "type": "state_changed",
    "data": {
      "entity_id": "light.living_room",
      "old_state": { "state": "off", ... },
      "new_state": { "state": "on", ... }
    }
  }
}
Call a service:
javascript
{
  "id": 2,
  "type": "call_service",
  "domain": "light",
  "service": "turn_on",
  "service_data": {
    "entity_id": "light.living_room",
    "brightness": 200
  }
}

// Response:
{
  "id": 2,
  "type": "result",
  "success": true,
  "result": [
    {
      "entity_id": "light.living_room",
      "state": "on",
      "attributes": { ... }
    }
  ]
}
Get current states:
javascript
{
  "id": 3,
  "type": "get_states"
}

// Response:
{
  "id": 3,
  "type": "result",
  "success": true,
  "result": [ ... ]  // Array of all entity states
}
Subscribe to specific trigger:
javascript
{
  "id": 4,
  "type": "subscribe_trigger",
  "trigger": {
    "platform": "state",
    "entity_id": "light.living_room"
  }
}

// Trigger fires when light state changes:
{
  "id": 4,
  "type": "event",
  "event": { ... }
}
认证:
javascript
// Message 1: Server sends (automatically)
{
  "type": "auth_required",
  "ha_version": "2025.1.0"
}

// Message 2: Client responds
{
  "type": "auth",
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

// Message 3: Server confirms
{
  "type": "auth_ok",
  "ha_version": "2025.1.0"
}
订阅事件:
javascript
{
  "id": 1,
  "type": "subscribe_events",
  "event_type": "state_changed"
}

// Responses come as:
{
  "id": 1,
  "type": "event",
  "event": {
    "type": "state_changed",
    "data": {
      "entity_id": "light.living_room",
      "old_state": { "state": "off", ... },
      "new_state": { "state": "on", ... }
    }
  }
}
调用服务:
javascript
{
  "id": 2,
  "type": "call_service",
  "domain": "light",
  "service": "turn_on",
  "service_data": {
    "entity_id": "light.living_room",
    "brightness": 200
  }
}

// Response:
{
  "id": 2,
  "type": "result",
  "success": true,
  "result": [
    {
      "entity_id": "light.living_room",
      "state": "on",
      "attributes": { ... }
    }
  ]
}
获取当前状态:
javascript
{
  "id": 3,
  "type": "get_states"
}

// Response:
{
  "id": 3,
  "type": "result",
  "success": true,
  "result": [ ... ]  // Array of all entity states
}
订阅特定触发器:
javascript
{
  "id": 4,
  "type": "subscribe_trigger",
  "trigger": {
    "platform": "state",
    "entity_id": "light.living_room"
  }
}

// Trigger fires when light state changes:
{
  "id": 4,
  "type": "event",
  "event": { ... }
}

Code Examples

代码示例

Python - REST API

Python - REST API

python
import requests
import os

HA_URL = os.getenv("HA_URL", "http://localhost:8123")
HA_TOKEN = os.getenv("HA_TOKEN")

headers = {
    "Authorization": f"Bearer {HA_TOKEN}",
    "Content-Type": "application/json"
}
python
import requests
import os

HA_URL = os.getenv("HA_URL", "http://localhost:8123")
HA_TOKEN = os.getenv("HA_TOKEN")

headers = {
    "Authorization": f"Bearer {HA_TOKEN}",
    "Content-Type": "application/json"
}

Get all states

Get all states

response = requests.get(f"{HA_URL}/api/states", headers=headers) response.raise_for_status() states = response.json()
response = requests.get(f"{HA_URL}/api/states", headers=headers) response.raise_for_status() states = response.json()

Get single entity

Get single entity

response = requests.get(f"{HA_URL}/api/states/light.living_room", headers=headers) light_state = response.json() print(f"Light state: {light_state['state']}") print(f"Brightness: {light_state['attributes'].get('brightness', 'N/A')}")
response = requests.get(f"{HA_URL}/api/states/light.living_room", headers=headers) light_state = response.json() print(f"Light state: {light_state['state']}") print(f"Brightness: {light_state['attributes'].get('brightness', 'N/A')}")

Call service

Call service

service_data = { "entity_id": "light.living_room", "brightness": 180, "transition": 2 } response = requests.post( f"{HA_URL}/api/services/light/turn_on", headers=headers, json=service_data ) response.raise_for_status() print(f"Service call successful: {response.json()}")
service_data = { "entity_id": "light.living_room", "brightness": 180, "transition": 2 } response = requests.post( f"{HA_URL}/api/services/light/turn_on", headers=headers, json=service_data ) response.raise_for_status() print(f"Service call successful: {response.json()}")

Handle errors

Handle errors

try: response = requests.get(f"{HA_URL}/api/states/invalid.entity", headers=headers) response.raise_for_status() except requests.exceptions.HTTPError as e: if e.response.status_code == 401: print("ERROR: Token invalid or expired") elif e.response.status_code == 404: print("ERROR: Entity not found") else: print(f"ERROR: {e}")
undefined
try: response = requests.get(f"{HA_URL}/api/states/invalid.entity", headers=headers) response.raise_for_status() except requests.exceptions.HTTPError as e: if e.response.status_code == 401: print("ERROR: Token invalid or expired") elif e.response.status_code == 404: print("ERROR: Entity not found") else: print(f"ERROR: {e}")
undefined

Python - WebSocket API

Python - WebSocket API

python
import asyncio
import aiohttp
import json
import os

HA_URL = os.getenv("HA_URL", "http://localhost:8123").replace("http", "ws")
HA_TOKEN = os.getenv("HA_TOKEN")

async def monitor_light_changes():
    async with aiohttp.ClientSession() as session:
        async with session.ws_connect(f"{HA_URL}/api/websocket") as ws:
            # Wait for auth_required
            msg = await ws.receive_json()
            print(f"Server: {msg}")

            # Send auth
            await ws.send_json({
                "type": "auth",
                "access_token": HA_TOKEN
            })

            # Wait for auth_ok
            msg = await ws.receive_json()
            print(f"Server: {msg}")

            # Subscribe to state changes
            await ws.send_json({
                "id": 1,
                "type": "subscribe_events",
                "event_type": "state_changed"
            })

            # Listen for events
            async for msg in ws:
                event = msg.json()
                if event.get("type") == "event":
                    data = event["event"]["data"]
                    print(f"Entity: {data['entity_id']}")
                    print(f"  Old state: {data['old_state']['state']}")
                    print(f"  New state: {data['new_state']['state']}")
python
import asyncio
import aiohttp
import json
import os

HA_URL = os.getenv("HA_URL", "http://localhost:8123").replace("http", "ws")
HA_TOKEN = os.getenv("HA_TOKEN")

async def monitor_light_changes():
    async with aiohttp.ClientSession() as session:
        async with session.ws_connect(f"{HA_URL}/api/websocket") as ws:
            # Wait for auth_required
            msg = await ws.receive_json()
            print(f"Server: {msg}")

            # Send auth
            await ws.send_json({
                "type": "auth",
                "access_token": HA_TOKEN
            })

            # Wait for auth_ok
            msg = await ws.receive_json()
            print(f"Server: {msg}")

            # Subscribe to state changes
            await ws.send_json({
                "id": 1,
                "type": "subscribe_events",
                "event_type": "state_changed"
            })

            # Listen for events
            async for msg in ws:
                event = msg.json()
                if event.get("type") == "event":
                    data = event["event"]["data"]
                    print(f"Entity: {data['entity_id']}")
                    print(f"  Old state: {data['old_state']['state']}")
                    print(f"  New state: {data['new_state']['state']}")

Run the monitoring loop

Run the monitoring loop

asyncio.run(monitor_light_changes())
undefined
asyncio.run(monitor_light_changes())
undefined

JavaScript - REST API

JavaScript - REST API

javascript
const HA_URL = process.env.HA_URL || "http://localhost:8123";
const HA_TOKEN = process.env.HA_TOKEN;

const headers = {
  "Authorization": `Bearer ${HA_TOKEN}`,
  "Content-Type": "application/json"
};

// Get all states
async function getAllStates() {
  const response = await fetch(`${HA_URL}/api/states`, { headers });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  return response.json();
}

// Get single entity
async function getEntityState(entityId) {
  const response = await fetch(`${HA_URL}/api/states/${entityId}`, { headers });
  if (response.status === 404) {
    throw new Error(`Entity ${entityId} not found`);
  }
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const state = await response.json();
  console.log(`${entityId}: ${state.state}`);
  console.log(`Attributes:`, state.attributes);
  return state;
}

// Call service
async function callService(domain, service, data) {
  const response = await fetch(
    `${HA_URL}/api/services/${domain}/${service}`,
    {
      method: "POST",
      headers,
      body: JSON.stringify(data)
    }
  );
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`HTTP ${response.status}: ${error}`);
  }
  return response.json();
}

// Example usage
(async () => {
  try {
    const states = await getAllStates();
    console.log(`Found ${states.length} entities`);

    await getEntityState("light.living_room");

    const result = await callService("light", "turn_on", {
      entity_id: "light.living_room",
      brightness: 180
    });
    console.log("Service call successful");
  } catch (error) {
    console.error(error);
  }
})();
javascript
const HA_URL = process.env.HA_URL || "http://localhost:8123";
const HA_TOKEN = process.env.HA_TOKEN;

const headers = {
  "Authorization": `Bearer ${HA_TOKEN}`,
  "Content-Type": "application/json"
};

// Get all states
async function getAllStates() {
  const response = await fetch(`${HA_URL}/api/states`, { headers });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  return response.json();
}

// Get single entity
async function getEntityState(entityId) {
  const response = await fetch(`${HA_URL}/api/states/${entityId}`, { headers });
  if (response.status === 404) {
    throw new Error(`Entity ${entityId} not found`);
  }
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const state = await response.json();
  console.log(`${entityId}: ${state.state}`);
  console.log(`Attributes:`, state.attributes);
  return state;
}

// Call service
async function callService(domain, service, data) {
  const response = await fetch(
    `${HA_URL}/api/services/${domain}/${service}`,
    {
      method: "POST",
      headers,
      body: JSON.stringify(data)
    }
  );
  if (!response.ok) {
    const error = await response.text();
    throw new Error(`HTTP ${response.status}: ${error}`);
  }
  return response.json();
}

// Example usage
(async () => {
  try {
    const states = await getAllStates();
    console.log(`Found ${states.length} entities`);

    await getEntityState("light.living_room");

    const result = await callService("light", "turn_on", {
      entity_id: "light.living_room",
      brightness: 180
    });
    console.log("Service call successful");
  } catch (error) {
    console.error(error);
  }
})();

JavaScript - WebSocket API

JavaScript - WebSocket API

javascript
const HA_URL = (process.env.HA_URL || "http://localhost:8123").replace(/^http/, "ws");
const HA_TOKEN = process.env.HA_TOKEN;

async function subscribeToStateChanges() {
  const ws = new WebSocket(`${HA_URL}/api/websocket`);

  return new Promise((resolve, reject) => {
    ws.onopen = () => console.log("WebSocket connected");

    ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      console.log("Message:", msg);

      if (msg.type === "auth_required") {
        // Respond to auth challenge
        ws.send(JSON.stringify({
          type: "auth",
          access_token: HA_TOKEN
        }));
      } else if (msg.type === "auth_ok") {
        // Authentication successful, subscribe to events
        ws.send(JSON.stringify({
          id: 1,
          type: "subscribe_events",
          event_type: "state_changed"
        }));
      } else if (msg.type === "event" && msg.event?.type === "state_changed") {
        // Handle state change
        const data = msg.event.data;
        console.log(`${data.entity_id} changed:`);
        console.log(`  ${data.old_state.state}${data.new_state.state}`);
      }
    };

    ws.onerror = (error) => {
      console.error("WebSocket error:", error);
      reject(error);
    };

    ws.onclose = () => {
      console.log("WebSocket closed");
      resolve();
    };
  });
}

subscribeToStateChanges();
javascript
const HA_URL = (process.env.HA_URL || "http://localhost:8123").replace(/^http/, "ws");
const HA_TOKEN = process.env.HA_TOKEN;

async function subscribeToStateChanges() {
  const ws = new WebSocket(`${HA_URL}/api/websocket`);

  return new Promise((resolve, reject) => {
    ws.onopen = () => console.log("WebSocket connected");

    ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      console.log("Message:", msg);

      if (msg.type === "auth_required") {
        // Respond to auth challenge
        ws.send(JSON.stringify({
          type: "auth",
          access_token: HA_TOKEN
        }));
      } else if (msg.type === "auth_ok") {
        // Authentication successful, subscribe to events
        ws.send(JSON.stringify({
          id: 1,
          type: "subscribe_events",
          event_type: "state_changed"
        }));
      } else if (msg.type === "event" && msg.event?.type === "state_changed") {
        // Handle state change
        const data = msg.event.data;
        console.log(`${data.entity_id} changed:`);
        console.log(`  ${data.old_state.state}${data.new_state.state}`);
      }
    };

    ws.onerror = (error) => {
      console.error("WebSocket error:", error);
      reject(error);
    };

    ws.onclose = () => {
      console.log("WebSocket closed");
      resolve();
    };
  });
}

subscribeToStateChanges();

cURL - Common Operations

cURL - 常见操作

bash
undefined
bash
undefined

Get all entities

Get all entities

curl -X GET "http://localhost:8123/api/states"
-H "Authorization: Bearer ${HA_TOKEN}"
curl -X GET "http://localhost:8123/api/states"
-H "Authorization: Bearer ${HA_TOKEN}"

Get specific entity

Get specific entity

curl -X GET "http://localhost:8123/api/states/light.living_room"
-H "Authorization: Bearer ${HA_TOKEN}"
curl -X GET "http://localhost:8123/api/states/light.living_room"
-H "Authorization: Bearer ${HA_TOKEN}"

Turn on light with brightness

Turn on light with brightness

curl -X POST "http://localhost:8123/api/services/light/turn_on"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{ "entity_id": "light.living_room", "brightness": 200, "transition": 2 }'
curl -X POST "http://localhost:8123/api/services/light/turn_on"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{ "entity_id": "light.living_room", "brightness": 200, "transition": 2 }'

Turn off light

Turn off light

curl -X POST "http://localhost:8123/api/services/light/turn_off"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{"entity_id": "light.living_room"}'
curl -X POST "http://localhost:8123/api/services/light/turn_off"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{"entity_id": "light.living_room"}'

Set climate temperature

Set climate temperature

curl -X POST "http://localhost:8123/api/services/climate/set_temperature"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{ "entity_id": "climate.living_room", "temperature": 21 }'
curl -X POST "http://localhost:8123/api/services/climate/set_temperature"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{ "entity_id": "climate.living_room", "temperature": 21 }'

Get service schema

Get service schema

curl -X GET "http://localhost:8123/api/services/light"
-H "Authorization: Bearer ${HA_TOKEN}"
curl -X GET "http://localhost:8123/api/services/light"
-H "Authorization: Bearer ${HA_TOKEN}"

Call automation

Call automation

curl -X POST "http://localhost:8123/api/services/automation/trigger"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{"entity_id": "automation.my_automation"}'
curl -X POST "http://localhost:8123/api/services/automation/trigger"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{"entity_id": "automation.my_automation"}'

Render template

Render template

curl -X POST "http://localhost:8123/api/template"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{"template": "{{ states("sensor.temperature") }}"}'
undefined
curl -X POST "http://localhost:8123/api/template"
-H "Authorization: Bearer ${HA_TOKEN}"
-H "Content-Type: application/json"
-d '{"template": "{{ states("sensor.temperature") }}"}'
undefined

Known Issues Prevention

已知问题预防

IssueRoot CauseSolution
401 UnauthorizedToken invalid, expired, or malformedCreate new token in Settings → Create Long-Lived Access Token; verify Bearer prefix
404 Not FoundEntity doesn't exist or wrong domainCheck entity_id with
GET /api/states
; verify domain.service format
WebSocket auth timeoutDidn't respond to auth_required within 10sSend auth message immediately upon receiving auth_required
Attribute confusionMixing state string with attributes objectState is always a string; attributes contain metadata (brightness, color, etc.)
Service call fails silentlyWrong domain/service or missing required fieldsUse
GET /api/services
to discover available services and required parameters
问题根本原因解决方案
401 未授权令牌无效、过期或格式错误在设置中创建新的长期访问令牌;验证Bearer前缀
404 未找到实体不存在或域错误通过
GET /api/states
检查entity_id;验证domain.service格式
WebSocket认证超时未在10秒内响应auth_required收到auth_required后立即发送认证消息
属性混淆混淆状态字符串与属性对象状态始终是字符串;属性包含元数据(亮度、颜色等)
服务调用无响应失败域/服务错误或缺失必填字段使用
GET /api/services
发现可用服务和必填参数

Error Handling Patterns

错误处理模式

HTTP Status Codes

HTTP状态码

python
import requests

try:
    response = requests.get(f"{HA_URL}/api/states/{entity_id}", headers=headers)

    if response.status_code == 401:
        print("Token invalid/expired - create new token in HA UI")
    elif response.status_code == 403:
        print("Forbidden - token lacks necessary permissions")
    elif response.status_code == 404:
        print("Entity not found - check entity_id")
    elif response.status_code == 502:
        print("Home Assistant unavailable - check server status")
    elif response.status_code >= 500:
        print("Server error - try again later")
    else:
        response.raise_for_status()
        return response.json()
except requests.exceptions.ConnectionError:
    print("Cannot connect to Home Assistant - verify HA_URL and network")
except requests.exceptions.Timeout:
    print("Request timeout - Home Assistant is slow to respond")
python
import requests

try:
    response = requests.get(f"{HA_URL}/api/states/{entity_id}", headers=headers)

    if response.status_code == 401:
        print("Token invalid/expired - create new token in HA UI")
    elif response.status_code == 403:
        print("Forbidden - token lacks necessary permissions")
    elif response.status_code == 404:
        print("Entity not found - check entity_id")
    elif response.status_code == 502:
        print("Home Assistant unavailable - check server status")
    elif response.status_code >= 500:
        print("Server error - try again later")
    else:
        response.raise_for_status()
        return response.json()
except requests.exceptions.ConnectionError:
    print("Cannot connect to Home Assistant - verify HA_URL and network")
except requests.exceptions.Timeout:
    print("Request timeout - Home Assistant is slow to respond")

WebSocket Error Handling

WebSocket错误处理

javascript
const ws = new WebSocket(wsUrl);

ws.onerror = (error) => {
  console.error("WebSocket error:", error);
  // Reconnect after delay
  setTimeout(connect, 5000);
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === "result" && !msg.success) {
    console.error("Command failed:", msg.error);
  }
};

ws.onclose = () => {
  console.log("Connection closed");
  // Implement reconnection logic
  setTimeout(connect, 3000);
};
javascript
const ws = new WebSocket(wsUrl);

ws.onerror = (error) => {
  console.error("WebSocket error:", error);
  // Reconnect after delay
  setTimeout(connect, 5000);
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === "result" && !msg.success) {
    console.error("Command failed:", msg.error);
  }
};

ws.onclose = () => {
  console.log("Connection closed");
  // Implement reconnection logic
  setTimeout(connect, 3000);
};

Bundled Resources

附带资源

References

参考文档

Located in
references/
:
  • REST_API_REFERENCE.md
    - Complete REST endpoint documentation
  • WEBSOCKET_API_REFERENCE.md
    - WebSocket command reference
  • AUTHENTICATION.md
    - Auth token creation and security best practices
  • SERVICE_CATALOG.md
    - Common services by domain
Note: For deep dives on specific topics, see the reference files above.
位于
references/
目录下:
  • REST_API_REFERENCE.md
    - 完整的REST端点文档
  • WEBSOCKET_API_REFERENCE.md
    - WebSocket命令参考
  • AUTHENTICATION.md
    - 认证令牌创建与安全最佳实践
  • SERVICE_CATALOG.md
    - 按域分类的常见服务
注意: 如需深入了解特定主题,请查看上述参考文件。

Dependencies

依赖项

Required

必需依赖

PackageLanguagePurpose
requestsPythonHTTP client for REST API calls
aiohttpPythonAsync HTTP/WebSocket client
fetchJavaScriptNative HTTP client (browser/Node.js)
WebSocketJavaScriptNative WebSocket API (browser/Node.js)
语言用途
requestsPythonREST API调用的HTTP客户端
aiohttpPython异步HTTP/WebSocket客户端
fetchJavaScript原生HTTP客户端(浏览器/Node.js)
WebSocketJavaScript原生WebSocket API(浏览器/Node.js)

Optional

可选依赖

PackageLanguagePurpose
httpxPythonAdvanced HTTP client with streaming
websocketsPythonPure Python WebSocket library
axiosJavaScriptPromise-based HTTP client
语言用途
httpxPython支持流的高级HTTP客户端
websocketsPython纯Python WebSocket库
axiosJavaScript基于Promise的HTTP客户端

Official Documentation

官方文档

Troubleshooting

故障排除

Cannot authenticate - 401 Unauthorized

无法认证 - 401未授权

Symptoms: All API requests return 401, even with correct token.
Solution:
  1. Verify token in Home Assistant (Settings → Developer Tools → States/Services)
  2. Check Authorization header includes "Bearer " prefix
  3. Create new token if old one expired
  4. Ensure HA_URL and HA_TOKEN environment variables are set
bash
echo $HA_TOKEN  # Verify token is set
echo $HA_URL    # Verify URL is correct (http not https)
症状: 所有API请求均返回401,即使令牌正确。
解决方案:
  1. 在Home Assistant中验证令牌(设置 → 开发者工具 → 状态/服务)
  2. 检查Authorization头是否包含“Bearer ”前缀
  3. 若令牌过期,创建新令牌
  4. 确保已设置HA_URL和HA_TOKEN环境变量
bash
echo $HA_TOKEN  # Verify token is set
echo $HA_URL    # Verify URL is correct (http not https)

WebSocket connection closes immediately

WebSocket连接立即关闭

Symptoms: WebSocket connects then closes without response.
Solution:
  1. Verify Home Assistant version supports WebSocket API (2021.1+)
  2. Check firewall doesn't block WebSocket upgrade
  3. Ensure auth response is sent within 10 seconds of auth_required
  4. Use correct
    /api/websocket
    path, not
    /api/websocket/
javascript
// Incorrect path
ws = new WebSocket("ws://ha:8123/api/websocket/");

// Correct path
ws = new WebSocket("ws://ha:8123/api/websocket");
症状: WebSocket连接后立即关闭,无响应。
解决方案:
  1. 验证Home Assistant版本支持WebSocket API(2021.1+)
  2. 检查防火墙是否阻止WebSocket升级
  3. 确保在收到auth_required后10秒内发送认证响应
  4. 使用正确的
    /api/websocket
    路径,而非
    /api/websocket/
javascript
// Incorrect path
ws = new WebSocket("ws://ha:8123/api/websocket/");

// Correct path
ws = new WebSocket("ws://ha:8123/api/websocket");

Entity states appear as wrong type

实体状态类型错误

Symptoms: State should be "on"/"off" but appears as number or boolean.
Solution:
  1. Check entity's integration configuration
  2. Reload integration in Developer Tools
  3. Verify template/custom component isn't converting type
  4. State is always a string in API response; parse as needed
python
state = entity["state"]  # String: "on", "off", "123"
is_on = state == "on"   # Convert to boolean
症状: 状态应为“on”/“off”,但显示为数字或布尔值。
解决方案:
  1. 检查实体的集成配置
  2. 在开发者工具中重新加载集成
  3. 验证模板/自定义组件未转换类型
  4. API响应中的状态始终是字符串;按需解析
python
state = entity["state"]  # String: "on", "off", "123"
is_on = state == "on"   # Convert to boolean

For numeric states

For numeric states

temperature = float(entity["state"]) # Convert to float
undefined
temperature = float(entity["state"]) # Convert to float
undefined

Service call fails but no error returned

服务调用失败但无错误返回

Symptoms: Service call returns HTTP 200 but nothing happens.
Solution:
  1. Verify service exists:
    GET /api/services/{domain}
  2. Check required fields in service_data
  3. Verify entity_id is in entity_ids list, not as separate parameter
  4. Check entity supports service (light.turn_on won't work on switch)
bash
undefined
症状: 服务调用返回HTTP 200,但无任何效果。
解决方案:
  1. 验证服务是否存在:
    GET /api/services/{domain}
  2. 检查service_data中的必填字段
  3. 验证entity_id是否在entity_ids列表中,而非作为单独参数
  4. 检查实体是否支持该服务(light.turn_on不适用于开关)
bash
undefined

Correct - entity_id in service_data

Correct - entity_id in service_data

curl -X POST "http://ha:8123/api/services/light/turn_on"
-d '{"entity_id": "light.living_room"}'
curl -X POST "http://ha:8123/api/services/light/turn_on"
-d '{"entity_id": "light.living_room"}'

Incorrect - entity_id as path parameter

Incorrect - entity_id as path parameter

Setup Checklist

设置检查清单

Before using this skill, verify:
  • Home Assistant instance is running and accessible (test via browser)
  • Created Long-Lived Access Token (Settings → My Home → Create Token)
  • Token stored in environment variable (HA_TOKEN) or secure vault
  • Home Assistant URL set correctly (HA_URL with http not https)
  • Firewall allows API/WebSocket connections (ports 8123 or custom)
  • Python packages installed if using Python examples (pip install requests aiohttp)
  • Tested connectivity with simple curl/fetch request before building full integration
使用该技能前,请验证:
  • Home Assistant实例正在运行且可访问(通过浏览器测试)
  • 已创建长期访问令牌(设置 → 我的主页 → 创建令牌)
  • 令牌已存储在环境变量(HA_TOKEN)或安全密钥管理库中
  • Home Assistant URL设置正确(HA_URL使用http而非https)
  • 防火墙允许API/WebSocket连接(端口8123或自定义端口)
  • 若使用Python示例,已安装所需Python包(pip install requests aiohttp)
  • 在构建完整集成之前,已通过简单的curl/fetch请求测试连通性