mcpField
H
ha-api

ha-api

github.com/nodnarbnitram開発1684 回インストール27 週間前に更新
リポジトリ

ha-api について

ha-apiは、Home AssistantのREST APIとWebSocket APIを使った連携を支援するスキルです。認証、状態やサービスの操作、リアルタイム購読、エラー処理を開発者向けに解説します。

主な機能

  1. 01RESTエンドポイントの解説
  2. 02WebSocket認証と購読
  3. 03エンティティ状態とサービスの操作
  4. 04エラー処理とトラブルシューティング
  5. 05Python、JavaScript、cURLの例

活用シーン

  1. 01Home Assistantのエンティティ状態の取得
  2. 02外部アプリからのサービス呼び出し
  3. 03状態変更イベントのリアルタイム受信

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nodnarbnitram/claude-code-extensions --skill ha-api

インストール後、Claude Code で/ha-apiを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

Home Assistant API 連携

Home Assistant の REST API と WebSocket API を習得し、外部連携、状態管理、リアルタイム通信を実現します。

⚠️ 始める前に

このスキルは、API 連携でよくある 5 つのエラーを防ぎ、トークンのオーバーヘッドを約 30%削減します。

項目詳細
防止できる一般的なエラー5 件以上(認証、WebSocket のライフサイクル、状態形式、エラー処理)
トークンの削減量手動で API を調査する場合と比べて約 30%
セットアップ時間2~5 分(手動の場合は 15~20 分)
このスキルで防げる既知の問題
  1. 認証ヘッダーの誤り - Bearer トークンには、Authorization ヘッダーで「Bearer 」を前置する必要があります
  2. WebSocket のライフサイクル管理 - auth_required の処理漏れや、状態サブスクリプションの誤り
  3. 状態形式の不一致 - state と attributes の混同、JSON ペイロード構造の誤り
  4. エラーレスポンスの処理 - 4xx クライアントエラーと 5xx サーバーエラーを区別しないこと
  5. サービス呼び出し時のドメイン/サービスの不一致 - サービスエンドポイントへのルーティングの誤り

クイックスタート

ステップ 1:認証の設定
# In Home Assistant UI:
# Settings → My Home → Create Long-Lived Access Token
# Store token securely (never commit to git)

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

重要な理由: すべての API リクエストには Bearer トークン認証が必要です。外部アプリ用に専用トークンを作成しておけば、パスワードを変更せずにアクセスを取り消せます。

ステップ 2:REST API の接続をテストする
# Get all entity states
curl -X GET "${HA_URL}/api/states" \
  -H "Authorization: Bearer ${HA_TOKEN}" \
  -H "Content-Type: application/json"

重要な理由: 複雑な連携を構築する前に、Home Assistant のインスタンスにアクセスできることと、トークンが有効であることを確認します。

ステップ 3:API の種類を選択する
  • REST API:単発のリクエスト、状態の照会、サービス呼び出しに使用します(HTTP ポーリング)。
  • WebSocket API:リアルタイムイベント、継続的なサブスクリプション、低レイテンシ(数秒に対して約 50 ミリ秒)に使用します。

重要な理由: 用途によって適した API は異なります。リアルタイムアプリには WebSocket が適しており、たまにリクエストする場合は REST のほうがシンプルです。

重要なルール

✅ 必ず行うこと
  • ✅ アクセストークンは環境変数またはセキュアな保管庫に保存します(ハードコードしないでください)。
  • ✅ Authorization ヘッダーには「Bearer 」を前置します(大文字・小文字とスペースを正確に指定する必要があります)。
  • ✅ ほかのコマンドを送信する前に、WebSocket の auth_required メッセージを検証します。
  • ✅ HTTP エラーを処理します(401 = トークンが無効または期限切れ、404 = エンティティが見つからない、502 = HA を利用できない)。
  • ✅ サービス呼び出しには完全な domain/service を指定します(例:「turn_on」だけでなく「light/turn_on」)。
❌ 絶対にしないこと
  • ❌ アクセストークンを git にコミットしたり、ログで共有したりしないでください。
  • ❌ WebSocket の初期認証ハンドシェイクを省略しないでください。
  • ❌ Bearer トークン認証とユーザー名/パスワード認証を混在させないでください。
  • ❌ 状態値は常に文字列だと想定しないでください(「on」、123、null の場合があります)。
  • ❌ entity_id をパスパラメーターとして指定してサービスエンドポイントを呼び出さないでください(代わりに JSON ペイロードを使用します)。
よくある間違い

❌ 誤り - Bearer の前置がない:

