> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Azure DevOps

> Client-mode REST API connector for Azure DevOps Services: create and update work items from plant alarms with JSON Patch, comment, query with WIQL, start YAML pipeline runs and queue builds for edge firmware and gateway deployments, poll runs, timelines, logs and artifacts, and register service hooks to a Coreflux server connector

Technical Preview

## Overview

A client-mode connector for the [Azure DevOps Services REST API 7.1](https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-7.1) that connects the plant floor to the engineering backlog and to edge CI/CD. A typical flow creates a Bug or Issue when the broker sees a recurring alarm, adds the readings as a comment, closes the item when the machine recovers, and finds open items with WIQL. On the delivery side it starts a YAML pipeline run or queues a build (for example an edge firmware build or a gateway configuration deployment), polls the run, reads its timeline, logs and artifacts, and cancels or tags builds. Service hook subscriptions send work item, build and pipeline events back to a Coreflux server connector.

One connector carries 43 events (32 GET, 8 POST, 2 PATCH, 1 PUT; 9 writes, no DELETE). Topics use `devops/azure-devops/<object>/<verb>`; each response is published on `<source topic>/response`. `WiqlQuery` and `WorkItemsBatchGet` are read-only POSTs.

> [!NOTE]
>
> The template was checked against Microsoft's [Azure DevOps REST OpenAPI specs](https://github.com/MicrosoftDocs/vsts-rest-api-specs) (api-version 7.1) and a local mock. Against the real `dev.azure.com`, 21 events were called anonymously on the public project `dnceng-public/public` through the connector's own endpoint templates. 17 returned data (`ConnectionData`, project, work item types, states, fields, area paths, batch get, pipelines, runs, builds, timelines, logs, linked work items, definitions). 4 returned the service's own `404` for work items, comments and saved queries that do not exist. Without credentials, 42 of 43 events showed that Azure DevOps connectors their path. A bad PAT got `401` (TF400813) on a private organization, and every event got `404` on an unknown organization. Nothing has run with a real credential yet, so status stays blocked until an authenticated read and a work item write pass.

## Setup

1. Create an **organization-scoped** personal access token (User settings > Personal access tokens) with only the scopes you need: **Work Items (Read & write)**, **Build (Read & execute)**, **Project and Team (Read)**, and for service hook subscriptions the scopes of the events you subscribe to. Global PATs stop working on December 1, 2026. For unattended use, prefer a Microsoft Entra service principal or managed identity added to the organization as a user.
2. Build the header value once and keep it in a broker secret (for example `AZURE_DEVOPS_AUTHORIZATION`). Inject it in a LoT action with `GET SECRET`. Never retain topics that carry it.
3. Read the project once with `WorkItemTypesList`, `FieldsList` and `ClassificationNodesList` to get the work item types, field reference names and area paths you will write.

## Authentication

Every event sends the payload field `authorization` unchanged as the `Authorization` header:

| Credential | `authorization` value |
| - | - |
| Personal access token | `Basic ` + base64 of `:` + PAT (empty user name) |
| Microsoft Entra ID (service principal, managed identity, user) | `Bearer ` + access token for the Azure DevOps resource `499b84ac-1321-427f-aa17-267ca6975798` (URI `https://app.vssps.visualstudio.com`), scope `.../.default` |

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "Basic OnlvdXItcGF0LWhlcmU=", "organization": "contoso", "project": "Plant%20Ops"}
```

Every event also sends `X-TFS-FedAuthRedirect: Suppress`. Without it, a missing, bad or expired credential gets a `302` redirect to the sign-in page (`203` HTML if the client follows it) instead of a `401` JSON error.

> [!WARNING]
>
> On an organization with **public** projects, Azure DevOps treats a bad credential as anonymous: public reads still answer `200` and only private data or writes answer `401`. If reads must come from a real identity, call `ConnectionData` first and check that `authenticatedUser` is not `Anonymous`.

## Organization, project and api-version

The base address is `https://dev.azure.com`; each endpoint starts with `/{organization}`, and project-scoped events continue with `/{project}`. Put both in the payload (URL-encode project names with spaces, for example `Plant%20Ops`). Every endpoint pins its `api-version`: `7.1` (the latest GA version), `7.1-preview.4` for work item comments and `7.1-preview.1` for `ConnectionData`. Preview resources answer `400` to plain `7.1`.

