Azure DevOps keeps deleted repositories in a Recycle Bin. That’s useful when a repo was deleted by accident, but if you want to permanently remove 50 of them, there’s no bulk action in the UI. You delete them one by one. I wrote a Python script that does it in bulk through the Azure DevOps REST API.

The script is on GitHub. It’s the same idea as the TLS audit script: if the portal makes you repeat the same click many times, use the API.

What the script does

It reads a JSON file with the deleted repositories and calls the delete endpoint for each one. It asks for all configuration interactively, so nothing is hardcoded, and prints a summary of successful and failed deletions at the end.

How it works

The script uses the git/recycleBin endpoint. The delete function:

def delete_repository(base_url, organization, project, repository_id, api_version, headers, auth):
"""
Permanently delete a repository from the Azure DevOps Recycle Bin.
"""
# Construct the URL
url = f"{base_url}/{organization}/{project}/_apis/git/recycleBin/repositories/{repository_id}?api-version={api_version}"
try:
response = requests.delete(url, headers=headers, auth=auth)
if response.status_code == 200:
print(f"✅ Successfully deleted repository ID: {repository_id}")
return True
else:
print(f"❌ Failed to delete repository ID: {repository_id}. Status Code: {response.status_code}. Response: {response.text}")
return False
except Exception as e:
print(f"⚠️ Error deleting repository ID: {repository_id}. Error: {str(e)}")
return False

A successful delete returns 200. For anything else, the script logs the status code and response body.

Authentication is Basic Auth with a Personal Access Token (PAT) as the password:

headers = {
'Content-Type': 'application/json'
}
auth = ('', pat) # Empty username, PAT as password

The repository list comes from a JSON file:

def load_json(file_path):
"""
Load JSON data from a file.
"""
with open(file_path, 'r') as f:
return json.load(f)

It expects a value array with objects that have at least an id:

{
"count": 2,
"value": [
{
"id": "12345678-1234-1234-1234-123456789012",
"name": "old-repo-1"
},
{
"id": "87654321-4321-4321-4321-210987654321",
"name": "old-repo-2"
}
]
}

Running the script

The script prompts for its inputs:

base_url = input("🔗 Enter Azure DevOps Base URL (e.g., https://dev.azure.com): ").strip()
organization = input("🏢 Enter your Organization Name: ").strip()
project = input("📁 Enter your Project Name: ").strip()
api_version = input("🔍 Enter API Version (e.g., 7.1): ").strip()
json_file_path = input("📄 Enter the path to your JSON file (e.g., deleted_repos.json): ").strip()
print("\n🔐 Enter your Azure DevOps Personal Access Token (PAT).")
print(" Note: Your PAT will not be displayed as you type.")
pat = getpass("PAT: ").strip()

The PAT is read with getpass, so it isn’t shown on screen.

Then it loops over the repositories. Entries without an id are skipped and counted as failures:

for repo in repositories:
repo_id = repo.get('id')
repo_name = repo.get('name')
if not repo_id:
print(f"⚠️ Repository name '{repo_name}' does not have an 'id'. Skipping.")
failure_count += 1
continue
success = delete_repository(base_url, organization, project, repo_id, api_version, headers=headers, auth=auth)
if success:
success_count += 1
else:
failure_count += 1

The summary at the end:

📋 Deletion Process Completed.
✅ Successfully deleted: 48
❌ Failed to delete: 2

Getting the repository list

Query the Recycle Bin with the REST API and save the response to a file:

Terminal window
GET https://dev.azure.com/{organization}/{project}/_apis/git/recycleBin/repositories?api-version=7.1

If you already have the repository IDs from somewhere else, you can also write the JSON file by hand using the structure above.

Get the script

Terminal window
git clone https://github.com/Timohone/azure-devops-repo-recycle-bin-cleaner
cd azure-devops-repo-recycle-bin-cleaner
pip install requests
python delete_repositories.py

The README explains how to create the PAT and prepare the JSON file.

The code is short and easy to adapt, for example to change the authentication method or add filtering. If you use Azure DevOps pipelines for infrastructure, the EPAC pipeline patterns post may also be useful.


Sources

  1. “Azure DevOps REST API reference,” Microsoft Learn, https://learn.microsoft.com/en-us/rest/api/azure/devops/
  2. “Repositories - Delete,” Azure DevOps REST API, Microsoft Learn, https://learn.microsoft.com/en-us/rest/api/azure/devops/git/repositories/delete
  3. “Use personal access tokens to authenticate,” Microsoft Learn, https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate