/// <summary> /// Parses the error structure which is common when errors are raised from the api /// </summary> /// <param name="serializer"></param> /// <param name="errorJson"></param> /// <returns></returns> internal static List <ApiError> ParseErrors(this JsonDotNetSerializer serializer, string errorJson) { if (string.IsNullOrWhiteSpace(errorJson)) { throw new ArgumentNullException(nameof(errorJson), "errorJson can not be empty, null or whitespace"); } serializer.RootProperty = "errors"; return(serializer.Deserialize <List <ApiError> >(errorJson)); }
/// <summary> /// Set the Goals for the current logged in user; individual goals, multiple or all can be specified in one call.</summary> /// <param name="caloriesOut"></param> /// <param name="distance"></param> /// <param name="floors"></param> /// <param name="steps"></param> /// <param name="activeMinutes"></param> /// <returns></returns> public async Task <ActivityGoals> SetGoalsAsync(int caloriesOut = default(int), decimal distance = default(decimal), int floors = default(int), int steps = default(int), int activeMinutes = default(int)) { // parameter checking; at least one needs to be specified if ((caloriesOut == default(int)) && (distance == default(decimal)) && (floors == default(int)) && (steps == default(int)) && (activeMinutes == default(int))) { throw new ArgumentException("Unable to call SetGoalsAsync without specifying at least one goal parameter to set."); } var messageContentParameters = new Dictionary <string, string>(); if (caloriesOut != default(int)) { messageContentParameters.Add("caloriesOut", caloriesOut.ToString()); } if (distance != default(decimal)) { messageContentParameters.Add("distance", distance.ToString()); } if (floors != default(int)) { messageContentParameters.Add("floors", floors.ToString()); } if (steps != default(int)) { messageContentParameters.Add("steps", steps.ToString()); } if (activeMinutes != default(int)) { messageContentParameters.Add("activeMinutes", activeMinutes.ToString()); } var apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/-/activities/goals/daily.json"); //caloriesOut=100&distance=1.0&floors=1&steps=8000&activeMinutes=10 HttpResponseMessage response = await HttpClient.PostAsync(apiCall, new FormUrlEncodedContent(messageContentParameters)); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var seralizer = new JsonDotNetSerializer { RootProperty = "goals" }; return(seralizer.Deserialize <ActivityGoals>(responseBody)); }
/// <summary> /// The Get Sleep Logs List endpoint returns a list of a user's sleep logs (including naps) /// before or after a given day with offset, limit, and sort order. /// </summary> /// <param name="dateToList"> The date in the format yyyy-MM-ddTHH:mm:ss. Only yyyy-MM-dd is required. Set sort to desc when using beforeDate.</param> /// <param name="decisionDate"></param> /// <param name="sort">The sort order of entries by date. Required. asc for ascending, desc for descending</param> /// <param name="limit">The max of the number of sleep logs returned. Required.</param> /// <param name="encodedUserId"></param> /// <returns></returns> public async Task <SleepLogListBase> GetSleepLogListAsync(DateTime dateToList, SleepEnum decisionDate, SortEnum sort, int limit, string encodedUserId = null) { string setSleepDecision, setSort; //decide if date retrieval is before or after switch (decisionDate) { case SleepEnum.After: setSleepDecision = "afterDate"; break; case SleepEnum.Before: setSleepDecision = "beforeDate"; break; default: //in case of some sort of error we will set our date to before setSleepDecision = "beforeDate"; break; } //decide if we are sorting asc or dsc switch (sort) { case SortEnum.Asc: setSort = "asc"; break; case SortEnum.Dsc: setSort = "desc"; break; default: //in case of some sort of error we will set our sort to asc setSort = "asc"; break; } var apiCall = FitbitClientHelperExtensions.ToFullUrl("/1.2/user/{0}/sleep/list.json?{1}={2}&sort={3}&offset=0&limit={4}", encodedUserId, setSleepDecision, dateToList.ToFitbitFormat(), setSort, limit); HttpResponseMessage respone = await HttpClient.GetAsync(apiCall); await HandleResponse(respone); string responseBody = await respone.Content.ReadAsStringAsync(); var serialzer = new JsonDotNetSerializer(); var data = serialzer.Deserialize <SleepLogListBase>(responseBody); return(data); }
/// <summary> /// GetFriends has to do some custom manipulation with the returned representation /// </summary> /// <param name="serializer"></param> /// <param name="friendsJson"></param> /// <returns></returns> internal static List <UserProfile> GetFriends(this JsonDotNetSerializer serializer, string friendsJson) { if (string.IsNullOrWhiteSpace(friendsJson)) { throw new ArgumentNullException(nameof(friendsJson), "friendsJson can not be empty, null or whitespace."); } serializer.RootProperty = "user"; var friends = JToken.Parse(friendsJson)["friends"]; return(friends.Children().Select(serializer.Deserialize <UserProfile>).ToList()); }
/// <summary> /// GetWeight has to doe some custom manipulation with the returned representation /// </summary> /// <param name="serializer"></param> /// <param name="weightJson"></param> /// <returns></returns> internal static Weight GetWeight(this JsonDotNetSerializer serializer, string weightJson) { if (string.IsNullOrWhiteSpace(weightJson)) { throw new ArgumentNullException(nameof(weightJson), "weightJson can not be empty, null or whitespace"); } var weightlogs = JToken.Parse(weightJson)["weight"]; var weight = new Weight(); weight.Weights = weightlogs.Children().Select(serializer.Deserialize <WeightLog>).ToList(); return(weight); }
/// <summary> /// Get the set body measurements for the date value and user specified /// </summary> /// <param name="date"></param> /// <param name="encodedUserId"></param> /// <returns></returns> public async Task <BodyMeasurements> GetBodyMeasurementsAsync(DateTime date, string encodedUserId = default(string)) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/body/date/{1}.json", encodedUserId, date.ToFitbitFormat()); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.Deserialize <BodyMeasurements>(responseBody)); }
/// <summary> /// Requests the lifetime activity details of the encoded user id or none supplied the current logged in user /// </summary> /// <param name="encodedUserId">encoded user id, can be null for current logged in user</param> /// <returns></returns> public async Task <ActivitiesStats> GetActivitiesStatsAsync(string encodedUserId = null) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/activities.json", encodedUserId); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.Deserialize <ActivitiesStats>(responseBody)); }
/// <summary> /// GetFat has to doe some custom manipulation with the returned representation /// </summary> /// <param name="serializer"></param> /// <param name="fatJson"></param> /// <returns></returns> internal static Fat GetFat(this JsonDotNetSerializer serializer, string fatJson) { if (string.IsNullOrWhiteSpace(fatJson)) { throw new ArgumentNullException(nameof(fatJson), "fatJson can not be empty, null or whitespace"); } var fatlogs = JToken.Parse(fatJson)["fat"]; var fat = new Fat(); fat.FatLogs = fatlogs.Children().Select(serializer.Deserialize <FatLog>).ToList(); return(fat); }
/// <summary> /// Requests the friends of the encoded user id or if none supplied the current logged in user /// </summary> /// <param name="encodedUserId">encoded user id, can be null for current logged in user</param> /// <returns>List of <see cref="UserProfile"/></returns> public async Task <List <UserProfile> > GetFriendsAsync(string encodedUserId = default(string)) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/friends.json", encodedUserId); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.GetFriends(responseBody)); }
/// <summary> /// Requests the devices for the current logged in user /// </summary> /// <returns>List of <see cref="Device"/></returns> public async Task <List <Device> > GetDevicesAsync() { var apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/-/devices.json"); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.Deserialize <List <Device> >(responseBody)); }
/// <summary> /// Gets the water date for the specified date - https://wiki.fitbit.com/display/API/API-Get-Water /// </summary> /// <remarks> /// GET https://api.fitbit.com/1/user/-/foods/log/water/date/yyyy-mm-dd.json /// </remarks> /// <param name="date"></param> /// <returns></returns> public async Task <WaterData> GetWaterAsync(DateTime date) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/foods/log/water/date/{1}.json", args: date.ToFitbitFormat()); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.Deserialize <WaterData>(responseBody)); }
/// <summary> /// Requests the activity details of the encoded user id or if none supplied the current logged in user for the specified date /// </summary> /// <param name="activityDate"></param> /// <param name="encodedUserId">encoded user id, can be null for current logged in user</param> /// <returns>FitbitResponse of <see cref="ActivitySummary"/></returns> public async Task <Activity> GetDayActivityAsync(DateTime activityDate, string encodedUserId = null) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/activities/date/{1}.json", encodedUserId, activityDate.ToFitbitFormat()); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.Deserialize <Activity>(responseBody)); }
/// <summary> /// Requests the sleep data for a specified date range for the logged in user /// </summary> /// <param name="endDate"></param> /// <param name="encodedUserId"></param> /// <param name="startDate"></param> /// <returns></returns> public async Task <SleepDateRangeBase> GetSleepDateRangeAsync(DateTime startDate, DateTime endDate, string encodedUserId = null) { var apiCall = FitbitClientHelperExtensions.ToFullUrl("/1.2/user/{0}/sleep/date/{1}/{2}.json", encodedUserId, startDate.ToFitbitFormat(), endDate.ToFitbitFormat()); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); var data = serializer.Deserialize <SleepDateRangeBase>(responseBody); return(data); }
/// <summary> /// Gets a list of the current subscriptions for the current logged in user /// </summary> /// <returns></returns> public async Task <List <ApiSubscription> > GetSubscriptionsAsync() { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/-/apiSubscriptions.json"); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer { RootProperty = "apiSubscriptions" }; return(serializer.Deserialize <List <ApiSubscription> >(responseBody)); }
/// <summary> /// Requests the sleep data for the specified date for the logged in user /// NOTE: This is for the V1 of the sleep api which is now Deprecated /// </summary> /// <param name="sleepDate"></param> /// <returns></returns> public async Task <SleepData> GetSleepAsync(DateTime sleepDate) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/sleep/date/{1}.json", args: sleepDate.ToFitbitFormat()); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); var data = serializer.Deserialize <SleepData>(responseBody); FitbitClientExtensions.ProcessSleepData(data); return(data); }
/// <summary> /// Creates a log entry for a sleep event and returns a response in the format requested /// </summary> /// <param name="startTime">Start time; hours and minutes in the format HH:mm. </param> /// <param name="duration">Duration in milliseconds.</param> /// <param name="date">Log entry date in the format yyyy-MM-dd. </param> /// <param name="encodedUserId"></param> /// <returns></returns> public async Task <SleepLogDateRange> PostLogSleepAsync(string startTime, int duration, DateTime date, string encodedUserId = null) { var apiCall = FitbitClientHelperExtensions.ToFullUrl("/1.2/user/{0}/sleep.json?date={1}&startTime={2}&duration={3}", encodedUserId, date.ToFitbitFormat(), startTime, duration); HttpResponseMessage respone = await HttpClient.PostAsync(apiCall, new StringContent(string.Empty)); await HandleResponse(respone); string responeBody = await respone.Content.ReadAsStringAsync(); var serialzer = new JsonDotNetSerializer(); return(serialzer.Deserialize <SleepLogDateRange>(responeBody)); }
/// <summary> /// Requests the user profile of the encoded user id or if none specified the current logged in user /// </summary> /// <param name="encodedUserId"></param> /// <returns><see cref="UserProfile"/></returns> public async Task <UserProfile> GetUserProfileAsync(string encodedUserId = default(string)) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}/profile.json", encodedUserId); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer { RootProperty = "user" }; return(serializer.Deserialize <UserProfile>(responseBody)); }
/// <summary> /// Get TimeSeries data for another user accessible with this user's credentials /// </summary> /// <param name="timeSeriesResourceType"></param> /// <param name="baseDate"></param> /// <param name="endDateOrPeriod"></param> /// <param name="encodedUserId"></param> /// <returns></returns> private async Task <TimeSeriesDataListInt> GetTimeSeriesIntAsync(TimeSeriesResourceType timeSeriesResourceType, DateTime baseDate, string endDateOrPeriod, string encodedUserId) { var apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/{0}{1}/date/{2}/{3}.json", encodedUserId, timeSeriesResourceType.GetStringValue(), baseDate.ToFitbitFormat(), endDateOrPeriod); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer { RootProperty = timeSeriesResourceType.ToTimeSeriesProperty() }; return(serializer.GetTimeSeriesDataListInt(responseBody)); }
/// <summary> /// GetTimeSeriesDataList has to do some custom manipulation with the returned representation /// </summary> /// <param name="serializer"></param> /// <param name="timeSeriesDataJson"></param> /// <returns></returns> internal static TimeSeriesDataListInt GetTimeSeriesDataListInt(this JsonDotNetSerializer serializer, string timeSeriesDataJson) { if (string.IsNullOrWhiteSpace(timeSeriesDataJson)) { throw new ArgumentNullException(nameof(timeSeriesDataJson), "timeSeriesDataJson can not be empty, null or whitespace."); } var dataPoints = JToken.Parse(timeSeriesDataJson)[serializer.RootProperty]; var result = new TimeSeriesDataListInt { DataList = (from item in dataPoints select new TimeSeriesDataListInt.Data { DateTime = DateTime.Parse(item["dateTime"].ToString()), Value = int.Parse(item["value"].ToString()) }).ToList() }; return(result); }
/// <summary> /// Request to get heart rate in specific in a range time /// </summary> /// <param name="date"></param> /// <param name="dateRangePeriod"></param> /// <param name="userId"></param> /// <returns></returns> public async Task <HeartActivitiesTimeSeries> GetHeartRateTimeSeries(DateTime date, DateRangePeriod dateRangePeriod, string userId = null) { if (string.IsNullOrWhiteSpace(userId)) { userId = "-"; } string apiCall = String.Format("https://api.fitbit.com/1.1/user/{0}/activities/heart/date/{1}/{2}.json", userId, date.ToString("yyyy-MM-dd"), dateRangePeriod.GetStringValue()); HttpResponseMessage response = await HttpClient.GetAsync(apiCall); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var seralizer = new JsonDotNetSerializer(); var fitbitResponse = seralizer.GetHeartActivitiesTimeSeries(responseBody); return(fitbitResponse); }
/// <summary> /// Logs the specified WaterLog item for the current logged in user - https://wiki.fitbit.com/display/API/API-Log-Water /// </summary> /// <param name="date"></param> /// <param name="log"></param> /// <returns></returns> public async Task <WaterLog> LogWaterAsync(DateTime date, WaterLog log) { string apiCall = FitbitClientHelperExtensions.ToFullUrl("/1/user/-/foods/log/water.json"); var items = new Dictionary <string, string>(); items.Add("amount", log.Amount.ToString()); items.Add("date", date.ToFitbitFormat()); apiCall = string.Format("{0}?{1}", apiCall, string.Join("&", items.Select(x => string.Format("{0}={1}", x.Key, x.Value)))); HttpResponseMessage response = await HttpClient.PostAsync(apiCall, new StringContent(string.Empty)); await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer { RootProperty = "waterLog" }; return(serializer.Deserialize <WaterLog>(responseBody)); }
internal static IntradayData GetIntradayTimeSeriesData(this JsonDotNetSerializer serializer, string intradayDataJson) { if (string.IsNullOrWhiteSpace(intradayDataJson)) { throw new ArgumentNullException(nameof(intradayDataJson), "intradayDataJson can not be empty, null or whitespace."); } var parsedJToken = JToken.Parse(intradayDataJson); // need to parse the date first JToken date; try { date = parsedJToken.SelectToken(serializer.RootProperty).First["dateTime"]; } catch (NullReferenceException nullReferenceException) { //We'll nullref here if we're querying a future date - Fitbit omits dateTime in that case. //Return null since this error will, in all cases, coincide with an otherwise empty (all zeros) object return(null); } var dataPoints = parsedJToken.SelectTokens(serializer.RootProperty + "-intraday.dataset"); var result = new IntradayData { DataSet = (from item in dataPoints.Children() select new IntradayDataValues { Time = DateTime.Parse(date + " " + item["time"]), Value = item["value"].ToObject <double>().ToString("R"), //converting to double is required to keep precision METs = item["mets"] != null ? item["mets"].ToString() : null, Level = item["level"] != null ? item["level"].ToString() : null }).ToList() }; return(result); }
/// <summary> /// Add subscription /// </summary> /// <param name="apiCollectionType"></param> /// <param name="uniqueSubscriptionId"></param> /// <param name="subscriberId"></param> /// <returns></returns> public async Task <ApiSubscription> AddSubscriptionAsync(APICollectionType apiCollectionType, string uniqueSubscriptionId, string subscriberId = default(string)) { string path = FormatKey(apiCollectionType, Constants.Formatting.TrailingSlash); string resource = FormatKey(apiCollectionType, Constants.Formatting.LeadingDash); string url = "/1/user/-/{1}apiSubscriptions/{3}{2}.json"; string apiCall = FitbitClientHelperExtensions.ToFullUrl(url, args: new object[] { path, resource, uniqueSubscriptionId }); if (!string.IsNullOrWhiteSpace(subscriberId)) { HttpClient.DefaultRequestHeaders.Add(Constants.Headers.XFitbitSubscriberId, subscriberId); } HttpResponseMessage response = await HttpClient.PostAsync(apiCall, new StringContent(string.Empty)); await HandleResponse(response); var responseBody = await response.Content.ReadAsStringAsync(); var serializer = new JsonDotNetSerializer(); return(serializer.Deserialize <ApiSubscription>(responseBody)); }
internal static HeartActivitiesTimeSeries GetHeartActivitiesTimeSeries(this JsonDotNetSerializer serializer, string heartActivitiesTimeSeries) { if (string.IsNullOrWhiteSpace(heartActivitiesTimeSeries)) { throw new ArgumentNullException("heartActivitiesTimeSeries", "heartActivitiesTimeSeries can not be empty, null or whitespace."); } var activitiesHeartIntraday = JToken.Parse(heartActivitiesTimeSeries)["activities-heart"]; //var dataset = activitiesHeartIntraday["dataset"]; var result = new HeartActivitiesTimeSeries() { HeartActivities = (from item in activitiesHeartIntraday select new HeartActivities { DateTime = DateTime.Parse(item["dateTime"].ToString()), //here, maybe pass in the date so we have a full object of date and time HeartRateZones = serializer.Deserialize <List <HeartRateZone> >(item["value"]["heartRateZones"]), CustomHeartRateZones = serializer.Deserialize <List <HeartRateZone> >(item["value"]["customHeartRateZones"]) }).ToList(), }; return(result); }
/// <summary> /// GetTimeSeriesDataList has to do some custom manipulation with the returned representation /// </summary> /// <param name="serializer"></param> /// <param name="timeSeriesDataJson"></param> /// <returns></returns> internal static HeartActivitiesIntraday GetHeartRateIntraday(this JsonDotNetSerializer serializer, DateTime date, string heartRateIntradayJson) { if (string.IsNullOrWhiteSpace(heartRateIntradayJson)) { throw new ArgumentNullException("heartRateIntradayJson", "heartRateIntradayJson can not be empty, null or whitespace."); } var activitiesHeartIntraday = JToken.Parse(heartRateIntradayJson)["activities-heart-intraday"]; var dataset = activitiesHeartIntraday["dataset"]; var result = new HeartActivitiesIntraday { Dataset = (from item in dataset select new DatasetInterval { Time = DateTime.Parse(date.ToString("yyyy-MM-dd") + " " + item["time"].ToString()), //here, maybe pass in the date so we have a full object of date and time Value = int.Parse(item["value"].ToString()) }).ToList(), DatasetInterval = Convert.ToInt32(activitiesHeartIntraday["datasetInterval"]), DatasetType = activitiesHeartIntraday["datasetType"].ToString() }; return(result); }
/// <summary> /// General error checking of the response before specific processing is done. /// </summary> /// <param name="response"></param> private async Task HandleResponse(HttpResponseMessage response) { if (!response.IsSuccessStatusCode) { // assumption is error response from fitbit in the 4xx range var errors = new JsonDotNetSerializer().ParseErrors(await response.Content.ReadAsStringAsync()); // rate limit hit if (429 == (int)response.StatusCode) { // not sure if we can use 'RetryConditionHeaderValue' directly as documentation is minimal for the header var retryAfterHeader = response.Headers.GetValues("Retry-After").FirstOrDefault(); if (retryAfterHeader != null) { int retryAfter; if (int.TryParse(retryAfterHeader, out retryAfter)) { throw new FitbitRateLimitException(retryAfter, errors); } } } // request exception parsing switch (response.StatusCode) { case HttpStatusCode.BadRequest: case HttpStatusCode.Unauthorized: case HttpStatusCode.Forbidden: case HttpStatusCode.NotFound: throw new FitbitRequestException(response, errors); } // if we've got here then something unexpected has occured throw new FitbitException($"An error has occured. Please see error list for details - {response.StatusCode}", errors); } }
public async Task <IntradayData> GetIntraDayTimeSeriesAsync(IntradayResourceType timeSeriesResourceType, DateTime dayAndStartTime, TimeSpan intraDayTimeSpan) { string apiCall; if (intraDayTimeSpan > new TimeSpan(0, 1, 0) && //the timespan is greater than a minute dayAndStartTime.Day == dayAndStartTime.Add(intraDayTimeSpan).Day) //adding the timespan doesn't go in to the next day { apiCall = string.Format("/1/user/-{0}/date/{1}/1d/time/{2}/{3}.json", timeSeriesResourceType.GetStringValue(), dayAndStartTime.ToFitbitFormat(), dayAndStartTime.ToString("HH:mm"), dayAndStartTime.Add(intraDayTimeSpan).ToString("HH:mm")); } else //just get the today data, there was a date specified but the timerange was likely too large or negative { apiCall = string.Format("/1/user/-{0}/date/{1}/1d.json", timeSeriesResourceType.GetStringValue(), dayAndStartTime.ToFitbitFormat()); } apiCall = FitbitClientHelperExtensions.ToFullUrl(apiCall); HttpResponseMessage response = null; try { response = await HttpClient.GetAsync(apiCall); } catch (FitbitRequestException fre) { if (fre.ApiErrors.Any(err => err.Message == Constants.FloorsUnsupportedOnDeviceError)) { return(null); } else { //otherwise, rethrow because we only want to alter behavior for the very specific case above throw; } } await HandleResponse(response); string responseBody = await response.Content.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(responseBody)) { throw new FitbitRequestException(response, null, "The Intraday data response body was null"); } var serializer = new JsonDotNetSerializer { RootProperty = timeSeriesResourceType.ToTimeSeriesProperty() }; IntradayData data = null; try { data = serializer.GetIntradayTimeSeriesData(responseBody); } catch (Exception ex) { FitbitRequestException fEx = new FitbitRequestException(response, null, "Serialization Error in GetIntradayTimeSeriesData", ex); throw fEx; } return(data); }