Direct Connection to API with makeWebRequest

Is it possible to connect directly with an api through Communications.makeWebRequest? Or do you have to post your data to a server first and then acquire it from there? For instance, if I wanted to post data to google sheets (or even an api that has very basic auth), can I do that directly through makeWebRequest? I've been attempting to post some data but keep getting -400 errors, while my flask server posts it successfully.

So I'm thinking about posting the data to my flask server first (which works) and then sending that to the ultimate application I want to analyze this data in. But I'd rather just connect directly. Not sure if anyone has experience with this.

  • I'm not sure I understand. What's the difference between "API" and "flask server"? Aren't both web services? Are both using valid SSL certificates?

  • -400 is INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, which usually indicates that there’s a mismatch between the response content, and what CIQ wants it to be (based on the content type). CIQ is a lot stricter about this kind of thing than most general-purpose frameworks.

    For example, even though a lone number, boolean, string, or null value is a valid JSON response, makeWebRequest will reject all of those things (if the request/response type is JSON), since makeWebRequest wants a JSON object so it can consistently return a dictionary for JSON responses.

    EDIT: an array will also be rejected by makeWebRequest when the response in JSON.

    Some other possibilities:

    - The response content type doesn’t match what Garmin thinks it should be (based on the request content-type.) e.g. for JSON, a response content type of "text/json; charset=UTF-8" (as opposed to “text/json”) may trigger this issue: https://forums.garmin.com/developer/connect-iq/f/discussion/7900/makewebrequest-json-error--400-invalid_http_body_in_network_response

    - URL params were passed in the url string, as opposed to the 2nd argument to makeWebRequest: https://forums.garmin.com/developer/connect-iq/f/discussion/8290/makewebrequest-issues

    Does it fail in both the sim and a real device, or just a real device?

    Can you post the code and url for your failing request? Is the failing url a public server so we can try it?

  • For instance a lot of applications(i.e. google sheets, notion, strava, etc.) have an API that you can make requests from and to (to perform certain actions in the app). So I was wondering if it was possible for the makeWebRequest function to send directly to an account I have with an application. As opposed to sending it to my own server and then sending it to the API. I know my server can make successful post requests, but when I try to post to that same application with MonkeyC makeWebRequest I keep getting an error. So I'm not sure if just direct connection is not available.

    ex:

    garmin --> google sheets

    vs

    garming --> flask server --> google sheets

  • Hmm it doesn't seem to be second thing.

    Here's the code I was using to test a simple post request to a Toggl API (a time tracking app), where ive replaced my actual ids with words for the sake of this. I believe it fits the way Toggl is asking for the data. But mainly just concerned if this is even possible/commonplace to just speak directly to an application like Toggl with Garmin. If not, I'll probably use my own server as a middle man.

        function onMenu() as Boolean {
            WatchUi.pushView(new Rez.Menus.MainMenu(), new TimeTrackerMenuDelegate(), WatchUi.SLIDE_UP);
            var message = {
                "duration" => 3600,
                "start" => "2024-06-21T15:53:15Z",
                "project_id" => myprojectid,
                "workspace_id" => myworkspaceid,
                "created_with" => "MonkeyC TimeTracker"
            };
    
            var options = {
                :method => Communications.HTTP_REQUEST_METHOD_POST,
                :header => {
                    "Authorization" => "Basic " + StringUtil.encodeBase64("myapitoken:api_token"),
                    "Content-Type" => "application/json"
                 }
            };
    
            Communications.makeWebRequest("https://api.track.toggl.com/api/v9/workspaces/myworkspaceid/time_entries", message, options, method(:onResponse));
            return true;
        }
  • Hmm looking at the API doc, a POST to time_entries returns a JSON object (in the case of a 200 response code), so in that case, it’s not the first thing I said (where a JSON response that’s not an object will be rejected).

    https://engineering.toggl.com/docs/api/time_entry/index.html#post-timeentries

    However, in case of a 403 response code, the following string is returned in the body (without quotes):

    User does not have access to this resource.

    Unfortunately this is not a a JSON object, and therefore makeWebRequest will fail with -400 (instead of returning the 403 code and response body that was actually returned by the server), assuming that the response type is still “application/json”. This is one of the problems that devs have encountered with makeWebRequest: the response is handled fine when it’s successful, but on error, makeWebRequest returns -400 (with no body) and it’s impossible for the app to display the error message that was actually returned in the response.

    Can you post a redacted version of the response with headers?

    I will say that a GET to time_entries will def fail in Connect IQ, assuming that the v9 api returns an array like the v8 api. (It's not 100% clear from the v9 docs, which only show an example of a null response, which will also cause makeWebRequest to return -400).

    (I forgot to mention above that makeWebRequest will also fail when an array is returned for a JSON response.)

    But mainly just concerned if this is even possible/commonplace to just speak directly to an application like Toggl with Garmin

    Yeah, that’s def the intent of makeWebRequest. It just so happens that CIQ / makeWebRequest has a lot more restrictions than any other framework, by design / implementation. Another example is that HTTP (without SSL) is only allowed to 127.0.0.1 / localhost as opposed to any IP address on a private network (as with most vendors.)

  • Hmm this is the only response I get, not sure if this is what you mean? 

      

    Interestingly enough I'm getting a 403 but no message (and I've tried changing the "Dictionary?" to "String?"). I believe the problem now has something to do with authorizations, but it's weird since I'm giving it exactly the same things as my flask server code. Is there another place I should look for responses for more info? Not sure I see the headers you're referring to.

  • Well I meant: what would a normal response look like when you successfully make the request outside of CIQ? e.g. From your flask server, from the command line with curl, or from Postman. And what would an “expected error case” look like? (e.g. user provides incorrect credentials). By knowing how it works normally, we can try to figure why it isn’t working for CIQ, because again, CIQ has stricter rules / weird behavior compared to every other framework.

    HTTP headers are metadata (key-value pairs) that are sent with every request and response. You have 2 request headers in your own code:

     :header => {
                    "Authorization" => "Basic " + StringUtil.encodeBase64("myapitoken:api_token"),
                    "Content-Type" => "application/json"
                 }

    Your CIQ app won’t have direct access to response headers, but they will affect the behavior of makeWebRequest (which is partially covered in the CIQ makeWebRequest docs). Again, if the Content-Type response header is “application/json”, then makeWebRequest will expect the response to be a JSON object (not an array, null, boolean, number, string or invalid JSON.) You can see response headers when testing with curl or Postman, for example. If you are testing in the CIQ simulator, then you should be able to use File > View HTTP Traffic to see headers.

    The reason I wanted to see your response body and headers was to determine why makeWebRequest is returning -400, because that’s usually a case of a mismatched Content-Type and response body (according to makeWebRequest’s rules). Obviously, now that makeWebRequest is returning 403 and null, it’s a different situation.

    To be 100% clear, when makeWebRequest returns -400, it means there’s something about the response body (and/or the response Content-Type) that makeWebRequest doesn’t like. The strict rules and weird behavior of makeWebRequest is why makeWebRequest fails in certain situations where other frameworks would succeed.

  • Ah ok, the only response I get from my flask server is <Response [200]> if it's successful. and a <Response [403]> if I mess up the api token.

      

    (I just made it post when I run the server for testing) 

    When I use curl the only thing thats returned to me in the terminal is a dictionary that contains all the inputs I gave it (start, duration, etc.) If I mess something up it simply tells me incorrect username, cannot access project, etc. in one line.

    Here are the headers from traffic:   but not exactly sure how to use this to troubleshoot.

    Seems I'm just stuck with the 403 error right now though so not sure how helpful this is.

  • 403 Forbidden

    The HTTP 403 Forbidden response status code indicates that the server understands the request but refuses to authorize it.

    This status is similar to 401, but for the 403 Forbidden status code, re-authenticating makes no difference. The access is tied to the application logic, such as insufficient rights to a resource.

  • Do you think it's a problem with Monkey C and Toggl's ability to communicate with each other then? Since my authorizations and the way I've requested is the same for my MonkeyC app and through my Flask Server but my MonkeyC gets a 403 and my Flask is posted successfully.