How to get attachments from Jira using Python

Jira, a sophisticated project management tool created by Atlassian, facilitates the planning, tracking, and release of software, making it a favorite among numerous software development teams. Jira also boasts features like customizable workflows, extensive reporting tools, and a potent API, all of which empower businesses to optimize their project management and boost productivity.

Given the platform’s versatility, it stores a variety of valuable data—and attachments are no exception. Getting attachments can help teams automate and streamline various processes, such as issue examination or data backups. And by leveraging the Jira API, you can programmatically fetch these files as needed.

To help you get attachments from the Jira API, we’ll cover everything you need to know. This includes setting up authentication for the Jira API and performing get requests to fetch attachments.

Authenticating with Jira

To interact with the Jira API and retrieve attachments, you need to authenticate your requests. 

Jira uses Basic Authentication, which requires you to include a header with your request. 

This header should be in the format <code class='blog_inline-code'>Authorization: Basic {Email Address}:{API-KEY}</code>. 

You can generate the Base-64 encoded string of your email address and API key, then use this string in your authorization header. In addition, your email address and API key are the credentials Jira uses to authenticate your API request. You can find your API key—which is a secure, revocable key—, within your Atlassian account.

Related: What you need to do to get projects from the Jira API

Pulling attachments from Jira

The Python code to pull attachments from the Jira API involves two steps. 

First, we need to hit the search API endpoint and paginate through the response. Second, we’ll retrieve the details of each issue and include the attachments by using the issue API endpoint.

Hit the search API endpoint

Here is the Python code that accomplishes this:


import requests
import base64
import json
# Authentication
email = "YOUR_EMAIL"
api_key = "YOUR_API_KEY"
base64_user_pass = base64.b64encode(f"{email}:{api_key}".encode()).decode()
# Pagination
offset = 0
limit = 50
has_next_page = True
# Search API Endpoint
search_url = "https://{DOMAIN}.atlassian.net/rest/api/3/search"
headers = {
    "Authorization": f"Basic {base64_user_pass}",
    "Accept": "application/json"
}
while has_next_page:
    query = {
        "expand": "renderedFields",
        "maxResults": limit,
        "startAt": offset
    }
    response = requests.get(search_url, headers=headers, params=query)
    data = response.json()
    if 'issues' in data:
        for issue in data['issues']:
            print(issue['id'])
    else:
        has_next_page = False
    offset += limit

This script will loop through paginated issues and print each issue's ID to the console.

Hit the issue API endpoint

The next step is to get the attachments of each issue. You can do this by hitting the issue API endpoint for each issue ID:


# Issue API Endpoint
issue_url = "https://{DOMAIN}.atlassian.net/rest/api/3/issue/{id}"
for issue_id in issue_ids:
    response = requests.get(issue_url.format(id=issue_id), headers=headers)
    issue_data = response.json()
    if 'fields' in issue_data and 'attachment' in issue_data['fields']:
        for attachment in issue_data['fields']['attachment']:
            print(attachment['filename'])

This script will print the filename of each attachment in each issue to the console.

An example of an individual item returned by this API Endpoint is:

