Azure DevOps API
I was once asked if the Microsoft Graph API was complicated. My reply? “Once you’ve wrestled with the Azure DevOps API, Microsoft Graph feels like a walk in paradise.”
At the beginning of my Azure DevOps API (ADO API) journey, I encountered numerous challenges, particularly when working with the API endpoints.
Endpoints
Regular ADO operations, such as managing repositories, pipelines, and work items, use the main dev.azure.com
endpoint. For account entitlement tasks, like managing users, access levels, licenses, and groups, the vsaex.dev.azure.com
endpoint is employed. Conversely, vssps.dev.azure.com
serves as the security and profile service API endpoint, handling authentication, authorization, security groups, permissions, security tokens, and more.
API design
I was once tasked with programmatically syncing Azure Service Principals to its corresponding Azure DevOps Groups, ensuring specific project permissions. Here’s a snippet to retrieve the service principal ID:
async _getServicePrincipalId() {
const servicePrincipalData = await this.func.getServicePrincipal('applicationId', servicePrincipalId);
const url = `https://vssps.dev.azure.com/${organization}/_apis/graph/storagekeys/${encodeURIComponent(servicePrincipalData.descriptor)}?api-version=7.1-preview.1`;
const response = await rest.get(url);
return response.result.value;
}
This method necessitates two separate API calls just to obtain a simple identifier. First, you fetch service principal data; then, you use a descriptor from that data to fetch a storage key. This two-step process complicates what should be a straightforward lookup.
Instead of directly retrieving the service principal ID, you’re:
1. Fetching service principal data
2. Extracting a descriptor
3. Using that descriptor to get a storage key
Different Endpoints for Similar Operations
Both methods are essentially managing service principal group/team memberships, yet they use distinctly different API endpoints.
Function to add Service Principal to Teams
async addServicePrincipalToTeam() {
const servicePrincipalId = await this._localFunc1();
const projectTeamMembers = await this._localFunc2();
const url = `https://vsaex.dev.azure.com/${organization}/_apis/GroupEntitlements/${groupId}/members/${servicePrincipalId}?api-version=7.2-preview.1`;
const payload = {
principalType: 'ServicePrincipal',
principalName: principalName
};
Function to update Service Principal Group
async updateServicePrincipalGroup() {
const projectId = await this._localFunc1();
const servicePrincipalId = await this._localFunc2();
const url = `https://vsaex.dev.azure.com/${organization}/_apis/serviceprincipalentitlements?api-version=7.1-preview.1`;
const headers = { 'Content-Type': 'application/json-patch+json' };
const payload = [
{
from: 'custom',
op: 'replace',
path: `/${servicePrincipalId}/projectEntitlements/${projectId}`,
value: {
group: {
groupType: groupType
},
projectRef: {
id: projectId
}
}
}
]
}
My assumption is that the API has evolved over time, or perhaps different teams within Azure DevOps manage these operations, creating what some might call technical debt.
Key takeway after discussion with a Principal Software Engineer at Microsoft
There’s an extreme amount of Customization you can do to the Azure DevOps tasks to fit any need (e.g. writing a C# project that is injected into each build that runs arbitrary SDK scans for certain Nuget packages being used, that shouldn’t be). I understand you can sort of (only Typescript) do this by writing a custom GitHub Action, but using rules based injections to do anything is where ADO shines.
Azure DevOps is a beast of a platform because it inherits all the source code from VSTS, there’s 20 years of software innovation baked into it. GitHub is comparatively a baby — which is why they forked Azure DevOps pipelines to make GitHub Actions and bring that VSTS secret sauce into GitHub when MSFT acquired it.
Conclusion
While navigating its complex endpoints and preview APIs can feel like solving a puzzle, the platform’s depth reveals a powerful system that can be tailored to almost any development workflow. For developers willing to invest the time, Azure DevOps offers a level of flexibility that transforms it from a mere tool to a fully programmable development ecosystem.