Azure DevOps Server (on-premises) is not covered by this template. It uses `https://<server>/<collection>/` and older API versions.

## Query parameters and bodies

Optional query parameters go in `query`, URL-encoded and **starting with `&`**. The connector appends `query` directly after the pinned `api-version`, for example `&statusFilter=inProgress&$top=20`. Leave `query` empty for defaults. Do not send a lone `&`: an empty parameter makes some endpoints fail (`build/builds` answers `400`, `build/definitions` answers `500`). `WorkItemsList` and `PipelineRunArtifactGet` need their query (`&ids=...`, `&artifactName=...`).

Writes send `body`. Work item create and update take a **JSON Patch** array and are sent as `application/json-patch+json`; everything else is `application/json`.

## Examples

Create a Bug when an alarm repeats (`devops/azure-devops/work-items/create`, `type` = `Bug`; use `&validateOnly=true` in `query` for a dry run):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "<secret>", "organization": "contoso", "project": "Plant%20Ops", "type": "Bug", "query": "", "body": [{"op": "add", "path": "/fields/System.Title", "value": "Press 7 over-temperature"}, {"op": "add", "path": "/fields/System.AreaPath", "value": "Plant Ops\\Line 2"}, {"op": "add", "path": "/fields/Microsoft.VSTS.TCM.ReproSteps", "value": "94.2 C at 14:02, limit 85 C"}, {"op": "add", "path": "/fields/System.Tags", "value": "coreflux; line-2"}]}
```

The response holds the new work item `id` and `rev`. Keep the id to update it later.

Close it when the press recovers, with a link to the trend panel (`devops/azure-devops/work-items/update`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "<secret>", "organization": "contoso", "project": "Plant%20Ops", "id": "4711", "query": "", "body": [{"op": "add", "path": "/fields/System.State", "value": "Done"}, {"op": "add", "path": "/fields/System.History", "value": "Back to 71 C at 15:10"}, {"op": "add", "path": "/relations/-", "value": {"rel": "Hyperlink", "url": "https://broker.example.com/panels/press7"}}]}
```

