vcpkg/.github/skills/analyze-ci-failures/references/azure-devops-api.md

8.5 KiB

Azure DevOps REST API Reference for vcpkg CI

Base URL

https://dev.azure.com/vcpkg/public/_apis

All endpoints below are appended to this base. The vcpkg/public project is publicly readable — no Authorization header is required.

API Version

Always append ?api-version=7.0 (or include it in query strings).


Build Endpoints

Get Build Details

GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}?api-version=7.0

Response fields:

{
  "id": 129315,
  "buildNumber": "20240315.1",
  "status": "completed",
  "result": "failed",
  "reason": "pullRequest",
  "sourceBranch": "refs/pull/12345/merge",
  "sourceVersion": "abc123def456...",
  "requestedFor": { "displayName": "Jane Developer" },
  "startTime": "2024-03-15T10:00:00Z",
  "finishTime": "2024-03-15T14:30:00Z",
  "definition": { "name": "vcpkg.CI" },
  "_links": {
    "web": { "href": "https://dev.azure.com/vcpkg/public/_build/results?buildId=129315" }
  }
}

result values: succeeded | failed | partiallySucceeded | canceled reason values: pullRequest | schedule | manual | individualCI | batchedCI


Get Build Timeline

GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/timeline?api-version=7.0

Returns all stages, jobs, tasks and their results.

Response structure:

{
  "records": [
    {
      "id": "guid",
      "parentId": "parent-guid-or-null",
      "type": "Job",
      "name": "x64_windows",
      "displayName": "x64_windows",
      "state": "completed",
      "result": "failed",
      "startTime": "2024-03-15T10:05:00Z",
      "finishTime": "2024-03-15T12:15:00Z",
      "log": {
        "id": 42,
        "type": "Container",
        "url": "https://dev.azure.com/vcpkg/public/_apis/build/builds/129315/logs/42"
      },
      "errorCount": 1,
      "warningCount": 0
    }
  ]
}

type values: Stage | Phase | Job | Task result values: succeeded | failed | skipped | canceled | succeededWithIssues

To find failed jobs:

failed_jobs = [r for r in records if r['type'] == 'Job' and r['result'] == 'failed']

Job names map to triplets (underscores in job names, hyphens in triplets):

  • x64_windows → x64-windows
  • x64_windows_static → x64-windows-static
  • arm64_osx → arm64-osx
  • x64_linux → x64-linux
  • arm_neon_android → arm-neon-android

Get a Specific Log

GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/logs/{logId}?api-version=7.0

Returns plain text of the log. Use this to read the "*** Test Modified Ports" task log, which contains REGRESSION: lines for all failures including those without artifact logs (e.g., FILE_CONFLICTS).

Finding the right log ID: Look up the task record in the timeline response — the log.id field on the "*** Test Modified Ports" task record gives the log ID. Alternatively, use log.url directly.

Scanning for REGRESSION lines:

$logText = Invoke-RestMethod "https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/logs/{logId}?api-version=7.0"
$logText -split "`n" | Where-Object { $_ -match '^REGRESSION:' }

Example output:

REGRESSION: kf6i18n:x64-windows failed with FILE_CONFLICTS. If expected, add kf6i18n:x64-windows=fail to .../ci.baseline.txt.
REGRESSION: kf6itemmodels:x64-windows failed with FILE_CONFLICTS. If expected, add kf6itemmodels:x64-windows=fail to .../ci.baseline.txt.
REGRESSION: v8:x64-windows failed with BUILD_FAILED. If expected, add v8:x64-windows=fail to .../ci.baseline.txt.

⚠️ FILE_CONFLICTS failures do NOT produce failure log artifacts. The only record of them is in this step log. Always scan all "Test Modified Ports" task logs before relying on artifacts alone.

List All Logs

GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/logs?api-version=7.0

Artifact Endpoints

List All Artifacts

GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/artifacts?api-version=7.0

Response:

