Representational State Transfer (REST) endpoint is a URL that runs a script. REST endpoints are configured programmatically. You can define REST endpoints in ScriptRunner, for example, to:

Adding a REST Endpoint

Follow this task to create a custom REST endpoint:

  1. Select the Cog icon, and then select General Configuration.

  2. Scroll to the ScriptRunner section in the left-hand navigation, and then select REST Endpoints.

  3. Select Add a New Item, and then select the Custom Endpoint

Use the following REST endpoint example to examine the different parts of the script:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate (1)

doSomething( (2)
    httpMethod: "GET", groups: ["jira-administrators"] (3)
) { MultivaluedMap queryParams, String body -> (4)
    return Response.ok(new JsonBuilder([abc: 42]).toString()).build() (5)
}
1 This line makes methods in your script recognisable as endpoints, which is required.
2 The name of the REST endpoint, which forms part of the URL. In this example, it is doSomething.
3 This line configures the endpoint and determines which HTTP verb to handle and what groups to allow.
4 This line contains parameters that are provided to your method body.
5 The body of your method, where you will return a javax.ws.rs.core.Response object.

You can add this REST endpoint to the list of configured endpoints as an inline script or by copying into a file and adding that file as a script file. To test this endpoint, type this text into your browser:

Notice the last part of the text is the name doSomething.
<jira_base_url>/rest/scriptrunner/latest/custom/doSomething

Alternatively, you could type this into the command line utility:

  • Again, notice the name doSomething in each command.

  • admin:admin corresponds to a username and password.

curl -u admin:admin <jira_base_url>/rest/scriptrunner/latest/custom/doSomething
{"abc":42}

If you are using a file, you can change the response. You may need to select the Scan button on the REST Endpoints page before calls to the endpoint return the new response. See the section on Script Root Scanning below.

Configuration

The general format of a method defining a REST endpoint is:

methodName (Map configuration, Closure closure)

For the configuration, only the following options are supported:

Key Value

httpMethod

Choose one of: GET, POST, PUT, DELETE

groups

One or more groups. If the requesting user is in any of the groups, the request is allowed.

Either or both of these can be omitted. If you omit the groups attribute, the endpoint will be available to unauthenticated users.

Use these parameters for the closure:

Parameter Type Description

MultivaluedMap

queryParams

This corresponds to the URL parameters.

String

Content

This is the body of the request for httpMethod (POST, PUT, etc.).

HttpServletRequest

Request

This returns the requesting user for the instance.

You can use any of these forms for your closure:

something() { MultivaluedMap queryParams ->
something() { MultivaluedMap queryParams, String body ->
something() { MultivaluedMap queryParams, String body, HttpServletRequest request ->

The contents of your closure depends on what you need access to.

Access Request URL

Sometimes you may need to use the URL path after your method name. In the following example, you want to retrieve /foo/bar:

<base_url>/rest/scriptrunner/latest/custom/doSomething/foo/bar

Use the 3-parameter form of the closure definition and call the getAdditionalPath method from the base class.

For example:

doSomething() { MultivaluedMap queryParams, String body, HttpServletRequest request ->

    def extraPath = getAdditionalPath(request)
    // extraPath will contain /foo/bar when called as above
}
In previous versions, an extraPath variable was used in the scripts. However, this is not thread-safe. Use the method above.

Script Root Scanning

As well as manually adding scripts or files via the UI, ScriptRunner scans your script roots for scripts that contain REST endpoints and automatically register them. To enable the scanning, set the property plugin.rest.scripts.package to a comma-delimited list of packages. For example:

-Dplugin.rest.scripts.package=com.acme.rest

On plugin enablement, scripts/classes under this package are scanned and registered if the scripts contain the following line:

@BaseScript CustomEndpointDelegate delegate

ScriptRunner also scans script plugins. To enable scanning all of your script roots, set the property to an empty string. For example:

-Dplugin.rest.scripts.package=

Examples

Create Priority Object

This example demonstrates plugging a gap in the official REST API. There is no way to create a new priority object.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.PriorityManager
import com.atlassian.jira.issue.fields.rest.json.beans.JiraBaseUrls
import com.atlassian.jira.issue.fields.rest.json.beans.PriorityJsonBean
import com.atlassian.jira.issue.priority.Priority
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import org.codehaus.jackson.map.ObjectMapper

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

def priorityManager = ComponentAccessor.getComponent(PriorityManager)
def baseUrls = ComponentAccessor.getComponent(JiraBaseUrls)

priority(
    httpMethod: "POST", groups: ["jira-administrators"]
) { MultivaluedMap queryParams, String body ->

    def mapper = new ObjectMapper()
    def bean = mapper.readValue(body, PriorityJsonBean)
    assert bean.name // must provide priority name
    assert bean.description // must provide priority description
    assert bean.iconUrl // must provide priority icon url
    assert bean.statusColor // must provide priority statusColor

    Priority priority
    try {
        priority = priorityManager.createPriority(bean.name, bean.description, bean.iconUrl, bean.statusColor)
    } catch (e) {
        return Response.serverError().entity([error: e.message]).build()
    }

    return Response.created(new URI("/rest/api/2/priority/${priority.id}")).build()
}

Most of this code is involved with validating the JSON that is passed to it. The validation ensures that all required fields for a priority object are present. The appropriate method on the response class sends the right status code. The status code is 500 (server error) if the priority already exists. The status code is 201 (created) if the priority is created.

To test, you could use the following code:

curl -X POST -H "Content-type: text/json" -u admin:admin --data "@priority.json" \
    <jira_base_url>/rest/scriptrunner/latest/custom/priority

priority.json is a text file that contains:

{
  "statusColor": "#009900",
  "description": "Major loss of function.",
  "iconUrl": "/images/icons/priorities/lowest.png",
  "name": "Cosmetic"
}

You can have multiple methods with the same name in the same file, which is useful to do simple CRUD REST APIs.

An example:

POST /priority - creates a priority
PUT /priority - updates a priority
DELETE /priority - deletes a priority
GET /priority - gets a priority
Downgrading ScriptRunner can cause REST endpoints to break due to a change in JSON format. Adaptavist does not recommend downgrading.

Get the User Making the Request

Use com.atlassian.sal.api.user.UserManager to get the current user from the http request.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.sal.api.user.UserManager
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript

import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

getCurrentUser { queryParams, body, HttpServletRequest request ->
    def userManager = ComponentAccessor.getOSGiComponentInstanceOfType(UserManager)
    def userProfile = userManager.getRemoteUser(request)
    return Response.ok(new JsonBuilder([currentUser: userProfile?.username]).toString()).build()
}

Have questions? Visit the Atlassian Community to connect, share, and learn with other Atlassian users and experts, including Adaptavist staff.

Ask a question about ScriptRunner for JIRA, Bitbucket Server, or Confluence.

Want to learn more? Check out courses on Adaptavist Learn, an online platform to onboard and train new users for Atlassian solutions.