LG ThinQ Connect
GEM driver for LG air conditioners and heat pumps through ThinQ Connect, LG's official open API. Authentication is a Personal Access Token (PAT) issued from LG's developer portal — no OAuth redirect to complete, no LG password stored on the controller.
This closes the LG gap in GEM's HVAC coverage. LG single-zone mini-splits, Art Cool units and Multi F systems with a Wi-Fi module were previously only reachable through a third-party gateway such as Intesis; this talks to them directly.
Feature coverage:
- Power on/off, target temperature, operating mode and fan speed on air conditioners
- Polled read of current temperature, target temperature, mode, fan speed, humidity where reported
- Discovery of every appliance on the LG account with its
deviceId, type and alias set_propertypassthrough so non-AC ThinQ appliances on the same account (washers, dryers, air purifiers, dehumidifiers) can still be driven from a macro
This is a cloud integration. LG publishes no local control path for ThinQ appliances, so the units go offline in GEM during an internet outage or an LG service incident. Where a site needs local-only climate control, an Intesis WMP gateway on the indoor unit is the alternative — see Intesis WMP.
Prerequisites
- The appliances already registered and working in the LG ThinQ mobile app. This driver reads an existing account; it cannot onboard hardware or join a unit to Wi-Fi.
- A Wi-Fi module fitted to each indoor unit. Many LG mini-splits ship without one — it is a separate part (PWFMDD200 or the model-specific equivalent) and on some models it must be installed by the dealer.
- A Personal Access Token created at
connect-pat.lgthinq.com, with the device scopes enabled for each appliance GEM should see. - Outbound HTTPS from the GEM host to
api-<region>.lgthinq.com.
Setup steps
-
Create the token. Sign in at
connect-pat.lgthinq.comwith the LG account that owns the appliances. Create a Personal Access Token and tick the scopes for each device you want GEM to read and control. A token issued before an appliance was added to the account will not see that appliance — regenerate it after adding hardware. -
Add the device. On Devices, create a device with driver
lg_thinqand set:token— the PAT (stored encrypted)country— the two-letter country code of the LG account, not necessarily the site's country
-
Leave
regionempty to start. It is derived fromcountry:kicfor Korea,aicfor the Americas,eiceverywhere else. Only set it explicitly if discovery comes back empty on a token you know is good. -
Discover the appliances. From the Script Console, run the device's
get_devicescommand. Each entry returns adevice_id,device_type,aliasandmodel. -
Create zones. Add one zone per appliance with the zone address set to that
device_id— the long opaque string, not the nickname you gave it in the LG app. -
Confirm state. Run
get_status. Each zone should come back with the values the driver read; those same values land on the zone as attributes.
Attribute reference
Device attributes
| Attribute | Required | Default | Purpose |
|---|---|---|---|
token | yes | — | Personal Access Token from connect-pat.lgthinq.com. Stored encrypted. |
country | yes | US | Two-letter country code of the LG account, sent as x-country. Must match the account. |
region | no | derived | Regional endpoint: aic, eic or kic. Derived from country when empty. |
api_base | no | — | Full endpoint override (https://api-aic.lgthinq.com). Wins over region. |
api_key | no | published key | Sent as x-api-key. Override only if LG rotates the published client key. |
client_id | no | gem-<device id> | Sent as x-client-id. Stable across restarts by default. Set it only to distinguish two GEM controllers on one LG account. |
status_interval | no | 60000 | Poll interval in milliseconds. Do not go below 30000 on an account with several appliances — the API is rate limited. |
request_timeout | no | 15000 | Per-request timeout in milliseconds. |
Attributes the driver writes
On each zone: state (on/off), temperature (current), setpoint (target), system_mode, fan_mode, temperature_unit, and humidity where the appliance reports it. Values are written only when they change.
Zone address format
The zone address is the ThinQ deviceId exactly as get_devices returns it. It is an opaque identifier, not the appliance alias, and it does not change when you rename the unit in the LG app.
Commands
| Command | Arguments | Notes |
|---|---|---|
get_devices | — | Lists the account's appliances. Run this first. |
get_device_state | address | Raw state document for one appliance. |
get_device_profile | address | Which resources and properties this model exposes. This is where you find names for set_property. |
on / off | address | Sets operation.airConOperationMode. |
set_temperature | address, level | Target temperature in the appliance's own unit. |
set_mode | address, mode | cool, heat, auto, fan, dry / air_dry, air_clean, or a raw ThinQ job mode. |
set_fan_mode | address, fan_mode | low, mid, high, auto (also slow, power), or a raw ThinQ wind strength. |
set_property | address, resource, property, value | Generic control write for appliance types this driver does not model. |
get_status | — | Polls every zone now and returns what was read. |
Driving a non-AC appliance
The API keys every control write by resource group, and the groups differ per appliance type. Rather than half-modelling a washer, the driver exposes the raw write. Read the group and property names from get_device_profile, then:
set_property address=<deviceId> resource=operation property=<propertyName> value=<value>
Known limitations
- Cloud only, polled only. Push events (
event/{id}/subscribe) are not wired up, so a change made at the wall unit or in the LG app takes up to onestatus_intervalto show in GEM. - Only the air-conditioner surface is modelled. Power, setpoint, job mode and fan speed. Swing/vane direction, timers, sleep mode, power-save and filter life are readable through
get_device_statebut are not mapped onto attributes or commands. - Modes are per-model. A unit with no dry mode returns an API error for
AIR_DRYrather than ignoring it. Checkget_device_profilebefore wiring a mode into a macro. - Two-setpoint (auto) mode is not exposed. Profiles that use
twoSetTemperaturefor a heat/cool deadband are readable butset_temperaturewrites the singletargetTemperatureonly. - No account onboarding. Adding hardware, joining Wi-Fi and naming units all happen in the LG app.
Troubleshooting
| Symptom | Check |
|---|---|
get_devices returns an empty list on a token you know is valid | country and region disagree with the account. Confirm country matches the LG account's region, then set region explicitly (aic / eic / kic). |
| Every request returns 401 or 403 | The PAT expired or lacks scope for the device. Regenerate it and re-enable the device scopes. |
One appliance is missing from get_devices | It is not registered to this LG account, has no Wi-Fi module, or was added after the token was issued. |
set_temperature returns an API error | The unit is off, or is in a mode with no setpoint (fan, air clean). Power it on and set a heating or cooling mode first. |
| Commands succeed but GEM state does not move | State is polled, not pushed. Wait one status_interval, or call get_device_state to confirm the appliance actually changed. |
| Errors appear under load with several appliances | LG rate-limits the API. Raise status_interval; the driver paces its own polling but many zones on a short interval will still trip it. |
Protocol notes
Requests go to https://api-<region>.lgthinq.com/<path> with these headers:
| Header | Value |
|---|---|
authorization | Bearer <PAT> |
x-country | Two-letter country code |
x-message-id | Fresh base64url token per request |
x-client-id | Stable client identifier |
x-api-key | Published ThinQ Connect client key |
x-service-phase | OP |
x-conditional-control | true, on control writes only |
Paths used: GET devices, GET devices/{id}/profile, GET devices/{id}/state, POST devices/{id}/control. Control bodies are {"<resource>": {"<property>": <value>}} — for example {"operation": {"airConOperationMode": "POWER_ON"}}.
Responses are wrapped as {"messageId": ..., "timestamp": ..., "response": {...}}; errors arrive as {"error": {"code": ..., "message": ...}} and are surfaced to the caller rather than retried.
The TLS certificate is fully verified on every call. GEM tolerates self-signed certificates on LAN devices, but the ThinQ endpoints are public hosts presenting a publicly-trusted chain, and every request carries the account-wide PAT — so a certificate error here is not something to work around. It means the connection is being intercepted; find the TLS-inspecting proxy or firewall instead.