{
  "count": 39,
  "value": [
    {
      "id": 1107682,
      "name": "failure logs for x64-windows",
      "source": "7922e5c4-...",
      "resource": {
        "type": "PipelineArtifact",
        "data": "DCCD3D9C96CE79A758B255BD87397A07371B8394EE52F6BFF35346C144D63F3501",
        "properties": {
          "RootId": "196FDE3B...",
          "artifactsize": "8586850",
          "HashType": "DEDUP1024K",
          "DomainId": "0"
        },
        "url": "https://dev.azure.com/vcpkg/.../_apis/build/builds/129315/artifacts?artifactName=failure%20logs%20for%20x64-windows&api-version=7.0",
        "downloadUrl": "https://artprodwus21.artifacts.visualstudio.com/.../_apis/artifact/{base64hash}/content?format=zip"
      }
    }
  ]
}

⚠️ resource.type is "PipelineArtifact", not "Container". The Container listing API (GET /_apis/resources/Containers/{id}) does not work for these artifacts. Use resource.downloadUrl directly to download the ZIP.

Artifact naming conventions:

Artifact Name Pattern Contents Priority
failure logs for {triplet} Per-port build failure log directories High — primary source
file lists for {triplet} Installed file manifests Low — skip unless needed
format.diff Formatting/manifest diff Medium — indicates PR format issues
z azcopy logs for {triplet} Binary cache transfer logs Low — infrastructure only

Download Artifact ZIP (Confirmed Working)

Use the downloadUrl from the artifact listing. It resolves to an artprodwus21.artifacts.visualstudio.com URL. web_fetch cannot download binary ZIPs — use PowerShell:

# $downloadUrl = value from resource.downloadUrl in the artifacts list response
Invoke-WebRequest -Uri $downloadUrl -OutFile "$tmpDir\{triplet}.zip" -UseBasicParsing
Expand-Archive "$tmpDir\{triplet}.zip" -DestinationPath "$tmpDir\{triplet}" -Force

# List failing ports — each is a subdirectory inside the artifact folder
$root = "$tmpDir\{triplet}\failure logs for {triplet}"
Get-ChildItem $root -Directory | Select-Object -ExpandProperty Name

The ZIP always contains one top-level folder named exactly "failure logs for {triplet}". Inside that folder, each failing port has its own subdirectory named after the port.

❌ The Container API (GET /_apis/resources/Containers/{containerId}) does not work for PipelineArtifact type. Do not attempt to use it.


Test Results Endpoints

List Test Runs for a Build

GET https://dev.azure.com/{org}/{project}/_apis/test/runs?buildId={buildId}&api-version=7.0

Response:

{
  "value": [
    {
      "id": 9876,
      "name": "x64-windows",
      "state": "Completed",
      "totalTests": 500,
      "passedTests": 497,
      "failedTests": 3
    }
  ]
}

Get Test Run Results

GET https://dev.azure.com/{org}/{project}/_apis/test/runs/{runId}/results?api-version=7.0&outcomes=Failed

Filter by outcomes=Failed to get only failures.

Response includes:

  • testCaseName — usually portname:triplet
  • outcome — Failed
  • errorMessage — short error description
  • stackTrace — full failure output

URL Parsing

Given https://dev.azure.com/vcpkg/public/_build/results?buildId=129315&view=results:

Component Value How to Extract
Organization vcpkg 4th path segment
Project public 5th path segment
Build ID 129315 buildId query parameter
# Python example
from urllib.parse import urlparse, parse_qs
url = "https://dev.azure.com/vcpkg/public/_build/results?buildId=129315&view=results"
parts = urlparse(url)
segments = parts.path.strip('/').split('/')
org = segments[0]       # "vcpkg"
project = segments[1]   # "public"
build_id = parse_qs(parts.query)['buildId'][0]  # "129315"

Rate Limits and Auth

  • Public projects (like vcpkg/public): No authentication needed; anonymous access is allowed
  • Rate limit: ~200 requests per minute for anonymous; increase by adding a PAT as Bearer token if needed
  • PAT header: Authorization: Basic {base64(":pat_token")}