Create and update Azure DevOps work items
Create and update Azure DevOps work items in one batched pipe: priority, state, tags, and sprint included.
Create and update Azure DevOps work items through the org-level batch endpoint. One fire carries many sub-requests: an entry with an id updates that work item, an entry without one creates a new item of the given type (Task by default). Each entry carries its own JSON Patch ops, so the same pipe sets priority, state, tags, and iteration path with no field-specific target. The org and project are fixed at install; the PAT is the secret, entered in the browser as an HTTP Basic credential. This pipe writes. Anyone who can post to its capture URL can change work items in your project, so endpoint signing is required posture for this recipe; set_endpoint_signing is the tool that turns it on, and get_endpoint answers whether it is already on. Because one fire is many sub-requests, each comes back with its own status code, and a 200 on the outer call can still hide a per-item failure: read the inner codes before reporting success. Two batch-level failures are worth knowing in advance. An entry naming a work item that is in the recycle bin fails the whole batch with TF237111, which reads like an area-path permission problem and is not, so suspect stale ids before you suspect the PAT. And every create in a batch needs its own unique negative synthetic id, which the transformation derives from each entry's position, so a rewrite that flattens those ids away earns VS403357 and loses every create after the first. This pipe does not delete work items, by design.
The credential never enters the model context: it lives in the FlurryPORT secret store, deliveries are signed server-side, and every send returns a receipt your agent can quote.
Install with your agent
npx flurryport mcp
Point your agent at the FlurryPORT MCP server (npx flurryport mcp) and ask it for the flurryport:azure-devops-manage recipe. Works from AI clients that can run a local process: desktop apps and terminal agents. Web-only chat clients cannot reach a local MCP server; open a desktop client instead.
Tools your agent gains
flry_azure_devops_manage- Create and update Azure DevOps work items in the configured org and project, many per fire. Input: items (required), each entry an object with ops (required JSON Patch array), id (present to update, absent to create), and type (create only, defaults to Task). Per-item results arrive on the replay execution, read them with get_replay_execution.
Setup walkthrough
AZURE_DEVOPS_PAT: In Azure DevOps: User settings, Personal access tokens, New Token, scope Work Items (Read & write). This recipe writes, so a read-only token will not do. The value to paste in the FlurryPORT secret page is the HTTP Basic credential, NOT the raw token: run `printf ':YOUR_PAT' | base64` and paste the result. The recipe sends it as `Authorization: Basic <value>`. A raw PAT answers 401, or 203 with a sign-in page.
Intent schema
{
"type": "object",
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"minItems": 1,
"description": "One entry per work item. Entries with an id update; entries without one create.",
"items": {
"type": "object",
"required": [
"ops"
],
"properties": {
"id": {
"type": "integer",
"description": "Work item id to update. Omit it to create a new work item instead."
},
"type": {
"type": "string",
"description": "Work item type for a create entry, such as Task, Bug, or User Story. Omit it and the entry creates a Task. Ignored on entries that carry an id."
},
"ops": {
"type": "array",
"minItems": 1,
"description": "JSON Patch operations against /fields/..., for example {\"op\": \"add\", \"path\": \"/fields/Microsoft.VSTS.Common.Priority\", \"value\": 3}.",
"items": {
"type": "object",
"required": [
"op",
"path"
],
"properties": {
"op": {
"type": "string"
},
"path": {
"type": "string"
},
"value": {}
}
}
}
}
}
}
}
}Install-time parameters
org- install-time: Azure DevOps organization.
project- install-time: Project within the organization. The batch URL is org-level, so this value is inlined into the transformation instead: creates address /PROJECT/_apis/wit/workitems/$Type, updates address the item directly.
Transformation
[$map($body.items, function($item, $i) {( { "method": "PATCH", "uri": ($exists($item.id) ? "/_apis/wit/workItems/" & $string($item.id) : "/" & $install.project & "/_apis/wit/workitems/$" & ($exists($item.type) ? $item.type : "Task")) & "?api-version=7.1", "headers": { "Content-Type": "application/json-patch+json" }, "body": ($exists($item.id) ? [$item.ops] : $append([{ "op": "add", "path": "/id", "value": -($i + 1) }], [$item.ops])) } )})]Delivery target
POST https://dev.azure.com/$install.org/_apis/wit/$batch?api-version=7.1
Placeholders like $secrets.NAME resolve server-side at delivery, never in the agent.
Gotchas
- This pipe writes, so anyone who can post to its capture URL can change work items. Endpoint signing is required posture here: set_endpoint_signing turns it on, get_endpoint says whether it already is.
- The dollar sign in $batch is part of the Azure DevOps path, not a placeholder, and stays exactly as written. Doubling a dollar is only correct where a literal dollar sits immediately before an install placeholder, the way the sibling azure-devops-workitem writes the work item type into its URL. There is no placeholder here, so there is nothing to double.
- $batch accepts item-family write uris only. A wiql query or a collection read sent through it answers 404 Unsupported Batch operation, which is why azure-devops-query and azure-devops-read are separate pipes.
- Sub-responses come back per item with their own status codes. A 200 on the outer call with a non-200 inner code is a per-item failure, so read the inner codes before reporting success.
- An update entry naming a deleted work item fails the WHOLE batch with a top-level 404 and the message TF237111, which reads like an area-path permission problem but is not: the item is in the recycle bin. Verify ids are live (azure-devops-read) before a big batch, and suspect stale ids before suspecting the PAT (diagnosed live 2026-08-01: three fires refused over one recycled id).
- Creates need the project segment in the sub-request uri; updates do not. The transformation branches on whether an entry carries an id, so send one items array and let it sort them.
- An iteration path has to exist before an item can be moved into it. Creating sprints is a Boards UI action and is not in this pipe.
- The outer request is application/json; each sub-request carries its own application/json-patch+json content type. Both are already in the recipe.
- Every create entry in one batch needs its own unique negative synthetic id. The transformation derives each id from the entry's position in the batch (-1 for the first entry, -2 for the second, and so on; positions held by update entries leave gaps, which Azure DevOps accepts). A batch that reuses one synthetic id is rejected with VS403357 and every create after the first fails, so do not flatten the transformation's indexed ids away.
- The Accept-Encoding identity header in the target template is load-bearing: without it Azure DevOps gzips responses and the stored response preview is unreadable binary. With it, stored responses are plain JSON (proven live on prod 2026-08-03).
- Bind this pipe's transformation with the predicate $exists($body.items). When two or more azure-devops pipes share one capture endpoint, every binding must carry its routing predicate at bind time, and the family's predicates are mutually exclusive by design: azure-devops-workitem routes on $exists($body.title), azure-devops-read on $exists($body.ids), azure-devops-query on $exists($body.wiql), azure-devops-manage on $exists($body.items). An unpredicated binding fires on every intent that reaches the endpoint (live repro 2026-08-03: a workitem binding bound without its predicate fired on every read intent).
- Safest test fire: a single update entry that sets a field to the value it already holds, for example one items entry with a known live id whose only op re-adds the current System.Title. It proves auth, routing, and the batch grammar without changing anything.
- This pipe does not delete work items, by design.
Where this fits
- Destination: Azure DevOps
- Part of the Azure DevOps work items suite
- Project tracking recipes
- What agent tokens can and cannot do
- All recipes
Related recipes
Query Azure DevOps work items with WIQL and read the matching ids back, without your AI ever holding the PAT.
Read fields for a batch of Azure DevOps work items by id: title, state, tags, priority, iteration.
Let your AI file Azure DevOps work items (bugs, tasks) the model never holds credentials for.
Let your AI file GitHub issues with a fine-grained PAT it never touches.
Search GitHub issues and read whole threads back, without your AI ever holding the PAT.
Update, close and reopen GitHub issues in batches, with the PAT held server-side.