Files
gree-controller/docs/API.md
T
2026-08-30 23:00:29 +02:00

1472 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GREE Controller API reference
HTTP and WebSocket API for GREE Controller **0.8.15**.
[← Main documentation](../README.md)
## Base URL and content type
Default local address:
```text
http://127.0.0.1:8787
```
JSON requests use:
```text
Content-Type: application/json
```
If `GREE_CONTROLLER_BASE_PATH=/gree` is configured, every HTTP and WebSocket path below is prefixed with `/gree`.
## Authentication
There are three access levels.
### Public
No token is required for:
```text
GET /api/health
GET /
GET /index.html
GET /app.js
GET /theme-init.js
GET /styles.css
GET /manifest.webmanifest
GET /sw.js
GET /favicon.svg
GET /lang/index.json
GET /lang/{file}
```
### Administrator API
All normal `/api/*` routes are administrator routes. If `GREE_CONTROLLER_APP_TOKEN` is empty, the controller intentionally operates in trusted-LAN mode and these routes do not require authentication.
When an app token is configured, send either:
```text
Authorization: Bearer APP_TOKEN
```
or:
```text
x-api-token: APP_TOKEN
```
Example:
```bash
BASE='http://127.0.0.1:8787'
TOKEN='replace-me'
curl -H "Authorization: Bearer $TOKEN" "$BASE/api/bootstrap"
```
### Restricted Home Assistant API
Generated Home Assistant access tokens always authenticate only the restricted integration surface under `/api/integrations/home-assistant/*`. The administrator app token is also accepted there.
Generated token secrets are returned only at creation time. SQLite stores their SHA-256 hash and a display prefix.
## Errors and HTTP status codes
API errors are JSON:
```json
{
"error": "message"
}
```
Common statuses:
| Status | Meaning |
| --- | --- |
| `200 OK` | Successful read/update/action. |
| `201 Created` | Resource created. |
| `204 No Content` | Successful delete/revoke. |
| `400 Bad Request` | Validation error or unsafe/invalid operation. |
| `401 Unauthorized` | Missing/incorrect token. |
| `404 Not Found` | Resource ID does not exist. |
| `409 Conflict` | Revision/concurrency conflict. |
| `502 Bad Gateway` | GREE/HA/integration communication failure. |
| `500 Internal Server Error` | Unexpected server/storage error. |
## Endpoint index
### Public and system
| Method | Endpoint | Description |
| --- | --- | --- |
| GET | `/api/health` | Lightweight process/control-engine health. |
| GET | `/api/bootstrap` | Complete initial application snapshot. |
| GET | `/api/system/info` | Runtime/system diagnostic information. |
| GET | `/ws` | Live WebSocket event stream. |
### Devices and discovery
| Method | Endpoint | Description |
| --- | --- | --- |
| POST | `/api/discovery` | Discover/bind GREE devices. |
| GET | `/api/devices` | List devices. |
| POST | `/api/devices` | Add a device manually. |
| GET | `/api/devices/{id}` | Read a device. |
| PATCH | `/api/devices/{id}` | Edit technical device configuration. |
| DELETE | `/api/devices/{id}` | Delete a device after safety checks. |
| POST | `/api/devices/{id}/bind` | Bind/re-bind a physical unit. |
| POST | `/api/devices/{id}/poll` | Poll one unit immediately. |
| POST | `/api/devices/{id}/command` | Send a direct/manual device command. |
### Thermostat zones, groups and house
| Method | Endpoint | Description |
| --- | --- | --- |
| GET | `/api/zones` | List zones. |
| POST | `/api/zones` | Create a zone. |
| GET | `/api/zones/{id}` | Read a zone. |
| PUT | `/api/zones/{id}` | Replace editable zone configuration. |
| DELETE | `/api/zones/{id}` | Delete zone after safe device shutdown. |
| POST | `/api/zones/{id}/control` | Quick/thermostat control of a zone. |
| POST | `/api/zones/{id}/schedule-template` | Replace zone schedules with a built-in template. |
| GET | `/api/groups` | List climate groups. |
| POST | `/api/groups` | Create a climate group. |
| GET | `/api/groups/{id}` | Read a group. |
| PUT | `/api/groups/{id}` | Replace group definition. |
| DELETE | `/api/groups/{id}` | Delete a group. |
| POST | `/api/groups/{id}/control` | Group power/mode/preset control. |
| POST | `/api/house/control` | Set global thermostat mode. |
| POST | `/api/house/power` | Set whole-house master power. |
| POST | `/api/house/preset` | Set/clear whole-house preset override. |
### Schedules and automations
| Method | Endpoint | Description |
| --- | --- | --- |
| GET | `/api/schedules` | List schedules. |
| POST | `/api/schedules` | Create schedule. |
| GET | `/api/schedules/{id}` | Read schedule. |
| PUT | `/api/schedules/{id}` | Replace schedule. |
| DELETE | `/api/schedules/{id}` | Delete schedule. |
| GET | `/api/automations` | List automations. |
| POST | `/api/automations` | Create automation. |
| GET | `/api/automations/{id}` | Read automation. |
| PUT | `/api/automations/{id}` | Replace automation. |
| DELETE | `/api/automations/{id}` | Delete automation. |
### History, control plan and events
| Method | Endpoint | Description |
| --- | --- | --- |
| GET | `/api/readings` | Legacy/device reading history. |
| GET | `/api/history` | Rich device/zone/HA history. |
| GET | `/api/control-plan` | Current resolved thermostat plan. |
| GET | `/api/events` | Event/debug log. |
| GET | `/api/events/retention` | Current event retention. |
| PUT | `/api/events/retention` | Update retention and prune immediately. |
### Settings, backup and diagnostics
| Method | Endpoint | Description |
| --- | --- | --- |
| GET | `/api/settings` | Public-safe runtime settings. |
| PUT | `/api/settings` | Update runtime settings. |
| GET | `/api/settings/export` | Export full application configuration. |
| POST | `/api/settings/import` | Import/replace application configuration. |
| GET | `/api/debug` | Read debug overlay settings. |
| PUT | `/api/debug` | Update debug overlay settings. |
| POST | `/api/integrations/home-assistant/test` | Test HA temperature read. |
| POST | `/api/integrations/notifications/test` | Send a test notification. |
### Access tokens and restricted Home Assistant API
| Method | Endpoint | Description |
| --- | --- | --- |
| GET | `/api/access-tokens` | List generated HA tokens without secrets. |
| POST | `/api/access-tokens` | Create restricted HA token. |
| DELETE | `/api/access-tokens/{id}` | Revoke token. |
| GET | `/api/integrations/home-assistant/devices` | Restricted device list. |
| POST | `/api/integrations/home-assistant/devices/{id}/command` | Restricted direct device command. |
| GET | `/api/integrations/home-assistant/control-plan` | Restricted control plan. |
| GET | `/api/integrations/home-assistant/groups` | HA-oriented group state. |
| POST | `/api/integrations/home-assistant/groups/{id}/control` | Restricted group control. |
| POST | `/api/integrations/home-assistant/house/control` | Restricted house mode. |
| POST | `/api/integrations/home-assistant/house/preset` | Restricted house preset. |
| POST | `/api/integrations/home-assistant/house/power` | Restricted master power. |
| POST | `/api/integrations/home-assistant/zones/{id}/control` | Restricted thermostat-zone control. |
---
## System endpoints
### `GET /api/health`
Public lightweight health check.
Response:
```json
{
"status": "ok",
"name": "gree-controller",
"version": "0.8.15",
"uptime_seconds": 1234,
"control_ready": true,
"time": "2026-08-30T06:54:00Z"
}
```
`control_ready=false` means the process is running but the thermostat engine has not yet completed its initial physical device synchronization.
### `GET /api/bootstrap`
Returns the initial Web UI snapshot:
```json
{
"devices": [],
"zones": [],
"groups": [],
"schedules": [],
"automations": [],
"access_tokens": [],
"settings": {},
"outdoor_temperature": null,
"system": {
"version": "0.8.15",
"uptime_seconds": 1234,
"auth_required": false,
"control_ready": true,
"database": "./data/gree-controller.db",
"device_count": 2,
"online_count": 2,
"simulator_count": 0,
"bind": "0.0.0.0:8787",
"base_path": "/",
"gree_interface": "auto",
"gree_received_frames": 809,
"gree_received_frames_by_device": {}
}
}
```
### `GET /api/system/info`
Returns the `system` diagnostic object independently of the full bootstrap. Useful for monitoring and **Settings → System status**.
---
## Device API
### Device object
A device response contains:
| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Stable controller ID. |
| `mac` | string | Normalized GREE MAC/CID identity. |
| `name` | string | User-visible name. |
| `ip` | string | Device IPv4 address. |
| `port` | integer | Usually `7000`. |
| `protocol_version` | integer | `0` unknown/auto, `1` legacy AES-ECB, `2` AES-GCM. |
| `model`, `firmware` | string | Discovered metadata when available. |
| `key` | string/null | GREE binding key. Treat as secret. |
| `cid` | string/null | GREE client/device identifier. |
| `enabled` | boolean | Technical device enable state. |
| `simulated` | boolean | Simulated vs physical. |
| `power` | boolean | Last known power. |
| `mode` | string | `auto`, `cool`, `dry`, `fan`, `heat`. |
| `target_temperature` | number | Last known unit setpoint. |
| `fan_speed` | integer | `0..5`; `0` is Auto. |
| `swing_vertical`, `swing_horizontal` | boolean | Swing state. |
| `quiet`, `turbo`, `light`, `air`, `xfan`, `health`, `sleep` | boolean | Optional GREE features. |
| `supports_*` | boolean/null | Capability learned from device status. |
| `current_temperature` | number/null | GREE indoor temperature. |
| `outdoor_temperature` | number/null | GREE outdoor temperature if available. |
| `temperature_sensor_offset` | boolean/null | Whether +40 °C wire offset behavior was detected. |
| `online` | boolean | Current communication state. |
| `response_time_ms` | integer/null | Latest successful controller round-trip. |
| `last_seen` | ISO-8601/null | Last successful communication. |
| `last_error` | string/null | Latest communication error. |
| `communication_failures` | integer | Consecutive/recorded communication failure counter. |
| `created_at`, `updated_at` | ISO-8601 | Resource timestamps. |
### `POST /api/discovery`
Request body, all fields optional:
```json
{
"timeout_ms": 6000,
"broadcast": "255.255.255.255:7000",
"protocol_version": 0,
"passes": 3
}
```
Rules:
- `timeout_ms`: effective range `500..30000` ms.
- `protocol_version`: `0` auto/both, `1` AES-ECB only, `2` AES-GCM only.
- `passes`: `1..10`.
- Missing values use runtime GREE settings.
Successful discovery merges known devices, tries binding devices that do not have a key, persists results and returns:
```json
{
"count": 1,
"devices": [],
"new_device_ids": ["gree-aabbccddeeff"]
}
```
### `GET /api/devices`
Returns `Device[]`.
### `POST /api/devices`
Manual add request:
```json
{
"name": "Living room",
"mac": "AABBCCDDEEFF",
"ip": "192.168.50.30",
"port": 7000,
"protocol_version": 1,
"key": null,
"simulated": false
}
```
Defaults: `port=7000`, `protocol_version=1`, `simulated=false`. MAC values are normalized. Duplicate MACs are rejected.
Returns `201 Created` with `Device`.
### `GET /api/devices/{id}`
Returns one `Device` or `404`.
### `PATCH /api/devices/{id}`
All fields optional:
```json
{
"name": "Bedroom",
"ip": "192.168.50.31",
"port": 7000,
"protocol_version": 2,
"key": "optional-binding-key",
"enabled": true
}
```
`key:null` clears the key. Changing protocol version clears the existing key/capability cache so the unit can be re-bound cleanly. Disabling a device goes through the controller's safe disable path.
### `DELETE /api/devices/{id}`
Returns `204`. Deletion is rejected if the device is referenced by an automation or cannot be safely detached from thermostat ownership. Associated zones/groups are cleaned only after the safety checks pass.
### `POST /api/devices/{id}/bind`
Performs/repeats GREE binding and returns updated `Device`. Simulated devices return unchanged.
### `POST /api/devices/{id}/poll`
Immediately polls one unit and returns updated `Device`.
### `POST /api/devices/{id}/command`
Direct/manual device control. This is deliberately different from thermostat-zone control.
All fields optional; at least one meaningful field should be sent:
```json
{
"power": true,
"mode": "cool",
"target_temperature": 22,
"fan_speed": 3,
"swing_vertical": true,
"swing_horizontal": false,
"quiet": false,
"turbo": false,
"light": true,
"air": false,
"xfan": false,
"health": false,
"sleep": false
}
```
Rules:
- modes: `auto`, `cool`, `dry`, `fan`, `heat`,
- target temperature is normalized to the supported GREE Celsius range `8..30`,
- fan speed is `0..5`,
- optional feature commands should be used only when the corresponding `supports_*` capability is true.
The backend sends only properties that differ from the last known device state. Climate-relevant direct commands can create/continue a manual-device takeover for an enabled thermostat zone so automation does not immediately fight the user.
---
## Zones
### Zone configuration
`POST /api/zones` and `PUT /api/zones/{id}` use this editable shape:
```json
{
"name": "Living room",
"device_id": "gree-aabbccddeeff",
"enabled": true,
"mode": "cool",
"inherit_house_mode": true,
"setpoint": 23.0,
"cool_comfort_setpoint": 23.0,
"cool_sleep_setpoint": 24.5,
"cool_away_setpoint": 27.0,
"heat_comfort_setpoint": 21.0,
"heat_sleep_setpoint": 19.0,
"heat_away_setpoint": 17.0,
"hysteresis": 0.6,
"min_on_seconds": 180,
"min_off_seconds": 180,
"min_adjust_seconds": 120,
"standby_offset_c": 2.0,
"smart_fan": true,
"sensor_source": "combined",
"ha_entity_id": "sensor.living_room_temperature",
"external_sensor_weight": 0.4,
"max_sensor_difference": 3.0,
"sensor_stale_after_seconds": 300,
"revision": 12
}
```
Important rules:
- `name` is required.
- `device_id` must reference an existing device and thermostat ownership must remain valid/safe.
- `mode`: `cool` or `heat` when not inheriting house mode.
- temperature/profile values are constrained to the supported thermostat range.
- `hysteresis`: controller-valid range is approximately `0.1..5.0` °C.
- `standby_offset_c`: bounded thermostat offset.
- `sensor_source`: `device`, `home_assistant` or `combined`.
- `external_sensor_weight`: `0..1`.
- `revision` is used for optimistic concurrency where supplied; stale updates can return `409`.
The returned `Zone` also contains runtime state including sensor readings, resolved/effective setpoints, demand, current preset, manual overrides, local Quick Thermostat ownership, temporary session state, device-manual takeover, control owner/source/reason, lockout timestamps and `created_at`/`updated_at`.
### `GET /api/zones`
Returns `Zone[]`.
### `POST /api/zones`
Creates a zone and returns `201 Created` with `Zone`.
### `GET /api/zones/{id}`
Returns one `Zone`.
### `PUT /api/zones/{id}`
Replaces editable zone configuration while preserving/reconciling runtime safety state. Returns updated `Zone`.
### `DELETE /api/zones/{id}`
Safely powers the owned device off before detaching thermostat ownership. Returns `204`.
### `POST /api/zones/{id}/control`
Quick thermostat endpoint. Body fields are optional and can be combined:
```json
{
"setpoint": 22.5,
"power": true,
"mode": "house",
"enabled": true,
"preset": "comfort",
"clear_override": false,
"clear_device_manual_override": false,
"clear_local_thermostat_override": false,
"temporary_quick_thermostat": null,
"clear_temporary_quick_thermostat": false
}
```
Semantics:
- `setpoint`: creates a quick custom thermostat target.
- `preset`: `auto`, `comfort`, `sleep`, `away`, `custom`; `auto` clears the profile override.
- `mode`: `house`, `cool`, `heat`; `house` restores global mode inheritance.
- `enabled`: zone automation enable state.
- `power:true`: local Quick Thermostat ownership — this zone can run through full thermostat logic even if its climate group is off.
- `power:false`: turns this zone off and creates a fresh backend-owned local hand-back timer (currently 15 minutes).
- `clear_local_thermostat_override:true`: immediately return local Quick Thermostat ownership to normal group/schedule control.
- `clear_device_manual_override:true`: explicitly hand a physical/direct manual takeover back to the thermostat.
- `clear_override:true`: clear ordinary quick preset/setpoint override.
- `temporary_quick_thermostat`: start/replace a persisted temporary session.
- `clear_temporary_quick_thermostat:true`: cancel that temporary session only.
Returns updated `Zone`.
#### Temporary Quick Thermostat request
```json
{
"start_kind": "now",
"start_delay_minutes": null,
"start_at": null,
"finish_kind": "duration",
"duration_minutes": 90,
"until": null,
"target_temperature": 23.0,
"temperature_operator": "within",
"tolerance_c": 0.3,
"hold_minutes": 60,
"max_duration_minutes": 240
}
```
`start_kind`:
- `now`
- `delay` + `start_delay_minutes`
- `at` + ISO-8601 `start_at`
`finish_kind`:
- `duration` + `duration_minutes`
- `until` + ISO-8601 `until`
- `temperature_reached`
- `temperature_stable` + `hold_minutes`
- `schedule_boundary`
Temperature operators:
- `within`
- `at_or_below`
- `at_or_above`
`max_duration_minutes` is an optional fail-safe for temperature-based sessions. Delayed sessions do not own the zone until their effective start. Runtime session state is persisted and exposed inside the returned zone.
Examples:
```bash
# Preset until next schedule boundary
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"preset":"sleep"}'
# Return to schedule
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"preset":"auto"}'
# Run a 90-minute temporary thermostat
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"temporary_quick_thermostat":{"start_kind":"now","finish_kind":"duration","duration_minutes":90,"target_temperature":23}}'
```
### `POST /api/zones/{id}/schedule-template`
Body:
```json
{
"template": "family"
}
```
Built-in templates:
| Template | Result |
| --- | --- |
| `family` | Comfort `06:3022:30`, Sleep overnight. |
| `child` | Comfort `06:3020:30`, Sleep overnight. |
| `bedroom` | Comfort `06:3022:00`, Sleep overnight. |
| `workday` | Weekday morning/away/evening/sleep plus weekend blocks. |
| `always` | 24-hour Comfort. |
Existing schedules for the zone are replaced after overlap validation. Response:
```json
{
"zone": {},
"schedules": []
}
```
---
## Groups
### Group object
```json
{
"id": "uuid",
"name": "Bedrooms",
"zone_ids": ["zone-1", "zone-2"],
"power_enabled": true,
"created_at": "...",
"updated_at": "..."
}
```
### `GET /api/groups`
Returns groups.
### `POST /api/groups`
```json
{
"name": "Bedrooms",
"zone_ids": ["zone-1", "zone-2"],
"power_enabled": true
}
```
A group must contain at least one existing zone. Returns `201 Created`.
### `GET /api/groups/{id}` / `PUT /api/groups/{id}` / `DELETE /api/groups/{id}`
Read, replace or delete a group. `DELETE` returns `204`.
### `POST /api/groups/{id}/control`
```json
{
"power": true,
"mode": "house",
"preset": "comfort"
}
```
All fields optional:
- `power`: group gate,
- `mode`: `house`, `cool`, `heat`,
- `preset`: `auto`, `comfort`, `sleep`, `away`.
Returns a group control/result object including updated members/state.
---
## Whole-house control
House thermostat mode and master power are intentionally separate.
### `POST /api/house/control`
```json
{
"mode": "cool"
}
```
Valid modes: `cool`, `heat`, `off`.
- `cool`/`heat` are explicit whole-house activation requests: master power is enabled, group gates are enabled and the thermostat arbiter starts eligible managed zones.
- `off` means **do not perform house-level thermostat control** for inherited zones. It does not itself change master power and does not forcibly power direct/manual devices down.
Returns public runtime settings.
### `POST /api/house/power`
```json
{
"power": false
}
```
`false` is authoritative: it disables group gates, clears local/manual ownership markers as required and powers every enabled physical device down. `true` resumes thermostat arbitration rather than blindly sending a bare ON frame.
Response includes:
```json
{
"power": false,
"devices": [],
"groups": [],
"settings": {},
"failed": []
}
```
### `POST /api/house/preset`
```json
{
"preset": "sleep"
}
```
Valid: `auto`, `comfort`, `sleep`, `away`.
A non-`auto` preset creates zone overrides that normally expire at each zone's next schedule boundary. `auto` clears them. Selecting a house preset also explicitly re-enables master power/group gates.
---
## Schedules
Schedule object/request:
```json
{
"zone_id": "zone-1",
"name": "Night",
"enabled": true,
"weekdays": [1, 2, 3, 4, 5, 6, 7],
"start_time": "22:30",
"end_time": "06:30",
"preset": "sleep",
"setpoint": 24.5
}
```
Rules:
- weekdays use ISO numbers `1=Monday ... 7=Sunday`,
- times use local `HH:MM`,
- crossing midnight is supported,
- `start_time == end_time` represents a 24-hour window for selected weekdays,
- preset: `comfort`, `sleep`, `away`, `custom`,
- custom setpoint: `8..30` °C,
- enabled schedules for the same zone cannot overlap.
For non-custom presets, the effective target comes from the zone's seasonal profile; `setpoint` is retained for compatibility.
Routes:
```text
GET /api/schedules
POST /api/schedules
GET /api/schedules/{id}
PUT /api/schedules/{id}
DELETE /api/schedules/{id}
```
Create returns `201`; delete returns `204`.
---
## Automations
Automation request/object fields:
```json
{
"name": "Hot room",
"enabled": true,
"trigger_kind": "temperature_above",
"trigger_device_id": "gree-aabbccddeeff",
"threshold": 27.0,
"at_time": null,
"action_device_id": "gree-aabbccddeeff",
"action_group_id": null,
"action_preset": null,
"action": {
"power": true,
"mode": "cool",
"target_temperature": 23
},
"cooldown_seconds": 300
}
```
Triggers:
- `temperature_above`: requires `trigger_device_id` + `threshold`,
- `temperature_below`: requires `trigger_device_id` + `threshold`,
- `time`: requires local `at_time` in `HH:MM`.
Action target is either:
- direct device: `action_device_id` + full `DeviceCommand`, or
- group: `action_group_id`; group automation supports only power, `house`/`cool`/`heat` mode and optional `action_preset` (`auto|comfort|sleep|away`).
The response also contains runtime `last_fired_at`, `created_at`, `updated_at`.
Routes:
```text
GET /api/automations
POST /api/automations
GET /api/automations/{id}
PUT /api/automations/{id}
DELETE /api/automations/{id}
```
Create returns `201`; delete returns `204`.
---
## History and readings
### `GET /api/readings`
Legacy/lightweight device history.
Query parameters:
| Parameter | Default | Description |
| --- | --- | --- |
| `device_id` | all | Optional device filter. |
| `hours` | `24` | Clamped to `1..87600` (10 years). |
| `limit` | `1500` | Row limit. |
Response:
```json
{
"readings": [
{
"id": 1,
"device_id": "gree-aabbccddeeff",
"timestamp": "...",
"indoor_temperature": 23.4,
"outdoor_temperature": 30.1,
"target_temperature": 23,
"power": true,
"source": "poll"
}
]
}
```
### `GET /api/history`
Rich chart/history API.
Query parameters:
| Parameter | Description |
| --- | --- |
| `scope` | `overview`, `zones`/`zone`, `devices`, `sensors`; default `zones`. |
| `zone_id` | Zone filter for zone scope. |
| `device_id` | Device filter for device scope. |
| `entity_id` | Home Assistant entity filter for sensor scope. |
| `hours` | Default `24`, clamped to 10 years. |
| `limit` | Default `12000`, clamped to `1..20000`. |
Bucket resolution:
| Range | Bucket |
| --- | --- |
| ≤ 6 h | 30 s |
| ≤ 24 h | 2 min |
| ≤ 7 d | 10 min |
| ≤ 30 d | 30 min |
| ≤ 90 d | 2 h |
| ≤ 1 y | 6 h |
| > 1 y | 24 h |
Zone reading fields:
```text
id, zone_id, device_id, timestamp,
gree_temperature, external_temperature, control_temperature,
target_temperature, device_setpoint, outdoor_temperature,
power, mode, fan_speed, demand, control_source, active_preset
```
Device reading fields are the `Reading` fields documented above. HA sensor rows contain:
```text
id, entity_id, zone_id, kind, timestamp, temperature
```
`scope=overview` returns all three families plus counts and per-family storage source.
When InfluxDB is enabled, older history can be read from Influx and merged with recent SQLite rows. A failed Influx query falls back to available SQLite data and reports `storage_warning` rather than failing the entire chart response.
---
## Control plan
### `GET /api/control-plan`
Returns the resolved machine-readable thermostat plan:
Top-level fields:
```text
generated_at
house_mode
house_preset
house_power
outdoor_temperature
control_strategy
night_mode_active
night_mode_start
night_mode_end
night_mode_max_fan_speed
next_events[]
zones[]
rules[]
```
Each zone plan includes:
```text
zone_id, zone_name, device_id, device_name,
enabled, effective_enabled,
mode, configured_mode, inherit_house_mode,
preset, preset_override,
current_temperature, target_temperature, device_setpoint,
desired_power, desired_mode,
actual_power, actual_mode, actual_setpoint,
demand, control_source,
manual_override_until,
local_thermostat_power, local_thermostat_resume_at,
device_manual_override, device_manual_override_until,
control_owner, control_command_source, control_since, resume_at, control_reason,
blocked_reason, lockout_until,
current_schedule_id, current_schedule_name,
next_events[]
```
This endpoint is the best way for another client to understand **desired vs actual state**, who owns control, and why a zone is blocked/paused.
---
## Events and retention
### `GET /api/events?limit=100`
Response:
```json
{
"events": [
{
"id": 1,
"timestamp": "...",
"level": "info",
"kind": "device.updated",
"message": "...",
"metadata": {}
}
]
}
```
### `GET /api/events/retention`
```json
{
"days": 30
}
```
### `PUT /api/events/retention`
```json
{
"days": 30
}
```
Value is clamped to `1..3650`; pruning happens immediately. Response includes `days` and number of removed rows.
---
## Runtime settings
### `GET /api/settings`
Returns a public-safe settings document. Secrets are blanked and accompanied by `*_configured` booleans where relevant.
Shape:
```json
{
"controller_id": "gree-controller",
"simulator_enabled": false,
"poll_interval_seconds": 15,
"zone_interval_seconds": 5,
"discovery_timeout_ms": 3000,
"discovery_broadcast": "255.255.255.255:7000",
"house_mode": "cool",
"house_power_enabled": true,
"control_strategy": "setpoint",
"outdoor_assist_enabled": true,
"history_retention_days": 30,
"history_compaction_enabled": true,
"event_log_retention_days": 30,
"suppress_device_beep": false,
"debug": {
"overlay_enabled": false,
"gree_frames": false
},
"night_mode": {
"enabled": false,
"start_time": "22:00",
"end_time": "06:00",
"max_fan_speed": 1,
"force_quiet": true,
"use_native_sleep": true
},
"notifications": {},
"influxdb": {},
"home_assistant": {}
}
```
#### Home Assistant settings
```json
{
"url": "http://homeassistant.local:8123",
"token": "",
"token_configured": true,
"default_entity_id": "sensor.room_temperature",
"outdoor_entity_id": "sensor.outdoor_temperature",
"sensor_stale_after_seconds": 300,
"allow_invalid_tls": false,
"sensor_aliases": {
"sensor.room_temperature": "Living room"
}
}
```
#### InfluxDB settings
```json
{
"enabled": true,
"version": "2",
"url": "http://influxdb:8086",
"database": "gree_controller",
"username": "",
"password": "",
"password_configured": false,
"org": "home",
"bucket": "gree_controller",
"token": "",
"token_configured": true,
"history_threshold_days": 30
}
```
Version `1` uses database/optional username/password. Version `2` uses org/bucket/token.
#### Notification settings
```json
{
"enabled": true,
"mode": "problems",
"provider": "pushover",
"pushover_app_token": "",
"pushover_user_key": "",
"pushover_configured": true,
"slack_webhook_url": "",
"slack_configured": false,
"discord_webhook_url": "",
"discord_configured": false,
"cooldown_seconds": 300,
"communication_failure_threshold": 3,
"target_timeout_minutes": 60,
"alert_types": {
"stale_sensor": true,
"sensor_errors": true,
"communication": true,
"target_timeout": true,
"automation": true,
"control_errors": true,
"important_events": true,
"other": true
}
}
```
Modes: `problems`, `important`. Providers: `pushover`, `slack`, `discord`.
### `PUT /api/settings`
Accepts the complete `RuntimeSettings` document. Important behavior:
- `house_mode` and `house_power_enabled` cannot be changed here; use House Control API.
- polling interval is clamped `2..3600` seconds.
- zone interval is clamped `2..3600` seconds.
- discovery timeout is clamped `300..30000` ms.
- `control_strategy` is normalized to `setpoint`.
- discovery broadcast must be `auto`, `auto:*` or a valid socket address.
- blank HA token preserves the saved token.
- blank Influx token/password preserve saved secrets.
- blank Pushover/Slack/Discord secret fields preserve saved secrets.
- HA sensor age is clamped `30..86400` seconds.
- notification cooldown: `30..86400` seconds.
- communication failure threshold: `2..100`.
- target timeout: `5..1440` minutes.
- retention windows: `1..3650` days.
- night mode times must be `HH:MM`; max fan is clamped `1..5`.
Returns the safe public settings form.
### `GET /api/settings/export`
Returns configuration format version `1`:
```text
format_version
exported_at
settings
devices[]
zones[]
groups[]
schedules[]
automations[]
```
Export includes GREE binding keys and integration credentials. It excludes metric history, event rows and generated API-token records. Treat the export as a secret.
### `POST /api/settings/import`
Accepts exactly the export document. The backend validates IDs/references/schedules/settings, safely stops devices whose ownership is being removed, clears transient ownership/timers/stale live state, replaces configuration, re-polls devices, then re-enables thermostat control.
Metric/event history and generated access-token records are preserved.
Response:
```json
{
"ok": true
}
```
---
## Debug API
### `GET /api/debug`
```json
{
"overlay_enabled": true,
"gree_frames": true
}
```
### `PUT /api/debug`
Accepts the same object, persists it and broadcasts `debug.settings`.
When overlay diagnostics are enabled, live HTTP requests generate `api.request` WebSocket events containing method, path, status and duration. When `gree_frames=true`, sanitized GREE protocol events are also sent as `gree.frame`.
The Web UI can display **All**, **Requests** or **GREE** subsets.
---
## Integration tests
### `POST /api/integrations/home-assistant/test`
```json
{
"entity_id": "sensor.room_temperature"
}
```
`entity_id` is optional; controller defaults/aliases are resolved. Response:
```json
{
"ok": true,
"temperature_c": 23.4,
"entity_id": "sensor.room_temperature"
}
```
### `POST /api/integrations/notifications/test`
Accepts a `NotificationSettings` object. Blank secret/webhook fields reuse saved secrets for the test.
Response:
```json
{
"ok": true
}
```
---
## Access tokens
### `GET /api/access-tokens`
Returns:
```json
[
{
"id": "uuid",
"name": "Home Assistant",
"token_prefix": "gree_controller_abc...",
"created_at": "..."
}
]
```
### `POST /api/access-tokens`
```json
{
"name": "Home Assistant"
}
```
Name length: `1..80`. If omitted, default is `Home Assistant`.
Response `201 Created`:
```json
{
"token": "gree_controller_FULL_SECRET_SHOWN_ONCE",
"item": {
"id": "uuid",
"name": "Home Assistant",
"token_prefix": "gree_controller_...",
"created_at": "..."
}
}
```
### `DELETE /api/access-tokens/{id}`
Revokes token and returns `204`.
---
## Restricted Home Assistant API
These routes always require a generated token or the administrator app token.
### `GET /api/integrations/home-assistant/devices`
Returns `Device[]`.
### `POST /api/integrations/home-assistant/devices/{id}/command`
Accepts `DeviceCommand`. A direct command is rejected if the device belongs to a disabled thermostat zone; re-enable the zone for normal HA/controller ownership or use the administrator technical device endpoint deliberately.
### `GET /api/integrations/home-assistant/control-plan`
Same payload as administrator `GET /api/control-plan`.
### `GET /api/integrations/home-assistant/groups`
Returns an HA-oriented derived group list. Each object includes:
```text
id, name, zone_ids, zone_names,
power_enabled, effective_power,
mode, preset, house_mode,
zone_count, enabled_zones, active_zones, demanding_zones,
device_count, online_devices, current_temperature,
members[], next_events[]
```
Member rows include zone/device identity, configured/effective enable state, mode/preset, room/target temperature, demand, source, schedule and current manual/local ownership markers.
### `POST /api/integrations/home-assistant/groups/{id}/control`
Same body/semantics as normal group control.
### `POST /api/integrations/home-assistant/house/control`
Same `{ "mode": "cool|heat|off" }` semantics as administrator house mode.
### `POST /api/integrations/home-assistant/house/preset`
Same `{ "preset": "auto|comfort|sleep|away" }` semantics.
### `POST /api/integrations/home-assistant/house/power`
Same `{ "power": true|false }` semantics.
### `POST /api/integrations/home-assistant/zones/{id}/control`
Same `ZoneControlPatch` thermostat semantics as the normal zone control endpoint. The internal source is recorded as Home Assistant thermostat control.
---
## WebSocket
### Connection
Without administrator authentication:
```text
ws://HOST:8787/ws
```
When `GREE_CONTROLLER_APP_TOKEN` is configured:
```text
ws://HOST:8787/ws?token=APP_TOKEN
```
Generated restricted HA tokens are not WebSocket administrator tokens.
### Message envelope
Every server event uses:
```json
{
"event": "device.updated",
"timestamp": "2026-08-30T06:54:00Z",
"data": {}
}
```
The first frame is always `bootstrap` with the same payload as `GET /api/bootstrap`, unless bootstrap generation itself fails.
Common live events include:
```text
bootstrap
device.created
device.updated
device.deleted
devices.discovered
zone.created
zone.updated
zone.deleted
group.created
group.updated
group.deleted
schedule.created
schedule.updated
schedule.deleted
schedule.template_applied
automation.created
automation.updated
automation.deleted
settings.updated
configuration.imported
debug.settings
api.request
gree.frame
log.created
```
Additional engine/integration events may be introduced without changing the envelope.
`api.request` data:
```json
{
"method": "GET",
"path": "/api/system/info",
"status": 200,
"duration_ms": 2
}
```
GREE debug events are emitted only when enabled and are intended for diagnostics, not as a stable protocol API.
---
## Localization endpoints
Language files are public so the UI can localize before administrator authentication.
### `GET /lang/index.json`
Returns the generated catalog of embedded packs.
### `GET /lang/{code}.json`
Returns one embedded language pack, e.g.:
```text
GET /lang/en.json
GET /lang/pl.json
```
---
## Practical API examples
Assume:
```bash
BASE='http://127.0.0.1:8787'
AUTH='Authorization: Bearer APP_TOKEN'
```
Discover devices:
```bash
curl -X POST "$BASE/api/discovery" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"protocol_version":0,"passes":3}'
```
Directly poll a device:
```bash
curl -X POST "$BASE/api/devices/DEVICE_ID/poll" -H "$AUTH"
```
Set a zone target:
```bash
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"setpoint":22.5}'
```
Return zone to automatic scheduling:
```bash
curl -X POST "$BASE/api/zones/ZONE_ID/control" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"preset":"auto","clear_local_thermostat_override":true}'
```
Turn the whole managed house off:
```bash
curl -X POST "$BASE/api/house/power" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"power":false}'
```
Read current ownership/desired-vs-actual state:
```bash
curl "$BASE/api/control-plan" -H "$AUTH"
```
Read 90 days of zone history:
```bash
curl "$BASE/api/history?scope=zones&zone_id=ZONE_ID&hours=2160" -H "$AUTH"
```
Create a restricted Home Assistant token:
```bash
curl -X POST "$BASE/api/access-tokens" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"name":"Home Assistant"}'
```
Use that token:
```bash
curl -H 'Authorization: Bearer gree_controller_RESTRICTED_TOKEN' \
"$BASE/api/integrations/home-assistant/control-plan"
```