This is an unofficial documentation of the local API used by the Home app to communicate with GH devices. GitHub Repo
Requests must be made over HTTPS, port 8443, so the base URL for these endpoints is: https://<google-home-ip>:8443/setup/
Get the IP of Google Home from the Google Home app (Device Settings -> End of the list) or from your router.
GET requests are simple, in the browser kind.
POST requests need to set the header (when there's a body): content-type: application/json
Since June 2019, most requests (with exceptions like /setup/eureka_info
) need a local authorization token.
There are 3 kinds of tokens involved here:
This token must be sent in all requests in the header cast-local-authorization-token
. It is short-lived (~1 day) and may change unexpectedly (with a sync, change in homegraph, etc.)
This is a standard google oauth2 access token. It is in the form ya29.***
.
This gives access to the Google Home Foyer API. These expire in an hour.
Use this to get the homegraph (and then the local authorization token above).
To get this access token, either a Google account username/password or a Google Master Token is needed. More info in the gist. Use the script from this gist.
This is in the form aas_et/***
and can be used to request access tokens.
The same script in the gist that gets the access token can also get the master token. Needs Google account creds.
Here's the whole flow from just a pair of username/password to using the local API.
Prerequisites:
python3 get_tokens.py
# Note down the access token printed.
./grpcurl -H 'authorization: Bearer ya29.a0Af****' \
-import-path /path/to/protos \
-proto /path/to/protos/google/internal/home/foyer/v1.proto \
googlehomefoyer-pa.googleapis.com:443 \
google.internal.home.foyer.v1.StructuresService/GetHomeGraph | jq '.home.devices[] | {deviceName, localAuthToken}'
# Note down the local auth token for the device you want.
curl -H "cast-local-authorization-token: LOCAL_AUTH_TOKEN" --verbose --insecure https://192.168.0.18:8443/setup/bluetooth/status
Simply returns a list of all supported timezones.
[- {
- "display_string": "Samoa Standard Time (Midway)",
- "offset": -660,
- "timezone": "Pacific/Midway"
}, - {
- "display_string": "Hawaii-Aleutian Standard Time (Honolulu)",
- "offset": -600,
- "timezone": "Pacific/Honolulu"
}
]
This gives "app device id", "certificate" and "signed data".
The app_id
in the request is mandatory and refers to Chromecast backdrop/screensaver app. It has to be set to E8C28D3C
.
The certificate is valid and issued by Chromecast ICA 6 (Audio Assist), Google Inc
.
Not sure what the other two are.
app_id required | string |
{- "app_id": "E8C28D3C"
}
{- "app_device_id": "...",
- "certificate": "-----BEGIN CERTIFICATE-----\nMIID...Oeb0\n-----END CERTIFICATE-----\n",
- "signed_data": "HAjp..."
}
Simply returns a list of all supported locales.
[- {
- "display_string": "Arabic - العربية",
- "locale": "ar"
}, - {
- "display_string": "German - Deutsch",
- "locale": "de"
}, - {
- "display_string": "English (United States)",
- "locale": "en-US"
}
]
Update: This seems to have changed now and is no longer possible. The error is also new.
Setting play_ready_message
to true plays a welcome message on the device saying "Hi, I'm your Google Assistant. I'm here to help. To learn a few things you can do, continue in the Google Home app."
play_ready_message required | boolean |
user_id required | string |
{- "play_ready_message": true,
- "user_id": "xxxxx"
}
{- "can_enroll": true,
- "enrollment_state": 0,
- "error_code": 0,
- "ready": false,
- "retryable": true
}
This gives most of the device info. The GET parameter param
is a comma separated list of json keys to fetch. Currently, these params are known: version,audio,name,build_info,detail,device_info,net,wifi,setup,settings,opt_in,opencast,multizone,proxy,night_mode_params,user_eq,room_equalizer,sign,aogh,ultrasound,mesh
Nested items can also be filtered using the dot notation. Example: audio.digital
The options
GET parameter is always set to detail
or detail,sign
. sign
signs the nonce
and returns some value.
The nonce
GET parameter is an integer value signed with needed (see option
parameter above).
params required | string Example: params=version,audio,name,build_info,detail,device_info,net,wifi,setup,settings,opt_in,opencast,multizone,proxy,night_mode_params,user_eq,room_equalizer,sign,aogh,ultrasound,mesh |
options required | string Example: options=detail,sign |
nonce required | integer <int32> Example: nonce=1234512345 |
{- "aogh": {
- "aogh_api_version": "2"
}, - "audio": {
- "digital": false
}, - "build_info": {
- "build_type": 2,
- "cast_build_revision": "1.46.195690",
- "cast_control_version": 1,
- "preview_channel_state": 4,
- "release_track": "preview-joining-stable-channel",
- "system_build_number": "195690"
}, - "detail": {
- "icon_list": [
- {
- "depth": 32,
- "height": 55,
- "mimetype": "image/png",
- "url": "/setup/icon.png",
- "width": 98
}
], - "locale": {
- "display_string": "English (United States)"
}, - "timezone": {
- "display_string": "India Standard Time (Kolkata)",
- "offset": 330
}
}, - "device_info": {
- "4k_blocked": 0,
- "capabilities": {
- "aogh_supported": true,
- "assistant_supported": true,
- "audio_hdr_supported": false,
- "audio_surround_mode_supported": false,
- "ble_supported": true,
- "bluetooth_audio_sink_supported": true,
- "bluetooth_audio_source_supported": true,
- "bluetooth_supported": true,
- "cloudcast_supported": true,
- "content_filters_supported": true,
- "display_supported": false,
- "fdr_supported": false,
- "hdmi_prefer_50hz_supported": false,
- "hdmi_prefer_high_fps_supported": false,
- "hotspot_supported": true,
- "https_setup_supported": true,
- "input_management_supported": true,
- "keep_hotspot_until_connected_supported": true,
- "multi_user_supported": true,
- "multichannel_group_supported": true,
- "multizone_supported": true,
- "night_mode_supported": true,
- "night_mode_supported_v2": true,
- "opencast_supported": false,
- "preview_channel_supported": true,
- "reboot_supported": true,
- "remote_ducking_supported": true,
- "separate_tts_volume_supported": true,
- "setup_supported": true,
- "sleep_mode_supported": true,
- "stats_supported": true,
- "system_sound_effects_supported": false,
- "user_eq_supported": true,
- "wifi_auto_save_supported": true,
- "wifi_regulatory_domain_locked": true,
- "wifi_supported": true
}, - "cloud_device_id": "003D...",
- "factory_country_code": "US",
- "hotspot_bssid": "FA:8F:CA:30:41:71",
- "local_authorization_token_hash": "hBt2...",
- "mac_address": "F4:F5:...",
- "manufacturer": "Google Inc.",
- "model_name": "Google Home",
- "product_name": "pineapple",
- "public_key": "MIIB...",
- "ssdp_udn": "baab...",
- "uma_client_id": "8ce0...",
- "uptime": 48509.169282,
- "weave_device_id": ""
}, - "multizone": {
- "audio_output_delay": 0,
- "audio_output_delay_hdmi": 0,
- "audio_output_delay_oem": 0,
- "aux_in_group": "",
- "dynamic_groups": [ ],
- "groups": [ ],
- "multichannel_status": 0
}, - "name": "Bedroom Speaker",
- "net": {
- "ethernet_connected": false,
- "ip_address": "192.168.0.18",
- "online": true
}, - "night_mode_params": {
- "device_override_do_not_disturb": 0,
- "do_not_disturb": true,
- "enabled": true,
- "led_brightness": 0.5799999833106995,
- "volume": 0.5899999737739563,
- "windows": [
- {
- "days": [
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6
], - "length_hours": 6,
- "start_hour": 23
}
]
}, - "opencast": {
- "pin_code": ""
}, - "opt_in": {
- "audio_hdr": false,
- "audio_surround_mode": 0,
- "autoplay_on_signal": true,
- "cloud_ipc": true,
- "hdmi_prefer_50hz": false,
- "hdmi_prefer_high_fps": true,
- "managed_mode": false,
- "opencast": true,
- "preview_channel": true,
- "remote_ducking": true,
- "stats": true,
- "ui_flipped": false,
- "wpa3_support_enabled": false
}, - "proxy": {
- "mode": "system"
}, - "settings": {
- "closed_caption": { },
- "control_notifications": 1,
- "country_code": "IN",
- "locale": "en-US",
- "network_standby": 0,
- "system_sound_effects": true,
- "time_format": 1,
- "timezone": "Asia/Kolkata",
- "wake_on_cast": 1
}, - "setup": {
- "qr_ssid_suffix": "",
- "setup_state": 60,
- "ssid_suffix": "k",
- "stats": {
- "num_check_connectivity": 0,
- "num_connect_wifi": 0,
- "num_connected_wifi_not_saved": 0,
- "num_initial_eureka_info": 0,
- "num_obtain_ip": 0
}, - "tos_accepted": true
}, - "sign": {
- "certificate": "-----BEGIN CERTIFICATE-----\nMIID...Oeb0\n-----END CERTIFICATE-----\n",
- "intermediate_certs": [
- "-----BEGIN CERTIFICATE-----\nMIID...O/bYS\n-----END CERTIFICATE-----\n"
], - "nonce": "1234512345",
- "signed_data": "Rr5Q..."
}, - "user_eq": {
- "high_shelf": {
- "frequency": 4500,
- "gain_db": 0,
- "quality": 0.707
}, - "low_shelf": {
- "frequency": 150,
- "gain_db": 0,
- "quality": 0.707
}, - "max_peaking_eqs": 0,
- "peaking_eqs": [ ]
}, - "version": 10,
- "wifi": {
- "bssid": "a0:ab:...",
- "has_changes": false,
- "noise_level": -90,
- "signal_level": -50,
- "ssid": "Nucl...",
- "wpa_configured": true,
- "wpa_id": 1,
- "wpa_state": 10
}
}
Update: This seems to have been removed. Returns 404 Not Found.
This endpoint tests internet download speed. Any sample file URL can be provided.
url required | string |
{- "bytes_received": 31457280,
- "response_code": 200,
- "time_for_data_fetch": 4722,
- "time_for_http_response": 316
}
This can simply reboot the device (params: "now"
) or factory reset the device (params: "fdr"
).
params required | string |
{- "params": "now"
}
This sets night mode options.
To view currently set values, use /setup/eureka_info.
If enabled
is set to false, night mode is disabled and the other values do not matter.led_brightness
and volume
refer to the maximum LED Brightness and Volume that is set during night mode.demo_to_user
is always set to true
so change in values will be visible in realtime (like brightness).windows
: A combination of length_hours
and start_hour
is used to define start and end times for night mode. In this example, night mode starts at 10 PM (22) and ends at 6 AM (8 hours later). windows.days
is an array of days of week when night mode will be enabled. Example: 0->Sunday, 1-> Monday, ..., 6->Saturday.
enabled required | boolean |
do_not_disturb required | boolean |
led_brightness required | number |
volume required | number |
demo_to_user required | boolean |
required | Array of objects (Window) |
{- "enabled": false,
- "do_not_disturb": true,
- "led_brightness": 0.44999998807907104,
- "volume": 0.46000000834465027,
- "demo_to_user": true,
- "windows": [
- {
- "length_hours": 8,
- "days": [
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6
], - "start_hour": 22
}
]
}
{- "do_not_disturb": true,
- "enabled": false,
- "led_brightness": 0.44999998807907104,
- "volume": 0.46000000834465027,
- "windows": [
- {
- "days": [
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6
], - "length_hours": 8,
- "start_hour": 22
}
]
}
This can set custom values to some options.
Only fields to be modified need to be sent, not all. The example has some modifiable fields.
TODO: List all modifiable fields.
Sending non-existant fields will still return a 200 OK, but they are not saved.
name required | string |
required | object (Settings1) |
required | object (OptIn1) |
{- "name": "Living Room",
- "settings": {
- "control_notifications": 2
}, - "opt_in": {
- "opencast": true,
- "preview_channel": true,
- "remote_ducking": true,
- "stats": true
}
}
This folder contains all endpoints related to Assistant's tasks like Do Not Disturb, Alarms and Timers, Accessibility and equalizer.
This gives a list of all active alarms and timers.
Both alarms and timers have id
s which can be used to delete them. (There is no known way of creating/deleting yet). The value of status
have different meanings for alarms and timers (given below).
Alarms have date_pattern
and time_pattern
with day, month, year, hour, minute, second. fire_time
is the same in unix time (milliseconds, not seconds).status
is 1 for set up and 2 for ringing.
Timers have original_duration
is the original duration.status
is 1 for set up and 3 for ringing.
{- "alarm": [
- {
- "date_pattern": {
- "day": 15,
- "month": 1,
- "year": 2018
}, - "time_pattern": {
- "hour": 6,
- "minute": 50,
- "second": 0
}, - "fire_time": 1515995400000,
- "id": "alarm/xxx",
- "status": 1
}
], - "timer": [
- {
- "fire_time": 1516176765589,
- "id": "timer/xxx",
- "original_duration": 20000,
- "status": 1
}
]
}
This deletes alarms and timers by their id.
ids
is a list of ids to be deleted. Sending invalid id still returns a 200 OK. The /
in the ids have to be escaped like \/
.
ids required | Array of strings |
{- "ids": [
- "timer/xxx",
- "alarm/xxx"
]
}
{- "success": true
}
This is for the Do Not Disturb option. Sending an empty-body POST returns the current value. Sending a new value changes it.
Content-Type required | string Example: application/json |
{- "notifications_enabled": true
}
This gets and sets alarms and timers volume.
Note: This is not the same as normal volume.
Volume is a float number in [0, 1] where 0 is minimum and 1 is maximum.
Sending an empty body gets the volume. Sending volume
sets the volume.
volume required | integer <int32> |
{- "volume": 1
}
{- "volume": 1
}
This can only set new equalizer values. To get already set values, use /setup/eureka_info.
The body is mandatory. It can either contain low_shelf
or high_shelf
or both.
low_shelf.gain_db
and high_shelf.gain_db
refer to bass and treble respectively.
Default values are 0 for both.
While the slider in the Home app only ranges from -6 to +6, they can be set to any integer like 50 or -100. These changes persist.
required | object (LowShelf1) |
required | object (HighShelf1) |
{- "low_shelf": {
- "gain_db": 0
}, - "high_shelf": {
- "gain_db": 0
}
}
This controls Accessibility sounds. hotword_enabled
is for 'Play start sound' and endpoint_enabled
is for 'Play end sound'.
Sending an empty-body POST request returns the current values.
Either of the fields or both can be sent and new values will be saved.
hotword_enabled required | boolean |
endpoint_enabled required | boolean |
{- "hotword_enabled": false,
- "endpoint_enabled": false
}
{- "endpoint_enabled": false,
- "hotword_enabled": false
}
See note for Bluetooth under /setup/bluetooth/status
For Part 2 only
This returns a list of all nearby bluetooth devices. While the Home app only shows speakers, this list contains all devices including TVs, mobiles, etc.
rssi
is signal strength, name
is name, mac_address
is mac address.device_class
and device_type
are bluetooth codes.
The Home app only lists those devices with expected_profiles
> 0. Basically, the device should function as a speaker.
[- {
- "device_class": 525372,
- "device_type": 3,
- "expected_profiles": 0,
- "mac_address": "xx:xx:xx:xx:xx:xx",
- "name": "KD-49X8200E",
- "rssi": -90
}, - {
- "device_class": 5898764,
- "device_type": 1,
- "expected_profiles": 0,
- "mac_address": "xx:xx:xx:xx:xx:xx",
- "name": "vivo 1714",
- "rssi": -96
}, - {
- "device_class": 2491396,
- "device_type": 1,
- "expected_profiles": 1,
- "mac_address": "xx:xx:xx:xx:xx:xx",
- "name": "Bluetooth Speaker",
- "rssi": -93
}
]
See note for Bluetooth under /setup/bluetooth/status
For both parts
This is to forget paired devices by mac address. Works for both kinds of devices (Part 1 and Part 2).
mac_address required | string |
bond required | boolean |
{- "mac_address": "xx:xx:xx:xx:xx:xx",
- "bond": false
}
See note for Bluetooth under /setup/bluetooth/status
For Part 1 only
This enables/disables Home's bluetooth discovery and other devices can pair with Home (where Home acts as a speaker).
enable_discovery required | boolean |
{- "enable_discovery": true
}
See note for Bluetooth under /setup/bluetooth/status
For Part 2 only
This pairs with other bluetooth speakers by mac address.
mac_address required | string |
connect required | boolean |
profile required | integer <int32> |
{- "mac_address": "54:13:79:49:19:22",
- "connect": true,
- "profile": 2
}
There are 2 parts of Bluetooth.
Part 1: Devices like phones connect to Home and play audio through Home.
For this, /setup/bluetooth/discovery is used to make Home discoverable. Then devices can connect to it as if Home is just another bluetooth speaker.Part 2: Bluetooth speakers connect to Home and Home plays audio through the speakers. For this, /setup/bluetooth/scan and /setup/bluetooth/scan_results are used to connect to other speakers.
The other endpoints are common for both parts.
For both parts
This gives the status of all bluetooth things.
audio_mode
is.discovery_enabled
states whether Home is discoverable. (Part 1)connecting_devices
is a list of all media sources (like phones) connected to Home. (Part 1)scanning_enabled
states whether Home scanning for other bluetooth speakers/devices. (Part 2)connected_devices
is a list of all speakers connected to Home. (Part 2){- "audio_mode": 0,
- "connecting_devices": [ ],
- "connected_devices": [
- {
- "device": {
- "bond_date": 1529248165656.613,
- "device_class": 2491396,
- "device_type": 1,
- "last_connect_date": 0,
- "mac_address": "xx:xx:xx:xx:xx:xx",
- "name": "PHILIPS BT64",
- "rssi": -255,
- "service_uuids": [
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}, - "enabled_profiles": 2
}
], - "remote_sink": {
- "bond_date": 1529248165656.613,
- "device_class": 2491396,
- "device_type": 1,
- "last_connect_date": 0,
- "mac_address": "xx:xx:xx:xx:xx:xx",
- "name": "PHILIPS BT64",
- "rssi": -255,
- "service_uuids": [
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}, - "discovery_enabled": false,
- "scanning_enabled": false
}
See note for Bluetooth under /setup/bluetooth/status
For Part 2 only
This initiates scan for other bluetooth speakers/devices. Scan results will be updated continuously for timeout
seconds.
To get the scan results, see /setup/bluetooth/scan_results.
enable required | boolean |
clear_results required | boolean |
timeout required | integer <int32> |
{- "enable": true,
- "clear_results": true,
- "timeout": 60
}
See note for Bluetooth under /setup/bluetooth/status
For both parts
This gives a list of all paired or 'bonded' devices. The response field names are self-descriptive.
[- {
- "bond_date": 1503212407260.55,
- "device_class": 5898764,
- "device_type": 1,
- "last_connect_date": 1514807829482.111,
- "mac_address": "xx:xx:xx:xx:xx:xx",
- "name": "Device Name",
- "rssi": -255,
- "service_uuids": [
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
- "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}
]
Note: Not sure how the password is encrypted. Might be using the public certificate from /setup/eureka_info. So this cannot be used as of now. If someone figures it out, please create a new issue here.
bssid required | string |
signal_level required | integer <int32> |
ssid required | string |
wpa_auth required | integer <int32> |
wpa_cipher required | integer <int32> |
enc_passwd required | string |
{- "bssid": "5c:0a:xx:xx:xx:xx",
- "signal_level": -42,
- "ssid": "myotherssid",
- "wpa_auth": 7,
- "wpa_cipher": 4,
- "enc_passwd": "xxxxxfPY="
}
This gets a list of all saved Wi-Fi networks.
Each network has ssid
, wpa_auth
, wpa_cipher
and wpa_id
.wpa_id
is an incrementing number used to identify a saved network.
#TODO: Add values for wpa_auth
and wpa_cipher
.
[- {
- "ssid": "Wifi name",
- "wpa_auth": 7,
- "wpa_cipher": 4,
- "wpa_id": 0
}
]
This gets a list of all nearby Wi-Fi access points.
The list only has the connected AP by default. Once a scan is triggered by /setup/scan_wifi
, the whole list is cached for ~3 minutes. Then it will revert to returning only the connected AP again.
[- {
- "bssid": "APBSSID1",
- "signal_level": -20,
- "ssid": "APSSID1",
- "wpa_auth": 7,
- "wpa_cipher": 4,
- "wpa_id": 0
}
]