Find open items raised by the broker (`devops/azure-devops/wiql/query`, then `devops/azure-devops/work-items/batch-get` with the returned ids):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "<secret>", "organization": "contoso", "project": "Plant%20Ops", "query": "&$top=50", "body": {"query": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project AND [System.Tags] CONTAINS 'coreflux' AND [System.State] <> 'Done' ORDER BY [System.ChangedDate] DESC"}}
```

Start an edge deployment pipeline (`devops/azure-devops/pipelines/runs/run`), then poll `devops/azure-devops/pipelines/runs/get` with the returned run id:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "<secret>", "organization": "contoso", "project": "Plant%20Ops", "pipeline": "12", "query": "", "body": {"resources": {"repositories": {"self": {"refName": "refs/heads/main"}}}, "templateParameters": {"line": "2", "target": "edge-gw-07"}}}
```

List running builds (`devops/azure-devops/builds/list`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "<secret>", "organization": "contoso", "project": "Plant%20Ops", "query": "&statusFilter=inProgress&$top=20"}
```

Send failed builds to a Coreflux server connector (`devops/azure-devops/hooks/subscriptions/create`; organization-level, the project goes in `publisherInputs.projectId`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"authorization": "<secret>", "organization": "contoso", "body": {"publisherId": "tfs", "eventType": "build.complete", "resourceVersion": "1.0", "consumerId": "webHooks", "consumerActionId": "httpRequest", "publisherInputs": {"projectId": "<project guid>", "buildStatus": "failed"}, "consumerInputs": {"url": "https://broker.example.com/azure-devops/events", "httpHeaders": "X-Coreflux-Key:<shared secret>", "resourceDetailsToSend": "minimal"}}}
```

> [!WARNING]
>
> Service hooks post to a public **HTTPS** URL; they cannot target localhost or private address ranges. The receiving side is a separate `ENABLE_SERVER` REST\_API connector, which is not part of this template. Protect it with `basicAuthCredentials` or a shared header in `httpHeaders` (header values are visible to anyone who can read the subscription). Other useful events: `workitem.created`, `workitem.updated` (publisher `tfs`) and `ms.vss-pipelines.run-state-changed-event` (publisher `pipelines`). List them with `HookEventTypesList`. Check deliveries with `HookNotificationsList`.

## Events

| Event | Method | Endpoint | Source topic | Write | Notes |
| - | - | - | - | - | - |
| `ConnectionData` | `GET` | `/{organization}/_apis/connectionData?api-version=7.1-preview.1` | `devops/azure-devops/connection-data/get` | | Smoke: who the token is (authenticatedUser id / displayName) and the organization instance id. Location service, not in the published specs; needs a -preview api-version (7.1 alone answers 400). Public organizations answer it anonymously. |
| `ProjectsList` | `GET` | `/{organization}/_apis/projects?api-version=7.1{query}` | `devops/azure-devops/projects/list` | | Projects the token can see. query e.g. \&stateFilter=wellFormed&\$top=100 (or empty; page with continuationToken). |
| `ProjectGet` | `GET` | `/{organization}/_apis/projects/{project}?api-version=7.1{query}` | `devops/azure-devops/projects/get` | | One project by name or id. query e.g. \&includeCapabilities=true (process template, source control) or empty. |
| `TeamsList` | `GET` | `/{organization}/_apis/projects/{project}/teams?api-version=7.1{query}` | `devops/azure-devops/teams/list` | | Teams in the project (area-path owners for routing work items). query e.g. &\$top=100 (or empty). |
| `WorkItemCreate` | `POST` | `/{organization}/{project}/_apis/wit/workitems/${type}?api-version=7.1{query}` | `devops/azure-devops/work-items/create` | Yes | Catalog 'POST wit/workitems/$Bug'. Create a Bug / Issue / Task from an alarm with a JSON Patch document (Content-Type application/json-patch+json). type: Bug, Task, Issue, User%20Story, ... (URL-encoded, the $ is in the template). body: \[\{"op":"add","path":"/fields/System.Title","value":"Press 7 over-temperature"},\{"op":"add","path":"/fields/System.AreaPath","value":"Plant\Line 2"},\{"op":"add","path":"/fields/Microsoft.VSTS.TCM.ReproSteps","value":"94.2 C at 14:02"},\{"op":"add","path":"/fields/System.Tags","value":"coreflux; line-2"}]. query e.g. \&validateOnly=true for a dry run, \&suppressNotifications=true, &\$expand=fields (or empty). |
| `WorkItemGet` | `GET` | `/{organization}/{project}/_apis/wit/workitems/{id}?api-version=7.1{query}` | `devops/azure-devops/work-items/get` | | One work item. query e.g. \&fields=System.State,System.AssignedTo or &$expand=relations (or empty; fields and $expand cannot be combined). |
| `WorkItemsList` | `GET` | `/{organization}/{project}/_apis/wit/workitems?api-version=7.1{query}` | `devops/azure-devops/work-items/list` | | Up to 200 work items by id. query (required): \&ids=101,102,103 plus e.g. \&fields=System.Title,System.State\&errorPolicy=omit. |
| `WorkItemsBatchGet` | `POST` | `/{organization}/{project}/_apis/wit/workitemsbatch?api-version=7.1` | `devops/azure-devops/work-items/batch-get` | | Read-only POST: up to 200 work items by id with chosen fields (use after WiqlQuery). body: \{"ids":\[101,102],"fields":\["System.Id","System.Title","System.State"],"errorPolicy":"omit"}. |
| `WorkItemUpdate` | `PATCH` | `/{organization}/{project}/_apis/wit/workitems/{id}?api-version=7.1{query}` | `devops/azure-devops/work-items/update` | Yes | Update fields, state, assignee, links with a JSON Patch document (application/json-patch+json). body: \[\{"op":"test","path":"/rev","value":3},\{"op":"add","path":"/fields/System.State","value":"Done"},\{"op":"add","path":"/fields/System.History","value":"Press 7 back to 71 C"},\{"op":"add","path":"/relations/-","value":\{"rel":"Hyperlink","url":"[https://broker.example.com/panels/press7](https://broker.example.com/panels/press7)"}}]. query e.g. \&suppressNotifications=true (or empty). |
| `WorkItemUpdatesList` | `GET` | `/{organization}/{project}/_apis/wit/workItems/{id}/updates?api-version=7.1{query}` | `devops/azure-devops/work-items/updates/list` | | Change history of a work item (who changed which field). query e.g. &$top=50&$skip=0 (or empty). |
| `WiqlQuery` | `POST` | `/{organization}/{project}/_apis/wit/wiql?api-version=7.1{query}` | `devops/azure-devops/wiql/query` | | Read-only POST: run a WIQL query, returns work item ids (then WorkItemsBatchGet). Team segment omitted (optional in the spec). body: \{"query":"SELECT \[System.Id] FROM WorkItems WHERE \[System.TeamProject] = @project AND \[System.Tags] CONTAINS 'coreflux' AND \[System.State] \<> 'Done' ORDER BY \[System.ChangedDate] DESC"}. query e.g. &\$top=50 (or empty). |
| `WiqlQueryById` | `GET` | `/{organization}/{project}/_apis/wit/wiql/{wiql_id}?api-version=7.1{query}` | `devops/azure-devops/wiql/run-saved` | | Run a saved query by its id (wiql\_id, from QueriesList). query e.g. &\$top=50 (or empty). |
| `WorkItemCommentAdd` | `POST` | `/{organization}/{project}/_apis/wit/workItems/{id}/comments?api-version=7.1-preview.4` | `devops/azure-devops/work-items/comments/add` | Yes | Add a discussion comment (preview resource: api-version 7.1-preview\.4; plain 7.1 answers 400). body: \{"text":"Bearing replaced, line restarted at 15:10"}. |
| `WorkItemCommentsList` | `GET` | `/{organization}/{project}/_apis/wit/workItems/{id}/comments?api-version=7.1-preview.4{query}` | `devops/azure-devops/work-items/comments/list` | | Comments on a work item (api-version 7.1-preview\.4). query e.g. &\$top=20\&order=desc (or \&ids=1,2 for specific comments; or empty). |
| `WorkItemTypesList` | `GET` | `/{organization}/{project}/_apis/wit/workitemtypes?api-version=7.1` | `devops/azure-devops/work-item-types/list` | | Work item types of the project's process (Bug, Task, Issue, ...) and their fields. |
| `WorkItemTypeStatesList` | `GET` | `/{organization}/{project}/_apis/wit/workitemtypes/{type}/states?api-version=7.1` | `devops/azure-devops/work-item-types/states/list` | | Valid states of one work item type (type e.g. Bug), to map machine states to work item states. |
| `FieldsList` | `GET` | `/{organization}/{project}/_apis/wit/fields?api-version=7.1{query}` | `devops/azure-devops/fields/list` | | Field reference names (System.Title, Microsoft.VSTS.Common.Priority, Custom.\*) for JSON Patch paths. query e.g. &\$expand=extensionFields (or empty). |
| `ClassificationNodesList` | `GET` | `/{organization}/{project}/_apis/wit/classificationnodes?api-version=7.1{query}` | `devops/azure-devops/classification-nodes/list` | | Area and iteration paths (e.g. Plant\Line 2) to connector work items per line or cell. query e.g. &\$depth=2. |
| `TagsList` | `GET` | `/{organization}/{project}/_apis/wit/tags?api-version=7.1` | `devops/azure-devops/tags/list` | | Work item tags defined in the project. |
| `QueriesList` | `GET` | `/{organization}/{project}/_apis/wit/queries?api-version=7.1{query}` | `devops/azure-devops/queries/list` | | Saved queries and folders (ids for WiqlQueryById). query e.g. &$depth=1&$expand=wiql (or empty). |
| `PipelinesList` | `GET` | `/{organization}/{project}/_apis/pipelines?api-version=7.1{query}` | `devops/azure-devops/pipelines/list` | | Pipelines in the project. query e.g. &\$top=50\&orderBy=name%20asc (or empty). |
| `PipelineGet` | `GET` | `/{organization}/{project}/_apis/pipelines/{pipeline}?api-version=7.1{query}` | `devops/azure-devops/pipelines/get` | | One pipeline (pipeline = id). query e.g. \&pipelineVersion=4 (or empty). |
| `PipelineRun` | `POST` | `/{organization}/{project}/_apis/pipelines/{pipeline}/runs?api-version=7.1{query}` | `devops/azure-devops/pipelines/runs/run` | Yes | Catalog 'POST pipelines/\{id}/runs'. Start a pipeline run, e.g. edge firmware build or deployment to a line gateway. body: \{"resources":\{"repositories":\{"self":\{"refName":"refs/heads/main"}}},"templateParameters":\{"line":"2","target":"edge-gw-07"},"variables":\{"TRIGGERED\_BY":\{"value":"coreflux","isSecret":false}}}; add "previewRun":true to only expand the YAML. query e.g. \&pipelineVersion=4 (or empty). |
| `PipelineRunsList` | `GET` | `/{organization}/{project}/_apis/pipelines/{pipeline}/runs?api-version=7.1` | `devops/azure-devops/pipelines/runs/list` | | Latest runs of a pipeline (up to 10000, newest first; no query parameters). |
| `PipelineRunGet` | `GET` | `/{organization}/{project}/_apis/pipelines/{pipeline}/runs/{run}?api-version=7.1` | `devops/azure-devops/pipelines/runs/get` | | One run: state (inProgress, completed) and result (succeeded, failed, canceled). Poll after PipelineRun. |
| `PipelineRunLogsList` | `GET` | `/{organization}/{project}/_apis/pipelines/{pipeline}/runs/{run}/logs?api-version=7.1{query}` | `devops/azure-devops/pipelines/runs/logs/list` | | Logs of a run. query e.g. &\$expand=signedContent (time-limited download URLs) or empty. |
| `PipelineRunArtifactGet` | `GET` | `/{organization}/{project}/_apis/pipelines/{pipeline}/runs/{run}/artifacts?api-version=7.1{query}` | `devops/azure-devops/pipelines/runs/artifacts/get` | | One published artifact of a run, e.g. a firmware image. query (required): \&artifactName=firmware&\$expand=signedContent. |
| `BuildsList` | `GET` | `/{organization}/{project}/_apis/build/builds?api-version=7.1{query}` | `devops/azure-devops/builds/list` | | Catalog 'GET build/builds?statusFilter='. Builds and pipeline runs. query e.g. \&statusFilter=inProgress or \&resultFilter=failed\&minTime=2026-10-05T06:00:00Z\&definitions=12&\$top=20\&queryOrder=finishTimeDescending. |
| `BuildQueue` | `POST` | `/{organization}/{project}/_apis/build/builds?api-version=7.1{query}` | `devops/azure-devops/builds/queue` | Yes | Queue a build of a definition (classic or YAML). body: \{"definition":\{"id":12},"sourceBranch":"refs/heads/main","templateParameters":\{"line":"2"},"parameters":"\{"system.debug":"false"}","tags":\["coreflux"]}. query e.g. \&ignoreWarnings=true (or empty). |
| `BuildGet` | `GET` | `/{organization}/{project}/_apis/build/builds/{build}?api-version=7.1{query}` | `devops/azure-devops/builds/get` | | One build: status, result, sourceVersion, timings. query e.g. \&propertyFilters=... (or empty). |
| `BuildUpdate` | `PATCH` | `/{organization}/{project}/_apis/build/builds/{build}?api-version=7.1{query}` | `devops/azure-devops/builds/update` | Yes | Cancel a running build: body \{"status":"cancelling"}. query: \&retry=true (with body \{}) re-runs the failed jobs of a finished build; or empty. (Keep-forever is a retention lease in 7.1, not a Build field; not packaged.) |
| `BuildTagsAdd` | `POST` | `/{organization}/{project}/_apis/build/builds/{build}/tags?api-version=7.1` | `devops/azure-devops/builds/tags/add` | Yes | Tag a build, e.g. with the line or gateway it was deployed to. body: \["line-2","deployed-edge-gw-07"]. |
| `BuildTimelineGet` | `GET` | `/{organization}/{project}/_apis/build/builds/{build}/timeline?api-version=7.1{query}` | `devops/azure-devops/builds/timeline/get` | | Stages, jobs and tasks of a build with state, result and error counts (timelineId omitted = the build's own timeline). query e.g. \&changeId=5 for incremental polling (or empty). |
| `BuildLogsList` | `GET` | `/{organization}/{project}/_apis/build/builds/{build}/logs?api-version=7.1` | `devops/azure-devops/builds/logs/list` | | Log ids and line counts of a build. |
| `BuildWorkItemsList` | `GET` | `/{organization}/{project}/_apis/build/builds/{build}/workitems?api-version=7.1{query}` | `devops/azure-devops/builds/work-items/list` | | Work items linked to a build (what a firmware release fixes). query e.g. &\$top=50 (or empty). |
| `BuildDefinitionsList` | `GET` | `/{organization}/{project}/_apis/build/definitions?api-version=7.1{query}` | `devops/azure-devops/builds/definitions/list` | | Build definitions (ids for BuildQueue / BuildsList). query e.g. \&name=edge-firmware\*\&includeLatestBuilds=true&\$top=50 (or empty). |
| `HookPublishersList` | `GET` | `/{organization}/_apis/hooks/publishers?api-version=7.1` | `devops/azure-devops/hooks/publishers/list` | | Event publishers (tfs = work items, builds, code; pipelines = YAML pipeline runs, stages, approvals). |
| `HookEventTypesList` | `GET` | `/{organization}/_apis/hooks/publishers/{publisher}/eventtypes?api-version=7.1` | `devops/azure-devops/hooks/event-types/list` | | Event types of one publisher (publisher e.g. tfs: workitem.created, workitem.updated, build.complete; pipelines: ms.vss-pipelines.run-state-changed-event) with their filter inputs. |
| `HookSubscriptionsList` | `GET` | `/{organization}/_apis/hooks/subscriptions?api-version=7.1{query}` | `devops/azure-devops/hooks/subscriptions/list` | | Service hook subscriptions. query e.g. \&consumerId=webHooks\&eventType=build.complete (or empty). |
| `HookSubscriptionCreate` | `POST` | `/{organization}/_apis/hooks/subscriptions?api-version=7.1` | `devops/azure-devops/hooks/subscriptions/create` | Yes | Send Azure DevOps events to a Coreflux server connector (Web Hooks consumer). body: \{"publisherId":"tfs","eventType":"build.complete","resourceVersion":"1.0","consumerId":"webHooks","consumerActionId":"httpRequest","publisherInputs":\{"projectId":"\<project guid>","buildStatus":"failed"},"consumerInputs":\{"url":"[https://broker.example.com/azure-devops/events","httpHeaders":"X-Coreflux-Key:\&lt;shared](https://broker.example.com/azure-devops/events","httpHeaders":"X-Coreflux-Key:\&lt;shared) secret>","resourceDetailsToSend":"minimal"}}. |
| `HookSubscriptionGet` | `GET` | `/{organization}/_apis/hooks/subscriptions/{subscription}?api-version=7.1` | `devops/azure-devops/hooks/subscriptions/get` | | One subscription (status enabled / onProbation / disabledBySystem ...). |
| `HookSubscriptionReplace` | `PUT` | `/{organization}/_apis/hooks/subscriptions/{subscription}?api-version=7.1` | `devops/azure-devops/hooks/subscriptions/replace` | Yes | Replace a subscription, e.g. re-enable it after the broker was offline (send the full subscription from HookSubscriptionGet with "status":"enabled"). |
| `HookNotificationsList` | `GET` | `/{organization}/_apis/hooks/subscriptions/{subscription}/notifications?api-version=7.1{query}` | `devops/azure-devops/hooks/notifications/list` | | Recent deliveries of a subscription with result and HTTP response (debug the broker endpoint). query e.g. \&maxResults=20\&result=failed (or empty). |

## Limits

Azure DevOps meters load in throughput units (TSTUs): 200 TSTUs per identity in any sliding five-minute window, and the same per pipeline. Above that, requests are delayed or rejected with `429`. Honor `Retry-After` and watch the `X-RateLimit-*` response headers. Batch reads with `WorkItemsBatchGet` (up to 200 ids) instead of one `WorkItemGet` per item, and poll run state at a sensible interval.

## Not packaged

* All DELETE endpoints (work items, comments, builds, subscriptions) on purpose. Close work items by state instead.
* Releases (classic release pipelines). They live on a different host (`vsrm.dev.azure.com`) and need a second connector.
* Work item attachment upload (binary), Git repositories and pull requests, test plans, artifacts feeds, wikis, approvals and checks, agent pools and service connections.
* Build retention (keep forever is a retention lease in 7.1, not a build field).
* The incoming service hook direction: a separate `ENABLE_SERVER` connector.
* Azure DevOps Server (on-premises) collections.

## Suggested branches

`feat/rest-api-route-azure-devops` for the broker template and `feat/docs-rest-api-azure-devops` for this page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.