{
    "expand": "schema,names",
    "id": "10002",
    "self": "https://myinstance.atlassian.net/rest/api/2/issue/10002",
    "key": "PROJ-10002",
    "fields": {
        "statuscategorychangedate": "2019-07-02T12:32:49.134+0000",
        "issuetype": {
            "self": "https://myinstance.atlassian.net/rest/api/2/issuetype/5",
            "id": "5",
            "description": "The sub-task of the issue",
            "iconUrl": "https://myinstance.atlassian.net/secure/viewavatar?size=medium&avatarId=10318&avatarType=issuetype",
            "name": "Sub-task",
            "subtask": true,
            "avatarId": 10318,
            "entityId": "80ed3c99-57ff-4beb-9a24-31bdb051d650",
            "hierarchyLevel": 4
        },
        "timespent": null,
        "project": {
            "self": "https://myinstance.atlassian.net/rest/api/2/project/10001",
            "id": "10001",
            "key": "PROJ",
            "name": "Project",
            "simplified": false,
            "avatarUrls": {
                "48x48": "https://myinstance.atlassian.net/secure/projectavatar?pid=10001&avatarId=10412",
                "24x24": "https://myinstance.atlassian.net/secure/projectavatar?size=small&pid=10001&avatarId=10412",
                "16x16": "https://myinstance.atlassian.net/secure/projectavatar?size=xsmall&pid=10001&avatarId=10412",
                "32x32": "https://myinstance.atlassian.net/secure/projectavatar?size=medium&pid=10001&avatarId=10412"
            },
            "projectTypeKey": "software"
        },
        "fixVersions": [],
        "customfield_10018": {
            "hasEpicLinkFieldDependency": false,
            "showField": true,
            "nonEditableReason": {
                "reason": "NOT_EDITABLE",
                "message": "This field cannot be edited in the current transition."
            }
        },
        "customfield_10019": "Some value",
        "aggregatetimespent": null,
        "workratio": -1,
        "attachment": [
            {
                "self": "https://myinstance.atlassian.net/rest/api/2/attachment/10010",
                "id": "10010",
                "filename": "picture.jpg",
                "created": "2019-07-02T12:37:53.273+0000",
                "size": 23123,
                "mimeType": "image/jpeg",
                "content": "https://myinstance.atlassian.net/secure/attachment/10010/picture.jpg",
                "thumbnail": "https://myinstance.atlassian.net/secure/thumbnail/10010/picture.jpg",
                "author": {
                    "self": "https://myinstance.atlassian.net/rest/api/2/user?accountId=557058:2baf9c2d-6aad-4b09-a0b1-df776702a9e3",
                    "accountId": "557058:2baf9c2d-6aad-4b09-a0b1-df776702a9e3",
                    "avatarUrls": {
                        "48x48": "https://myinstance.atlassian.net/secure/useravatar?avatarId=10346",
                        "24x24": "https://myinstance.atlassian.net/secure/useravatar?size=small&avatarId=10346",
                        "16x16": "https://myinstance.atlassian.net/secure/useravatar?size=xsmall&avatarId=10346",
                        "32x32": "https://myinstance.atlassian.net/secure/useravatar?size=medium&avatarId=10346"
                    },
                    "displayName": "User Name",
                    "active": true,
                    "timeZone": "America/Los_Angeles",
                    "accountType": "atlassian"
                }
            }
        ],
        "lastViewed": "2019-07-02T12:37:57.802+0000"
    }
}

Related: What you need to do to fetch comments from Jira (using Python)

Testing your Jira integration

It's a common scenario where everything seems fine during the development and testing phases, but when you go live in the production environment, unexpected issues  arise. These problems could stem from network instability, data format discrepancies, code conflicts, among other factors.

To proactively identify and address potential issues, here's what you can do:

  • Create a test plan: This plan should outline what you aim to achieve with each test, ensuring thorough coverage of all integration aspects. For example, you might include tests for user authentication or data syncing.
  • Performance testing: This step is crucial to ensure your integration can handle real-world demands. Include tests like load testing, where you simulate a high number of users, or stress testing, to see how the system performs under extreme conditions.
  • Use the right tools: Choosing the best tools can significantly enhance your testing effectiveness. Consider tools like JMeter for performance testing or SoapUI for API testing, which can provide comprehensive testing capabilities.
  • Validate JSON and XML schemas: Ensuring that your JSON and XML schemas are correct is vital for seamless data exchange. You can do this by using schema validators to check for any inconsistencies or errors in your data formats.
  • Security testing: Last but not least, security testing is paramount. This involves checking for vulnerabilities that could be exploited once the integration is live, ensuring that both your data and the system are safeguarded against potential threats.

Final thoughts

In the diverse world of ticketing systems, Jira is hardly the only player. There's a whole universe of other ticketing applications out there, such as Asana, ClickUp, and Zendesk. 

Seamlessly integrate with the ticketing systems your clients use at scale by leveraging Merge's Ticketing Unified API. You can learn more about Merge and its Ticketing Unified API by scheduling a demo with one of our integration experts.