
Adding and Removing Jira Labels via the REST API Without a Prior GET
This page has been translated by machine translation. View original
Hi there! I'm Yuji Nishimura from the Ops Team!
When adding or removing Jira labels via the API, specifying fields.labels replaces the entire array. For this reason, people tend to follow the steps "GET the current labels → merge them locally → PUT them back." This approach increases the number of requests, and if another update lands between the GET and the PUT, that change gets overwritten. In fact, my own application used this approach and issued 2 GETs + 3 PUTs per issue.
Using the update operation of the Edit issue API, I was able to apply just the label additions and removals on the server side in a single request without a prior GET, so I'll introduce it along with my verification results.
What we'll do
What you'll learn in this article:
- How to add and remove labels without a prior GET using add / remove in
update.labels - The difference between
fieldsandupdateand when to use each - How to combine other fields (such as the due date) in the same request
The difference between fields and update
In the request body of Edit issue (PUT /rest/api/3/issue/{issueIdOrKey}), you can specify field edits in two ways: fields and update.
- fields: A simple method that sets values as they are. Array fields are replaced entirely by the array you send
- update: A method that specifies operations (verbs) such as
set/add/remove/edit/copy. Adding or removing elements of an array field can be expressed as operations
With update.labels, you only send operations such as "add this label" and "remove this label," so other labels are unaffected even if you don't know the current value. The rule of thumb is to use fields when setting a value wholesale and update for partial updates of an array.
Note that the official reference explicitly states that the same field cannot be included in both fields and update ("Fields included in here cannot be included in update.").
Trying it out
Test environment
- Jira Cloud (REST API v3)
- macOS + curl
- Authentication: Basic auth using an Atlassian account API token. Set your email address and API token in the environment variables
JIRA_EMAIL/JIRA_API_TOKEN - Verification date: 2026-08-31
The issue used for verification already has four labels attached.
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
"https://your-domain.atlassian.net/rest/api/3/issue/ISSUE-1?fields=labels" | jq -c '.fields.labels'
["existing-a","existing-b","existing-c","existing-d"]
1. Adding a label
PUT with a single add in update.labels.
curl -s -o /dev/null -w "%{http_code}\n" \
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-X PUT -H "Content-Type: application/json" \
"https://your-domain.atlassian.net/rest/api/3/issue/ISSUE-1" \
-d '{"update":{"labels":[{"add":"new-label"}]}}'
The result was 204 (No Content). When I checked the labels with a GET, the existing four labels were untouched and only the specified label had been added.
["existing-a","existing-b","existing-c","existing-d","new-label"]
2. Adding and removing in one request
Swapping labels, as in "remove the old label and attach a new one," can also be done in a single request by listing add and remove side by side.
curl -s -o /dev/null -w "%{http_code}\n" \
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-X PUT -H "Content-Type: application/json" \
"https://your-domain.atlassian.net/rest/api/3/issue/ISSUE-1" \
-d '{"update":{"labels":[{"add":"replacement-label"},{"remove":"new-label"}]}}'
The result was 204, and checking with a GET showed that both operations had been applied.
["existing-a","existing-b","existing-c","existing-d","replacement-label"]
3. Updating another field at the same time
fields and update can coexist in one request as long as they don't specify the same field. Let's add a label and set the due date at the same time.
curl -s -o /dev/null -w "%{http_code}\n" \
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-X PUT -H "Content-Type: application/json" \
"https://your-domain.atlassian.net/rest/api/3/issue/ISSUE-1" \
-d '{"update":{"labels":[{"add":"another-label"}]},"fields":{"duedate":"2026-09-30"}}'
The result was 204, and checking with a GET showed that both the label addition and the duedate setting were applied in a single PUT.
{"labels":["existing-a","existing-b","existing-c","existing-d","replacement-label","another-label"],"duedate":"2026-09-30"}
Key points of the behavior
- Writing the same field in both returns 400
- As stated in the official reference, sending
fields.labelsandupdate.labelstogether returned400with the error messageField 'labels' cannot appear in both 'fields' and 'update'
- As stated in the official reference, sending
- Removing a nonexistent label does not cause an error
- Doing a
removeon a label that wasn't attached returned204and did not affect the other labels. Repeating the same request doesn't change the result, so it's a behavior that makes retries easy
- Doing a
- You can receive the updated state with
returnIssue=true- On success, the response is
204with no body by default, but I also confirmed that adding the query parameterreturnIssue=truereturns200with the updated issue. This also lets you skip the GET to confirm the changes
- On success, the response is
Summary
Replacing the read-modify-write of GET → merge → PUT with add / remove in update.labels reduces the number of requests and also avoids conflicts with concurrent updates. The application I mentioned at the beginning was also reduced to 1 PUT per issue with this replacement.
Similar operations are defined for array fields other than labels, so whenever you want to do a partial update, it's a good idea to first check whether update can be used.
I hope this helps someone.
Reference links:
- Edit issue - The Jira Cloud platform REST API
- Updating an issue via the JIRA REST APIs - Atlassian Developer (an explanation for Jira Server / Data Center)
- Manage API tokens for your Atlassian account