How to get attachments from Jira using JavaScript

Jira, a dynamic ticketing system for issue tracking and project management, offers countless API endpoints that can help you get the data and documents you need. And while there’s a myriad of important types of data and documents you can collect, attachments are arguably the most important.

Fetching attachments lets you enhance data transparency, streamline operations, improve collaboration, elevate your record keeping, and much more.

To help you retrieve attachments, whether that’s for your product or your internal applications, we’ll cover how you can set up authentication for the Jira API and use 'get' requests to retrieve attachments. We’ll then introduce you to a single API endpoint that can help you connect your product with dozens of ticketing systems (including Jira).

Note: Discover how you can fetch attachments from Jira via Python by reading this article.

Authenticating with Jira

Authentication is a crucial aspect when interacting with the Jira API, as it ensures secure access to your data. 

To authenticate your requests, you need to provide the necessary credentials in the header of your HTTP request. 

The format you should use is `Authorization: Basic {Email Address}:{API-KEY}`. This means that you need to include your email address and API key, both encoded in Base64, in the header. 

Make sure to replace `{Email Address}` and `{API-KEY}` with your actual email address and API key.

Related: A guide to using JavaScript to retrieve comments from Jira 

Pulling attachments from Jira

The script below uses the `axios` library to make HTTP requests and the `btoa` function to encode the authentication header. Moreover, the `getAttachments` function fetches issues from the Jira API, with each issue being fetched by the `GET /search` API endpoint. The script then iterates over each issue and fetches its attachments using the `GET /issue/{id}` API endpoint. Each attachment is added to the `attachments` array.

Finally, the script continues fetching issues in batches of 50 (the `maxResults` query parameter) until there are no more issues to fetch (when the `issues` array is empty).


// Required node modules
const axios = require('axios');
const btoa = require('btoa');
// Your Jira domain 
const DOMAIN = 'your-domain';
// Your email address and API key
const EMAIL = 'your-email';
const API_KEY = 'your-api-key';
// Basic authentication header
const AUTH_HEADER = {
  headers: {
    Authorization: `Basic ${btoa(`${EMAIL}:${API_KEY}`)}`
  }
};
const MAX_RESULTS = 50;
// Function to get attachments
async function getAttachments() {
  let attachments = [];
  let startAt = 0;
  while (true) {
    const searchUrl = `https://${DOMAIN}.atlassian.net/rest/api/3/search?expand=renderedFields&maxResults=${MAX_RESULTS}&startAt=${startAt}`;
    const searchResponse = await axios.get(searchUrl, AUTH_HEADER);
    const issues = searchResponse.data.issues;
    if (issues.length === 0) {
      break;
    }
    for (const issue of issues) {
      const issueUrl = `https://${DOMAIN}.atlassian.net/rest/api/3/issue/${issue.id}`;
      const issueResponse = await axios.get(issueUrl, AUTH_HEADER);
      attachments.push(...issueResponse.data.fields.attachment);
    }
    startAt += MAX_RESULTS;
  }
  return attachments;
}

Once all issues and their respective attachments have been fetched, the `getAttachments` function returns the `attachments` array. Here’s an example of an individual item returned by this API endpoint:


