Navigation: Home > API Reference > WorkItemTrackingApi > Error Handling
This document provides guidance on handling errors that may occur when using the WorkItemTrackingApi class.
When working with the WorkItemTrackingApi, you may encounter several categories of errors:
These errors occur when the API client cannot authenticate with Azure DevOps due to invalid or expired credentials.
try {
const workItem = await workItemTrackingApi.getWorkItem(42);
} catch (error) {
if (error.statusCode === 401) {
console.error("Authentication failed. Your credentials may be invalid or expired.");
// Handle authentication error (e.g., prompt for new token)
}
}
These errors occur when the authenticated user doesn't have sufficient permissions to perform the requested operation.
try {
const workItem = await workItemTrackingApi.updateWorkItem(
patchDocument,
workItemId,
projectName
);
} catch (error) {
if (error.statusCode === 403) {
console.error("You don't have permission to update this work item.");
// Handle permission error (e.g., request access or notify user)
}
}
These errors occur when the requested resource (work item, query, etc.) doesn't exist.
try {
const workItem = await workItemTrackingApi.getWorkItem(999999);
} catch (error) {
if (error.statusCode === 404) {
console.error("Work item not found. It may have been deleted or never existed.");
// Handle not found error (e.g., show user-friendly message)
}
}
These errors occur when the provided data doesn't meet the requirements of the API.
try {
const patchDocument = [
{ op: "add", path: "/fields/System.Title", value: "" } // Empty title
];
const workItem = await workItemTrackingApi.createWorkItem(
patchDocument,
projectName,
"Bug"
);
} catch (error) {
if (error.statusCode === 400) {
console.error("Validation error:", error.message);
// Extract validation details if available
if (error.body && error.body.value) {
console.error("Details:", error.body.value);
}
// Handle validation error (e.g., show form errors to user)
}
}
These errors occur when you've exceeded the API rate limits.
try {
// Making many API calls in a loop
const results = await Promise.all(
workItemIds.map(id => workItemTrackingApi.getWorkItem(id))
);
} catch (error) {
if (error.statusCode === 429) {
console.error("Rate limit exceeded. Try again later or reduce request frequency.");
// Handle rate limiting (e.g., implement exponential backoff)
}
}
Status Code | Error Type | Common Causes | Resolution Strategies |
---|---|---|---|
400 | Bad Request | Invalid data, malformed JSON, missing required fields | Validate inputs before sending, check for required fields |
401 | Unauthorized | Invalid/expired token, missing authentication | Refresh token, check authentication headers |
403 | Forbidden | Insufficient permissions | Request appropriate permissions, check project access |
404 | Not Found | Resource doesn't exist | Verify IDs are correct, check if resource was deleted |
409 | Conflict | Concurrent modification, version conflict | Fetch latest version, implement retry logic |
429 | Too Many Requests | Exceeded rate limits | Implement backoff strategy, batch requests |
500 | Server Error | Internal Azure DevOps error | Retry with exponential backoff |
503 | Service Unavailable | Service outage or maintenance | Retry later, check Azure DevOps status page |
The simplest pattern for handling errors:
try {
const workItem = await workItemTrackingApi.getWorkItem(42);
console.log(workItem.fields["System.Title"]);
} catch (error) {
console.error("Error retrieving work item:", error.message);
}
A more comprehensive approach that handles different error types:
async function getWorkItemSafely(api, workItemId, projectName) {
try {
return await api.getWorkItem(workItemId, projectName);
} catch (error) {
switch (error.statusCode) {
case 401:
console.error("Authentication error. Please sign in again.");
// Trigger authentication flow
break;
case 403:
console.error("You don't have permission to access this work item.");
break;
case 404:
console.error(`Work item ${workItemId} was not found.`);
break;
case 429:
console.error("Rate limit exceeded. Retrying after delay...");
// Wait and retry
await new Promise(resolve => setTimeout(resolve, 5000));
return getWorkItemSafely(api, workItemId, projectName); // Retry
default:
console.error(`Unexpected error (${error.statusCode}): ${error.message}`);
}
// Optionally rethrow or return null/default value
return null;
}
}
For operations that might fail due to transient issues:
async function withRetry(operation, maxRetries = 3, initialDelay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// Only retry on specific status codes that might be transient
const retryableCodes = [429, 500, 503];
if (!retryableCodes.includes(error.statusCode)) {
throw error; // Don't retry client errors
}
console.warn(`Attempt ${attempt} failed, retrying in ${initialDelay / 1000}s...`);
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, initialDelay));
initialDelay *= 2; // Double the delay for next attempt
}
}
throw lastError; // All retries failed
}
// Usage
try {
const workItem = await withRetry(() =>
workItemTrackingApi.getWorkItem(42)
);
console.log(workItem.fields["System.Title"]);
} catch (error) {
console.error("All retry attempts failed:", error.message);
}
Create custom error classes for more structured error handling:
class WorkItemApiError extends Error {
constructor(statusCode, message, details) {
super(message);
this.name = 'WorkItemApiError';
this.statusCode = statusCode;
this.details = details;
}
isAuthError() {
return this.statusCode === 401 || this.statusCode === 403;
}
isNotFoundError() {
return this.statusCode === 404;
}
isValidationError() {
return this.statusCode === 400;
}
isRateLimitError() {
return this.statusCode === 429;
}
isServerError() {
return this.statusCode >= 500;
}
}
// Usage
async function safeGetWorkItem(api, workItemId, projectName) {
try {
return await api.getWorkItem(workItemId, projectName);
} catch (error) {
const details = error.body ? error.body.value : undefined;
throw new WorkItemApiError(
error.statusCode || 500,
error.message,
details
);
}
}
// Using the custom error
try {
const workItem = await safeGetWorkItem(workItemTrackingApi, 42, "MyProject");
// Process work item
} catch (error) {
if (error.isAuthError()) {
// Handle authentication/authorization error
} else if (error.isNotFoundError()) {
// Handle not found error
} else if (error.isServerError()) {
// Handle server error, possibly retry
} else {
// Handle other errors
}
}
When performing batch operations, you may want to continue even if some operations fail:
async function updateWorkItemsBatch(api, projectName, updates) {
const results = {
successful: [],
failed: []
};
// Process in parallel but handle errors individually
const updatePromises = updates.map(async update => {
try {
const patchDocument = update.fields.map(field => ({
op: "add",
path: `/fields/${field.name}`,
value: field.value
}));
const updatedWorkItem = await api.updateWorkItem(
patchDocument,
update.id,
projectName
);
results.successful.push({
id: update.id,
workItem: updatedWorkItem
});
return updatedWorkItem;
} catch (error) {
results.failed.push({
id: update.id,
error: {
statusCode: error.statusCode,
message: error.message,
details: error.body ? error.body.value : undefined
}
});
return null; // Don't throw, allowing other operations to continue
}
});
// Wait for all operations to complete (successful or not)
await Promise.all(updatePromises);
return results;
}
// Usage
const results = await updateWorkItemsBatch(workItemTrackingApi, "MyProject", [
{ id: 42, fields: [{ name: "System.State", value: "Active" }] },
{ id: 43, fields: [{ name: "System.State", value: "Resolved" }] }
]);
console.log(`Updated ${results.successful.length} work items successfully`);
if (results.failed.length > 0) {
console.error(`Failed to update ${results.failed.length} work items:`);
results.failed.forEach(failure => {
console.error(`Work Item #${failure.id}: ${failure.error.message}`);
});
}