How to get Project Administartors via API in Azure DevOps?
Azure Azure DevOps
Table of contents:
When managing projects in Azure DevOps, establishing proper governance and generating comprehensive reports often requires access to specific organizational data. One crucial piece of information is the membership of the default Project Administrator team, which is automatically created with each new project.
However, working with the ADO API can be challenging, especially when compared to other Microsoft APIs like Graph or Azure. The process of retrieving the Project Administrator team members involves multiple API calls across different endpoints, making it less straightforward than one might expect.
This article aims to guide you through the process of obtaining Project Administrator team membership information using two methods:
- REST API calls
- Postman
By following this guide, you’ll learn how to navigate the complexities of the Azure DevOps API and efficiently retrieve the data you need for your project management and reporting tasks.
Let’s dive into the details of each approach and explore how to overcome the “jumps” between different API endpoints to gather this essential information.
Prerequisites #
Before you begin, ensure you have the following:
Azure DevOps Organization
- Create one using any Microsoft account here
- Must have at least one existing project
Permissions
- Minimum required: Member of the default project team
- This can be verified in the Project’s settings under the “Teams” page
Personal Access Token (PAT)
- Required for querying Azure DevOps services using Python Client Libraries
- Must include the following scopes:
- Graph: Read
- Identity: Read
- Member Entitlement Management: Read
- Project and Team: Read
Ensure all these prerequisites are met before proceeding with the API queries or Python implementation. This will guarantee smooth access to the required Azure DevOps resources and data.
Azure DevOps API endpoints #
When working with the Azure DevOps REST API, it’s crucial to understand that the API is distributed across multiple domain URLs. This segmentation can be confusing at first, but understanding it is key to successfully querying the API.
- Primary endpoint for most AzDO operations
- Examples:
/_apis/git
,/_apis/projects
➡️ https://vsaex.dev.azure.com
- Handles Member Entitlement Management
- Example:
/_apis/groupentitlements
➡️ https://vssps.dev.azure.com
- Manages people and groups
- Examples:
/_apis/identities
,/_apis/graph/groups
➡️ https://status.dev.azure.com
- Provides status information
- Example:
/_apis/status
Note: The reason for this distribution isn’t well-documented, but it likely relates to how ADO’s backend services are organized.
When making API calls, ensure you’re using the correct domain URL for your specific query. Using the wrong URL will result in a 404 error, even if the rest of your query is correct.
Retrieving Project Administrator Team Members #
To get the default project team members, follow these steps:
- Get the Project’s ID
- Retrieve the Project’s scope descriptor value
- Fetch the Project’s groups using the scope descriptor
- Get the members of the default Project Administrators group
All these queries use the GET method. You can execute them in a browser, but make sure you’re authenticated in your Azure DevOps organization first.
In the following sections, we’ll dive into each of these steps in detail, providing the specific API calls and explaining the responses.
Project ID #
That’s the simpliest one (here the {project_id}
would be 69c09eba-78ed-4d1f-a14f-1d6a61457caa
):
The endpoint: https://dev.azure.com/{org}/_apis/projects/{project_id}
1
2{
3 "id": "69c09eba-78ed-4d1f-a14f-1d6a61457caa",
4 "name": "MyShuttle",
5 "description": "Generated by Azure DevOps Demo Generator",
6 "url": "https://dev.azure.com/weekendsprints/_apis/projects/69c09eba-78ed-4d1f-a14f-1d6a61457caa",
7 "state": "wellFormed",
8 "revision": 223,
9 "_links": {
10 "self": {
11 "href": "https://dev.azure.com/weekendsprints/_apis/projects/69c09eba-78ed-4d1f-a14f-1d6a61457caa"
12 },
13 "collection": {
14 "href": "https://dev.azure.com/weekendsprints/_apis/projectCollections/85a78473-ca26-44e8-957d-99f3b7b2b03a"
15 },
16 "web": {
17 "href": "https://dev.azure.com/weekendsprints/MyShuttle"
18 }
19 },
20 "visibility": "private",
21 "defaultTeam": {
22 "id": "54c4897c-84d5-47c5-9dc9-db102a808d1b",
23 "name": "MyShuttle Team",
24 "url": "https://dev.azure.com/weekendsprints/_apis/projects/69c09eba-78ed-4d1f-a14f-1d6a61457caa/teams/54c4897c-84d5-47c5-9dc9-db102a808d1b"
25 },
26 "lastUpdateTime": "2023-11-21T21:40:02.243Z"
27}
Project’s scope descriptor value #
Let’s switch to another service (here the {project_id}
would be 69c09eba-78ed-4d1f-a14f-1d6a61457caa
):
The endpoint: https://vssps.dev.azure.com/{org}/_apis/graph/descriptors/{project_id}
1
2{
3 "value": "scp.NTM2NTJkNTEtNmJkNy00MWUzLTlmZDItNWRmMDUzN2ExMWYw",
4 "_links": {
5 "self": {
6 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Descriptors/69c09eba-78ed-4d1f-a14f-1d6a61457caa"
7 },
8 "storageKey": {
9 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/StorageKeys/scp.NTM2NTJkNTEtNmJkNy00MWUzLTlmZDItNWRmMDUzN2ExMWYw"
10 },
11 "subject": {
12 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Scopes/scp.NTM2NTJkNTEtNmJkNy00MWUzLTlmZDItNWRmMDUzN2ExMWYw"
13 }
14 }
15}
Project’s groups with the scope descriptor value #
With the value obtained before (here the {project_scope_descriptor}
would be scp.NTM2NTJkNTEtNmJkNy00MWUzLTlmZDItNWRmMDUzN2ExMWYw
):
The next endpoint: https://vssps.dev.azure.com/{org}/_apis/graph/groups?scopeDescriptor={project_scope_descriptor}
1
2{
3 "count": 11,
4 "value": [
5 {
6 "subjectKind": "group",
7 "description": "Members of this group can perform all operations in the team project.",
8 "domain": "vstfs:///Classification/TeamProject/69c09eba-78ed-4d1f-a14f-1d6a61457caa",
9 "principalName": "[MyShuttle]\\Project Administrators",
10 "mailAddress": null,
11 "origin": "vsts",
12 "originId": "b361ca03-96d8-4c3b-ba3f-b9fcbe20a28e",
13 "displayName": "Project Administrators",
14 "_links": {
15 "self": {
16 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Groups/vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMzYxOTI5NTU1LTM2MTQxNzE5NjktMjY4MTM2Mzk1Mi0xNDAwNTA4OTEyLTAtMC0wLTAtMQ"
17 },
18 "memberships": {
19 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Memberships/vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMzYxOTI5NTU1LTM2MTQxNzE5NjktMjY4MTM2Mzk1Mi0xNDAwNTA4OTEyLTAtMC0wLTAtMQ"
20 },
21 "membershipState": {
22 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/MembershipStates/vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMzYxOTI5NTU1LTM2MTQxNzE5NjktMjY4MTM2Mzk1Mi0xNDAwNTA4OTEyLTAtMC0wLTAtMQ"
23 },
24 "storageKey": {
25 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/StorageKeys/vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMzYxOTI5NTU1LTM2MTQxNzE5NjktMjY4MTM2Mzk1Mi0xNDAwNTA4OTEyLTAtMC0wLTAtMQ"
26 }
27 },
28 "url": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Groups/vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMzYxOTI5NTU1LTM2MTQxNzE5NjktMjY4MTM2Mzk1Mi0xNDAwNTA4OTEyLTAtMC0wLTAtMQ",
29 "descriptor": "vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMzYxOTI5NTU1LTM2MTQxNzE5NjktMjY4MTM2Mzk1Mi0xNDAwNTA4OTEyLTAtMC0wLTAtMQ"
30 },
31 ....
32 ]
33}
The cool thing about this group is that it’s created by ADO and has only read-only properties, so you can look for the “Project Administrators” display name without being worried about title changing.
Getting default Project Administrators members #
Finally, with the Project Team ID through the originId property, get the members (and switch to another service URL, where the {project_team_id}
would be b361ca03-96d8-4c3b-ba3f-b9fcbe20a28e
):
The endpoint: https://vsaex.dev.azure.com/{org}/_apis/GroupEntitlements/{project_team_id}/members
1{
2 "members": [
3 {
4 "user": {
5 "subjectKind": "user",
6 "metaType": "member",
7 "directoryAlias": "user-weekendsprints-nl",
8 "domain": "0ef6ab0c-c18f-430c-ab5b-8eacfd69402c",
9 "principalName": "user@example.com",
10 "mailAddress": "user@example.com",
11 "origin": "aad",
12 "originId": "98a2d022-fafc-4951-9ca9-13469fa8b230",
13 "displayName": "user",
14 "_links": {
15 "self": {
16 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Users/aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw"
17 },
18 "memberships": {
19 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Memberships/aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw"
20 },
21 "membershipState": {
22 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/MembershipStates/aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw"
23 },
24 "storageKey": {
25 "href": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/StorageKeys/aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw"
26 },
27 "avatar": {
28 "href": "https://dev.azure.com/weekendsprints/_apis/GraphProfile/MemberAvatars/aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw"
29 }
30 },
31 "url": "https://vssps.dev.azure.com/weekendsprints/_apis/Graph/Users/aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw",
32 "descriptor": "aad.YjhhNmQyNTktOWVmOS03MGM1LWEwNjEtNzQiYjdhNTViMmQw"
33 },
34 "extensions": [],
35 "id": "9bb7a948-cc2c-6215-b3c4-f8cce20819bc",
36 "accessLevel": {
37 "licensingSource": "account",
38 "accountLicenseType": "stakeholder",
39 "msdnLicenseType": "none",
40 "licenseDisplayName": "Stakeholder",
41 "status": "active",
42 "statusMessage": "",
43 "assignmentSource": "unknown"
44 },
45 "lastAccessedDate": "2024-10-10T05:25:15.9431108Z",
46 "dateCreated": "2024-10-10T05:25:14.1314949Z",
47 "projectEntitlements": [],
48 "groupAssignments": []
49 },
50 ...
51 ]
52}