{
    "expand": "schema,names",
    "id": "10236",
    "self": "https://your-domain.atlassian.net/rest/api/3/issue/10236",
    "key": "JRA-9",
    "fields": {
        "statuscategorychangedate": "2021-09-01T03:37:29.272+0000",
        "issuetype": {
            "self": "https://your-domain.atlassian.net/rest/api/3/issuetype/1",
            "id": "1",
            "description": "A problem or error.",
            "iconUrl": "https://your-domain.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype",
            "name": "Bug",
            "subtask": false,
            "avatarId": 10303,
            "entityId": "id_1",
            "hierarchyLevel": 0
        },
        "project": {
            "self": "https://your-domain.atlassian.net/rest/api/3/project/10000",
            "id": "10000",
            "key": "JRA",
            "name": "Sample Project",
            "projectTypeKey": "software",
            "simplified": true,
            "avatarUrls": {
                "48x48": "https://your-domain.atlassian.net/secure/projectavatar?pid=10000&avatarId=10424",
                "24x24": "https://your-domain.atlassian.net/secure/projectavatar?size=small&pid=10000&avatarId=10424",
                "16x16": "https://your-domain.atlassian.net/secure/projectavatar?size=xsmall&pid=10000&avatarId=10424",
                "32x32": "https://your-domain.atlassian.net/secure/projectavatar?size=medium&pid=10000&avatarId=10424"
            }
        },
        "fixVersions": [],
        "workratio": -1,
        "issuerestriction": {
            "issuerestrictions": {},
            "shouldDisplay": false
        },
        "watches": {
            "self": "https://your-domain.atlassian.net/rest/api/3/issue/JRA-9/watchers",
            "watchCount": 3,
            "isWatching": false
        },
        "lastViewed": "2021-09-01T05:07:46.798+0000",
        "created": "2021-09-01T03:37:29.272+0000",
        "priority": {
            "self": "https://your-domain.atlassian.net/rest/api/3/priority/3",
            "iconUrl": "https://your-domain.atlassian.net/images/icons/priorities/medium.svg",
            "name": "Medium",
            "id": "3"
        },
        "labels": [],
        "customfield_10018": {
            "hasEpicLinkFieldDependency": false,
            "showField": true,
            "nonEditableReason": {
                "reason": "NOT_EDITABLE",
                "message": "This field is not editable."
            }
        },
        "customfield_10019": "10021_*:*_1_*:*_10105_*|*_10100_*:*_1",
        "versions": [],
        "issuelinks": [],
        "updated": "2021-09-01T05:07:46.798+0000",
        "status": {
            "self": "https://your-domain.atlassian.net/rest/api/3/status/1",
            "description": "The issue is open and ready for the assignee to start work on it.",
            "iconUrl": "https://your-domain.atlassian.net/images/icons/statuses/open.png",
            "name": "Open",
            "id": "1",
            "statusCategory": {
                "self": "https://your-domain.atlassian.net/rest/api/3/statuscategory/2",
                "id": 2,
                "key": "new",
                "colorName": "blue-gray",
                "name": "To Do"
            }
        },
        "components": [],
        "timetracking": {},
        "attachment": [],
        "summary": "Login screen has rendering issue",
        "creator": {
            "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=5b10ac8d82e05b22cc7d4ef5",
            "accountId": "5b10ac8d82e05b22cc7d4ef5",
            "emailAddress": "admin@example.com",
            "avatarUrls": {
                "48x48": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/48",
                "24x24": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/24",
                "16x16": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/16",
                "32x32": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/32"
            },
            "displayName": "admin",
            "active": true,
            "timeZone": "Australia/Sydney",
            "accountType": "atlassian"
        },
        "subtasks": [],
        "reporter": {
            "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=5b10ac8d82e05b22cc7d4ef5",
            "accountId": "5b10ac8d82e05b22cc7d4ef5",
            "emailAddress": "admin@example.com",
            "avatarUrls": {
                "48x48": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/48",
                "24x24": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/24",
                "16x16": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/16",
                "32x32": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/5b10ac8d82e05b22cc7d4ef5/32"
            },
            "displayName": "admin",
            "active": true,
            "timeZone": "Australia/Sydney",
            "accountType": "atlassian"
        },
        "customfield_10000": "example custom field",
        "aggregateprogress": {
            "progress": 0,
            "total": 0
        },
        "progress": {
            "progress": 0,
            "total": 0
        },
        "votes": {
            "self": "https://your-domain.atlassian.net/rest/api/3/issue/JRA-9/votes",
            "votes": 0,
            "hasVoted": false
        },
        "comment": {
            "comments": [],
            "self": "https://your-domain.atlassian.net/rest/api/3/issue/10236/comment",
            "maxResults": 0,
            "total": 0,
            "startAt": 0
        },
        "worklog": {
            "startAt": 0,
            "maxResults": 20,
            "total": 0,
            "worklogs": []
        }
    }
}

       

Related: What you need to do to get projects from Jira (using JavaScript)

Tips for testing your Jira integration

Even if your integration is performing well in the initial stages, various issues can pop up in production, whether that's due to configuration errors, compatibility issues, unforeseen user scenarios, etc.

To effectively preempt these challenges, here are some best practices to follow:

  • Understand the API specifications: It’s essential to grasp the API specs fully. This means digging into the API documentation to understand request and response formats, endpoint behaviors, and error handling.
  • Set up a testing environment: Using this environment, you can simulate real-world scenarios like how the integration handles large data volumes or reacts to invalid user inputs to ensure robustness.
  • Test for different HTTP methods: Testing how the integration responds to various HTTP methods (GET, POST, PUT, DELETE) is crucial. Tools like Postman or Swagger can be really helpful for this, allowing you to send requests and inspect responses easily.
  • Check response status codes: Paying attention to response status codes is vital to ensure the integration is communicating correctly. You can do this by using tools to monitor and validate the codes returned by the API for different requests, ensuring they align with expected outcomes.
  • Continuous Integration and Continuous Deployment (CI/CD): Implementing CI/CD practices is important for maintaining a smooth and efficient development pipeline. This approach helps in automatically testing and deploying changes, ensuring that the integration remains stable and up-to-date.

Final thoughts

It’s worth remembering that your clients might be using other ticketing systems, whether that’s Asana, ServiceNow, ClickUp, etc.

You can offer customer-facing integrations with all of the ticketing systems your clients use by connecting to Merge's Ticketing Unified API.

Learn more about Merge’s Ticketing Unified API, along with the other features and capabilities provided by the platform, by scheduling a demo with one of our integration experts.