# Hass Config Flow

> Uses Home Assistant APIs on NixOS for integration config flows, entity/device inspection, and token setup. Use for live HA API work rather than declarative Nix edits.

- Skill: `edmundmiller/hass-config-flow` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add edmundmiller/hass-config-flow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/edmundmiller/hass-config-flow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: edmundmiller (https://skillmd.com/u/edmundmiller)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/edmundmiller/hass-config-flow

---


# Home Assistant REST API

Docs: https://developers.home-assistant.io/docs/api/rest/

NixOS `extraComponents` bundles integration code, but config-flow-only
integrations (Spotify, Matter, HomeKit Controller, Cast, etc.) require
the REST API or UI to complete setup.

## hass-cli (preferred for inspection/simple calls)

`home-assistant-cli` is installed on the NUC. Prefer it over raw curl for
entity listing, service calls, device/area management, and event watching.

```bash
# On NUC (after getting a token):
export HASS_SERVER=http://localhost:8123
export HASS_TOKEN=<token>

hass-cli state list 'light.*'          # list entities by glob
hass-cli state get light.office        # get single entity (yaml)
hass-cli service call homeassistant.toggle --arguments entity_id=light.office
hass-cli device list                   # all devices with area
hass-cli area list                     # all areas
hass-cli device assign Kitchen --match "Kitchen Light"  # bulk assign area
hass-cli event watch                   # watch all events
hass-cli event watch deconz_event      # watch specific event type
hass-cli -o yaml state list            # yaml output
hass-cli -o json state list 'light.*' | jq '[.[] | {entity: .entity_id, name: .attributes.friendly_name, state: .state}]'
hass-cli -o json state list 'light.*' | python3 -c "import json,sys; d=json.load(sys.stdin); print([x['entity_id'] for x in d if x['state']=='on'])"
```

**Note:** `hass-cli info` is broken on current HA (deprecated endpoint). All other commands work.

Use raw curl (below) for config flows, app credentials, and anything hass-cli doesn't cover.

## Querying the API (inline SSH)

Scripts in `scripts/` exist but are local — they can't be referenced by
path on the NUC. Use inline SSH commands instead.

### Get a token

```bash
TOKEN=$(ssh nuc "sudo python3 -c '
import hashlib, hmac, base64, time, json
auth = json.load(open(\"/var/lib/hass/.storage/auth\"))
for t in auth[\"data\"][\"refresh_tokens\"]:
    if t.get(\"client_name\") == \"agent-automation\":
        header = base64.urlsafe_b64encode(json.dumps({\"alg\":\"HS256\",\"typ\":\"JWT\"}).encode()).rstrip(b\"=\")
        now = int(time.time())
        payload = base64.urlsafe_b64encode(json.dumps({\"iss\":t[\"id\"],\"iat\":now,\"exp\":now+86400*365}).encode()).rstrip(b\"=\")
        sig_input = header + b\".\" + payload
        sig = base64.urlsafe_b64encode(hmac.new(t[\"jwt_key\"].encode(), sig_input, hashlib.sha256).digest()).rstrip(b\"=\")
        print((sig_input + b\".\" + sig).decode())
        break
'" 2>/dev/null)
```

### List entities (by domain)

```bash
ssh nuc "curl -s -H 'Authorization: Bearer $TOKEN' http://localhost:8123/api/states" | python3 -c "
import json, sys
states = json.load(sys.stdin)
for s in sorted(states, key=lambda x: x['entity_id']):
    eid = s['entity_id']
    name = s['attributes'].get('friendly_name', '')
    domain = eid.split('.')[0]
    if domain in ('light', 'switch', 'cover', 'media_player', 'fan', 'binary_sensor', 'scene', 'humidifier'):
        print(f'{eid:55s} {name}')
"
```

Change the `domain in (...)` filter as needed, or remove it for all entities.

### Call a service

```bash
ssh nuc "curl -s -X POST -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{\"entity_id\": \"media_player.tv\"}' \
  http://localhost:8123/api/services/media_player/turn_off"
```

### Start a config flow

```bash
ssh nuc "curl -s -X POST -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{\"handler\": \"spotify\"}' \
  http://localhost:8123/api/config/config_entries/flow"
```

### Helper scripts (reference)

Scripts in `scripts/` are useful as reference for the API patterns but
must be piped via SSH or inlined — they aren't deployed to the NUC.

| Script               | Purpose                            |
| -------------------- | ---------------------------------- |
| `ha-token.sh`        | Generate JWT from auth storage     |
| `ha-api.sh`          | General-purpose API wrapper        |
| `ha-entities.sh`     | List entities by domain            |
| `ha-integrations.sh` | List configured integrations       |
| `ha-call.sh`         | Call a service on an entity        |
| `ha-flow.sh`         | Manage config flows (start/submit) |

## References

Read these for detailed information:

| File                                 | Contents                                                                                                                                     |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `references/integration-flows.md`    | Per-integration config flow behavior, abort reasons, mDNS discovery commands                                                                 |
| `references/default-integrations.md` | NixOS `defaultIntegrations` list — what's auto-loaded, Nix config examples                                                                   |
| `references/device-protocols.md`     | Identify device protocol (Matter/Zigbee/Thread/HomeKit) from device registry; vendor model-name conventions; decision tree for ZHA migration |

## Token generation

HA long-lived tokens are HS256 JWTs signed with a per-token key in
`/var/lib/hass/.storage/auth`. Use `scripts/ha-token.sh` or inline:

```bash
ssh nuc "sudo bash ha-token.sh"           # uses "agent-automation" token
ssh nuc "sudo bash ha-token.sh my-token"  # use a different token name
```

If no token exists yet, create via HA UI:
**Profile → Security → Long-Lived Access Tokens → Create Token**

Verify: `curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8123/api/`
→ `{"message":"API running."}`

## API quick reference

All requests to `http://127.0.0.1:8123` with `Authorization: Bearer $TOKEN`.

| Action              | Method | Endpoint                                      | Body                                                       |
| ------------------- | ------ | --------------------------------------------- | ---------------------------------------------------------- |
| Health check        | GET    | `/api/`                                       | —                                                          |
| HA config           | GET    | `/api/config`                                 | —                                                          |
| List states         | GET    | `/api/states`                                 | —                                                          |
| Get entity state    | GET    | `/api/states/{entity_id}`                     | —                                                          |
| Set entity state    | POST   | `/api/states/{entity_id}`                     | `{"state": "...", "attributes": {...}}`                    |
| Fire event          | POST   | `/api/events/{event_type}`                    | `{...event_data}`                                          |
| Call service        | POST   | `/api/services/{domain}/{service}`            | `{"entity_id": "..."}` + service data                      |
| List services       | GET    | `/api/services`                               | —                                                          |
| List config entries | GET    | `/api/config/config_entries/entry`            | —                                                          |
| Start config flow   | POST   | `/api/config/config_entries/flow`             | `{"handler": "domain"}`                                    |
| Submit flow step    | POST   | `/api/config/config_entries/flow/{flow_id}`   | step-specific data                                         |
| Abort flow          | DELETE | `/api/config/config_entries/flow/{flow_id}`   | —                                                          |
| Delete config entry | DELETE | `/api/config/config_entries/entry/{entry_id}` | —                                                          |
| Add app credentials | POST   | `/api/config/application_credentials`         | `{"domain":"...","client_id":"...","client_secret":"..."}` |
| Render template     | POST   | `/api/template`                               | `{"template": "{{ states('...') }}"}`                      |
| Check config        | POST   | `/api/config/core/check_config`               | —                                                          |

## Key workflows

### Config flow (non-OAuth)

```bash
ha-flow.sh start cast           # auto-discovery, usually creates entry immediately
ha-flow.sh start matter         # returns form → submit with URL
ha-flow.sh submit <flow_id> '{"url":"ws://localhost:5580/ws"}'
```

### OAuth integrations (Spotify, Google, etc.)

1. Register app credentials first (abort reason: `missing_credentials`)
2. Start config flow — returns auth URL for user

```bash
ha-api.sh POST /api/config/application_credentials \
  '{"domain":"spotify","client_id":"ID","client_secret":"SECRET"}'
ha-flow.sh start spotify
```

See `references/integration-flows.md` for per-integration details.

## NixOS context

- Auth storage: `/var/lib/hass/.storage/auth`
- API: `http://127.0.0.1:8123` (localhost only), HTTPS via Tailscale serve
- Public URL: `https://homeassistant.cinnamon-rooster.ts.net/`
- `defaultIntegrations` auto-loads input helpers, automation, scene, script,
  etc. — see `references/default-integrations.md`

