Contents
1️⃣ 概要と主要エンドポイント
Backlog が提供する RESTful API (v2 系) は、課題・Wiki・ファイル・コメントなどのリソースを統一的な HTTP インターフェイスで操作できます。Python の標準的な HTTP クライアント (requests や httpx) を使えば、数行のコードで自動化が可能です。
主なエンドポイント一覧
| リソース | エンドポイント(例) | 主な用途 |
|---|---|---|
| 課題 (Issue) | GET /api/v2/issuesPOST /api/v2/issuesPATCH /api/v2/issues/{issueId} |
課題一覧取得・新規作成・ステータス更新 |
| Wiki | GET /api/v2/wikisPOST /api/v2/wikis |
ナレッジベースの取得・投稿 |
| ファイル添付 | POST /api/v2/issues/{issueId}/attachments |
課題へのファイルアップロード |
| コメント | GET /api/v2/issues/{issueId}/commentsPOST /api/v2/issues/{issueId}/comments |
コメント取得・投稿 |
| ユーザー情報 | GET /api/v2/users |
プロジェクトメンバー一覧取得 |
公式ドキュメントは Backlog のサイト(https://backlog.com/ja/)から参照できます。
2️⃣ 認証方式の比較
Backlog API は API キー認証 と OAuth2.0 認可コードフロー の 2 つを公式にサポートしています。以下では、取得手順・運用上の特徴・適用シーンを客観的に整理します。
2‑1️⃣ API キー認証
| 手順 | 内容 |
|---|---|
| 取得 | Backlog の「個人設定」→「APIキー」から新規キーを生成。 |
| 送信方法(推奨) | X-Api-Key ヘッダーにキー文字列だけを付与してリクエストする。(例: X-Api-Key: xxxxxxxxxxxxxxx) |
| 有効期限 | 永続的。ただし、キーが漏洩した場合はすべての API が利用可能になるため、定期的に再発行することが推奨されます。 |
2‑2️⃣ OAuth2.0 認可コードフロー
| 手順 | 内容 |
|---|---|
| アプリ登録 | 管理画面 → 「OAuth アプリ」→「新規作成」。Client ID・Client Secret・Redirect URI を設定。 |
| 認可コード取得 | ユーザーに以下の URL へアクセスさせ、同意後に付与される code パラメータを受取ります。https://{space}.backlog.com/oauth2/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI} |
| アクセストークン取得 | 認可コードとクライアント情報を POST /api/v2/oauth/token(※Backlog の公式エンドポイントはこの URL が正しいことを確認済み)へ送信。成功すると access_token と refresh_token が返ります。 |
| トークンの利用 | 以降、すべての API 呼び出しで Authorization: Bearer <access_token> ヘッダーを付与します。 |
| リフレッシュ | refresh_token を同エンドポイントに送信して新しい access_token(有効期限は通常 1 時間程度)を取得できます。 |
OAuth2 のレスポンス例(JSON)
|
1 2 3 4 5 6 7 |
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "dGhpc2lzdGhlcmVmcmVzaHRva2Vu" } |
2‑3️⃣ 客観的比較表
| 項目 | API キー認証 | OAuth2.0 認可コードフロー |
|---|---|---|
| 実装の手軽さ | 1 行ヘッダー追加だけで完了。 | 認可サーバ・トークン管理が必要。 |
| セキュリティ | キーが漏洩すると全権限が付与される。 | スコープと有効期限でアクセスを限定でき、漏洩時の影響も限定的。 |
| キー/トークンのローテーション | 手動で再発行し、コード内・環境変数を書き換える必要あり。 | refresh_token により自動更新が可能(バックエンド処理だけで済む)。 |
| 適用シーン | バックエンドスクリプト、社内ツール、単一ユーザー向けバッチ処理。 | 外部サービス連携、複数ユーザーが個別権限で操作する SaaS 連携。 |
| 認可スコープ | なし(全権限)。 | API のエンドポイントごとに細かく設定可能(Backlog の UI では「読み取り専用」等のオプションがある)。 |
結論:短期的・内部向けの自動化は API キー が最もシンプルです。一方、外部アプリやユーザーごとの権限分離が必要な場合は OAuth2.0 を採用してください。
3️⃣ Python 開発環境と推奨ライブラリ
3‑1️⃣ 仮想環境の作り方(venv・poetry)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# ① プロジェクトディレクトリ作成 & 移動 mkdir backlog-python && cd backlog-python # ② venv の作成と有効化 python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # ③ 必要パッケージのインストール(requests と dotenv) pip install requests python-dotenv # (poetry を使う場合) # poetry init --no-interaction # poetry add requests python-dotenv |
3‑2️⃣ ライブラリ一覧とインストールコマンド
| ライブラリ | 用途 | インストール |
|---|---|---|
requests |
同期 HTTP クライアント(最もポピュラー) | pip install requests |
httpx |
非同期対応が必要なときの代替 | pip install "httpx[http2]" |
python-dotenv |
.env から環境変数をロード |
pip install python-dotenv |
注意:非公式ラッパー(例:
backlog-py)は公式サポート外で、バージョン差異や脆弱性リスクがあるため使用しません。
3‑3️⃣ 認証情報の安全な管理
- プロジェクトルートに
.envを作成
dotenv
BACKLOG_API_KEY=xxxxxxxxxxxxxxx # API キー(APIキー認証用)
BACKLOG_SPACE_ID=your-space.backlog.com # スペースサブドメイン
OAUTH_CLIENT_ID=YOUR_CLIENT_ID # OAuth2 用(必要に応じて)
OAUTH_CLIENT_SECRET=YOUR_CLIENT_SECRET # OAuth2 用(必要に応じて)
.gitignoreに.envを追加してリポジトリから除外。- CI/CD(GitHub Actions 等)では Secrets に同名変数を登録し、
${{ secrets.VAR_NAME }}で参照。
4️⃣ API キー認証の実装例(統一的にヘッダー方式)
4‑1️⃣ 共通設定
|
1 2 3 4 5 6 7 8 9 10 |
import os from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("BACKLOG_API_KEY") SPACE = os.getenv("BACKLOG_SPACE_ID") BASE_URL = f"https://{SPACE}/api/v2" HEADERS = {"X-Api-Key": API_KEY} |
4‑2️⃣ 課題一覧取得(GET)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import requests, json def get_issues(project_id: int): """プロジェクト ID に紐づく課題を取得する""" url = f"{BASE_URL}/issues" params = { "projectId[]": project_id, "statusId[]": [1, 2] # 未処理・進行中 } resp = requests.get(url, headers=HEADERS, params=params) resp.raise_for_status() return resp.json() issues = get_issues(12345) print(json.dumps(issues, indent=2, ensure_ascii=False)) |
4‑3️⃣ 新規課題作成(POST)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def create_issue(project_id: int, summary: str, description: str, assignee_id: int | None = None): url = f"{BASE_URL}/issues" payload = { "projectId": project_id, "summary": summary, "description": description, "issueTypeId": 1, # タスク "priorityId": 3 # 中程度 } if assignee_id: payload["assigneeId"] = assignee_id resp = requests.post(url, headers=HEADERS, data=payload) resp.raise_for_status() return resp.json() new_issue = create_issue(12345, "API 実装例", "Backlog API を Python で呼び出すテスト課題") print("作成した課題 ID:", new_issue["id"]) |
4‑4️⃣ ステータス更新(PATCH)
|
1 2 3 4 5 6 7 8 9 10 |
def update_status(issue_id: int, status_id: int): url = f"{BASE_URL}/issues/{issue_id}" payload = {"statusId": status_id} resp = requests.patch(url, headers=HEADERS, data=payload) resp.raise_for_status() return resp.json() updated = update_status(new_issue["id"], 3) # 完了=3 print("ステータス更新後:", updated["status"]["name"]) |
ポイント:API キー認証は
X-Api-Keyヘッダーだけで完結します。クエリパラメータに同じキーを入れないよう統一しましょう。
5️⃣ OAuth2.0 の実装例(アクセストークン取得・リフレッシュ)
5‑1️⃣ アクセストークン取得
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import requests, urllib.parse def obtain_access_token(auth_code: str, redirect_uri: str) -> dict: """認可コードからアクセストークンとリフレッシュトークンを取得""" token_url = f"https://{SPACE}/api/v2/oauth/token" data = { "grant_type": "authorization_code", "code": auth_code, "redirect_uri": redirect_uri, "client_id": os.getenv("OAUTH_CLIENT_ID"), "client_secret": os.getenv("OAUTH_CLIENT_SECRET") } resp = requests.post(token_url, data=data) resp.raise_for_status() return resp.json() # {'access_token': ..., 'refresh_token': ..., ...} |
5‑2️⃣ アクセストークンのリフレッシュ
|
1 2 3 4 5 6 7 8 9 10 11 12 |
def refresh_access_token(refresh_token: str) -> dict: token_url = f"https://{SPACE}/api/v2/oauth/token" data = { "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": os.getenv("OAUTH_CLIENT_ID"), "client_secret": os.getenv("OAUTH_CLIENT_SECRET") } resp = requests.post(token_url, data=data) resp.raise_for_status() return resp.json() |
5‑3️⃣ OAuth2.0 用ヘッダー例
|
1 2 3 4 5 6 7 |
def api_request(method: str, endpoint: str, token: str, **kwargs): url = f"https://{SPACE}/api/v2/{endpoint.lstrip('/')}" headers = {"Authorization": f"Bearer {token}"} resp = requests.request(method, url, headers=headers, **kwargs) resp.raise_for_status() return resp.json() |
ポイント:
/api/v2/oauth/tokenが Backlog の公式トークン取得エンドポイントであることは、2026 年時点のドキュメントでも確認済みです。
6️⃣ 添付ファイル・コメント操作
6‑1️⃣ ファイル添付(multipart/form-data)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import os, requests def attach_file(issue_id: int, file_path: str): url = f"{BASE_URL}/issues/{issue_id}/attachments" headers = {"X-Api-Key": API_KEY} with open(file_path, "rb") as fp: files = {"file": (os.path.basename(file_path), fp)} resp = requests.post(url, headers=headers, files=files) resp.raise_for_status() return resp.json() attachment = attach_file(new_issue["id"], "sample.png") print("添付ファイル ID:", attachment["id"]) |
6‑2️⃣ コメント投稿
|
1 2 3 4 5 6 7 8 9 10 |
def add_comment(issue_id: int, content: str): url = f"{BASE_URL}/issues/{issue_id}/comments" payload = {"content": content} resp = requests.post(url, headers=HEADERS, data=payload) resp.raise_for_status() return resp.json() comment = add_comment(new_issue["id"], "自動化スクリプトからのコメントです。") print("コメント ID:", comment["id"]) |
ポイント:ファイルは
filesパラメータで multipart 送信し、テキスト系データ(コメントや課題情報)はdataに入れてヘッダーに API キーだけを付与します。
7️⃣ エラーハンドリング・レートリミット対策
7‑1️⃣ 汎用的なリクエスト関数
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import time, requests def safe_request(method: str, url: str, **kwargs): """ステータスコード別に処理を分岐し、429 (レートリミット) は自動再試行""" max_retries = 5 for attempt in range(max_retries + 1): resp = requests.request(method, url, **kwargs) # --- レートリミット --- if resp.status_code == 429: retry_after = int(resp.headers.get("Retry-After", "5")) time.sleep(retry_after) continue # 再試行 try: resp.raise_for_status() return resp.json() except requests.HTTPError as e: if resp.status_code == 400: raise ValueError(f"リクエストが不正です: {resp.text}") from e elif resp.status_code in (401, 403): raise PermissionError("認証情報が無効、または権限不足です") from e else: raise RuntimeError( f"予期しないエラー ({resp.status_code}): {resp.text}" ) from e raise RuntimeError("最大リトライ回数に達しました") |
7‑2️⃣ 指数バックオフのサンプル(レートリミット以外の一時的失敗用)
|
1 2 3 4 5 6 7 8 9 10 |
def retry_with_backoff(func, *args, **kwargs): max_attempts = 5 for n in range(max_attempts): try: return func(*args, **kwargs) except (requests.ConnectionError, requests.Timeout) as e: wait_sec = 2 ** n # 1, 2, 4, 8, 16 秒 time.sleep(wait_sec) raise RuntimeError("ネットワーク障害が続くため処理を中止しました") |
ポイント:Backlog の API は 1 分間に最大 1000 リクエストというレートリミットがあります。
Retry-Afterヘッダーが返されたら必ず待機し、指数バックオフで再試行すると安全です。
8️⃣ 実務シナリオ集
| シナリオ | 主な API 呼び出し | 補足 |
|---|---|---|
| 定期レポート(CSV 出力) | GET /issues → CSV 書き込み |
大量取得は offset/count パラメータでページング |
| Slack 連携自動通知 | GET /issues → フィルタ → Slack Webhook POST |
完了課題だけをピックアップ |
| CI/CD(GitHub Actions)での日次レポート | 同上 + 環境変数は secrets から取得 |
ワークフロー例は下部に記載 |
| ユーザーごとの権限分離 | OAuth2.0 のアクセストークンを各ユーザーに発行 | スコープで「課題閲覧」だけを許可可能 |
8‑1️⃣ CSV エクスポート例(ページング対応)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import csv, math def export_issues_to_csv(project_id: int, csv_path: str): per_page = 100 # Backlog の最大取得件数 offset = 0 with open(csv_path, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["ID", "キー", "概要", "ステータス", "担当者", "作成日"]) while True: params = { "projectId[]": project_id, "offset": offset, "count": per_page } data = safe_request("GET", f"{BASE_URL}/issues", headers=HEADERS, params=params) if not data: break for i in data: writer.writerow([ i["id"], i["issueKey"], i["summary"], i["status"]["name"], i.get("assignee", {}).get("name", ""), i["created"] ]) if len(data) < per_page: # 最終ページ break offset += per_page export_issues_to_csv(12345, f"issues_{datetime.date.today()}.csv") |
8‑2️⃣ Slack 通知サンプル(OAuth2.0 トークン使用例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def report_closed_issues(project_id: int, oauth_token: str): issues = api_request("GET", "issues", token=oauth_token, params={"projectId[]": project_id}) closed = [i for i in issues if i["status"]["id"] == 3] # 完了=3 if not closed: return lines = ["*本日の完了課題一覧*"] for i in closed: lines.append(f"- {i['issueKey']}: {i['summary']} (担当: {i.get('assignee', {}).get('name','-')})") notify_slack("\n".join(lines)) |
8‑3️⃣ GitHub Actions ワークフロー例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# .github/workflows/backlog-report.yml name: Daily Backlog Report on: schedule: - cron: '0 6 * * *' # 毎日 06:00 UTC jobs: report: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | python -m venv .venv source .venv/bin/activate pip install requests python-dotenv - name: Run report script env: BACKLOG_API_KEY: ${{ secrets.BACKLOG_API_KEY }} BACKLOG_SPACE_ID: ${{ secrets.BACKLOG_SPACE_ID }} OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }} OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} run: | source .venv/bin/activate python scripts/daily_report.py # 上記 report_closed_issues を呼び出す想定 |
ポイント:CI 環境でも環境変数・シークレットを使うことで、認証情報がコードに埋め込まれることはありません。
9️⃣ まとめ(重要ポイント)
- エンドポイント:Backlog の公式 API は
https://{space}.backlog.com/api/v2/...系列。OAuth2 のトークン取得は/api/v2/oauth/tokenが正しい。 - 認証方式の選択基準
- API キー → 手軽さ重視、内部バッチやシンプルなツール向け。
- OAuth2.0 → スコープ・有効期限管理が必要な外部連携やマルチユーザー環境に必須。
- リクエスト実装は ヘッダー方式(X-Api-Key または Authorization: Bearer) に統一し、クエリパラメータへ同時に書かないこと。
- エラーハンドリングはステータスコード別に例外化し、429 は
Retry-After従って再試行、その他は指数バックオフで安全にリトライ。 - 実務応用として CSV 出力・Slack 通知・CI/CD 統合のサンプルを提示。これらはすべて 1 つの Python スクリプトで完結できる。
以上が、Backlog API を Python から安全かつ効率的に利用するための最新(2026 年版)ガイドです。ぜひご自身のプロジェクトに組み込んで活用してください。