I'm trying to write a simple console app to communicate to my Honeywell thermostat. They offer a free-to-use REST API that's documented here: https://developer.honeywellhome.com/ . I am having trouble making a simple GET call right after authenticating, and I don't know what I'm doing wrong. I was hoping someone could help me here.
Summarized, my process consists of 3 steps:
My console csproj is very simple:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
The app gets registed here, it's pretty standard: https://developer.honeywellhome.com/user/me/apps
For this example, let's assume Resideo gives me these values that I'll use later:
ABCD1234WXYZ9876The structure of the access token json response is described here: https://developer.honeywellhome.com/authorization-oauth2/apis/post/accesstoken
This is how I define the class for json deserialization:
internal class ResideoToken
{
[JsonPropertyName("refresh_token_expires_in")]
public string RefreshTokenExpiration { get; set; } = string.Empty;
[JsonPropertyName("api_product_list")]
public string ApiProductList { get; set; } = string.Empty;
[JsonPropertyName("organization_name")]
public string OrganizationName { get; set; } = string.Empty;
[JsonPropertyName("developer.email")]
public string DeveloperEmail { get; set; } = string.Empty;
[JsonPropertyName("token_type")]
public string TokenType { get; set; } = string.Empty;
[JsonPropertyName("issued_at")]
public string IssuedAt { get; set; } = string.Empty;
[JsonPropertyName("client_id")]
public string ClientId { get; set; } = string.Empty;
[JsonPropertyName("access_token")]
public string AccessToken { get; set; } = string.Empty;
[JsonPropertyName("application_name")]
public string ApplicationName { get; set; } = string.Empty;
[JsonPropertyName("scope")]
public string Scope { get; set; } = string.Empty;
[JsonPropertyName("expires_in")]
public string ExpiresIn { get; set; } = string.Empty;
[JsonPropertyName("refresh_count")]
public string RefreshCount { get; set; } = string.Empty;
[JsonPropertyName("status")]
public string Status { get; set; } = string.Empty;
}
And this is how I'm successfully authenticating:
string appId = "ABCD1234";
string secret = "WXYZ9876";
using HttpClient client = new()
{
BaseAddress = new Uri(uriString: "https://api.honeywell.com/", uriKind: UriKind.Absolute)
};
KeyValuePair<string, string>[] encodedContentCollection =
[
new("Content-Type", "application/x-www-form-urlencoded"),
new("grant_type", "client_credentials")
];
using HttpRequestMessage request = new(HttpMethod.Post, "oauth2/accesstoken")
{
Content = new FormUrlEncodedContent(encodedContentCollection)
};
string base64AppIdAndSecret = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{appId}:{secret}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64AppIdAndSecret);
using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode(); // Should throw if not 200-299
using Stream responseContentStream = await response.Content.ReadAsStreamAsync();
ResideoToken token = await JsonSerializer.DeserializeAsync<ResideoToken>(responseContentStream, JsonSerializerOptions.Default) ??
throw new Exception("Could not deserialize response stream to a ResideoToken");
The simplest case I found is to get the list of locations and devices using GET method and passing one parameter: https://developer.honeywellhome.com/lyric/apis/get/locations
// I had this originally, but it was incorrect -> client.DefaultRequestHeaders.Add("Bearer", token.AccessToken);
client.DefaultRequestHeaders.Authentication = new AuthenticationHeaderValue("Bearer", token.AccessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Base URL has already been established in the client
// According to the instructions, the apikey is the AppID
using HttpResponseMessage locationResponse = await client.GetAsync($"v2/locations?apikey={appId}");
locationResponse.EnsureSuccessStatusCode(); // This is failing with 401 unauthorized
// I am never able to reach this
string result = await locationResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Locations: {result}");
As you can see, the GetAsync call fails with 401. This is the exception:
Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
This is strange, because if I print the base64 string generated by my code to console, copy it, and use it in a curl call, it succeeds:
$ curl -X GET --header "Authorization: Bearer AbCdEfGhIjKlMnOpQrStUvWxYz==" "https://api.honeywell.com/v2/locations?apikey=ABCD1234"
# This prints a huge valid json file describing all the details of the locations and my devices as described in https://developer.honeywellhome.com/lyric/apis/get/locations
The 3rd step is where all the problems happen, so these are the things I'm unsure about:
curl?Thanks in advance.
Edit: I fixed the line that sets the Bearer token but I am still getting the exact same 401 exception. Also added the missing usings.
at the step 3 (Call any REST API using the provided access token) i see that you do this for adding the bearer token:
client.DefaultRequestHeaders.Add("Bearer", token.AccessToken);
I think you should instead do this to indicate you want to add authorization (as you were doing at step 2):
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
The OAuth2 Client Credentials documentation mentions that in addition to the bearer token you also have to specify a UserRefID HTTP header:
If a user and device(s) have been authorized to your API Key, API calls can be made on the user's behalf by adding an HTTP Header called "UserRefID" and the value of the header is the UserID obtained from the API.
Example Request:
curl --location --request GET 'https://api.honeywell.com/v2/locations?apikey=(yourApiKey)'
--header 'UserRefID: 625115'
--header 'Authorization: Bearer (yourclientcredentialstoken)'
But I have no idea what API they are talking about when they say UserID obtained from the API. Maybe they are referring to the registration APIs?
Unfortunately it's pretty hard for someone without a physical device to help you because as mentioned in their FAQ, a physical device is required to use the APIs:
Yes, currently we do not have a device simulator, so you need to have a device or have access to an account with a device attached to use the API.
I have tried their interactive API on https://developer.honeywellhome.com/lyric/apis/get/locations but clicking on the OAuth 2.0 Set… button redirects to a resideo login page where I can't login. I think it's the same account as on https://account.honeywellhome.com (both mention a lyric thing) but I can't figure out how to create on account here. I have created an account on https://www.resideo.com but it looks like it's not the same thing!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With