0

Basic web API usage

I'm trying to write a little tool to put data into Canary tags through the Web API but am hitting my lack of knowledge on such things.

Looking at the endpoints in the Identity tile, I see that the REST API port is 55353. But if I go to http://localhost:55353/api/getUserToken I get ERR_EMPTY_RESPONSE and if I use https I get a 404 error.

In my C# code, I calling that URL with username, password, timezone and application in the request body and I'm getting the same as I get in the browser.

 

Is there an example starting project for interacting with the Canary web API? Any hints what I'm doing wrong? Here's my code for getting a user token:

       public static async Task<string> GetUserTokenAsync(string server, string username, string password)
        {
            string port = "55353";
            //string url = $"https://{server}/api/getUserToken";
            string url = $"http://{server}:{port}/api/getUserToken";   // not secure
            var requestBody = new
            {
                username = username,
                password = password,
                timezone = "UTC",
                application = "WebServiceCollector"
            };

            string json = JsonSerializer.Serialize(requestBody);
            HttpContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

            HttpResponseMessage response = await client.PostAsync(url, content);
            response.EnsureSuccessStatusCode();

            string responseBody = await response.Content.ReadAsStringAsync();
            var responseData = JsonSerializer.Deserialize<Dictionary<string, string>>(responseBody);

            return responseData["userToken"];
        }

 

 

Regards,

 

Alistair.

10 replies

null
    • smason
    • 4 wk ago
    • Reported - view

    Hi ,

    If you're on v24, there is no longer a /getUserToken call. You should create an API token within the Identity service which would then be linked to an internal Canary user. This is is the token you would use to pass in to the /getSessionToken function. If you're using the gRPC API, the endpoint is 55291. If using the web API, 55293. These are both endpoints of the SaF service.

    https://helpcenter.canarylabs.com/t/g9y8gz4/using-the-write-api-version-24

    • alistair_frith
    • 2 wk ago
    • Reported - view

    Thanks Steve, yes we are running V24.

    Unfortunately, when I try following the instructions in that linked article, I get this

    Any suggestions why?

      • smason
      • 2 wk ago
      • Reported - view

      It looks like you're using http instead of https. See what happens if you change the URL to use https instead.

      • alistair_frith
      • 4 days ago
      • Reported - view

       Finally got back to this and yes, if I change to use https in PoastMan, then it works. Yay!

      But...

      My program still fails at the same point as before. It's certainly something silly and obvious I'm doing wrong and I'm just blind to it. Here's my code:
       

              public static async Task<string> GetSessionTokenAsync(string server)
              {
                  string port = "55293";
                  string url = $"https://{server}:{port}/api/getSessionToken";
                  string[] historians = new string[1];
                  historians[0] = server;
      
                  var requestBody = new
                  {
                      apiToken = CanaryApiToken,
                      historians = historians,
                      clientId = "WebServiceCollector"
                  };
      
                  string json = JsonSerializer.Serialize(requestBody);
                  Console.WriteLine($"Request URL: {url}\n       Body: {json}");
                  HttpContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
      
                  HttpResponseMessage response = await client.PostAsync(url, content); // <-- This call fails
                  response.EnsureSuccessStatusCode();
      
                  Console.WriteLine($"Response: {response.StatusCode}");
      
                  string responseBody = await response.Content.ReadAsStringAsync();
                  var responseData = JsonSerializer.Deserialize<Dictionary<string, string>>(responseBody);
      
                  return responseData["userToken"];
              }
      
      

      Here's the output (with URLs and tokens x'd out):
       

      Request URL: https://xxxx.xxxx.xxxx.com:55293/api/getSessionToken
             Body: {"apiToken":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","historians":["xxxx.xxxx.xxxx.com"],"clientId":"WebServiceCollector"}
      Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
         at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
         at WebServiceCollector.CanaryService.GetSessionTokenAsync(String server) in C:\Users\alistair.frith\source\repos\WebServiceCollector\WebServiceCollector\CanaryService.cs:line 45
         at WebServiceCollector.Worker.PutData(WeatherData weatherData) in C:\Users\alistair.frith\source\repos\WebServiceCollector\WebServiceCollector\Worker.cs:line 83
         at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
         at System.Threading.ThreadPoolWorkQueue.Dispatch()
         at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

      What's my silly mistake?

      • smason
      • 4 days ago
      • Reported - view

      I'm not a developer so I can't give too much insight here, but the one thing I see missing in your url string is "v1". It should be 

      "https://{server}:{port}/api/v1/getSessionToken"
      • alistair_frith
      • 4 days ago
      • Reported - view

       Fantastic, thank you, I knew it would be a silly mistake! I can now get a session token.

      But...  when I then try to use that session token to write a value into Canary, I get a 400 (Bad Request) error.

      Again, here's my code (I appreciate you are not a coder Steve but it might hlp someone else diagnose my latest silly mistake):

              public static async Task SendWeatherDataAsync(string server, string sessionToken, string path, WeatherData weatherData)
              {
                  string port = "55293";
                  string url = $"https://{server}:{port}/api/v1/sendData";
                  var requestBody = new
                  {
                      apiToken = sessionToken,
                      data = new[]
                      {
                      new
                      {
                          tag = $"{path}.CloudCover",
                          timestamp = DateTime.UtcNow.ToString("o"),
                          value = weatherData.Clouds.All
                      }
                  }
                  };
      
                  string json = JsonSerializer.Serialize(requestBody);
                  Console.WriteLine($"Request URL: {url}\n       Body: {json}");
                  HttpContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
      
                  HttpResponseMessage response = await client.PostAsync(url, content);
                  Console.WriteLine($"Response: {response.StatusCode}");
                  response.EnsureSuccessStatusCode();
              }
      

      And here's the output:

      Request URL: https://xxxx.xxxx.xxxx.com:55293/api/v1/sendData
             Body: {"apiToken":"20250218152449402_xxxxxxxxxxxx","data":[{"tag":"WeatherData.MyCompany.CloudCover","timestamp":"2025-02-18T15:24:49.4112972Z","value":19}]}
      Response: BadRequest
      Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
         at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
         at WebServiceCollector.CanaryService.SendWeatherDataAsync(String server, String sessionToken, String path, WeatherData weatherData) in C:\Users\alistair.frith\source\repos\WebServiceCollector\WebServiceCollector\CanaryService.cs:line 88
         at WebServiceCollector.Worker.PutData(WeatherData weatherData) in C:\Users\alistair.frith\source\repos\WebServiceCollector\WebServiceCollector\Worker.cs:line 90
         at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
         at System.Threading.ThreadPoolWorkQueue.Dispatch()
         at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
      

      I've got the "v1" in the URL and I'm using https. Maybe the tag path is wrong? or the whole "data" serialisation? Do I need the "servers" in there again? 
       

      • alistair_frith
      • 4 days ago
      • Reported - view

       Hmm, looking at the API reference at Canary Labs | Store and Forward Web API Documentation I should be using storeData, not sendData. Not sure where I got sendData from! 

      • alistair_frith
      • 2 days ago
      • Reported - view

       Ok, I've got data being written to Canary from my C# app through the web API. But I now have 2 more issues:
      1. Unless I leave several minutes between writes, canary gives me a 400 (Bad Request) error. If I only store new values every 10 minutes then it's fine but at 5 minutes I get this error. What can cause that?
      2.  When I write a new value to a tag, another value, the same as the previous value but with a "No Data (0x8000)" status gets written to the tag with a timestamp a fraction of a second after the previous value. What can cause that?

    • alistair_frith
    • 2 days ago
    • Reported - view

    I've solved it:

    I was retrieving a new session token on each scan. Canary was rejecting the new token presumably until the old one had expired (or some time after that). And because the old one had expired, it was assuming there was a data gap.

    Just getting the session token at the started and using that same token throughout has solved both issues.

      • smason
      • 2 days ago
      • Reported - view

      Yeah, once you create a session, you want to keep using the same session until you are done writing data. Creating multiple sessions that are writing to the same tags is going to create collisions in the SaF service.

      The NoData's are caused by a default setting when you first get a session token. By default, it will write a NoData one tick after the last value once the session closes. To remove this, you will want to set the autoWriteNoData setting to False when creating the token.

Content aside

print this pagePrint this page
  • Status Answered
  • 2 days agoLast active
  • 10Replies
  • 60Views
  • 3 Following