How to connect to Azure DevOps with a personal access token in C#

Updated on September 23, 2025

In this article, we’ll show you how to connect to Azure DevOps (ADO) with C# using a Personal Access Token (PAT). A Personal Access Token acts like a secure key that allows your application to connect to ADO without exposing your username and password. A PAT is safer than basic authentication because a PAT can be scoped to minimum permissions and revoked if leaked.

Protect your token

Although a personal access token is safer than a username and password, you should never hard-code your PAT in your source code. You should never commit your PAT into Git or any source-code control. Be just as careful with your PAT as you would a password.

Establishing a connection

This example uses the Azure DevOps SDK, which is the recommended approach unless you have specific reasons for calling the REST APIs directly. To establish a connection with the SDK, you create an instance of VssBasicCredential and pass it to VssConnection.

VssBasicCredential normally takes a username and password. You can use this same class to send a Personal Access Token instead. To do this, you leave the username blank, and you set the password to the token value.

Here is the core code:

// VssBasicCredential takes a username + password.
// In the case of PAT authentication, username is left empty,
// and PAT is provided as the "password".
var credentials = new VssBasicCredential(string.Empty, "your pat string");

// Create a connection to Azure DevOps using the URL + credentials
var connection = new VssConnection(new Uri("your org URL"), credentials);

// Get a client for the Work Item Tracking service
// This client is used to query, read, and update work items
var witClient = connection.GetClient<WorkItemTrackingHttpClient>();

Instruction for a quick example app

This example is written as a console app that connects to Azure DevOps with the Azure DevOps (formerly Team Foundation Services) SDK. You will need Visual Studio with the .NET SDK installed.

  1. Create a new console app.

  2. Install required NuGet packages:

    Install-Package Microsoft.TeamFoundationServer.Client

  3. Copy and paste the code below into your main file

    The code below contains a working example of connecting to Azure DevOps and performing a query. You will need to update a few lines with your project name, organization URL and personal access token — see the comments. In a real app you would pull these values from a configuration file or user settings.

  4. Generate a personal access token

    See How to generate a personal access token in Azure DevOps.

  5. Paste the token into the designated line

    Remember, this is just a demonstration. For a real app, you should never store a personal access token in source code.

  6. Review the source code comments and run the code

  7. If the query does not return anything, check the work item type

    The example query is designed to work with the default Basic template in Azure DevOps. If the query doesn’t return any records, you may need to change the query to reflect your particular template. Identify a status that your work items have, e.g., Completed, and use that instead of “To Do”.

Code

// ----------------------------
// Required Namespaces
// ----------------------------

// Azure DevOps (formerly TFS) client SDK for accessing
// Work Item Tracking (WIT) REST APIs
// NuGet: Microsoft.TeamFoundationServer.ExtendedClient
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;

// Models for work items (e.g., WorkItem, Wiql, WorkItemQueryResult)
// NuGet: Microsoft.TeamFoundationServer.ExtendedClient
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;

// Provides credentials classes (like VssBasicCredential) for
// authenticating with Azure DevOps
// NuGet: Microsoft.VisualStudio.Services.Client
using Microsoft.VisualStudio.Services.Common;

// Provides the VssConnection class, which represents a connection
// to an Azure DevOps organization
// NuGet: Microsoft.VisualStudio.Services.Client
using Microsoft.VisualStudio.Services.WebApi;

// Standard .NET namespaces
using System;
using System.Linq;                  // for LINQ (used in .Select and .Any)
using System.Threading.Tasks;       // for async/await support

namespace DevopsDemo
{
    class Program
    {
        // Main method: entry point of the console app
        // "async Task Main" allows us to use "await" inside Main
        static async Task Main(string[] args)
        {
            // ------------------------------------
            // 1. Define connection details
            // ------------------------------------
            // Organization URL
            string url = "https://dev.azure.com/your_organization";
            // Personal Access Token (PAT) generated from Azure DevOps
            string pat = "your_personal_access_token";
            // Project name inside Azure DevOps
            string project = "your_project";
            
            // ------------------------------------
            // 2. Authenticate using PAT
            // ------------------------------------
            // VssBasicCredential takes a username + password.
            // In the case of PAT authentication, username is left empty,
            // and PAT is provided as the "password".
            var credentials = new VssBasicCredential(string.Empty, pat);

            // Create a connection to Azure DevOps using the URL + credentials
            var connection = new VssConnection(new Uri(url), credentials);

            // Get a client for the Work Item Tracking service
            // This client is used to query, read, and update work items
            var witClient = connection.GetClient<WorkItemTrackingHttpClient>();

            // ------------------------------------
            // 3. Define a WIQL query
            // ------------------------------------
            // WIQL = Work Item Query Language (SQL-like syntax to query
            // work items in Azure DevOps)
            Wiql wiql = new Wiql()
            {
                // This query selects all work items in the given project
                // where the State = "To Do".
                // We are requesting only Id and Title fields.
                Query = $"Select [System.Id], [System.Title] " +
                        $"From WorkItems " +
                        $"Where [System.TeamProject] = '{project}' " +
                        $"AND [System.State] = 'To Do'"
            };

            // ------------------------------------
            // 4. Execute the WIQL query
            // ------------------------------------
            // This returns references (IDs + URLs) to the work
            // items that match the query.
            var result = await witClient.QueryByWiqlAsync(wiql);

            // ------------------------------------
            // 5. If work items are found, fetch details
            // ------------------------------------
            if (result.WorkItems.Any())
            {
                // Extract the work item IDs from the query result
                var ids = result.WorkItems.Select(wi => wi.Id).ToArray();

                // Get full work item details (fields, relations, etc.)
                // WorkItemExpand.Fields tells the client to include field data
                var workItems = await witClient.GetWorkItemsAsync(ids, expand: WorkItemExpand.Fields);

                // ------------------------------------
                // 6. Print work item details
                // ------------------------------------
                foreach (var wi in workItems)
                {
                    // Print the unique ID
                    Console.WriteLine($"Work Item Id: {wi.Id}"); 
                    // Print the title field
                    Console.WriteLine($"Title       : {wi.Fields["System.Title"]}"); 
                    // Separator line
                    Console.WriteLine(new string('-', 40)); 
                }
            }
            else
            {
                // No results found
                Console.WriteLine("No work items found.");
            }
        }
    }
}

See also

Reference

License

Licensed under CC BY 4.0

You are free to share and adapt this content for any purpose as long as you give appropriate credit in a reasonable manner.

No affiliate links

We do not participate in affiliate marketing, and we are not paid to mention products.

Leave a Reply

Your email address will not be published. Required fields are marked *