Files
gree-controller/docs/API.md
T
2026-09-01 14:18:00 +02:00

40 KiB
Raw Blame History

GREE Controller API reference

HTTP and WebSocket API for GREE Controller 0.9.3.

← 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 Send a one-shot ON/OFF command to all enabled units.
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 one-shot all-units 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.3",
  "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.3",
    "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 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

{
  "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 actions are one-shot physical commands and never create a persistent group/zone gate.


Whole-house control

House thermostat rules and global power actions are intentionally separate. Global ON/OFF is one-shot; it is not an automation enable/disable state.

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 one-shot physical command. false sends OFF to every technically enabled unit; true sends ON to every technically enabled unit. It does not change group enablement, local thermostat ownership, direct/manual takeover, schedules, house mode or automation rules. Those controllers may issue a later command independently. Pending compressor-protection tasks from before the global action are cleared and may be recreated by a later fresh thermostat decision. When compressor protection is enabled, a global true request for a recently stopped thermostat-managed unit is queued until its safe start deadline and appears in the compressor queue; false is never delayed by compressor protection.

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 because global ON/OFF is one-shot.
  • 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/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"

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.