curl -X GET "http://ha:8123/api/states" \
  -H "Authorization: ${HA_TOKEN}"  # Missing "Bearer "

✅ 正しい - Bearer の前置が必要:

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

理由: Home Assistant の API は標準の Bearer トークン認証を使用します。「Bearer 」という接頭辞によって、ユーザー名/パスワードではなくトークンベースの認証方式であることをサーバーに伝えます。

REST API エンドポイントのリファレンス

States エンドポイント

すべての状態を取得:

GET /api/states
Authorization: Bearer {token}

レスポンス(200 OK):

[
  {
    "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 /api/states/{entity_id}
Authorization: Bearer {token}

エンティティの状態を作成/更新:

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):

{
  "entity_id": "sensor.custom_sensor",
  "state": "on",
  "attributes": { ... }
}
Services エンドポイント

利用可能なすべてのサービスを取得:

GET /api/services
Authorization: Bearer {token}

レスポンス(200 OK):

[
  {
    "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": { ... }
    }
  }
]

サービスを呼び出す:

POST /api/services/{domain}/{service}
Authorization: Bearer {token}
Content-Type: application/json

{
  "entity_id": "light.living_room",
  "brightness": 180,
  "transition": 2
}

レスポンス(200 OK):

[
  {
    "entity_id": "light.living_room",
    "state": "on",
    "attributes": { ... }
  }
]
Events エンドポイント

すべてのイベントを取得:

GET /api/events
Authorization: Bearer {token}

イベントを発火する:

POST /api/events/{event_type}
Authorization: Bearer {token}
Content-Type: application/json

{
  "custom_data": "value"
}
History エンドポイント

エンティティの履歴を取得:

GET /api/history/period/{timestamp}?filter_entity_id={entity_id}
Authorization: Bearer {token}

レスポンス(200 OK):

[
  [
    {
      "entity_id": "sensor.temperature",
      "state": "22.5",
      "attributes": { ... },
      "last_changed": "2025-12-31T12:00:00+00:00"
    }
  ]
]
Config エンドポイント

Home Assistant の設定を取得:

GET /api/config
Authorization: Bearer {token}

レスポンス(200 OK):

{
  "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 エンドポイント

テンプレートをレンダリングする:

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

{
  "template": "{{ states('sensor.temperature') }}"
}

レスポンス(200 OK):

{
  "template": "{{ states('sensor.temperature') }}",
  "result": "22.5"
}

WebSocket API リファレンス

接続フロー
  1. /api/websocket への WebSocket 接続を開きます
  2. auth_required メッセージを受信します(10 秒以内に応答する必要があります)
  3. トークンを含む auth メッセージを送信します
  4. auth_ok 確認を受信します
  5. サブスクリプション/コマンドを送信します
  6. 応答とイベントをリアルタイムで受信します
WebSocket コマンド

認証:

// 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"
}

イベントをサブスクライブする:

{
  "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", ... }
    }
  }
}

サービスを呼び出す:

{
  "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": { ... }
    }
  ]
}

現在の状態を取得する:

{
  "id": 3,
  "type": "get_states"
}

// Response:
{
  "id": 3,
  "type": "result",
  "success": true,
  "result": [ ... ]  // Array of all entity states
}

特定のトリガーをサブスクライブする:

{
  "id": 4,
  "type": "subscribe_trigger",
  "trigger": {
    "platform": "state",
    "entity_id": "light.living_room"
  }
}

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

コード例

Python - REST API
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
response = requests.get(f"{HA_URL}/api/states", headers=headers)
response.raise_for_status()
states = response.json()

# 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')}")

# 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()}")

# 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}")
Python - WebSocket API
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
asyncio.run(monitor_light_changes())
JavaScript - REST API
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
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 - 一般的な操作
# Get all entities
curl -X GET "http://localhost:8123/api/states" \
  -H "Authorization: Bearer ${HA_TOKEN}"

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

# 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
  }'

# 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"}'

# 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
  }'

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

# 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"}'

# 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\") }}"}'

既知の問題の予防

問題根本原因解決策
401 Unauthorizedトークンが無効、期限切れ、または形式が正しくありませんSettings → Create Long-Lived Access Token で新しいトークンを作成し、Bearer プレフィックスを確認します
404 Not Foundエンティティが存在しないか、ドメインが間違っていますGET /api/states で entity_id を確認し、domain.service 形式を確認します
WebSocket 認証タイムアウトauth_required に 10 秒以内に応答しませんでしたauth_required を受信したらすぐに auth メッセージを送信します
属性の混同state 文字列と attributes オブジェクトを混同していますstate は常に文字列です。attributes にはメタデータ(明るさ、色など)が含まれます
サービス呼び出しが失敗しても通知されないドメイン/サービスが間違っているか、必須フィールドが不足していますGET /api/services を使って、利用可能なサービスと必須パラメーターを確認します

