Home Assistant
Connects one or more Home Assistant instances to Clapilot — shared dashboard tiles, entity browsing, camera snapshots, device control, automation management (including automations that start Clapilot), and spoken announcements on Home Assistant voice satellites and speakers via the REST API.
What it does
home-assistant is a bundled iframe module that connects Clapilot to one or more Home Assistant
instances over the Home Assistant REST API. Per connected instance it provides:
- a shared Dashboard of tiles bound to Home Assistant entities: camera snapshot tiles, toggle and control tiles (light, switch, fan, cover, lock, climate, media player), sensor value tiles, and generic state tiles, with rename, remove, and a per-tile detail dialog with drag sliders for position, brightness, speed, temperature, and volume. The layout is customizable like the main Dashboard: drag a tile by its header grip to reorder it, drag the bottom-right corner handle to resize it in whole columns/rows, or use the tile menu (size presets Small 1×1, Wide 2×1, Tall 1×2, Large 2×2, Full width 4×1, plus Move up/down; the grip also accepts arrow keys). Tiles flow in order on a 1–4 column grid that follows the available width; spans wider than the current column count are clamped for display only. The iOS/macOS client offers the same size presets plus drag-and-drop reordering;
- a searchable, domain-filterable Geräte / Entities list of every entity the instance exposes
(
GET /api/states) with state and friendly name, plus an "add to dashboard" action per entity; - Sprachgeräte / Voice targets: Home Assistant assist satellites and media players that agents can use for spoken announcements, with discovery, enable/disable, TTS engine selection for media players, and a test announcement;
- Automationen / Automations: every Home Assistant automation with on/off switch and "run now", a YAML editor to create, edit, and delete automations, and the setup for automations that start Clapilot automations (see Automations).
The module UI is tabbed — Dashboard, Geräte, Sprachgeräte, Automationen — and localized in German,
English, and Italian. Connection management is not part of the module UI; it lives under
Settings → Module → Home Assistant. When no connection exists the module shows an empty state with a
link to /settings/home-assistant.
Connections, dashboard tiles, and voice targets are workspace-global (org-level): every
authenticated user of the instance sees the same connected instances and works on the same shared
dashboard. created_by on all three tables is attribution/audit only and is never used as a read/write
filter.
The module is not installed by default. It appears in the Module Store as a bundled module and must
be enabled by an admin; a global migration (db/migrations/322_home_assistant_module_default_off.sql)
seeds a module_installs override so it also stays off on instances using the legacy_all install
policy. Installing the module runs its schema migration
(bundled-modules/home-assistant/migrations/001_create_home_assistant_schema.sql), which creates the
home_assistant_connections, home_assistant_dashboard_tiles, and home_assistant_voice_targets
tables; 002_managed_automations.sql adds home_assistant_managed_automations, which remembers the
automations created through Clapilot (connection, Home Assistant config id, created_by/updated_by
for attribution). The module handler also bootstraps the schema on first use (with module_schema_migrations
bookkeeping and an advisory lock), so it works even if install-time migrations were skipped.
Connecting an instance
Admins connect instances under Settings → Module → Home Assistant (/settings/home-assistant, admin
only, module-gated). Each connection has a name, a base URL, and a long-lived access token:
- In Home Assistant, open your user profile (
Profil → Sicherheit/Profile → Security), scroll to Long-lived access tokens, and create a token for Clapilot. Copy it once — Home Assistant does not show it again. - In Clapilot, enter the instance name, the base URL (for example
http://homeassistant.local:8123orhttps://ha.example.com), and the token. LAN and private hosts are allowed — Home Assistant usually runs on the local network. The base URL must behttp:orhttps:with a host, no credentials, and no path or query; trailing slashes are stripped. - Save. The connection is verified live through
GET /api/configbefore it is stored; the response'slocation_nameandversionare recorded on the connection together withlast_verified_at. The first connection becomes the default instance for all list endpoints and tools that do not pass aconnection_id.
Reading states, controlling devices, and announcing work with any Home Assistant user's token. Managing
automations uses Home Assistant's admin-only config API, so create the token as a Home Assistant
administrator if Clapilot should create or edit automations; with a non-admin token the automation
editor answers home_assistant_admin_required while everything else keeps working.
Per connection the settings panel offers verify, edit (name, URL, new token), set default, and delete (two-step inline confirm). Deleting a connection cascades its tiles and voice targets; if it was the default, the oldest remaining connection is promoted. A "test connection" action verifies a URL/token pair without saving it.
All Home Assistant requests are made server-side by the module handler with
Authorization: Bearer <token> and a 15-second timeout. Authentication failures (401/403 from Home
Assistant) surface as 422 home_assistant_auth_failed, network errors and timeouts as
502 home_assistant_unreachable, and other non-2xx answers as 502 home_assistant_error; the last error
is also stored on the connection.
Credential storage
Long-lived access tokens are stored AES-256-GCM-encrypted in the credentials JSONB column of
home_assistant_connections, keyed from HOME_ASSISTANT_TOKEN_ENCRYPTION_SECRET (falling back to
AUTH_SECRET) via HKDF. Tokens are never returned by any API or agent tool: connection objects only
expose has_token: true. Rotating a token in Home Assistant requires editing the connection with the new
token.
Dashboard
The dashboard is one shared tile grid per connection. Tiles reference an entity (entity_id, unique per
connection) and carry a tile_kind, an optional title override (otherwise the entity's friendly name), a
size (small | wide), a position, and a free-form config object. The tile kind defaults from the
entity domain when a tile is added:
| Entity domain | Tile kind | Tile actions |
|---|---|---|
camera.* | camera | Live snapshot via the module's camera proxy (cache-control: no-store) |
switch, input_boolean, siren, automation | toggle | Turn on / off / toggle |
light.* | toggle | Turn on / off / toggle, brightness (set_brightness, data.brightness_pct 0–100), colour temperature (set_color_temp, data.color_temp_kelvin 1000–10000) — both sent as light.turn_on |
fan.* | toggle | Turn on / off / toggle, speed (set_percentage, data.percentage 0–100) |
humidifier.* | toggle | Turn on / off / toggle, target humidity (set_humidity, data.humidity 0–100) |
script, scene | toggle | Activate only (no state) |
cover.* | cover | Open / close / stop (toggle maps to cover.toggle), position (set_position, data.position 0–100), tilt (set_tilt_position, data.tilt_position 0–100, plus open_tilt / close_tilt) |
lock.* | lock | Lock / unlock |
climate.* | climate | Target temperature (set_temperature), HVAC mode (set_hvac_mode), fan mode (set_fan_mode) |
media_player.* | media_player | Play / pause / play-pause, track skip, volume (volume_set, data.volume_level 0–1), mute |
sensor, binary_sensor, weather, person, device_tracker, sun | sensor | Value display only |
| everything else | state | Raw state display |
Tile states are fetched from Home Assistant in a single GET /api/states call and merged into the tile
list. When the instance is unreachable, tiles are still returned with state: null and a
warning: "home_assistant_unreachable" so the layout stays editable.
Clicking a tile's name opens a detail dialog for that entity. The dialog offers large drag sliders for the continuous values the entity supports — cover position and tilt, light brightness, fan speed, climate target temperature, and media player volume — together with quick presets (0 / 25 / 50 / 75 / 100 %), open / stop / close for covers, and the entity's current attributes. The tile itself keeps its compact inline controls, so the common on/off and step actions stay one click away without opening the dialog.
Tile actions (POST dashboard/tiles/:id/action) are a convenience layer over
POST /api/services/<domain>/<service>; the state is re-read after each call. Numeric payloads are range
checked server-side and unsupported actions for a domain answer 400 validation_error. Any other service
can be invoked directly through POST services/call.
Voice targets
A voice target is a Home Assistant entity that can play a spoken message. Two kinds are supported:
| Kind | Entity | How the announcement is sent |
|---|---|---|
assist_satellite | assist_satellite.* (Home Assistant Voice PE, ESPHome satellites, …) | POST /api/services/assist_satellite/announce with { entity_id, message } (and preannounce: false when requested) |
media_player | media_player.* (speakers, Sonos, Chromecast, …) | POST /api/services/tts/speak with { entity_id: <tts_entity_id>, media_player_entity_id, message } — a tts.* engine entity is required |
Targets are discovered from the instance's states (GET voice-targets/discover lists satellites, media
players, and available tts.* engines), added with a display name, and can be enabled/disabled without
being removed. A disabled target is skipped by broadcast announcements and rejects direct announcements
with 409 target_disabled. Every announcement updates last_announced_at or last_error on the target,
and the settings panel offers a localized test announcement per target.
Home Assistant renders the speech. Announcements use Home Assistant's own TTS pipeline
(assist_satellite.announce / tts.speak); Clapilot's chat TTS is not involved. Playing Clapilot-generated
audio on a Home Assistant media player (media_player.play_media with a Clapilot audio URL) is future
work — Clapilot's /api/chat/audio/[id] is session-scoped and cannot be fetched by Home Assistant today.
Voice targets are output-only announcement endpoints and are distinct from paired Clapilot voice devices: a voice device is a Clapilot-owned speaker bound to one user that records speech and talks to the personal chat/live-voice endpoints, while a Home Assistant voice target only receives text that Home Assistant speaks. The two systems do not share tables, tokens, or settings screens.
API
The module API lives under /api/modules/home-assistant/api and is authenticated like all module handler
APIs (session cookie or agent service auth); the iframe UI, the settings panels, and the agent tools all
use it. All endpoints return {"error":"module_not_installed"} while the module is disabled. Connection
create/update/delete and the connection test are admin-only (403 forbidden otherwise; re-verifying a stored
connection is open to every user);
reading entities, controlling devices, managing tiles and voice targets, and announcing are available to
every authenticated user. List endpoints accept an optional connection_id and fall back to the default
connection; with no connection at all they answer 409 no_connection.
See API Reference for the endpoint list: connections (list/create/update/verify/
delete/test), entities (list with search/domain/limit, domain counts, entity detail), services/call,
camera/:entity_id/snapshot (binary image), dashboard (tile list with merged states, tile
create/update/move/delete/action), voice targets (list/discover/create/update/delete/announce), and the
multi-target announce endpoint, and automations (list with optional bridge status, bridge status, detail
with config/config_yaml, create, update, delete, and enable/disable/trigger actions). Error codes: 409 no_connection, 404 connection_not_found,
400 invalid_base_url, 400 token_required, 422 home_assistant_auth_failed,
502 home_assistant_unreachable, 502 home_assistant_error, 400 invalid_entity_id,
400 invalid_service, 404 tile_not_found, 409 tile_exists, 404 target_not_found,
409 target_disabled, 400 tts_entity_required, 400 unsupported_voice_entity,
404 automation_not_found, 409 automation_not_editable, 409 automation_not_loaded,
400 automation_invalid, 422 home_assistant_admin_required, 403 forbidden, and
400 validation_error. Error bodies are { error: <code>, message?: <human string> }.
Agent tools
home_assistant_list_connections, home_assistant_list_entities, home_assistant_get_state,
home_assistant_call_service, home_assistant_list_dashboard_tiles,
home_assistant_add_dashboard_tile, home_assistant_update_dashboard_tile,
home_assistant_remove_dashboard_tile,
home_assistant_list_voice_targets, home_assistant_announce, home_assistant_list_automations,
home_assistant_get_automation, home_assistant_save_automation, home_assistant_delete_automation,
home_assistant_set_automation_enabled, and home_assistant_trigger_automation are install-gated on this module
(native bundle home_assistant) and run against the module API above, so chat agents and live-voice
agents can read states, control devices, manage the shared dashboard, and send spoken announcements to
Home Assistant voice satellites and speakers, and create and manage Home Assistant automations.
home_assistant_call_serviceis mutating — it changes real devices in the home immediately, so the agent should confirm the target entity and action with the user before calling it.home_assistant_add_dashboard_tile,home_assistant_update_dashboard_tile(rename, resize viasize, reorder via 0-basedposition), andhome_assistant_remove_dashboard_tilemutate the shared dashboard that every workspace user sees.home_assistant_announcespeaks a message on the named targets (target_idsortarget_names, matched case-insensitively against target name and entity id) or on all enabled targets when no target is given; the result lists per-target success or error.home_assistant_save_automation(create withoutautomation_id, update with it — top-level keys replace,nullremoves) andhome_assistant_delete_automationneed the user's approval: they are classifiedoutboundanddeletefor the agent action approval gate, because a saved automation keeps acting on real devices without anyone watching. Unattended runs can only use them when the scheduled task pre-approved them.home_assistant_list_automationsalso reports the bridge status and snippet so the agent can explain the one-time setup.home_assistant_set_automation_enabledandhome_assistant_trigger_automationswitch or run an automation immediately (ordinary writes, likehome_assistant_call_service).
Connecting an instance is intentionally not an agent tool — access tokens must not pass through chat; admins connect instances in Settings. See Agent Tool Contracts for the full contracts.
Automations
The Automationen tab lists every automation.* entity of the instance, sorted by name, with its
last trigger time, a "Clapilot" badge for automations created through Clapilot, and a "YAML" badge for
automations without a config id. Each row has an on/off switch (automation.turn_on /
automation.turn_off) and a run button (automation.trigger, which skips the conditions like Home
Assistant's "Run actions").
Selecting an automation opens its detail with the full configuration in a YAML editor — the same format
as Home Assistant's "Edit in YAML". New automation opens the editor with a small template (a daily
time trigger that creates a persistent notification). Saving sends the configuration to Home Assistant,
which validates it, writes automations.yaml, and reloads the automation; Home Assistant's validation
message is shown under the editor when it rejects a configuration. Delete is a two-step inline confirm.
Clapilot manages automations through the config API that Home Assistant's own automation editor uses
(/api/config/automation/config/<id>). It is not part of Home Assistant's documented REST API but has
been stable for years because the editor depends on it. Consequences:
- only automations stored in
automations.yaml(the ones created in the Home Assistant editor or in Clapilot) are editable. Automations defined in other YAML files or packages are shown read-only; they can still be switched on/off and run; - the connection's token must belong to a Home Assistant administrator (see Connecting an instance);
- saving replaces the whole automation in Home Assistant; the agent tool merges top-level keys on top of
the stored configuration first. Legacy singular keys (
trigger,condition,action) are saved in the plural form of Home Assistant 2024.10+, the minimum supported version for editing.
Starting Clapilot from Home Assistant
A Home Assistant automation can start a Clapilot automation through Clapilot's existing webhook trigger
(/api/automation-webhooks/<token>, see Tasks). Home Assistant has no built-in action
that sends an HTTP request without YAML configuration, so an administrator adds this one-time
rest_command block to Home Assistant's configuration.yaml and restarts Home Assistant:
rest_command:
clapilot:
url: "https://your-clapilot-host/api/automation-webhooks/{{ token }}"
method: post
content_type: "application/json"
payload: "{{ (payload | default({})) | tojson }}"
timeout: 20
The tab shows the status of this bridge (Home Assistant's service list contains rest_command.clapilot
or not) and the block with the instance's real address filled in (the public base URL from the settings)
behind Show setup, with a copy button. After that, any automation can call:
actions:
- action: rest_command.clapilot
data:
token: "<webhook token of the Clapilot automation>"
payload:
automation: "{{ this.attributes.friendly_name }}"
entity_id: "{{ trigger.entity_id | default('') }}"
description: "{{ trigger.description | default('') }}"
The editor's Start a Clapilot automation helper lists the Clapilot automations with a webhook trigger
and renders this action with the right token to copy. Clapilot receives payload as the webhook body and
runs the linked automation with it as event context. Saving an automation that calls
rest_command.clapilot while the bridge is missing succeeds (Home Assistant only checks services when
the action runs) but returns the warning clapilot_bridge_missing, which the editor shows. The webhook
token is a secret: whoever knows it can start that Clapilot automation.
The agent does the same in one conversation: it creates the Clapilot automation with a webhook trigger
(scheduled_tasks_create), then the Home Assistant automation with the rest_command.clapilot action
(home_assistant_save_automation), after the user approved it.
Apple clients
Both surfaces are native SwiftUI; the Apple clients never load the module's web page.
The settings surface lists Home Assistant in Settings → Module (gated like the other module settings
entries on install state and admin role) and provides the connection list with add/edit/verify/delete plus
voice-target management (list, enable toggle, discover/add, test announcement).
The module itself is a native screen in the app's side menu (MainAppSection.homeAssistant, shown only
while the module is installed, Views/HomeAssistantView.swift). It mirrors the web module: a Dashboard
mode with the shared tiles and their inline controls, a Geräte mode with the searchable,
domain-filterable entity list and add-to-dashboard, a Sprachgeräte mode for the voice targets, and an
Automationen mode (Views/HomeAssistantAutomationViews.swift) with the automation list (on/off switch,
run button, Clapilot/YAML badges, bridge status with copyable setup block) and a detail screen —
full-screen on iPhone, a sheet on iPad and Mac — with the YAML editor (smart quotes and autocorrection
off), save, run, on/off, and delete with confirmation; "+" opens the editor with the template.
Tapping a tile or an entity opens the native entity detail sheet
(Views/HomeAssistantEntityDetailView.swift) with the same large drag slider as the web dialog — filled
from the top for cover position, from the bottom for brightness, fan speed, and target humidity — plus the
quick presets, per-domain controls, and the attribute list. Tile mutations go through the tile-action
endpoint; controls opened from an entity row call POST services/call with the equivalent service. The
dashboard polls every 15 s, camera tiles every 10 s, and the open detail sheet every 6 s, each only while
the scene is active.
