CLI for interacting with Azure DevOps via REST API, without depending on the az CLI.
- .NET 10 Runtime
- Authentication via either a Microsoft Entra ID account or an Azure DevOps Personal Access Token (see Authentication below)
Install as a .NET Global Tool from NuGet:
dotnet tool install -g azure-devops-cliUpdate or uninstall:
dotnet tool update -g azure-devops-cli
dotnet tool uninstall -g azure-devops-cliThe command is invoked as devops.
dotnet publish DevOps/DevOps.csproj -c Release -o ./publishAdd the publish directory to your PATH, or copy the executable to a directory already on your PATH.
Pick one authentication method:
# Option A — Microsoft Entra ID (interactive browser sign-in)
devops config -o https://dev.azure.com/myorg
devops config --login
# Option B — Personal Access Token
devops config -o https://dev.azure.com/myorg -P <PAT>Then the shared settings:
devops config -p MyProject # set default project
devops config -T "MyProject Team" # set default team (for iteration and area resolution)
devops config -e your@email.com # set email manually if auto-detection fails
devops config --show # display current configuration
devops config --logout # sign out of Entra ID (clear the cached token)
devops config --reset # remove all configuration and sign out
devops config --refresh-cache # force re-fetch of iteration and area on next createWhen you sign in (--login) or provide a --pat, the CLI automatically fetches your display name and email from Azure DevOps. The email is used to resolve --assigned-to me.
| Option | Alias | Description |
|---|---|---|
--org |
-o |
Azure DevOps organization URL |
--login |
Sign in interactively with Microsoft Entra ID | |
--logout |
Sign out and clear the cached Entra ID token | |
--tenant |
Entra ID tenant ID or domain to sign in against (defaults to your home tenant) | |
--pat |
-P |
Personal Access Token |
--project |
-p |
Default project (used when --project is omitted from other commands) |
--team |
-T |
Default team for resolving the active iteration and area path (defaults to {Project} Team) |
--email |
-e |
Your email address, used to resolve --assigned-to me. Set manually if auto-detection fails |
--border |
Table border style for list output: minimal (default), square, or markdown |
|
--show |
Display the current configuration (auth mode and masked secret) | |
--reset |
Remove all local configuration and cache, and sign out | |
--refresh-cache |
Force re-fetch of iteration and area path on next create |
Note on
-p/-P:-pis always--project. The uppercase-Pis each command's secondary flag:--parentonlist,mineandnormalize;--priorityoncreateandupdate;--patonconfig.
Shows the core fields plus area, iteration, tags, parent, reason, the description, and a
browser URL that opens the item directly. Add --comments to also fetch the discussion.
devops get -i 1234
devops get -i 1234 --comments # also show the discussion comments
devops get -i 1234 -p AnotherProject
devops get -i 1234 -o json # flat projection for scripting| Option | Alias | Description |
|---|---|---|
--id |
-i |
Work item ID (required) |
--project |
-p |
Project name (uses default if configured) |
--comments |
Also fetch and show the discussion comments (extra API call) | |
--output |
-o |
Output format: json or csv. Defaults to a detailed view |
Comments require a separate (preview) API call, so they are opt-in via
--comments. The--outputjson/csv modes always return the flat field projection.
Shortcut for list --assigned-to me. The ASSIGNED TO column is omitted since all items belong to the current user.
devops mine
devops mine -s Active
devops mine -t Bug
devops mine -s "In Progress" -t Task
devops mine -P 1234 # only children of work item 1234| Option | Alias | Description |
|---|---|---|
--project |
-p |
Project name (uses default if configured) |
--state |
-s |
Filter by state |
--type |
-t |
Filter by work item type |
--query |
-q |
Additional WIQL WHERE clause |
--parent |
-P |
Filter by parent work item ID |
--top |
-n |
Maximum number of work items to fetch (default: 50) |
--output |
-o |
Output format: json or csv. Defaults to a table |
devops list
devops list -s Active
devops list -t Bug -a me
devops list -p MyProject -s "In Progress" -t Task
devops list -P 1234 # only children of work item 1234
devops list -n 200 # fetch up to 200 items instead of the default 50
devops list -s Active -o json # machine-readable output for scripting
devops list -q "[System.IterationPath] UNDER 'MyProject\\Sprint 1'"Queries fetch up to --top items (default 50). When more match than were fetched, the footer says so — for example Showing 50 of 312 work items - use --top to fetch more. Items are retrieved in batches of 200 behind the scenes, which is the maximum the Azure DevOps batch endpoint accepts.
| Option | Alias | Description |
|---|---|---|
--project |
-p |
Project name (uses default if configured) |
--state |
-s |
Filter by state (e.g., Active, Closed, Resolved) |
--type |
-t |
Filter by work item type (e.g., Task, Bug, User Story) |
--assigned-to |
-a |
Filter by assignee. Use me for the current user |
--query |
-q |
WIQL WHERE clause for advanced filtering |
--parent |
-P |
Filter by parent work item ID |
--top |
-n |
Maximum number of work items to fetch (default: 50) |
--output |
-o |
Output format: json or csv. Defaults to a table |
On creation, the active iteration and the team's default area path are resolved automatically. Use --iteration or --area to override. The resolved values are cached for the rest of the month.
devops create -t "Fix login bug"
devops create -t "New auth endpoint" --type "User Story" -s Active -a me -P 2
devops create -t "Implement service" -e 6 -y Development -R 1234
devops create -t "Backlog item" -I "MyProject\Backlog"
devops create -t "Task with custom field" -f "Custom.SomeField=value" -f "Custom.Another=other"
devops create -t "Análise Técnica" -R 1234 --normalize # title becomes "PBI 1234 - Análise Técnica"| Option | Alias | Description |
|---|---|---|
--title |
-t |
Work item title (required) |
--project |
-p |
Project name (uses default if configured) |
--type |
Work item type (default: Task) |
|
--state |
-s |
Initial state (e.g., New, Active) |
--assigned-to |
-a |
Assignee email or display name. Use me for the current user |
--description |
-d |
Description |
--priority |
-P |
Priority from 1 (highest) to 4 (lowest) |
--iteration |
-I |
Iteration path. Defaults to the active sprint of the configured team |
--area |
-A |
Area path. Defaults to the team's default area |
--estimate |
-e |
Estimated work in hours (Custom.EstimateWork) |
--activity-type |
-y |
Activity type (Microsoft.VSTS.Common.Activity), e.g., Development, Testing, Design |
--field |
-f |
Custom field in Key=Value format. Repeatable for multiple fields |
--related-id |
-R |
ID of the work item to relate to |
--relation-type |
-r |
Relation type (default: parent). See table below |
--normalize |
Prefix the title with the parent type and ID (e.g., PBI 1234 - <title>). Requires a parent relation |
Relation types:
| Value | Description |
|---|---|
parent |
The new item is a child of the specified item |
child |
The new item is a parent of the specified item |
related |
Generic relation |
blocks |
The new item blocks the specified item |
blocked-by |
The new item is blocked by the specified item |
Only fields explicitly provided are updated. No field has a default that causes unintended writes.
devops update -i 1234 -s Closed
devops update -i 1234 -t "New title" -P 1
devops update -i 1234 -a me -e 8 -y Testing
devops update -i 1234 -c "Dependency resolved."
devops update -i 1234 -I "MyProject\Sprint 4"
devops update -i 1234 -R 5678 --relation-type blocks
devops update -i 1234 -f "Custom.ReviewEstimate=2"| Option | Alias | Description |
|---|---|---|
--id |
-i |
Work item ID (required) |
--project |
-p |
Project name (uses default if configured) |
--title |
-t |
New title |
--state |
-s |
New state |
--assigned-to |
-a |
New assignee. Use me for the current user |
--description |
-d |
New description |
--priority |
-P |
New priority (1–4) |
--iteration |
-I |
New iteration path |
--area |
-A |
New area path |
--estimate |
-e |
Estimated work in hours (Custom.EstimateWork) |
--activity-type |
-y |
Activity type (e.g., Development, Testing, Design) |
--field |
-f |
Custom field in Key=Value format. Repeatable |
--comment |
-c |
Add a comment to the work item history |
--related-id |
-R |
ID of the work item to relate to |
--relation-type |
-r |
Relation type (default: related). See create for valid values |
Renames Tasks whose title follows the [Role] Description pattern (e.g., created by third parties) to <PARENT_TYPE> <PARENT_ID> - [Role] Description. The parent type is abbreviated: Product Backlog Item becomes PBI; any other type is uppercased (e.g., Bug -> BUG). Tasks already normalized or without a parent are skipped. By default only Tasks assigned to the current user are processed.
devops normalize # normalize my tasks
devops normalize --dry-run # preview without applying
devops normalize -s Active # only tasks in a given state
devops normalize -P 1234 # only children of work item 1234
devops normalize -a any # all tasks, regardless of assignee| Option | Alias | Description |
|---|---|---|
--project |
-p |
Project name (uses default if configured) |
--state |
-s |
Filter by state |
--assigned-to |
-a |
Filter by assignee. Use me (default) or any for all |
--parent |
-P |
Restrict to children of a specific parent ID |
--dry-run |
Preview changes without applying them | |
--top |
-n |
Maximum number of tasks to fetch (default: 200) |
devops comment -i 1234 "Dependency resolved, ready for review."
devops comment -i 1234 "Blocked by infra team." -p AnotherProject| Option | Alias | Description |
|---|---|---|
--id |
-i |
Work item ID (required) |
message |
Comment text (positional, required) | |
--project |
-p |
Project name (uses default if configured) |
Shortcut for update --state. Fetches the current state first and shows the full transition in the output. Multiple IDs are processed in parallel.
devops state -i 1234 -s "In Progress"
devops state -i 1234 5678 9012 -s "Closed"
devops state -i 1234 -s "Done" -p AnotherProjectOutput: Work item #1234: To Do -> In Progress
| Option | Alias | Description |
|---|---|---|
--id |
-i |
Work item ID (required). Multiple IDs space-separated: -i 1 2 3 |
--state |
-s |
Target state (required, e.g. In Progress, Closed, Done) |
--project |
-p |
Project name (uses default if configured) |
Moves the work items to the project recycle bin (recoverable, not a permanent delete). Prompts for confirmation unless --force. Multiple IDs are processed in parallel.
devops delete -i 1234
devops delete -i 1234 5678 9012
devops delete -i 1234 --force| Option | Alias | Description |
|---|---|---|
--id |
-i |
Work item ID(s) (required). Space-separated: -i 1 2 3 |
--project |
-p |
Project name (uses default if configured) |
--force |
Skip the confirmation prompt |
devops open -i 1234
devops open -i 1234 -p AnotherProject| Option | Alias | Description |
|---|---|---|
--id |
-i |
Work item ID (required) |
--project |
-p |
Project name (uses default if configured) |
devops pipelines
devops pipelines -n "deploy"
devops pipelines -p AnotherProject| Option | Alias | Description |
|---|---|---|
--project |
-p |
Project name (uses default if configured) |
--name |
-n |
Filter by pipeline name (partial match) |
Shows the most recent runs of a pipeline, newest first. Use the pipeline ID from pipelines.
devops runs -i 42
devops runs -i 42 -n 25
devops runs -i 42 -p AnotherProjectOutput columns: ID, NAME, STATE (e.g. inProgress, completed), RESULT (e.g. succeeded, failed; - while still running), CREATED.
| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pipeline (definition) ID (required). See pipelines |
--project |
-p |
Project name (uses default if configured) |
--top |
-n |
Number of most recent runs to show (default: 10) |
Triggers a new run of a pipeline. Without --branch, it runs the pipeline's default branch.
devops run -i 42
devops run -i 42 -b main
devops run -i 42 -b refs/heads/release/1.0 -p AnotherProjectOn success it prints the new run ID, its state, and a link to follow it in the browser.
| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pipeline (definition) ID (required). See pipelines |
--project |
-p |
Project name (uses default if configured) |
--branch |
-b |
Branch to run (main or refs/heads/main). Defaults to the pipeline's default branch |
Pull requests belong to a repository, specified with --repo (required for pr-create, optional filter for pr-list). pr-get, pr-open, pr-vote, pr-comment, pr-abandon and pr-complete work by PR ID and resolve the repository automatically, so they need neither project nor repo.
pr-list --mineandpr-voteneed your user ID, which is captured duringconfig --login/config --pat. If they report a missing user ID, re-runconfigto refresh it.
devops pr-list
devops pr-list -r MyRepo -s active
devops pr-list -r MyRepo -t main
devops pr-list -s all -n 50
devops pr-list --mine| Option | Alias | Description |
|---|---|---|
--project |
-p |
Project name (uses default if configured) |
--repo |
-r |
Repository name. If omitted, lists across all repos in the project |
--status |
-s |
active (default), completed, abandoned, or all |
--target |
-t |
Filter by target branch (e.g., main) |
--top |
-n |
Maximum number of PRs to show (default: 25) |
--mine |
Only pull requests you created |
devops pr-get -i 123Shows status, source/target branches, author, reviewers with their votes, the web URL, and the description.
| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pull request ID (required) |
devops pr-create -r MyRepo -s feature/login -t main --title "Add login"
devops pr-create -r MyRepo -t main --title "Add login" # source = current git branch
devops pr-create -r MyRepo -t main --title "WIP" -d "Details..." --draft
devops pr-create -r MyRepo -t main --title "Add login" --reviewers me,jane@contoso.com
devops pr-create -r MyRepo -t main --title "Add login" -w 1234,1235Branches accept either the short name (main) or the full ref (refs/heads/main). When --source is omitted, the current git branch is detected automatically from .git/HEAD.
--reviewers accepts me (you), a reviewer GUID, or an email / display name (resolved through the Identities API). --work-item links existing work items to the new PR.
| Option | Alias | Description |
|---|---|---|
--repo |
-r |
Repository name (required) |
--source |
-s |
Source branch (defaults to the current git branch) |
--target |
-t |
Target branch (required) |
--title |
Pull request title (required) | |
--description |
-d |
Pull request description |
--draft |
Create as a draft | |
--reviewers |
Reviewers to add: me, a GUID, or email/display name (comma-separated) |
|
--work-item |
-w |
Work item IDs to link to the pull request (comma-separated) |
--project |
-p |
Project name (uses default if configured) |
devops pr-open -i 123| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pull request ID (required) |
Casts your vote (self-adding as a reviewer if needed). Works by PR ID; the repository is resolved automatically.
devops pr-vote -i 123 -v approve
devops pr-vote -i 123 -v reject
devops pr-vote -i 123 -v reset| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pull request ID (required) |
--vote |
-v |
approve, approve-suggestions, reject, wait, or reset (required) |
Posts a top-level comment thread. Works by PR ID; the repository is resolved automatically.
devops pr-comment -i 123 -m "Looks good, one nit on the naming."| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pull request ID (required) |
--message |
-m |
Comment text (required) |
devops pr-abandon -i 123| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pull request ID (required) |
Merges the PR using its last merge source commit. Fails if the PR has no merge commit (e.g. a draft or with conflicts).
devops pr-complete -i 123
devops pr-complete -i 123 --delete-source| Option | Alias | Description |
|---|---|---|
--id |
-i |
Pull request ID (required) |
--delete-source |
Delete the source branch after completing |
The CLI supports two authentication methods. Your choice is stored in config.json as the active auth mode and switching is just a matter of re-running config.
devops config -o https://dev.azure.com/myorg
devops config --loginSigns in through an interactive browser flow (MSAL), using the well-known public client of the Azure CLI — no app registration is required. The token is scoped to Azure DevOps (499b84ac-1321-427f-aa17-267ca6975798/.default) and cached securely (DPAPI on Windows, the keychain on macOS, an encrypted file on Linux), then refreshed silently. Your effective permissions are those your account already has in the organization.
The Azure CLI (
az) does not need to be installed. The sign-in is performed by MSAL inside the CLI; only the Azure CLI's public client ID is reused as an identifier. (This does require that the "Microsoft Azure CLI" application itself is allowed in your Entra tenant.)
If your account is a guest in another tenant or your organization enforces Conditional Access, pass the tenant explicitly:
devops config --login --tenant contoso.onmicrosoft.comEntra sign-in requires the organization policy "Allow access via Microsoft Entra authentication" to be enabled (on by default for Entra-backed organizations).
Session lifetime. Access tokens are refreshed silently, so you normally sign in once and stay authenticated for weeks — the token cache survives terminal restarts and reboots. A new interactive sign-in is only needed after long inactivity, a credential change, or when your organization's Conditional Access policy requires it. In those cases a regular command stops with a clear message asking you to run devops config --login again; the CLI never opens a browser unexpectedly during other commands (keeping it safe for scripts and CI).
devops config -o https://dev.azure.com/myorg -P <PAT>Go to User Settings → Personal Access Tokens → New Token and grant the following scopes:
| Scope | Permission | Used by |
|---|---|---|
| Work Items | Read & Write | All work item commands |
| Code | Read & Write | Pull request commands. Read covers pr-list/pr-get/pr-open; pr-create needs Read & Write |
| Build | Read & Execute | pipelines and runs need Read; queueing with run needs Execute |
The PAT is stored encrypted (DPAPI) on Windows and in an owner-only file (600) on Linux/macOS.