Last updated Dec 23, 2024

JIRA Developer Documentation: JIRA REST API Version 2 Tutorial

Applicable:

JIRA 5.0

Status:

LEGACY. This tutorial applies to Jira versions that have reached end of life. For updated information, check REST APIs and Jira REST API examples page.

JIRA REST API Overview

JIRA's REST API is for anyone who wants to interact with JIRA in a programmatic fashion. This could be developers who want to integrate their software or other Atlassian applications with JIRA, system administrators who want to automate a JIRA feature or process, or developers who want to write gadgets or mashups for JIRA.

JIRA's REST API should be your "Remote API of Choice", when working with JIRA.

This document does not explain everything that can be done with REST. You should always refer to the canonical JIRA REST API documentation for full details on what resources are available, what HTTP methods they support and what the content of requests and responses will be.

There are also a number of other examples on this site of how to perform some specific tasks, especially related to creating and editing issues.

Introduction to JIRA's REST API

JIRA's REST API provides access to a number of resources that are accessible via normal URLs. JIRA's response format is JSON. Because REST is built off the same technologies as the web itself, you generally do not need complicated libraries to use it. In fact, you can start exploring JIRAs REST API simply through your web browser.

Why only provide JSON responses and not XML?

Just like everyone else in the real world, we're faced with time and resource constraints. Supporting two serialisation formats requires more time than just a single one. Hence, for our initial release of the JIRA REST API, we decided to provide "more stuff in JSON" rather than "less stuff in JSON and XML".

JIRA's REST API is Evolving

JIRA's REST API now covers the majority of functions related to accessing and manipulating issues as well as many administration tasks.

JIRA's REST API will continue to grow to support more functions in later releases.

JIRA REST API Implementation

This section provides some background information about JIRA's REST API:

URI Structure

The format of all REST URIs in JIRA is:

1
2
http://hostname/rest/<api-name>/<api-version>/<resource-name>

JIRA's REST API is provided by a plugin that is anchored under the URI path component /rest/. Hence, if your JIRA site is running at:

1
2
http://example.com/jira

Then the anchor point for REST will be:

1
2
http://example.com/jira/rest

The <api-name> part of the URI is the name of the JIRA REST API, which is simply api. There is also an auth API, which is documented in User Authentication.

Each version of a REST API is indicated by the <api-version> part of the URI. Since the JIRA REST API is likely to evolve, we hope to provide as much backward compatibility as possible.

About JIRA REST API versions:

The current version of the JIRA REST API is "2".

The final part of the URI is the <resource-name>. This is the actual REST API "resource" itself that determines what response you will receive. A REST resource is analogous to an object in OO programming or a database row in a database system. JIRA's REST API resources have names like "issue", "user" or "attachment".

Different resources may expect additional path parameters, often to identify an individual resource. For instance, putting all of the above together, the URI to an issue with the key MKY-1 would look like:

1
2
http://hostname/rest/api/2/issue/MKY-1

User Authentication

JIRA's REST API is protected by the same restrictions which are provided via JIRAs standard web interface. This means that if you do not log in, you are accessing JIRA anonymously. Furthermore, if you log in and do not have permission to view something in JIRA, you will not be able to view it using the JIRA REST API either.

In most cases, the first step in using the JIRA REST API is to authenticate a user account with your JIRA site. For the purposes of this tutorial we will use HTTP BASIC Authentication, but any authentication that works against JIRA will work against the REST API. This includes:

  1. OAuth
  2. HTTP Cookies
  3. Trusted Applications
  4. os_username/os_password query parameters

Using BASIC Authentication in production

It is not recommended to use BASIC Authentication over plain HTTP, since passwords are transmitted in plain text on every request. On production systems you will want to use HTTPS/SSL, or use one of the alternative authentication methods that does not suffer from this drawback.

For more information about using Basic Authentication with the JIRA REST API, refer to JIRA REST API Example - Basic Authentication. For OAuth authentication refer to JIRA REST API Example - OAuth authentication.

CAPTCHAs

