This document provides guidelines to Atlassian developers who are designing REST APIs for Atlassian applications. We are publishing these design principles and guidelines for viewing by the wider community for these reasons:
These design guidelines focus on the implementation details for REST modules. Any implementations must also follow the Atlassian REST API policy which define the principles behind Atlassian's REST APIs.
Atlassian has been working towards creating standardised REST APIs across all of our applications. Some of our applications already provide REST APIs, and some applications are providing new REST APIs or updating their existing APIs in an upcoming release.
Each application (Confluence, Jira, Crowd, etc) will provide its own REST APIs, exposing the application-specific functionality. The goal is for these application-specific APIs to share common design standards, as described on this page.
For details of each application's APIs, please refer to the development hub in the documentation for the application concerned.
These guidelines are for Atlassian developers who are designing REST APIs for Atlassian applications. The goal is to keep REST API implementations as consistent as possible. These guidelines apply to all REST APIs, including:
These guidelines provide one way of doing things that might not always be the only way nor the best way. If you think that is the case, feel free to move away from the guidelines. You should be aware of potential repercussions such as security issues, consistency issues, etc.
We strongly recommend that someone who is familiar with these guidelines should review your REST API code.
The examples used in these guidelines assume that we have a REST API supplied by an Atlassian plugin called the 'Unified Plugin Manager' plugin, 'UPM' or 'upm'. (This is a real plugin that is currently under development.) The UPM allows you to discover and manage the plugins installed on an Atlassian application.
Given a list of foo
entities, the following URI scheme provides access to the list of foo
entities and to a particular foo
:
URI | Notes |
---|---|
| This returns a list of the Method: GET. |
| This returns the full content of the Method: GET. |
Sub-elements of a foo
entity are made available as sub-resources of /foo/{key
}.
For our example, let's take a plugin resource within the 'upm' REST API.
Use this URI to access a list of plugins:
http://host:port/context/rest/upm/1/plugin
Use this URI to access the plugin resource with a key of a-plugin-key
:
http://host:port/context/rest/upm/1/plugin/a-plugin-key
Structure of the above URI:
host
and port
define the host and port where the application lives.context
is the servlet context of the application. For example, for Confluence this would typically be confluence
.rest
denotes the REST API.upm
is the path declared in the REST module type in the plugin descriptor.1
is the API version. See the section on API version control below.Below is a list of standard query parameters. These names are reserved in our REST APIs and should be used according to the notes in the table below.
Query Parameter | Notes |
---|---|
| Used for title expansion. See section on title expansion below. |
| An integer specifying the starting point when paging through a list of entities. Defaults to 0. |
| The maximum number of results when paging through a list of entities. No default is provided. We recommend that APIs should define their own default. Applications should also define a hard limit on the number of items that can be requested at the same time. |
By default, REST APIs must support multiple content types.
Representation | Requested via... |
---|---|
JSON | Requested via one of the following:
|
XML | Requested via one of the following:
|
Entities can have links to each other. This is represented by the <link>
tag. The <link>
tag supports the following attributes:
Attribute | Description |
---|---|
| The URI of the entity linked to. |
| The relationship between the containing entity and the entity linked to.
|
| Optional. A short description of the entity linked to. |
Links to the URI that will perform operations on an entity have their rel
attribute set to edit
, delete
or add
. We recommend that entities provide these links as part of their content.
Links should follow some simple rules:
http://host:port/context/rest/api/
, then links to other resources should use this as their base URL..xml
, .json
, etc, links should have this same extension as far as possible.The following plugin entity has a <link>
tag with a rel
attribute identifying the entity's own ID via a URI pointing to itself:
1 2<plugin key="a-plugin-key" enabled="true" expand="modules,info"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key" /> <info name="A plugin"/> <modules size="2" expand="module"/> </plugin>
Every addressable entity should have a <link>
tag with the attribute rel="self"
pointing to its own URI. See the section on linking above.
Entities SHOULD be served with an ETag header.
Read-only entities and lists of entities may be served without an ETag header. Providing an ETag header for these resources will help with caches and conditional requests, but the header need not be included if calculating the ETag is too expensive or difficult.
The ETag for a resource MUST be the same regardless of the requested representation or title expansion state.
The server MUST treat If-Match and If-None-Match headers provided by clients as defined in RFC 2616.
PUT
or DELETE
API requests SHOULD provide an If-Match header, and SHOULD react meaningfully to 412 (Precondition Failed) responses.
Collections of entities should define the following attributes:
Attribute | Description |
---|---|
| The total size of items available in the collection. This can be different from the actual number of items currently listed in the collection if the |
| If used to filter the collection elements. |
| If used to filter the collection elements. |
In the response below, the <modules>
entity has a size
attribute indicating that there are 2 plugin modules in the collection:
1 2<plugin key="a-plugin-key" enabled="true" expand="modules,info"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key" /> <info name="A plugin"/> <modules size="2" expand="module"/> </plugin>
In order to be minimise network traffic and simplify some APIs from the client perspective, we recommend that APIs provide title expansion. This works by using the expand
query parameter and setting its value to the last path element of the entity's schema.
The expand
query parameter allows a comma-separated list of identifiers to expand. For example, the value modules,info
requests the expansion of entities for which the expand
identifier is modules
and info
.
You can use the dot notation to specify expansion of entities within another entity. For example modules.module
would expand the plugin entity (because its expand
identifier is modules
) and the module entities within the plugin. See example 4 below.
Expandable entities should be declared by parent entities in the form of an expand
attribute. In example 1, the plugin entity declares modules
and info
as being expandable. The attribute should not be confused with the query parameter which specifies which entities are expanded.
For our example, let's take a plugin resource within the 'upm' REST API.
Use this URI to access the plugin resource without specifying title expansion:
http://host:port/context/rest/upm/1/plugin/a-plugin-key
The response will contain:
1 2<plugin key="a-plugin-key" enabled="true" expand="modules,info"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key" /> <info name="A plugin"/> <modules size="2" expand="module"/> </plugin>
info
ElementUse the following URI to expand the info
element in our plugin resource:
http://host:port/context/rest/upm/1/plugin/a-plugin-key?expand=info
Now the response will contain:
1 2<plugin key="a-plugin-key" enabled="true" expand="modules,info"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key" /> <info name="A plugin"> <description>This is an awesome plugin</description> <version>1.1</version> </info> <modules size="2" expand="module"/> </plugin>
Use this URI to expand the module collection in our plugin resource:
http://host:port/context/rest/upm/1/plugin/a-plugin-key?expand=modules
The response will contain:
1 2<plugin key="a-plugin-key" enabled="true" expand="modules,info"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key" /> <info name="A plugin"/> <modules size="2" expand="module"> <module key="module-key-1"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-1" /> </module> <module key="module-key-2"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-2" /> </module> </modules> </plugin>
Note that the above URI does not expand each individual module.
Use the following URI to expand the modules inside the collection within our plugin resource:
http://host:port/context/rest/upm/1/plugin/a-plugin-key?expand=modules.module
The response will contain:
1 2<plugin key="a-plugin-key" enabled="true" expand="modules,info"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key" /> <info name="A plugin"/> <modules size="2" expand="module"> <module key="module-key-1"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-1" /> <name>Module 1</name> <description>This is my first module</description> </module> <module key="module-key-2"> <link rel="self" href="http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-2" /> <name>Module 2</name> <description>This is my second module</description> </module> </modules> </plugin>
You can also set indices to expand a given set of items in the collection. The index is 0 based:
modules[3]
will expand only the module at index 3 of the collection.modules[1:3]
will expand modules ranging from index 1 to 3 (included). Note that [:3]
and [3:]
notations also work and the range goes respectively from the beginning of the collection and to the end of the collection.modules[-1]
will expand the last module. Other negative index values also work and indices are counted from the end of the collection.APIs must be subject to version control. The version number of an API appears in its URI. For example, use this URI structure to request version 1 of the 'upm' API:
http://host:port/context/rest/upm/1/...
When an API has multiple versions, we recommend:
latest
key-word.For example, if versions 1 and 2 of the 'upm' API are available, the following two URIs would point to the same resources:
host:port/context/rest/upm/latest/
http:host:port/context/rest/upm/2/
A version number indicates a certain level of backwards-compatibility the client can expect, and as such, extra care should be taken to maintain this trust. The following lists which types of changes necessitate a new version and which don't:
Every REST API MUST at least accept basic authentication.
Other authentication options are optional, such as Trusted Apps, OS username and password as query parameters, or 'remember me' cookies.
By default, access to all resources (using any method) requires the client to be authenticated. Resources that should be available anonymously MUST be marked as such. The default implementation SHOULD use the AnonymousAllowed
annotation.
Authorisation is NOT handled by the REST APIs themselves. It is the responsibility of the service layer to make sure the authenticated client (user) has the correct permissions to access the given resource.
Atlassian uses the XSRF acronym for Cross-site request forgery and not CSRF.
To protect against XSRF attacks:
Ensure that GET requests are idempotent
For REST API classes or methods that will not be used in normal browser 'form' posts
annotate them with @Consumes({MediaType.APPLICATION\_XML, MediaType.APPLICATION\_JSON})
:
1 2Class example: @Path("/a") @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public class RestHelloWorldService { ... } Method example: public class RestAnotherService { ... @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @POST @Path("/b") public Response getUncompletedUsers() { ... } }
This protects against XSRF as browser cross-domain simple requests cannot have a content type of MediaType.APPLICATION_XML or MediaType.APPLICATION_JSON. See http://www.w3.org/TR/cors/#terminology for further information on 'simple requests'.
It is also noting that because of issues in the implementation of the beacon API in Chrome, atlassian-rest performs origin xsrf checks on requests in some cases to prevent content-types that should not be allowed to be sent cross-domain without CORS permitting them.
For atlassian-rest versions prior to 3.0.0, annotate them with @com.atlassian.plugins.rest.common.security.RequiresXsrfCheck
. The atlassian-rest-refimpl-plugin has an example of using this annotation. See RequiresXsrfCheck for further information.
In atlassian-rest version 3.0.0 and later mutative resources such as POST
are automatically XSRF protected.
For REST API classes or methods that will be used from normal browser 'form' posts
As of atlassian-rest 2.8.0-m9 the @com.atlassian.plugins.rest.common.security.RequiresXsrfCheck
annotaion can be used to perform XSRF token validation on rest form submissions. This XSRF token validation implementation uses the SAL provided XsrfTokenValidator and XsrfTokenAccessor interfaces. See RequiresXsrfCheck for further information.
A plugin which uses this annotation to provide XSRF token validation on a REST method will need to ensure that in all corresponding forms there is a hidden XSRF token parameter with a XSRF token value obtained from the [XsrfTokenAccessor](https://developer.atlassian.com/ static/javadoc/sal/2.6/reference/com/atlassian/sal/api/xsrf/XsrfTokenAccessor.html) interface.
For example, the exampleXsrfTokenProtectedMethod
method annotated with the RequiresXsrfCheck annotation in the following class
1 2@Path("/exampleXsrfTokenProtectedMethod") @Consumes({MediaType.TEXT_PLAIN}) public class RestHelloWorldService { @POST @com.atlassian.plugins.rest.common.security.RequiresXsrfCheck public Response exampleXsrfTokenProtectedMethod( args... ) { ... } }
would have a corresponding form similar to the following
1 2<form name="do-something" action="/rest/examplePlugin/latest/exampleXsrfTokenProtectedMethod" method="post" enctype="text/plain"> <input type="hidden" name="atl_token" value="xsrfTokenValueHere"> <input type="text" name="something"> </form>
In version 3.0.0 and later of atlassian-rest XSRF protection is enabled by default. It is possible to opt-out of XSRF protection for a rest method by using the com.atlassian.annotations.security.XsrfProtectionExcluded
annotation as provided by Atlassian annotations (versions >= 0.12). Please avoid using the XsrfProtectionExcluded
annotation as much as possible.
For completeness, here is an example of using the XsrfProtectionExcluded
annotation.
1 2package com.random.plugin.code.rest; import com.atlassian.annotations.security.XsrfProtectionExcluded; import javax.ws.rs.Path; import javax.ws.rs.POST; /** */ @Path ("/thing") public class XsrfCheck { @Path("post") @POST @XsrfProtectionExcluded // does not mutate state. public String nonMutativePost() { return "Request succeeded"; } }
The following diagram shows when XSRF protection is enforced on a request to a rest resource in atlassian-rest 3.0.0 and later versions.
Also in atlassian-rest 3.0.0 a value of "nocheck" for the X-Atlassian-Token XSRF header has been deprecated and will result in a warning when used appearing in the logs. Since, rest 2.9.1 a value of "no-check", is accepted in addition to the old "nocheck" rest value for the X-Atlassian-Token header ( REST-263 - REST-164 Implemented checking the X-Atlassian-Token header with the wrong value Resolved ).
By using jQuery's ajaxSetup or the ajaxPrefilter function it is possible to make all same origin jQuery ajax requests contain the CSRF X-Atlassian-Token header. This is incredibly useful as it means that existing jQuery ajax usages should not need to be modified.
For example bamboo uses the ajaxPrefilter function like so:
1 2AJS.$.ajaxPrefilter( function(options, originalOptions, jqXHR) { function isMutativeHttpMethod(method) { return !(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } if (isMutativeHttpMethod(options.type)) { options.crossDomain = false; jqXHR.setRequestHeader("X-Atlassian-Token", "no-check"); } });
Here is an example using ajaxSetup instead of ajaxPrefilter:
1 2function isMutativeHttpMethod(method) { return !(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ crossDomain: false, beforeSend: function(xhr, settings) { if (isMutativeHttpMethod(settings.type)) { xhr.setRequestHeader("X-Atlassian-Token", "no-check"); } } });
If CORS is configured in a product such that it permits setting the content-type or the X-Atlassian-Token header then XSRF protection can be bypassed by CORS allowed origins. This is intentional and supported by design.
REST APIs SHOULD support conditional GET requests. This will allow the client to cache resource representations. For conditional GET requests to work, the client MUST send the ETag value when requesting resources using the If-None-Match request header. The ETag is the one provided by a previous request to that same URI. See the section on version control for entities below.
Server implementations should acknowledge the If-None-Match
header and check whether the response should be a 304
(Not Modified). See the section on response codes below.
PUT or DELETE API requests SHOULD provide an If-Match header, and SHOULD react meaningfully to 412
(Precondition Failed) responses. See the section on response codes below.
The ETag is the one provided by a previous GET request to that same URI. See the section on version control for entities below.
Some items worth discussing in the guidelines are currently out of scope:
This list shows the common HTTP response codes and some brief guidelines on how to use them. For the complete list of HTTP response codes, please refer to section 6 of RFC 2616.
Code | Description |
---|---|
200 | Request was processed as expected. Name: OK. Notes:
|
201 | Request created an entity. Name: Created. Notes:
|
202 | The request has been acknowledged but cannot be processed in real time. Name: Accepted. Notes:
|
204 | Name: No content. |
301 | The requested resource has been moved to another location (URI). Name: Moved Permanently. Notes:
|
304 | The requested resource has not been modified. Name: Not Modified. Notes:
|
401 | Client is not authenticated or does not have sufficient permission to perform this request. Name: Unauthorized. Notes:
|
404 | No resource was found at this location (URI). Name: Not found. Notes:
|
412 | The client specified some preconditions that are not valid. Name: Precondition Failed. Notes:
|
5xx | Any server-side error. Name: Server-Side Error. Notes:
|
The link element is used for hyperlinking entities and as entity IDs:
1 2<link title="This is my resource" rel="edit" href="http://host:port/context/rest/api/1/myresource" />
See the sections on linking and entity IDs above.
Any request which produces a status code with no body will have a body formatted like this:
1 2<status> <plugin key="a-plugin-key" version="1.0" /> <status-code>201</status-code> <sub-code>604</sub-code> <message>Some human readable message</message> <etag>233223</etag> <resources-created> <link rel="self" href="http:..." /> </resources-created> <resources-updated /> </status>
Element | Description |
---|---|
| Describes the plugin which provides the REST API. This element is present only if the REST API is provided by a plugin. |
| The actual HTTP code of the response. See the section on response codes above. |
| Another code that defines the error more specifically. It is up to REST API developers to define their various codes. |
| This element is present when resources have been created. It consists of |
| This element is present when resources have been updated. It consists of |
Developing a REST Service Plugin
REST Plugin Module
Basics of Exposing REST APIs via Plugins
Rate this page: