Files
gree-controller/docs/API.md
T
2026-09-02 09:27:24 +02:00

45 KiB
Raw Blame History

GREE Controller API reference

HTTP and WebSocket API for GREE Controller 0.9.14.

← Main documentation

Base URL and content type

Default local address:

http://127.0.0.1:8787

JSON requests use:

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:

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:

Authorization: Bearer APP_TOKEN

or:

x-api-token: APP_TOKEN

Example:

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:

{
  "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 control enable/mode/preset/custom-temperature control.
POST /api/house/control Set global thermostat mode.
POST /api/house/power Bulk ON/OFF for all thermostats and enabled units; no persistent global gate.
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/home-assistant/entity Read raw HA entity state/attributes for shared Flow input diagnostics.
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 bulk all-thermostat/all-unit power action.
POST /api/integrations/home-assistant/zones/{id}/control Restricted thermostat-zone control.

System endpoints

GET /api/health

Public lightweight health check.

Response:

{
  "status": "ok",
  "name": "gree-controller",
  "version": "0.9.14",
  "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:

{
  "devices": [],
  "zones": [],
  "groups": [],
  "schedules": [],
  "automations": [],
  "access_tokens": [],
  "settings": {},
  "outdoor_temperature": null,
  "system": {
    "version": "0.9.14",
    "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:

{
  "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:

{
  "count": 1,
  "devices": [],
  "new_device_ids": ["gree-aabbccddeeff"]
}

GET /api/devices

Returns Device[].

POST /api/devices

Manual add request:

{
  "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:

{
  "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:

{
  "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:

{
  "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,
  "separate_hysteresis": false,
  "cool_hysteresis": 0.6,
  "heat_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: shared controller hysteresis, valid range 0.1..5.0 °C.
  • separate_hysteresis: when true, cooling uses cool_hysteresis and heating uses heat_hysteresis; both use the same 0.1..5.0 °C range.
  • 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

Manual setpoint values are retained with 0.1 °C precision. The physical GREE unit setpoint is still rounded to the whole-degree resolution supported by the protocol.

Quick thermostat endpoint. Body fields are optional and can be combined:

{
  "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 regardless of whether group-level control is enabled for its climate group.
  • 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 automation. If there is no active schedule, manual preset/setpoint, Temporary Quick Thermostat or other explicit thermostat intent, the zone remains physically OFF instead of falling back to implicit Comfort.
  • 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

{
  "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:

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

{
  "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:

{
  "zone": {},
  "schedules": []
}

Groups

Group object

{
  "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

{
  "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

{
  "power": true,
  "mode": "house",
  "preset": "custom",
  "setpoint": 22.3
}

All fields optional:

  • power: enable/disable group-level control (legacy field name; this is not a device power gate),
  • mode: house, cool, heat,
  • preset: auto, comfort, sleep, away, custom,
  • setpoint: custom group target in the 830°C range; requires preset: "custom".

A custom setpoint creates the same override for every member zone. For explicit Web/Home Assistant group control it stays active until the group is changed/released; scheduled group automation remains bounded by the normal schedule hand-back. Returns a group control/result object including updated members/state.

Group power is a scoped bulk-power/control action. power=false immediately powers member units off and releases group:* ownership; members are stored as individually-off thermostats rather than being blocked by membership in an OFF group. This group-created OFF is indefinite (no 15-minute local hand-back), so the regulator cannot restart the unit by itself; a user can still turn an individual thermostat back on independently. power=true clears that scoped thermostat-OFF state and immediately re-runs group thermostat arbitration. Explicit Web/Home Assistant group control has higher priority than house rules and schedules; scheduled group automations still respect higher-priority manual/local ownership. Group mode/preset/setpoint changes are accepted only while the group is ON. Global ON/OFF never creates a persistent global gate. Global OFF does leave member thermostats locally OFF until they are explicitly re-enabled by local/group/global ON.


Whole-house control

House thermostat rules and global power actions are intentionally separate. Global ON/OFF does not create a master automation gate; OFF is persisted as per-zone local OFF so ordinary demand cannot immediately undo it.

POST /api/house/control

{
  "mode": "cool"
}

Valid modes: cool, heat, off.

  • cool/heat select the house rule used by zones that inherit the global mode and immediately re-run arbitration for free zones. Explicit local/group/direct ownership is preserved.
  • off means do not perform house-level thermostat control for inherited free zones. It does not block local thermostats, groups, device-manual control or controller automations.

Returns public runtime settings.

POST /api/house/power

{
  "power": false
}

This endpoint is a bulk thermostat power action without a persistent blocking global gate. false first stores every thermostat as an indefinite local OFF and then sends immediate OFF to every technically enabled unit, so the next normal demand cycle cannot turn the house back on. A later per-zone Resume automation clears that local OFF but does not invent a Comfort demand: without an active schedule/override/temporary or another explicit intent the zone stays OFF. true is itself an explicit whole-house ON intent, releases local OFF markers for all thermostats and sends ON to all technically enabled units. Group enablement, profiles, schedules and house mode are preserved. Pending compressor-protection tasks from before the action are cleared. When compressor protection is enabled, protected global starts are queued until their safe deadline; OFF is never delayed.

Response includes:

{
  "power": false,
  "one_shot": true,
  "devices": [],
  "groups": [],
  "settings": {},
  "failed": []
}

POST /api/house/preset

{
  "preset": "sleep"
}

Valid: auto, comfort, sleep, away.

A non-auto preset creates overrides for free house-controlled zones and normally expires at each zone's next schedule boundary. auto clears those free-zone overrides. Explicit local thermostat, group, temporary thermostat and direct/manual ownership is not overwritten by a house profile action.


Schedules

Schedule object/request:

{
  "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:

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:

{
  "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:

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:

{
  "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:

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:

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:

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:

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:

{
  "events": [
    {
      "id": 1,
      "timestamp": "...",
      "level": "info",
      "kind": "device.updated",
      "message": "...",
      "metadata": {}
    }
  ]
}

GET /api/events/retention

{
  "days": 30
}

PUT /api/events/retention

{
  "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:

{
  "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

{
  "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

{
  "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

{
  "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 cannot be changed here; use House Control API. house_power_enabled is a legacy compatibility field and is normalized to true; global ON/OFF no longer uses a persistent master gate.
  • 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:

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:

{
  "ok": true
}

Debug API

GET /api/debug

{
  "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

{
  "entity_id": "sensor.room_temperature"
}

entity_id is optional; controller defaults/aliases are resolved. Response:

{
  "ok": true,
  "temperature_c": 23.4,
  "entity_id": "sensor.room_temperature"
}

POST /api/integrations/home-assistant/entity

Reads the current raw Home Assistant entity document used by the shared-Flow-input diagnostics UI.

{
  "entity_id": "climate.gas_boiler"
}

Response includes the raw state, availability, attributes and timestamps:

{
  "ok": true,
  "entity_id": "climate.gas_boiler",
  "state": "heat",
  "available": true,
  "attributes": { "hvac_action": "heating" },
  "last_changed": "2026-09-02T06:30:00Z",
  "last_updated": "2026-09-02T06:30:05Z"
}

POST /api/integrations/notifications/test

Accepts a NotificationSettings object. Blank secret/webhook fields reuse saved secrets for the test.

Response:

{
  "ok": true
}

Access tokens

GET /api/access-tokens

Returns:

[
  {
    "id": "uuid",
    "name": "Home Assistant",
    "token_prefix": "gree_controller_abc...",
    "created_at": "..."
  }
]

POST /api/access-tokens

{
  "name": "Home Assistant"
}

Name length: 1..80. If omitted, default is Home Assistant.

Response 201 Created:

{
  "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:

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:

ws://HOST:8787/ws

When GREE_CONTROLLER_APP_TOKEN is configured:

ws://HOST:8787/ws?token=APP_TOKEN

Generated restricted HA tokens are not WebSocket administrator tokens.

Message envelope

Every server event uses:

{
  "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:

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:

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

GET /lang/en.json
GET /lang/pl.json

Practical API examples

Assume:

BASE='http://127.0.0.1:8787'
AUTH='Authorization: Bearer APP_TOKEN'

Discover devices:

curl -X POST "$BASE/api/discovery" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"protocol_version":0,"passes":3}'

Directly poll a device:

curl -X POST "$BASE/api/devices/DEVICE_ID/poll" -H "$AUTH"

Set a zone target:

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:

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:

curl -X POST "$BASE/api/house/power" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"power":false}'

Read current ownership/desired-vs-actual state:

curl "$BASE/api/control-plan" -H "$AUTH"

Read 90 days of zone history:

curl "$BASE/api/history?scope=zones&zone_id=ZONE_ID&hours=2160" -H "$AUTH"

Create a restricted Home Assistant token:

curl -X POST "$BASE/api/access-tokens" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"name":"Home Assistant"}'

Use that token:

curl -H 'Authorization: Bearer gree_controller_RESTRICTED_TOKEN' \
  "$BASE/api/integrations/home-assistant/control-plan"

Visual Flow API

Flow is the source-of-truth representation for visual schedule/automation logic. GET /api/flows lists Flows; GET /api/flows/:id, POST /api/flows, PUT /api/flows/:id and DELETE /api/flows/:id manage them. A Flow payload contains name, enabled, optional description, nodes and edges.

Saving a Flow validates the DAG and compiles it atomically. A thermostat action driven only by one weekday block and one time-range block is emitted as a native schedule when its settings are schedule-compatible. More complex graphs are emitted as Flow-triggered automations. Generated schedules and automations expose flow_id / flow_node_id, use stable names in the form flow-<stable-unique-id>, and cannot be edited or deleted through their legacy endpoints; edit the owning Flow instead.

Condition blocks support weekday, time/date ranges, application Night mode, outdoor/device/zone temperatures, house/device/zone/group state, arbitrary Home Assistant state/numeric/attribute/availability sources, and shared_input references. Shared inputs are stored in home_assistant.flow_inputs and resolve dynamically at evaluation time, allowing the same condition definition to be reused across multiple Flows. Logic blocks support AND, OR and NOT. Action blocks target thermostat zones, GREE devices or climate groups. Direct GREE actions map to the existing DeviceCommand fields including fan, swing, quiet, turbo, light, air, xfan, health and sleep; ownership rules may suppress fields that would fight an active thermostat. Flow group actions use the existing group controller and support power, house/heat/cool mode, auto/comfort/sleep/away and the existing custom group target.

Compressor protection queue

Runtime settings expose compressor_protection_enabled and compressor_protection_seconds (301800; default 180). While enabled, thermostat starts and Heat/Cool reversals that fall inside the protection window are represented on the owning zone by compressor_pending_action, compressor_pending_since, and compressor_pending_until.

  • POST /api/zones/:id/compressor-queue/cancel cancels the currently pending compressor-protection task for one thermostat.
  • POST /api/compressor-queue/cancel-all cancels all currently pending compressor-protection tasks.

Cancellation suppresses the same pending intent until a new explicit thermostat/group/house command re-arms it, a scheduled Temporary Quick Thermostat session takes ownership, or a different mode/target creates a new intent. Safety OFF commands are not delayed by compressor protection. Direct technical device commands remain immediate manual-control operations.

Flow portability and diagnostics

GET /api/flows/:id/export returns a versioned gree-controller-flow JSON document containing only the source graph. Generated schedules/automations are not exported.

POST /api/flows/import accepts either that envelope or a direct Flow source payload, creates a new Flow ID/revision, validates it and atomically recompiles outputs.

POST /api/flows/simulate accepts { flow, flow_id?, at?, overrides?, log? }. at is RFC3339. overrides maps Flow node IDs to simulated values. The endpoint validates and compiles the graph, evaluates every action and returns per-node traces plus matched, would_execute and blocked_reason (for example disabled zone/device, manual/local/temporary thermostat ownership, thermostat-output conflict or disabled Flow). It never mutates thermostat/device/group/schedule/automation state. Home Assistant read/attribute/parse failures evaluate safely as false and are visible in the trace instead of accidentally satisfying NOT/neq logic.

GET /api/flows/:id/logs?limit=100 returns events associated with the Flow or its generated automations.

Existing Flow updates require expected_revision. A mismatched revision returns HTTP 409 to prevent stale editor tabs from overwriting newer graphs. Source Flow plus generated outputs are replaced atomically in one database transaction while configuration/automation/schedule/thermostat-cycle operations are serialized.

Additional Flow condition blocks are house_mode, device_state, zone_state, group_state, night_mode, ha_attribute, ha_available, constant and shared_input, alongside weekday/time/date, temperature, Home Assistant state/numeric and AND/OR/NOT blocks. device_state can inspect enabled/online/power/mode/fan/swing/quiet/turbo/light/air/xfan/health/sleep state. The editor ships 37 categorized editable templates covering comfort, energy, safety, night, reliability, Home Assistant heat-source coordination and advanced multi-branch logic. The template UI adds search, favorites/recent history, a graph preview and runtime requirement checks; shared inputs expose usage links and HA-backed inputs can be tested against live entity state.