Repository Sync Guide

Azure DevOps repo to architecture diagram: spaces in the project name and all

By the engineer who builds Datadef, from client work on data platforms · Reviewed August 21, 2026

Azure DevOps repositories carry a small tax that other hosts do not: organization, project, and repository are three separate names, any of them may contain spaces, and the browser URL puts a _git segment in the middle. Tools that were written for owner/repo tend to fail here in confusing ways. This is what a connection has to handle, and what happens once it does.

7 min readFor platform teams whose code lives in Azure Repos rather than on GitHub

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

170/20003 credits left
Try:

No account needed · Editable canvas, not a picture

Three names, one reference

An Azure DevOps repository is addressed by organization, project, and repository. All three go into the stored reference, and any of them may legally contain interior spaces, which is why a project called BU - AMP is completely normal and completely fatal to a tool that assumed two path segments.

What the connection accepts is deliberately broad. The canonical org/project/repo form works. So does the browser URL with its _git segment, which is dropped. Percent-encoded segments are decoded, so BU%20-%20AMP becomes BU - AMP rather than a literal with percent signs in it. Legacy organization.visualstudio.com URLs are recognized, with the organization recovered from the subdomain and a leading DefaultCollection removed. A trailing .git and a version query string are stripped.

Validation is stricter than the paste rules, and on purpose. Interior spaces pass for Azure DevOps because the platform allows them, while tabs, newlines, control characters, traversal sequences, and anything that looks like a different host are rejected before a request is ever made.

All of these normalize to: Contoso/BU - AMP/platform-infra

  Contoso/BU - AMP/platform-infra
  https://dev.azure.com/Contoso/BU%20-%20AMP/_git/platform-infra
  dev.azure.com/Contoso/BU - AMP/_git/platform-infra
  https://Contoso.visualstudio.com/DefaultCollection/BU - AMP/_git/platform-infra
  https://dev.azure.com/Contoso/BU%20-%20AMP/_git/platform-infra?version=GBmain

The token, and the sign-in page that pretends to be data

Private repositories take a personal access token with the Code scope set to Read, the one Microsoft documents as vso.code: read source code and metadata about commits, branches, and other version control artifacts. It is sent as basic authentication with an empty username, which is the shape Azure DevOps expects, and it is validated by listing refs before anything is stored. Storage is encrypted, and only the last four characters are ever shown again.

Azure DevOps has an authentication behavior worth calling out, because it is where integrations usually break in a way nobody can debug. An unauthenticated request for a private resource does not return 401. It returns a 203 with an HTML sign-in page, or a redirect to the Microsoft sign-in host. A naive client parses that as JSON and reports a syntax error, which sends people hunting for the wrong problem.

Datadef classifies both shapes as what they are: the token is missing, expired, or issued for a different organization. A 401 or 403 means the token was presented and refused, so check the Code Read scope. A 404 means the organization, project, or repository name does not resolve for this token, so check the three names and their casing.

Refs are matched exactly

The Azure DevOps refs API filters by prefix, so asking for heads/main also matches a branch named main-old. The sync pins the exact name before resolving a commit, which is why a diagram tracking main never quietly follows a similarly named branch.

Every call the sync makes, and its API version

Four read endpoints, all pinned to api-version=7.1, which is the whole surface a security review has to look at. Refs are listed with a filter of heads or tags, tags with peelTags=true so an annotated tag resolves to the commit it wraps, and the listing follows the x-ms-continuationtoken response header for up to three pages of results.

The tree comes from items with recursionLevel=Full and a version descriptor pinned to the resolved commit id, capped at 20,000 entries. File content comes from the same items endpoint with includeContent=true and the same commit pin, so every file in one corpus is read at one commit rather than drifting across a push that lands mid-sync.

Each request has a 15 second timeout and a ceiling on the response body it will read. Nothing in that list writes: no pull request is opened, no status is posted, no pipeline is queued, and no work item is touched.

GET /refs?filter=heads&api-version=7.1
GET /refs?filter=tags&peelTags=true&api-version=7.1
GET /items?recursionLevel=Full&versionDescriptor.version=<sha>&versionDescriptor.versionType=commit
GET /items?path=/<file>&includeContent=true&versionDescriptor.version=<sha>

What gets drawn from an Azure Repos project

The same reading pipeline as every other host: infrastructure as code first, then containers and orchestration, then data files, then API specs, then CI definitions, then dependency manifests, then prose. Up to 40 files and 250KB, with a count of every directory three levels deep so the shape of the untouched parts is still visible. The CI rule matches azure-pipelines.yml and its suffixed siblings, so azure-pipelines-infra.yml and azure-pipelines-deploy.yml are read too, up to 8 pipeline files.

The items API reports a size per entry, which Azure DevOps shares with GitHub and GitLab does not. That is worth one sentence because it changes the reading: anything over 400KB is dropped from the candidate list before a single byte is fetched, rather than being fetched and then discarded.

Terraform repositories take a dedicated path instead. Every .tf and .tfvars file is parsed, modules become zones, registry module calls keep their source and version, and per-environment counts come from the tfvars rather than from three cloned diagrams. Nothing is initialized, no state is read, and no Azure credentials are involved. The detail is in Azure Terraform diagram generator.

The output is a canvas plus an architecture.md, both regenerated on the daily sync and on demand. The diagram embeds in an Azure DevOps wiki page as a live image, which is covered in embed diagrams in an Azure DevOps wiki.

FAQ

Does an Azure DevOps project name with spaces work?

Yes. Organization, project, and repository names may contain interior spaces, and the reference keeps them. Percent-encoded pastes are decoded, so a URL containing BU%20-%20AMP is stored as BU - AMP. Tabs, newlines, and control characters are rejected, but plain spaces are allowed for this provider specifically.

Which personal access token scope is required?

Code set to Read. That covers listing refs, listing the repository tree, and reading file contents, which is everything the sync calls. The token is sent as basic authentication with an empty username, validated by listing refs before storage, and never used against a write endpoint.

Why do Azure DevOps integrations often fail with a parsing error?

Because Azure DevOps answers an unauthenticated request for a private resource with a 203 and an HTML sign-in page, or with a redirect to the Microsoft sign-in host, instead of a 401. A client that expects JSON reports a syntax error. Datadef treats both responses as an authentication failure and says so.

Can I paste the URL from my browser address bar?

Yes. The _git segment is removed, a trailing .git is stripped, a version query string such as ?version=GBmain is dropped, percent-encoded segments are decoded, and legacy organization.visualstudio.com URLs are converted, with the organization recovered from the subdomain and a leading DefaultCollection segment dropped. All of those forms normalize to the same stored reference of organization, project, and repository.

Does it work with Azure DevOps Server hosted on our own network?

No. The supported hosts are the cloud ones: dev.azure.com, github.com, and gitlab.com. On-premises Azure DevOps Server is not connectable, and the connection flow accepts no custom host, so a server hostname is rejected before any request is made. The alternative for an on-premises org is the MCP server, where a coding agent reads the checkout on your own machine and draws from it.