Sometimes you are going to want to retrieve work items from your Azure DevOps Organization, maybe to perform some complex functions that you can’t do inside of DevOps. Maybe it’s merging data between different projects, comparing who did what work across different organizations, etc, etc.
Whatever the reason, you’re going to want to get that data.
An easy way to do this is with the Wiql (Work Item Query Language) that is available for any Azure DevOps Project.
Obtaining a Personal Access Token
The first thing you are going to need to do is obtain a Personal Access Token. This is your authentication key to your project that will allow your code to connect to Azure DevOps. To do this, you’ll click the little gear icon beside your own profile icon and select “Personal Access Token”.

When creating a token, only add the permissions you need; this project involves you and your organization’s data. Important to note is the “Organization” field, where a default option exists to use this token across all of your Organizations. I would not recommend this if you don’t need it.

Constructing the Query
Wiql is a lot like SQL (as its name implies), and hence what you are sending into the Wiql service are Wiql queries. That said, you probably don’t know all the fields you need to query, but to simplify this task, Microsoft has created an extension called “Wiql Editor” that you can install in your environment.
The easiest way to get started with Wiql is to open an existing query and go into Editor mode. From there, you can click on the 3 vertical dots in the top right corner and select

From there, you can see the query used, copy and paste it for our later service call.

Calling the Service
With our Access Token, Organization, Project, and query, we are ready to write some code. What you see below will query DevOps for the fields I required from the WorkItems table.
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", _PersonalAccessToken))));
var values = new Dictionary<string, string>();
values.Add("query", $"SELECT [System.Id], [System.Title], [System.State] FROM WorkItems");
var payload = JsonConvert.SerializeObject(values);
var content = new StringContent(payload, Encoding.UTF8, "application/json");
using (HttpResponseMessage response = client.PostAsync(string.Concat(_Uri, "/_apis/wit/wiql?api-version=6.1"), content).Result)
{
try
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
_HistoricalResultsJson = JObject.Parse(responseBody);
}
catch (Exception ex)
{
// Handle Error
}
}
}
The results of making this call are a list of all the items I queried for and the fields that were requested. In the below case, you will notice that even though we requested additional fields, all we got back was Id. This is by design as the next you will need to take is to query the individual work item for details about the Id.
{
“queryType”: “flat”,
“queryResultType”: “workItem”,
“asOf”: “2025-05-21T02:41:36.207Z”,
“columns”: [
{
“referenceName”: “System.Id”,
“name”: “ID”,
“url”: “https://dev.azure.com/PROJECT/_apis/wit/fields/System.Id”
},
{
“referenceName”: “System.Title”,
“name”: “Title”,
“url”: “https://dev.azure.com/PROJECT/_apis/wit/fields/System.Title”
},
{
“referenceName”: “System.State”,
“name”: “State”,
“url”: “https://dev.azure.com/PROJECT/_apis/wit/fields/System.State”
}
],
“sortColumns”: [
{
“field”: {
“referenceName”: “System.CreatedDate”,
“name”: “Created Date”,
“url”: “https://dev.azure.com/PROJECT/_apis/wit/fields/System.CreatedDate”
},
“descending”: false
}
],
“workItems”: [
{
“id”: 10,
“url”: “https://dev.azure.com/PROJECT/3f733a67-0b26-413a-8179-36f6b47752e0/_apis/wit/workItems/10”
},
{
“id”: 11,
“url”: “https://dev.azure.com/PROJECT/3f733a67-0b26-413a-8179-36f6b47752e0/_apis/wit/workItems/11”
},
{
“id”: 12,
“url”: “https://dev.azure.com/PROJECT/3f733a67-0b26-413a-8179-36f6b47752e0/_apis/wit/workItems/12”
},
{
Restricting Searches by Date
When querying for work items, you will invariably want to query by some date field. Azure DevOps does not localize timezone datetime objects and instead uses UTC times. Therefore, it is important to convert your datetime fields to the appropriate format before submitting your request.
string UtcStartDate = startDate.ToUniversalTime().ToString("yyyy-MM-ddT00:00:00Z");
string UtcEndDate = endDate.ToUniversalTime().ToString("yyyy-MM-ddT00:00:00Z");
Your updated WHERE clause in your Wiql would look something like this.
WHERE [System.CreatedDate] >= ‘{UtcStartDate}’ AND [System.CreatedDate] <= ‘{UtcEndDate}’ ORDER BY [System.CreatedDate] ASC