NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    【Atlassian】Jira API (Data Center/ Cloud)の実行方法 (スクリプト例付き)

    本記事は  ブログ書き初めウィーク  2日目の記事です。
    📝  1日目  ▶▶ 本記事 ▶▶  3日目  📅

    はじめに

    はじめまして、ネットコムに入社して6か月目の西出です。ようやく気軽に話せる人が増えてきました。

    今回、業務でJiraのAPIを使って、Jiraチケットから情報を取ってくる機会があったので、Jira APIの実行方法をまとめていきたいと思います。

    業務では、Jira Software Data Center(Server)を使用しておりますが、試しに個人でJira CloudでもAPIを実行してみたので、Jira Software Data Center(Server)とJira Cloud、両方のAPI実行方法を説明していきます。

    (以下、Jira Software Data Center(Server)をJira Data Centerと略記します。)

    (Jira Cloudは無料でアカウント作成が可能です! 参照: Unlock the Best Jira Pricing Plans for Your Team Today | Atlassian)

    AtlassianとJiraの概要

    AtlassianとJiraについて、簡潔にまとめます。

    Jiraについては、2種類あることを知らなかったので、それぞれの違いについてもまとめました。

    Atlassianとは

    Atlassianは、ソフトウェア開発・IT運用チーム向けのコラボレーション製品を提供する企業で、代表的な製品に Jira(プロジェクト・課題管理)、Confluence(情報共有・Wiki) などがあります。

    Jiraは課題管理・プロジェクト管理の中心的なツールで、REST APIを通じて外部からデータ取得や自動化が可能です。

    Jira Data Center / Cloud の違い

    Jira Data Center
    • オンプレ or 自社ホスティング向けの Jira
      • 会社のサーバーや自社のクラウド(AWS など)に 自分たちでインストールして運用するタイプ
      • インフラ・アップデート・バックアップは自社で管理
      • カスタマイズ自由度は高いが、管理コストも大きい
      • 高度なセキュリティ・コンプライアンスに対応
      • 以前は「Jira Software Server」、現在は「Jira Software Data Center」が主流
    Jira Cloud
    • Atlassian がクラウドで提供する SaaS 版 Jira
      • インフラ管理はすべて Atlassian 側
      • 常に最新機能を自動反映
      • カスタマイズの自由度はオンプレより制限があるが、管理コストは大幅に軽い
      • セキュリティ・コンプライアンスの対応はプランによる
    まとめ
    種類 運用場所 管理者の負担 カスタマイズ性 特徴
    Jira Data Center 自社サーバー or 自前クラウド 大きい 高い インフラ含めて全部自社管理
    Jira Cloud Atlassian のクラウド 小さい 中程度 常に最新、SaaS、手軽

    Jira API 実行方法

    APIを実行するためのトークンを取得し、それらを環境変数に登録し、スクリプトを実行、という流れで説明していきたいと思います。

    Jira Data Center

    個人用アクセストークン取得

    ※1 Jira Data Center トークンの権限はそのユーザーがJira Data Centerで持つ権限と同じです。

    ※2 Jira Data Center の管理者になったことが無く、憶測になりますが、以下の可能性が考えられます。

    • 管理者が個人アクセストークン発行を禁止している可能性がある
    • 管理者の設定により、画面の表示が変わる可能性がある

    ①右上のアイコンボタンを押下し、プロファイルボタンを押下

    ②左側のペインの個人用アクセストークンを選択

    ③トークンを作成ボタンを押下

    ④任意の名前、期限切れまでの日数を入力し、作成ボタンを押下

    ⑤表示されたトークン情報を保管

    APIリファレンスの参照

    Jira Data Centerは自社環境にインストールされているJira Data Centerのバージョンにより、APIのバージョンも異なります。

    そのため、以下のcurlコマンドでJira Data Centerのバージョンを確認し、そのバージョンに適したAPIリファレンスを参照する必要があります。

    curl --request GET \
      --url "<Jira Data Center Base URL>/rest/api/2/serverInfo" \
      --header "Accept: application/json" \
      --header "Authorization: Bearer <Jira Data Center個人用アクセストークン>"
    

    コマンドの実行結果にversionパラメータがあるため、その値を以下の${version}部分に入力することで、適切なAPIリファレンスが参照できます。

    https://docs.atlassian.com/software/jira/docs/api/REST/${version}/

    (例:2026/01/14時点での最新バージョン)

    Jira Cloud

    Jira API トークン取得

    ①右上のアイコンボタンを押下し、アカウント設定を管理ボタンを押下

    ②セキュリティタブを選択

    ③API トークンの作成と管理を押下

    ④認証コード入力

    ⑤API トークンを作成ボタンを押下

    ⑥任意の名前と有効期限を入力し、作成ボタンを押下

    ⑦表示されたトークン情報を保管

    APIリファレンスの参照

    Jira CloudではAPIはグローバルに公開されている最新のバージョンのAPIを利用することができます。

    以下URLを開いた画面の、以下赤枠部分から、任意のAPIのリファレンスを参照できます。

    https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#about

    スクリプト例紹介

    今回の業務での要件は、対象範囲に作業したチケットの作業ログの一覧を取得したいというものでした。

    なので、以下のようなロジックのスクリプトを作成しました。

    • プロジェクトに関連する指定日以降に更新されたチケットを全取得

      • 更新日の日付範囲指定にすると、終了日以降に更新されたチケットが対象から外れてしまうため、開始日のみ指定

        • スクリプトやExcelで後で対象範囲を絞れるので終了日を指定しなくていいと判断
    • そのチケットの作業ログの一覧を作業ログごとに1行ずつCSVで出力する

    上記のロジックに加えて、Jira Data CenterはBearer認証、Jira CloudはBasic認証という認証方法の違いがあるため、今回はどちらでも使用できるようなスクリプト例にしております。

    前提

    pythonでスクリプトを作成しています。pythonの実行環境が必要になります。今回はpythonの環境構築手順などは省略します。

    また、requestsライブラリのインストールが必要です。

    環境変数登録

    ■Linux/macの場合

    # Jira Data Center Base URL
    export JIRA_DATA_CENTER_BASE_URL="<Jira Data Center Base URL>"
    # Jira Cloud Base URL
    export JIRA_CLOUD_BASE_URL="<Jira Cloud Base URL>"
    # Jira Data Center個人用アクセストークン
    export JIRA_DATA_CENTER_ACCESS_TOKEN="<Jira Data Center個人用アクセストークン>"
    # Jira Cloud Emailアドレス
    export JIRA_CLOUD_EMAIL="<Jira Cloud Emailアドレス>"
    # Jira Cloud APIトークン
    export JIRA_CLOUD_API_TOKEN="<Jira Cloud APIトークン>"
    

    ■windows(PowerShell)の場合

    # Jira Data Center Base URL
    $env:JIRA_DATA_CENTER_BASE_URL = "<Jira Data Center Base URL>"
    # Jira Cloud Base URL
    $env:JIRA_CLOUD_BASE_URL = "<Jira Cloud Base URL>"
    # Jira Data Center個人用アクセストークン
    $env:JIRA_DATA_CENTER_ACCESS_TOKEN = "<Jira Data Center個人用アクセストークン>"
    # Jira Cloud Emailアドレス
    $env:JIRA_CLOUD_EMAIL = "<Jira Cloud Emailアドレス>"
    # Jira Cloud APIトークン
    $env:JIRA_CLOUD_API_TOKEN = "<Jira Cloud APIトークン>"
    スクリプト実行

    以下の変数の値を任意に変更してから実行してください。実行すると、jira_worklogs.csvというファイルが出力されます。

    • IS_CLOUD:Jira Data CenterならFalse、Jira CloudならTrue

    • PROJECT_KEY:Jira Data Centerならプロジェクト名、Jira Cloudならスペース名

    • START_DATE:更新指定開始日

    import requests
    
    import os
    import csv
    from datetime import datetime
    
    # ====== COMMON CONFIGURATION ======
    # Jira Data Center or Jira Cloud
    IS_CLOUD = True
    # Jira Data Center URL
    JIRA_DATA_CENTER_BASE_URL = os.getenv("JIRA_DATA_CENTER_BASE_URL")
    # Jira Data Center 個人用アクセストークン
    JIRA_DATA_CENTER_ACCESS_TOKEN = os.getenv("JIRA_DATA_CENTER_ACCESS_TOKEN")
    # Jira Cloud URL
    JIRA_CLOUD_BASE_URL = os.getenv("JIRA_CLOUD_BASE_URL")
    # Jira Cloud Email
    JIRA_CLOUD_EMAIL = os.getenv("JIRA_CLOUD_EMAIL")
    # Jira Cloud APIトークン
    JIRA_CLOUD_API_TOKEN = os.getenv("JIRA_CLOUD_API_TOKEN")
    
    # ====== CONFIGURATION ======
    # プロジェクトキー
    PROJECT_KEY = "<プロジェクト名>"
    # 開始日
    START_DATE = "<開始日>"
    
    OUTPUT_FILE = "jira_worklogs.csv"
    
    # ====== AUTHENTICATION ======
    HEADERS = {
        "Accept": "application/json",
        "Authorization": f"Bearer {JIRA_DATA_CENTER_ACCESS_TOKEN}"
    }
    
    AUTH = (JIRA_CLOUD_EMAIL, JIRA_CLOUD_API_TOKEN)
    
    def safe_request(method, url, **kwargs):
        """共通のリクエスト関数"""
        try:
            response = requests.request(method, url, timeout=15, **kwargs)
    
            # ステータスコードチェック
            if response.status_code >= 400:
                raise Exception(f"API Error {response.status_code}: {response.text[:200]}")
    
            # JSON decode
            try:
                return response.json()
            except ValueError:
                raise Exception("JSON decode error: レスポンスがJSONではありません")
    
        except requests.exceptions.Timeout:
            raise Exception("Timeout Error: APIレスポンスが15秒以内に返りませんでした")
    
        except requests.exceptions.ConnectionError:
            raise Exception("Connection Error: Jiraサーバーに接続できません")
    
        except Exception as e:
            raise Exception(f"Unexpected Error: {str(e)}")
    
    
    def search_issues(start_date):
        """
        指定した更新日付範囲でJiraチケットをJQL検索する
        """
        # JQL生成
        jql = (
            f'project = "{PROJECT_KEY}" '
            f'AND updated >= "{start_date}" '
            'ORDER BY updated ASC'
        )
    
        headers = HEADERS
        auth = AUTH
    
        issues = []
        start_at = 0
        max_results = 50
        nextPageToken = ""
    
        if IS_CLOUD:
            url = f"{JIRA_CLOUD_BASE_URL}/rest/api/3/search/jql"
        else:
            url = f"{JIRA_DATA_CENTER_BASE_URL}/rest/api/2/search"
    
        while True:
            if IS_CLOUD:
                params = {
                    "jql": jql,
                    "nextPageToken": nextPageToken,
                    "maxResults": max_results,
                    "fields": "summary,assignee",
                }
                data = safe_request("GET", url, headers=headers, params=params, auth=auth)
                issues.extend(data["issues"])
    
                # 最終ページの場合
                if data["isLast"]:
                    break
    
                nextPageToken = data["nextPageToken"]
            else:
                params = {
                    "jql": jql,
                    "startAt": start_at,
                    "maxResults": max_results,
                    "fields": "summary,assignee"
                }
                data = safe_request("GET", url, headers=headers, params=params)
                issues.extend(data.get("issues", []))
    
                # 最終ページの場合
                if start_at + max_results >= data.get("total", 0):
                    break
                start_at += max_results
    
        return issues
    
    
    def get_worklogs(issue_key):
        """
        各チケットの作業ログを取得する
        """
        headers = HEADERS
        auth = AUTH
    
        if IS_CLOUD:
            url = f"{JIRA_CLOUD_BASE_URL}/rest/api/3/issue/{issue_key}/worklog"
            data = safe_request("GET", url, headers=headers, auth=auth)
        else:
            url = f"{JIRA_DATA_CENTER_BASE_URL}/rest/api/2/issue/{issue_key}/worklog"
            data = safe_request("GET", url, headers=headers)
    
        return data.get("worklogs", [])
    
    
    def format_worklog_start(started_str):
        """
        Jiraの作業ログstarted文字列から
        ミリ秒・タイムゾーンを除去して整形する
        """
        try:
            dt = datetime.strptime(started_str, "%Y-%m-%dT%H:%M:%S.%f%z")
            return dt.strftime("%Y-%m-%d %H:%M:%S")
        except ValueError:
            # ミリ秒なしパターンのフォールバック(レア)
            dt = datetime.strptime(started_str, "%Y-%m-%dT%H:%M:%S%z")
            return dt.strftime("%Y-%m-%d %H:%M:%S")
    
    
    def main(start_date, output_csv="jira_worklogs.csv"):
        try:
            # 日付範囲のチケット一覧を取得
            issues = search_issues(start_date)
        except Exception as e:
            print(f"[ERROR] チケット検索中にエラーが発生しました: {e}")
            return
    
        # CSV出力
        with open(output_csv, "w", newline="", encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(
                [
                    "ID",
                    "タイトル",
                    "担当者",
                    "作業ログ開始時間",
                    "作業ログ消費時間",
                    "作業ログ記入者",
                ]
            )
    
            for issue in issues:
                key = issue["key"]
                fields = issue["fields"]
    
                # チケットタイトル
                title = fields["summary"]
                # チケット担当者
                assignee = fields["assignee"]["displayName"] if fields["assignee"] else ""
    
                # 作業ログ取得(エラーの場合はスキップして続行)
                try:
                    worklogs = get_worklogs(key)
                except Exception as e:
                    print(f"[WARN] 作業ログ取得失敗: {key} - {e}")
                    continue
    
                for worklog in worklogs:
                    # 作業ログの開始時間(msとタイムゾーン省く)
                    start_time = start_time = format_worklog_start(worklog["started"])
                    # 消費時間[秒]
                    time_spent_seconds = worklog["timeSpentSeconds"]
                    # 作業ログ入力者
                    author = worklog["author"]["displayName"]
    
                    writer.writerow(
                        [key, title, assignee, start_time, time_spent_seconds, author]
                    )
    
        print(f"CSV出力完了: {output_csv}")
    
    
    if __name__ == "__main__":
        main(START_DATE, OUTPUT_FILE)
    
    実行結果例

    実際の業務データは載せられないので、実行結果をマスクした例を記載します。

    ID,タイトル,担当者,作業ログ開始時間,作業ログ消費時間,作業ログ記入者
    INFRA-4632,チケット1,担当者1,2025-10-22 13:24:00,25200,担当者1
    INFRA-4171,チケット2,担当者2,2025-08-20 13:53:00,25200,担当者2
    INFRA-4171,チケット2,担当者2,2025-08-27 14:28:00,21600,担当者2
    INFRA-4171,チケット2,担当者2,2025-09-03 15:21:00,25200,担当者2
    INFRA-4171,チケット2,担当者2,2025-09-17 14:21:00,3600,担当者2
    INFRA-4171,チケット2,担当者2,2025-09-24 15:20:00,21600,担当者3
    INFRA-4171,チケット2,担当者2,2025-10-01 15:59:00,28800,担当者3
    INFRA-4171,チケット2,担当者2,2025-10-08 13:16:00,7200,担当者3
    INFRA-4416,チケット3,担当者3,2025-08-13 19:20:00,7200,担当者3
    INFRA-4666,チケット4,担当者4,2025-10-29 15:15:00,18000,担当者4
    INFRA-4666,チケット4,担当者4,2025-11-05 11:29:00,3600,担当者4

    Tips

    今回は、1つのチケットに対して、複数人が作業する場合があるかつ、日付範囲で作業ログを合計したいという要件があったため、Jira APIを使っていますが、1つのチケットに対して、1人しか作業していないかつ、チケットの合計の消費時間のみ取得したい場合は、ConfluenceのJira Issues マクロの使用をおすすめします。

    以下のようにすると、ステータスがクローズの担当者と作業ログの合計が載ったチケット一覧が表示できます。

    ※業務情報が含まれるため、黒塗りしています。

    まとめ

    今回、Jira APIの実行方法をData Center / Cloud の両面で実際に実行できるスクリプトを付けて、説明してきました。

    Jiraでは多数のAPIが用意されており、取得できる情報は多様にあります。

    ただし、APIで意味のあるデータが取れるかどうかは、Jiraチケットの運用次第だと思います。

    正しく、Jiraチケットを運用して、Jiraの価値を最大化していきましょう!

    おわりに

    次は、Jira Cloudのスコープ付きAPIキーの使用方法でつまずいたので、それについて書こうと思ってます。

    この記事が、どなたかのヒントになれば、うれしいです。

    執筆者:西出 文滉 インフラエンジニア