CAPTCHA upon login was a security feature incorporated into JIRA 4.1. This feature is 'triggered' after several consecutive failed log in attempts, after which the user is required to interpret a distorted picture of a word and type that word into a text field with each subsequent log in attempt.

Be aware that you cannot use JIRA's REST API to authenticate with a JIRA site, once JIRA's CAPTCHA upon login feature has been triggered.

When you get an error response from JIRA, you can check for the presence of an X-Seraph-LoginReason header in the response, which will contain more information. A value of AUTHENTICATION_DENIED means the application rejected the login without even checking the password, which most commonly indicates that JIRA's CAPTCHA feature has been triggered.

This may change:

We would like to improve REST API responses from JIRA where the CAPTCHA upon login feature has been triggered. Examining headers is fairly common in HTTP, but we believe it would be better to generate some useful JSON too.

JIRA REST API Responses

This section demonstrates how to use some of the key features of this release of the JIRA REST API.

We are using cURL in this section to make JIRA REST API calls with user authentication (above) and each call is followed by a typical JSON response.

Simple Issue Response

In the following example, BASIC Authentication is used to retrieve an issue's content. The following call (using context path "/jira"):

1
2
curl -u admin:admin http://localhost:8090/jira/rest/api/2/issue/MKY-1

Would generate this standard response:

1
2
{

    "expand": "renderedFields,names,schema,transitions,editmeta,changelog",
    "id": "10000",
    "self": "http://localhost:8090/jira/rest/api/2/issue/10000",
    "key": "MKY-1",
    "fields": {
        "summary": "First Test Issue",
        "issuetype": {
            "self": "http://localhost:8090/jira/rest/api/2/issuetype/1",
            "id": "1",
            "description": "A problem which impairs or prevents the functions of the product.",
            "iconUrl": "http://localhost:8090/jira/images/icons/bug.gif",
            "name": "Bug",
            "subtask": false
        },
        "status": {
            "self": "http://localhost:8090/jira/rest/api/2/status/1",
            "description": "The issue is open and ready for the assignee to start work on it.",
            "iconUrl": "http://localhost:8090/jira/images/icons/status_open.gif",
            "name": "Open",
            "id": "1"
        },
        "labels": [ ],
        "votes": {
            "self": "http://localhost:8090/jira/rest/api/2/issue/MKY-1/votes",
            "votes": 0,
            "hasVoted": false
        },
        "workratio": -1,
        "assignee": {
            "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
            "name": "admin",
            "emailAddress": "admin@example.com",
            "avatarUrls": {
                "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
                "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
            },
            "displayName": "Administrator",
            "active": true
        },
        "fixVersions": [ ],
        "resolution": null,
        "attachment": [ ],
        "resolutiondate": null,
        "reporter": {
            "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
            "name": "admin",
            "emailAddress": "admin@example.com",
            "avatarUrls": {
                "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
                "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
            },
            "displayName": "Administrator",
            "active": true
        },
        "project": {
            "self": "http://localhost:8090/jira/rest/api/2/project/MKY",
            "id": "10001",
            "key": "MKY",
            "name": "monkey",
            "avatarUrls": {
                "16x16": "http://localhost:8090/jira/secure/projectavatar?size=small&pid=10001&avatarId=10011",
                "48x48": "http://localhost:8090/jira/secure/projectavatar?pid=10001&avatarId=10011"
            }
        },
        "versions": [ ],
        "environment": null,
        "updated": "2011-11-22T09:23:02.302+1100",
        "created": "2011-11-22T09:22:59.899+1100",
        "priority": {
            "self": "http://localhost:8090/jira/rest/api/2/priority/3",
            "iconUrl": "http://localhost:8090/jira/images/icons/priority_major.gif",
            "name": "Major",
            "id": "3"
        },
        "description": null,
        "duedate": null,
        "components": [ ],
        "comment": {
            "startAt": 0,
            "maxResults": 1,
            "total": 1,
            "comments": [
                {
                    "self": "http://localhost:8090/jira/rest/api/2/issue/10000/comment/10000",
                    "id": "10000",
                    "author": {
                        "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
                        "name": "admin",
                        "emailAddress": "admin@example.com",
                        "avatarUrls": {
                            "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
                            "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
                        },
                        "displayName": "Administrator",
                        "active": true
                    },
                    "body": "comment",
                    "updateAuthor": {
                        "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
                        "name": "admin",
                        "emailAddress": "admin@example.com",
                        "avatarUrls": {
                            "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
                            "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
                        },
                        "displayName": "Administrator",
                        "active": true
                    },
                    "created": "2011-11-22T09:23:02.129+1100",
                    "updated": "2011-11-22T09:23:02.129+1100",
                    "visibility": {
                        "type": "role",
                        "value": "Administrators"
                    }
                }
            ]
        },
        "watches": {
            "self": "http://localhost:8090/jira/rest/api/2/issue/MKY-1/watchers",
            "watchCount": 0,
            "isWatching": false
        }
    }

}

