39 KiB
GREE Controller API reference
HTTP and WebSocket API for GREE Controller 0.9.0.
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 |
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:
{
"status": "ok",
"name": "gree-controller",
"version": "0.9.0",
"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.0",
"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 range500..30000ms.protocol_version:0auto/both,1AES-ECB only,2AES-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:
nameis required.device_idmust reference an existing device and thermostat ownership must remain valid/safe.mode:coolorheatwhen not inheriting house mode.- temperature/profile values are constrained to the supported thermostat range.
hysteresis: shared controller hysteresis, valid range0.1..5.0°C.separate_hysteresis: whentrue, cooling usescool_hysteresisand heating usesheat_hysteresis; both use the same0.1..5.0°C range.standby_offset_c: bounded thermostat offset.sensor_source:device,home_assistantorcombined.external_sensor_weight:0..1.revisionis used for optimistic concurrency where supplied; stale updates can return409.
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;autoclears the profile override.mode:house,cool,heat;houserestores 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:
nowdelay+start_delay_minutesat+ ISO-8601start_at
finish_kind:
duration+duration_minutesuntil+ ISO-8601untiltemperature_reachedtemperature_stable+hold_minutesschedule_boundary
Temperature operators:
withinat_or_belowat_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:30–22:30, Sleep overnight. |
child |
Comfort 06:30–20:30, Sleep overnight. |
bedroom |
Comfort 06:30–22: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 the8–30°Crange; requirespreset: "custom".
A custom setpoint creates the same temporary manual override for every member zone and normally expires at that zone's next schedule boundary. 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, so a user can turn a thermostat back on independently. power=true clears that scoped thermostat-OFF state and immediately re-runs group thermostat arbitration. Explicit Web/Home Assistant group power actions take over older member manual/temporary ownership; scheduled group automations still respect higher-priority manual/local ownership. Group mode/preset/setpoint changes are accepted only while the group is ON. Whole-house power remains independent and authoritative.
Whole-house control
House thermostat mode and master power are intentionally separate.
POST /api/house/control
{
"mode": "cool"
}
Valid modes: cool, heat, off.
cool/heatare explicit whole-house activation requests: master power is enabled and the thermostat arbiter starts eligible managed zones. Existing per-group control enable/disable settings are preserved.offmeans 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
{
"power": false
}
false is authoritative: it clears local/manual ownership markers as required and powers every enabled physical device down. It does not modify per-group control enable/disable settings. true resumes thermostat arbitration rather than blindly sending a bare ON frame.
Response includes:
{
"power": false,
"devices": [],
"groups": [],
"settings": {},
"failed": []
}
POST /api/house/preset
{
"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 explicitly re-enables master power while preserving each group's control enable/disable setting.
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_timerepresents 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: requirestrigger_device_id+threshold,temperature_below: requirestrigger_device_id+threshold,time: requires localat_timeinHH:MM.
Action target is either:
- direct device:
action_device_id+ fullDeviceCommand, or - group:
action_group_id; group automation supports only power,house/cool/heatmode and optionalaction_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_modeandhouse_power_enabledcannot be changed here; use House Control API.- polling interval is clamped
2..3600seconds. - zone interval is clamped
2..3600seconds. - discovery timeout is clamped
300..30000ms. control_strategyis normalized tosetpoint.- 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..86400seconds. - notification cooldown:
30..86400seconds. - communication failure threshold:
2..100. - target timeout:
5..1440minutes. - retention windows:
1..3650days. - night mode times must be
HH:MM; max fan is clamped1..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 (30–1800; 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/cancelcancels the currently pending compressor-protection task for one thermostat.POST /api/compressor-queue/cancel-allcancels 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.