エラー処理パターン

HTTP ステータスコード
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 エラー処理
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);
};

同梱リソース

リファレンス

references/ にあります:

Note: 特定のトピックを詳しく調べる場合は、上記のリファレンスファイルを参照してください。

依存関係

必須
パッケージ言語用途
requestsPythonREST API 呼び出し用の HTTP クライアント
aiohttpPython非同期 HTTP/WebSocket クライアント
fetchJavaScriptネイティブ HTTP クライアント(ブラウザー/Node.js)
WebSocketJavaScriptネイティブ WebSocket API(ブラウザー/Node.js)
オプション
パッケージ言語用途
httpxPythonストリーミングに対応した高度な HTTP クライアント
websocketsPython純粋な Python WebSocket ライブラリ
axiosJavaScriptPromise ベースの HTTP クライアント

公式ドキュメント

トラブルシューティング

認証できない - 401 Unauthorized

症状: 正しいトークンを使っていても、すべての API リクエストが 401 を返します。

解決策:

  1. Home Assistant でトークンを確認します(Settings → Developer Tools → States/Services)
  2. Authorization ヘッダーに「Bearer 」プレフィックスが含まれていることを確認します
  3. 古いトークンの有効期限が切れている場合は、新しいトークンを作成します
  4. HA_URL と HA_TOKEN の環境変数が設定されていることを確認します
echo $HA_TOKEN  # Verify token is set
echo $HA_URL    # Verify URL is correct (http not https)
WebSocket 接続がすぐに閉じる

症状: WebSocket は接続しますが、応答なしで閉じます。

解決策:

  1. Home Assistant のバージョンが WebSocket API に対応していることを確認します(2021.1+)
  2. ファイアウォールが WebSocket のアップグレードをブロックしていないことを確認します
  3. auth_required を受信してから 10 秒以内に認証応答を送信します
  4. /api/websocket/ ではなく、正しい /api/websocket パスを使用します
// Incorrect path
ws = new WebSocket("ws://ha:8123/api/websocket/");

// Correct path
ws = new WebSocket("ws://ha:8123/api/websocket");
エンティティの状態が誤った型で表示される

症状: 状態は「on」/「off」であるはずなのに、数値またはブール値として表示されます。

解決策:

  1. エンティティのインテグレーション設定を確認します
  2. 開発者ツールでインテグレーションを再読み込みします
  3. テンプレートやカスタムコンポーネントによって型が変換されていないか確認します
  4. API レスポンスでは状態は常に文字列です。必要に応じて解析してください
state = entity["state"]  # String: "on", "off", "123"
is_on = state == "on"   # Convert to boolean

# For numeric states
temperature = float(entity["state"])  # Convert to float
サービス呼び出しが失敗するが、エラーが返されない

症状: サービス呼び出しは HTTP 200 を返しますが、何も起こりません。

解決策:

  1. サービスが存在することを確認します:GET /api/services/{domain}
  2. service_data の必須フィールドを確認します
  3. entity_id が別のパラメーターとしてではなく、entity_ids リストに含まれていることを確認します
  4. エンティティがそのサービスに対応していることを確認します(light.turn_on は switch では動作しません)
# Correct - entity_id in service_data
curl -X POST "http://ha:8123/api/services/light/turn_on" \
  -d '{"entity_id": "light.living_room"}'

# Incorrect - entity_id as path parameter
curl -X POST "http://ha:8123/api/services/light/turn_on/light.living_room"

セットアップチェックリスト

このスキルを使用する前に、以下を確認してください。

  • Home Assistant インスタンスが稼働しており、アクセス可能(ブラウザーでテスト)
  • Long-Lived Access Token を作成済み(Settings → My Home → Create Token)
  • トークンを環境変数(HA_TOKEN)またはセキュアな保管庫に保存済み
  • Home Assistant URL が正しく設定されている(HA_URL は https ではなく http)
  • ファイアウォールで API/WebSocket 接続が許可されている(ポート 8123 またはカスタムポート)
  • Python の例を使用する場合、Python パッケージをインストール済み(pip install requests aiohttp)
  • インテグレーション全体を構築する前に、シンプルな curl/fetch リクエストで接続をテスト済み

コメント