Expandable Properties

As you can see from the example above, the very first entry is something called expand. In the JIRA REST API, some of the response information is 'hidden' within expandable properties. In order to see this information, you must explicitly expand it by including the expand query parameter in your REST API call.

To find out what properties are expandable, look at the expand field in the JSON response to see what other information is available.

Expandable properties allow the generation of optimal REST API responses, which avoid the following problems associated with offering 'too much' or 'too little' information:

  • If responses provide too little information, you may require many more HTTP GET REST API calls to collect all the information you require. Overloading a JIRA site with this volume of REST API calls could generate network latency, which in turn could severely impact JIRA's performance.
  • If responses provide too much information, then the network bandwidth, memory usage and processing load of a JIRA server could be severely impacted if the server receives multiple simultaneous REST API calls. For instance, if all we want to know about an issue from a REST API call is its key, then there is no need to retrieve additional information about the issue in the response, such as a potentially extensive list of comments.

Expandable properties give you some control over the data you get back.

In the following example, an issue's content is being retrieved, along with the expanded details of its fields property, which excludes details about comments or attachments. The following call (using context path "/jira"):

1
2
curl -u admin:admin http://localhost:8090/jira/rest/api/2/issue/MKY-1?expand=renderedFields

Would generate a response like this (some part of the response have been removed for brevity and clarity):

1
2
{

    "expand": "renderedFields,names,schema,transitions,editmeta,changelog",
    "id": "10000",
    "self": "http://localhost:8090/jira/rest/api/2/issue/10000",
    "key": "MKY-1",
    "fields": { ......    },
    "renderedFields": {
        "environment": "",
        "updated": "Today 9:23 AM",
        "created": "Today 9:22 AM",
        "description": "",
        "attachment": [ ],
        "comment": {
            "startAt": 0,
            "maxResults": 1,
            "total": 1,
            "comments": [
                {
                    "self": "http://localhost:8090/jira/rest/api/2/issue/10000/comment/10000",
                    "id": "10000",
                    "author": {
                        "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
                        "name": "admin",
                        "emailAddress": "admin@example.com",
                        "avatarUrls": {
                            "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
                            "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
                        },
                        "displayName": "Administrator",
                        "active": true
                    },
                    "body": "comment",
                    "updateAuthor": {
                        "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
                        "name": "admin",
                        "emailAddress": "admin@example.com",
                        "avatarUrls": {
                            "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
                            "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
                        },
                        "displayName": "Administrator",
                        "active": true
                    },
                    "created": "Today 9:23 AM",
                    "updated": "Today 9:23 AM",
                    "visibility": {
                        "type": "role",
                        "value": "Administrators"
                    }
                }
            ]
        }
    }

}

Rendered Fields:

The renderedFileds expansion only includes fields where the rendered data is different from that in the fields section. This includes Wiki rendered fields and Date fields.

Let's take a closer look at the "reporter" field from the example above:

1
2
"reporter": {
    "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
    "name": "admin",
    "emailAddress": "admin@example.com",
    "avatarUrls": {
        "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
        "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
    },
    "displayName": "Administrator",
    "active": true
},

