Using Jira REST API in C# to create Worklogs

Hi,

So I am working on an application which tracks the user’s activity in order to accurately track time. This means the time is being tracked on the application and now I want to send the time tracked to the relevant issue in Jira.

So far I’ve been using a console application to try and achieve this goal as a test, but I’ve been running into some issues:

Getting this error:

{"errorMessages":["Worklog body is not valid.","Worklog must not be null."],"errors":{"worklog":"INVALID_INPUT"}}

This is my JiraTimeLogger.cs code:

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace JiraTimeLogger
{
    public class JiraClient
    {
        private readonly HttpClient _httpClient;

        public JiraClient(string jiraDomain, string username, string apiToken)
        {
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri($"https://{jiraDomain}.atlassian.net/")
            };

            var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{apiToken}"));
            _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authToken);
        }

        public async Task LogWorkAsync(string issueIdOrKey, string timeSpent, string comment)
        {
            var url = $"rest/api/3/issue/{issueIdOrKey}/worklog";

            var payload = new
            {
                timeSpentSeconds = 60,
                comment = comment,
                started = "2024-11-06T13:11:01.778-0500"
                //started = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ssK")
            };

            var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
            var response = await _httpClient.PostAsync(url, content);

            if (response.IsSuccessStatusCode)
            {
                Console.WriteLine("Work logged successfully.");
            }
            else
            {
                Console.WriteLine($"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}");
            }
        }
    }
}

This is my code in Program.cs:

#region Main
using JiraTimeLogger;

var jiraDomain = "andytesting";
var username = "andries.benade@xxx.co.za";
var apiToken = "xxx";

#region Jira Logger
// Initialize the Jira client
var jiraClient = new JiraClient(jiraDomain, username, apiToken);

// Log work
var issueIdOrKey = "CCS-7";
var timeSpent = "1h";
var comment = "Eating old baked beans";

await jiraClient.LogWorkAsync(issueIdOrKey, timeSpent, comment);
#endregion

Console.WriteLine("End of Process.");
Console.ReadLine();
#endregion

On an additional note: As you can see in my JiraTimeLogger class, I have also hard coded the date since it would otherwise say my date is in the incorrect format, but looking at the way I specified the date format in the comment, it seems correct to me.

Hello @AndriesBenade

If you look at the examples shown in the documentation for the v3 Add Worklog endpoint, you’ll see that the worklog comment must be encoded in ADF format, which you have not done.

If you are not prepared to use ADF, then use the v2 Add Worklog endpoint instead.

Hi @AndriesBenade,
i looked into your code, and i think there are a few things you can check and adjust to get your work log to Jira correctly. The error message you’re receiving indicates that the worklog body is not valid, which often means that the payload you’re sending is not in the expected format.

You can try something like this:

1. Time Spent

Ensure that timeSpentSeconds is accurately reflecting the total number of seconds for the time spent. In your payload, it’s hardcoded to 60 seconds. If you intend to pass a value like "1h" or "30m", this should be converted to seconds (e.g., "1h" = 3600 seconds).

2. Correctly Formatted Payload

Make sure that the JSON payload sent to Jira includes all required fields in the correct format. Here’s a revised version of your LogWorkAsync method:

public async Task LogWorkAsync(string issueIdOrKey, string timeSpent, string comment)
{
    var url = $"rest/api/3/issue/{issueIdOrKey}/worklog";

    // Convert timeSpent to seconds
    var timeSpentSeconds = ConvertTimeSpentToSeconds(timeSpent);

    var payload = new
    {
        timeSpent = timeSpent,
        timeSpentSeconds = timeSpentSeconds,
        comment = new
        {
            type = "doc",
            version = 1,
            content = new[]
            {
                new
                {
                    type = "paragraph",
                    content = new[]
                    {
                        new { type = "text", text = comment }
                    }
                }
            }
        },
        started = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK")
    };

    var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
    var response = await _httpClient.PostAsync(url, content);

    if (response.IsSuccessStatusCode)
    {
        Console.WriteLine("Work logged successfully.");
    }
    else
    {
        Console.WriteLine($"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}");
    }
}

private int ConvertTimeSpentToSeconds(string timeSpent)
{
    // Example conversion for "1h" = 3600 seconds
    if (timeSpent.EndsWith("h"))
    {
        return int.Parse(timeSpent.Replace("h", "")) * 3600;
    }
    // Add other conversions as needed
    return 60; // Default to 60 seconds
}

3. Comment Field

As far as i know, Jira requires comments to be in a specific format. The above code uses an object for the comment, matching the format Jira expects. You should adjust your comment to fit this structure like this:

Additional Notes:

  • URL Format: Ensure your URL path is correct.
  • Date Format: Ensure the date format is correct. The provided format "yyyy-MM-dd'T'HH:mm:ss.fffK" should work fine.

Implementing these changes should help resolve the issue. If the error persists, double-check the exact payload structure that Jira’s API expects by referring to Jira’s REST API documentation.

Chears,
Daniel

Hello @DanielGrabke

Please note that you cannot use the timeSpent and timeSpentSeconds parameters together in the same request; you must use only one or the other.

Hi @sunnyape,
thats a good point and i havent considered it. Thx for the reminder.
I adjusted the code accordingly.

Adjusted Code for Logging Work in Jira

Here’s an updated version of your LogWorkAsync method that only uses timeSpentSeconds . This method converts the timeSpent string to seconds internally and uses that for the API request:

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace JiraTimeLogger
{
    public class JiraClient
    {
        private readonly HttpClient _httpClient;

        public JiraClient(string jiraDomain, string username, string apiToken)
        {
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri($"https://{jiraDomain}.atlassian.net/")
            };

            var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{apiToken}"));
            _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authToken);
        }

        public async Task LogWorkAsync(string issueIdOrKey, string timeSpent, string comment)
        {
            var url = $"rest/api/3/issue/{issueIdOrKey}/worklog";

            // Convert timeSpent to seconds
            var timeSpentSeconds = ConvertTimeSpentToSeconds(timeSpent);

            var payload = new
            {
                timeSpentSeconds = timeSpentSeconds,
                comment = comment,
                started = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK")
            };

            var content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
            var response = await _httpClient.PostAsync(url, content);

            if (response.IsSuccessStatusCode)
            {
                Console.WriteLine("Work logged successfully.");
            }
            else
            {
                Console.WriteLine($"Error: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}");
            }
        }

        private int ConvertTimeSpentToSeconds(string timeSpent)
        {
            // Example conversion for "1h" = 3600 seconds
            if (timeSpent.EndsWith("h"))
            {
                return int.Parse(timeSpent.Replace("h", "")) * 3600;
            }
            // Add other conversions as needed
            return 60; // Default to 60 seconds
        }
    }
}

This code ensures you’re only sending timeSpentSeconds in your API request.

Cheers,
Daniel