The Adaptavist Group LogoDocumentation

Make API Calls with Fetch

Learn how to call Jira and external REST APIs from your scripts with Fetch, including GET and POST requests, error handling, and authentication overrides.

Fetch is a built-in JavaScript API for making HTTP requests. It's the TypeScript equivalent of Unirest or HttpBuilder, which Groovy developers used on Jira Data Center, and it's the standard way to call external services or REST APIs from your scripts.

Because Fetch is built into the JavaScript runtime, there's nothing to install or import. It's always available. For a complete reference, see the MDN Fetch API documentation.

Fetch is built around two core interfaces: Request, which represents the outgoing request, and Response, which represents what comes back. When you call fetch(), it returns a Promise that resolves to a Response as soon as the server responds with headers. The body hasn't arrived yet, which is why you need a second await to parse it:

const response = await fetch('https://api.example.com/data')  // waits for headers
const data = await response.json()                             // waits for the body

Basic usage

Fetch is asynchronous, so you always use it with await. It returns a Response object, which you then parse depending on the content type.

const response = await fetch('/rest/api/3/myself')
const me = await response.json()
console.log(me.displayName)

Notice the two await calls: one to wait for the response, and one to parse the body. This is a common pattern you'll use every time.

GET requests

const response = await fetch('/rest/api/3/issue/JRA-1?fields=summary,status')

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`)
}

const issue = await response.json()
console.log(issue.fields.summary)

response.ok is true for any 2xx status code. Always check it before parsing the body.

POST requests

To send data, pass a second argument to fetch with the method, headers, and body:

const response = await fetch('/rest/api/3/issue', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    fields: {
      project: { key: 'JRA' },
      issuetype: { name: 'Task' },
      summary: 'Created via fetch'
    }
  })
})

const created = await response.json()
console.log(created.key)

Error handling

That response.ok check isn't optional politeness. Fetch only throws an error for network failures, so a 404 or a 500 is a normal result as far as Fetch is concerned, and without the check your script carries on and tries to parse an error page as though it were data.

This is a common gotcha for developers new to Fetch. Throwing lets the run be marked as failed.

Calling Atlassian's APIs

You can call Jira's REST API directly with Fetch using a /rest/api/3/ path. Authentication is handled automatically based on the Run as setting configured on your script, so there's nothing extra to set up:

const response = await fetch('/rest/api/3/myself')
const user = await response.json()
console.log(user.displayName)

Overriding authentication

By default, calls use the item's Run as setting. You can override it for a single request: with an authorization header on a fetch call, or with the as option on a HAPI call.

Override a fetch request

Pass an authorization header of Forge as=app to run as the ScriptRunner app, or Forge as=user:<accountId> to run as a specific user:

// Run as the ScriptRunner app
const response = await fetch('/rest/api/3/myself', {
  headers: { authorization: 'Forge as=app' },
})

// Run as a specific user
const accountId = '557058:f58131cb-b67d-43c7-b30d-6b58d40bd077'
const response = await fetch('/rest/api/3/myself', {
  headers: { authorization: `Forge as=user:${accountId}` },
})

Three rules apply to the header:

  • The Forge scheme is case-insensitive.
  • Pass headers as a plain object, not a new Headers(...) instance.
  • A malformed value falls back to the Run as setting rather than failing, so a typo is silent.

Override a HAPI call

HAPI methods take an as option that does the same job:

import * as Issues from '@scriptrunnerhq/hapi/issues'

// Run as the ScriptRunner app
const issue = await Issues.getByKey('JRA-1', { as: 'app' })

// Run as a specific user
const issue = await Issues.getByKey('JRA-1', { as: `user:${accountId}` })

When an override applies

Both overrides apply to a single request. Anything you don't override follows the item's Run as setting.

Overrides have no effect in field behaviours. Those run in the user's browser, and all their requests are made as the current user.

Some operations need app context. Reading a user's email address, for example, works regardless of their profile visibility settings, but only because Jira returns the address to the app itself.

Search documentation

Start typing to search the docs.