Lots of fields like this one will have a "self" link, which takes you to the canonical location for that resource. Sometimes there will be more information there. For instance, right now we don't know the timezone of the user. Hence, if we follow that link (using context path "/jira"):

1
2
curl -u admin:admin 'http://localhost:8090/jira/rest/api/2/user?username=admin'

Would generate a response like this:

1
2
{
    "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
    "name": "admin",
    "emailAddress": "admin@example.com",
    "avatarUrls": {
        "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
        "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
    },
    "displayName": "Administrator",
    "active": true,
    "timeZone": "Australia/Sydney",
    "groups": {
        "size": 3,
        "items": [ ]
    },
    "expand": "groups"
}

This gives us the timezone of the user "admin". There is also an expand option here which shows we can also get the users groups.

1
2
curl -u admin:admin 'http://localhost:8090/jira/rest/api/2/user?username=admin&expand=groups'

Would generate a response like this:

1
2
{
    "self": "http://localhost:8090/jira/rest/api/2/user?username=admin",
    "name": "admin",
    "emailAddress": "admin@example.com",
    "avatarUrls": {
        "16x16": "http://localhost:8090/jira/secure/useravatar?size=small&avatarId=10062",
        "48x48": "http://localhost:8090/jira/secure/useravatar?avatarId=10062"
    },
    "displayName": "Administrator",
    "active": true,
    "timeZone": "Australia/Sydney",
    "groups": {
        "size": 3,
        "items": [
            {
                "name": "jira-administrators"
            },
            {
                "name": "jira-developers"
            },
            {
                "name": "jira-users"
            }
        ]
    },
    "expand": "groups"

Issue Information

For system fields the field_id and field_name will be the same. For custom field the field_id will be a unique descriptor in the format of customfield_10040 while the field_name will be the text description provided when it is created (e.g. "Participants").

The field_type is intended to provide some indication about the format and content of field_value. It is intended to be an opaque identifier.

The format of field_value will vary depending on the field in question. For description, for instance, it will be a simple string. For comment it will be an array of comment objects.

Rendering

Within the fields section all text will be presented in its raw format. If you have configured a field to use the wiki renderer then you may also want to get the HTML rendered version of the field. This can be accessed via the renderedFields expandable entity when you provide the expand=renderedFields query parameter.

Modifying JIRA Data

For an explanation of how to modify issues in JIRA see Updating an Issue via the JIRA REST APIs and refer to the examples for creating and editing issues.

Let's write a small python script that will use our REST interface to graph the relationships between issues in our JIRA site.

We'll use the small helper library (restkit) to simplify the REST requests. This library is not strictly necessary -- after all, REST is just HTTP. However, restkit can be used for convenience. We also rely on Google Charts to do the actual graphing for us too.

Using the JIRA REST API is fairly simple:

  • you make an HTTP call,
  • get some data back,
  • then do something with that data.

In the following example, 95% of the code is doing something other than interacting with the JIRA REST API. So before we see the full example, let's highlight the actual REST usage out of context to show how simple it usually is. This example uses Python:

1
2
resource = Resource(url + '/rest/api/2/issue/%s' % key, pool_instance=None, filters=[auth])
response = resource.get(headers = {'Content-Type' : 'application/json'})
    if response.status_int == 200:
        # Not all resources will return 200 on success. There are other success status codes. Like 204. We've read
        # the documentation though and know what to expect here.
        issue = json.loads(response.body_string())
        return issue

This performs a GET on the issue, checks for a successful response, and the parses the JSON response into a Python dictionary. The filters=[auth] line is how we told restkit to perform BASIC Authentication. Later on, we'll reach into this Python dictionary to grab the data we want for our work:

1
2
fields = issue['fields']
if fields.has_key('subtasks'):
    for subtask in issue['fields']['subtasks']:
        # do work with a subtask

You can view the full source by downloading the attachment: draw-chart.py

You can see the script's command line options using the standard command:

1
2
./draw-chart.py --help

You can test this against your JIRA site with:

1
2
./draw-chart.py --user=username --password=password --jira=<url-of-your-jira-site>

The output should look similar to:

1
2
Fetching JRADEV-1391
Fetching JRADEV-2062
Fetching JRADEV-2063
Fetching JRADEV-1107
Fetching JRADEV-112
Fetching JRADEV-1108
Fetching JRADEV-1218
Fetching JRADEV-1219
Fetching JRADEV-1220
Fetching JRADEV-1221
Fetching JRADEV-1684
Fetching JRADEV-2064
Fetching JRADEV-1390
Fetching JRADEV-1389
Fetching JRADEV-1388
Fetching JRADEV-2125
Fetching JRADEV-1264
Fetching JRADEV-1256
Writing to issue_graph.png

Open up the issue_graph.png to show an image that should look something like this:

Blue lines with arrows denote SubTasks.

Example #2: Quickview Inline Dialog Plugin

This time, we'll create a JIRA plugin that uses the REST API. We want to look through all the comments on the issue and add a little tooltip that will pop up when you hover over a link to a JIRA issue.

The popup should contain a "quick view" of information about the target issue (similar to the example shown in the following image) so that we do not have to click the issue's link to see this information.

We can achieve this using a Web Resource Context. This lets our plugin put JavaScript just on the View Issue page of JIRA.

First, we define the Web Resource Context in our atlassian-plugin.xml:

1
2
<web-resource key="remote-link" name="Remote Issue Linking">
        <resource name="linker.js" type="download" location="linker.js"/>
        <context>jira.view.issue</context>
    </web-resource>

Then we have linker.js look in the comment body for URLs that 'look like' they might point to JIRA issues. Next, obtain the JSON representation of the issue using JIRA's REST API, do some quick formatting on it and put it into an AUI InlineDialog.

1
2
jQuery(document).ready(function() {
    var i=new Date().getTime()
    jQuery("#issue_actions_container").find('.action-body a').each(function() {
        if (this.href.match(/\/browse\/[A-Z]+\-\d+$/)) {
            var split = this.href.split('/browse/')
            var base = split[0]
            var key = split[1]
            var options = { cacheContent: true, onHover: true, showDelay: 400, hideDelay: 400, closeOthers: false, width: 500 }
            var draw = function(contents, trigger, showPopup) {
                jQuery.getJSON(base + '/rest/api/latest/issue/' + key, function(data) {
                    var fields = data["fields"]
                    contents.empty()
                    contents.append(
                        "<ul class=\"item-details\">"
                        + "<li>"
                        + "<dl><dt>Summary: </dt>" + "<dd>" + fields["summary"] + "</dd></dl>"
                        + "<dl><dt>Type: </dt>" + "<dd>" + fields["issuetype"]["name"] + "</dd></dl>"
                        + "<dl><dt>Priority: </dt>" + "<dd>" + fields["priority"]["name"] + "</dd></dl>"
                        + "<dl><dt>Status: </dt>" + "<dd>" + fields["status"]["name"] + "</dd></dl>"
                        + "<dl><dt>Assignee: </dt>" + "<dd>" + fields["assignee"]["name"] + "</dd></dl>"
                        + "<dl><dt>Description: </dt>" + "<dd>" + fields["description"] + "</dd></dl>"
                        + "</li></ul>")
                    contents.append("<form id=\"add-watch\" name=\"watch\" action=\"\">")
                    jQuery("<input type=\"button\" name=\"button\" value=\"Watch\"/>").click(function() {
                        // We don't actually know our own username...and we need it to add a Watcher. So we get it from the
                        // "current user" resource
                        jQuery.getJSON(base + '/rest/auth/latest/session', function(data) {
                            jQuery.ajax({ type: "POST", url: base + "/rest/api/latest/issue/" + key + "/watchers", data: data['name'], dataType: "json", contentType: "application/json" })
                        })
                    }).appendTo(contents)
                    contents.append("</form>")
                    showPopup()
                })
            }
            AJS.InlineDialog(jQuery(this), "issue-linking-" + (i++), draw, options)
        }
    })
})

Further reading

Rate this page: