This guide provides solutions for common connection problems when using the Azure DevOps Node API's WebApi Core components to connect to Azure DevOps services.
Symptoms:
connection.connect()
or any API methodPossible Causes and Solutions:
Invalid Personal Access Token (PAT):
Error: TF400813: The user 'xxxxxxxx' is not authorized to access this resource.
Solution: Verify your PAT is valid and has not expired. Generate a new PAT in your Azure DevOps organization settings.
Insufficient PAT Permissions:
Error: The current user does not have permission to access this resource.
Solution: Make sure your PAT has the correct scopes for the APIs you're trying to access:
Basic Authentication Issues:
// The following code might fail because Microsoft is phasing out basic authentication
const authHandler = azdev.getBasicHandler("username", "password");
Solution: Microsoft is phasing out basic authentication. Use a PAT instead:
// Use Personal Access Token authentication instead
// Security Note: In production, store tokens in environment variables
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
Symptoms:
Solutions:
Check Token Expiry: Bearer tokens typically have a limited lifetime. Ensure your token is still valid.
Use the Correct Factory Method:
// Incorrect way - don't use this approach
const authHandler = new azdev.BearerCredentialHandler(token);
// Correct way - use the factory method
const token = "your-bearer-token";
const authHandler = azdev.getBearerHandler(token);
// OR use the convenience factory method
const orgUrl = "https://dev.azure.com/your-organization";
const token = "your-bearer-token";
const connection = azdev.WebApi.createWithBearerToken(orgUrl, token);
Symptoms:
Solutions:
Ignore SSL Errors (for development/testing only):
// Configure connection to ignore SSL errors
// WARNING: This should only be used in development/testing environments
const options = {
ignoreSslError: true
};
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler, options);
⚠️ Warning: Using
ignoreSslError: true
in production is a security risk!
Provide CA Certificate:
// Configure connection with a custom CA certificate
// This is the recommended approach for custom certificates
const options = {
cert: {
caFile: "/path/to/ca-certificate.pem"
}
};
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler, options);
Set NODE_TLS_REJECT_UNAUTHORIZED Environment Variable (for testing only):
// In your code (not recommended for production)
// WARNING: This disables SSL certificate validation for ALL connections
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
⚠️ Warning: This disables SSL certificate validation for all HTTPS connections in your Node.js application!
Symptoms:
Solutions:
Configure Proxy Settings:
// Configure connection with proxy settings
const options = {
proxy: {
proxyUrl: "http://your-proxy-server:port",
proxyUsername: "username", // If proxy requires authentication
proxyPassword: "password" // If proxy requires authentication
}
};
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler, options);
Set Environment Variables: The library will automatically pick up proxy settings from standard environment variables:
# Set these before running your Node.js application
export HTTP_PROXY=http://proxy-server:port
export HTTPS_PROXY=http://proxy-server:port
export NO_PROXY=localhost,127.0.0.1
Bypass Proxy for Internal Resources:
// Configure proxy with bypass rules for internal resources
const options = {
proxy: {
proxyUrl: "http://proxy-server:port",
proxyBypassHosts: [
"internal-resource.com",
"*.internal.domain"
]
}
};
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler, options);
Symptoms:
Solutions:
Configure Socket Timeout:
// Configure connection with a custom socket timeout
const options = {
socketTimeout: 60000 // 60 seconds
};
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler, options);
Enable Request Retries:
// Configure connection with retry settings
const options = {
allowRetries: true, // Enable automatic retries
maxRetries: 3 // Maximum number of retry attempts
};
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler, options);
Symptoms:
Solutions:
Check Server URL: Ensure the organization URL is correct. The URL should be in the format:
https://dev.azure.com/your-organization
or for older Azure DevOps accounts:
https://your-organization.visualstudio.com
Check Firewall Settings: Ensure your firewall allows outbound connections to Azure DevOps services:
Symptoms:
Solutions:
Check Resource Existence: Ensure the resource you're trying to access exists:
import * as azdev from "azure-devops-node-api";
async function getWorkItem(id: number) {
try {
// Setup connection
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler);
// Get Work Item Tracking API client
const workItemTrackingApi = await connection.getWorkItemTrackingApi();
// Get work item
const workItem = await workItemTrackingApi.getWorkItem(id);
console.log(`Work Item #${workItem.id}: ${workItem.fields["System.Title"]}`);
return workItem;
} catch (error) {
// Handle specific error types
if (error.statusCode === 404) {
console.error(`Work item ${id} does not exist`);
return null;
} else if (error.statusCode === 401) {
console.error("Authentication error. Check your credentials or token.");
} else {
console.error(`Error getting work item: ${error.message}`);
}
throw error;
}
}
Check API Version Compatibility: Some API methods may require specific API versions. The library should handle this automatically, but check for any API version-specific issues.
Symptoms:
Solutions:
/**
* Executes a function with exponential backoff retry for rate limiting
* @param fn - The async function to execute
* @param maxRetries - Maximum number of retry attempts
* @returns The result of the function
*/
async function executeWithBackoff<T>(fn: () => Promise<T>, maxRetries = 5): Promise<T> {
let retries = 0;
while (true) {
try {
// Attempt to execute the function
return await fn();
} catch (error) {
// Only retry on rate limiting errors (HTTP 429)
if (error.statusCode !== 429 || retries >= maxRetries) {
throw error;
}
// Calculate exponential backoff delay
const delay = Math.pow(2, retries) * 1000;
console.log(`Rate limited. Retrying in ${delay}ms...`);
// Wait for the calculated delay
await new Promise(resolve => setTimeout(resolve, delay));
retries++;
}
}
}
// Example usage
async function getRepositories(projectName: string) {
const orgUrl = "https://dev.azure.com/your-organization";
const token = process.env.AZURE_DEVOPS_PAT || "your-personal-access-token";
const authHandler = azdev.getPersonalAccessTokenHandler(token);
const connection = new azdev.WebApi(orgUrl, authHandler);
const gitApi = await connection.getGitApi();
// Use the executeWithBackoff function to handle rate limiting
return await executeWithBackoff(() => gitApi.getRepositories(projectName));
}
To diagnose connection issues, enable request logging:
Node.js Environment:
// Before creating the WebApi instance
const verbose = true;
process.env.AZURE_DEVOPS_NODE_API_LOG_LEVEL = verbose ? "debug" : "info";
process.env.AZURE_DEVOPS_NODE_API_LOG_OUTPUT = "console";
Always verify your connection is established before making API calls:
let connectionData;
try {
// Connect and explicitly check connection status
connectionData = await connection.connect();
console.log(`Connected to ${connectionData.instanceId}`);
console.log(`API version: ${connectionData.apiVersion}`);
// Now it's safe to proceed with API calls
const witApi = await connection.getWorkItemTrackingApi();
} catch (error) {
console.error("Connection failed:", error.message);
// Handle error appropriately
}
Test basic connectivity to Azure DevOps services before debugging API-specific issues:
# Simple connectivity test using curl
curl -s -o /dev/null -w "%{http_code}" https://dev.azure.com/your-organization
Error Code | Description | Solution |
---|---|---|
TF400813 | User not authorized | Check authentication credentials and permissions |
TF400898 | Authentication failed | Verify PAT or credentials are valid |
TF400904 | PAT has expired | Generate a new PAT |
TF401013 | Invalid OAuth token | Refresh or obtain a new token |
TF30063 | Account doesn't exist | Check organization URL |
TF400897 | JWT token expired | Refresh OAuth token |
TF429012 | Too many requests | Implement rate limiting and backoff |